[beken-72xx] Fix digitalRead() returning a stale level inside GPIO ISR (pulse_counter/hlw8012 read 0)#394
Open
blafois wants to merge 1 commit into
Open
Conversation
On BK7238 (and BK7231x) a GPIO edge interrupt can reach the ISR after the pin has already returned to its idle level for a fast pulse train (IRQ latency exceeds the pulse high-time). digitalRead() inside the interrupt handler therefore returns a stale level. Consumers that infer the edge direction by reading the pin in the ISR -- e.g. ESPHome's pulse_counter and hlw8012/BL0937 energy metering, which do `mode = digital_read() ? rising_edge_mode : falling_edge_mode` -- then misclassify every edge and count nothing. On a BK7238 plug with a BL0937, power/voltage/current all read 0. The ISR already knows which edge fired: data->irqMode holds the armed edge (before it is flipped for CHANGE emulation), which implies the pin's logical level. Cache that level and return it from digitalRead() while the pin's handler is running. Tested on BK7238 (LSC/Action smart plug, BL0937): ESPHome pulse_counter went from 0 -> 120k pulses/min and hlw8012 from 0 W -> live power/current/ voltage. Single shared GPIO IRQ vector with non-reentrant dispatch, so the two file-scope state variables cannot race across nested handlers. Refs: libretiny-eu#393
Author
|
@kuba2k2 ? |
Member
|
This is an interesting workaround. However, since we haven't had issues on BK7231x using BL0937, I'd prefer if your changes were scoped to BK7238 specifically, and perhaps could be adapted to other chips later on - we don't want to break something that's working. A simple #if LT_BK7238 should do the trick. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On BK7238 (and likely other BK7231x), calling
digitalRead()inside a GPIO interrupt handler can return a stale level. For a fast pulse train the edge interrupt reaches the ISR after the pin has already returned to its idle level (interrupt latency exceeds the pulse high-time), so the register read no longer reflects the edge that fired.This breaks any consumer that determines the edge direction by reading the pin inside the ISR. The most common case is ESPHome's
pulse_counter(and everything built on it —hlw8012,cse7766, …), whose ISR does:With the default
rising_edge_mode = INCREMENT, falling_edge_mode = DISABLE, anddigital_read()always returning LOW inside the ISR, every edge is treated as falling and discarded → the counter never increments. On a BK7238 smart plug with a BL0937 energy meter, power/voltage/current all read0.Root cause (verified on hardware)
I instrumented this extensively on a BK7238 (LSC/Action plug, BL0937) using a always-present ~4 kHz signal on a GPIO:
digitalRead()in a loop: works (reads the toggling level).INTSTA: the edge latches correctly.attachInterrupt()RISING / FALLING / CHANGE: the ISR fires correctly (~2 k/s, ~2 k/s, ~4 k/s).digitalRead()inside the ISR returned LOW on 72124/72124 calls — never the post-edge level.So the interrupt path is fine; only the in-ISR pin read is unreliable. There is no separate fast GPIO input-data register on this SoC (unlike ESP8266's
GPI, whoseISRInternalGPIOPin::digital_read()reads the live pad register directly), so reading “faster” isn’t possible here.Fix
The ISR already knows which edge fired:
data->irqModeholds the armed edge (before it is flipped for CHANGE emulation), which implies the pin's logical level. Cache that level in file-scope state and return it fromdigitalRead()while the pin's handler is executing. No behavior change outside an ISR.GPIO interrupts share a single IRQ vector with non-reentrant dispatch (
gpio_isr), so the two file-scope variables cannot race across nested handlers.Before / after (BK7238 + BL0937, same pin/signal)
pulse_counter(default mode)hlw8012powerhlw8012current / voltageNotes
beken-72xxwhere I could test on real hardware. Therealtek-ambandlightning-ln882hcores use the same CHANGE-emulation pattern and may benefit from the same treatment, but I have not verified them and left them untouched.