From ad7775740bdec213edd193ea07b78ba3b16ba66e Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Tue, 11 Feb 2025 04:29:25 -0300 Subject: [PATCH] Rethinking demodulation code using interrupts --- examples/LinkIR_demo/src/main.cpp | 7 ++- lib/LinkIR.hpp | 33 ++++++------ lib/iwram_code/LinkIR.cpp | 87 +++++++++++++++++++------------ 3 files changed, 74 insertions(+), 53 deletions(-) diff --git a/examples/LinkIR_demo/src/main.cpp b/examples/LinkIR_demo/src/main.cpp index 93d2ba9..5569615 100644 --- a/examples/LinkIR_demo/src/main.cpp +++ b/examples/LinkIR_demo/src/main.cpp @@ -8,7 +8,7 @@ void sendNECSignal(); void receiveNECSignal(); void sendGeneric38kHzSignal(); void receiveGeneric38kHzSignal(); -void monitor(); +void monitor(bool& b); // (1) Create a LinkIR instance LinkIR* linkIR = new LinkIR(); @@ -55,7 +55,7 @@ int main() { if (Common::didPress(KEY_LEFT, left)) receiveGeneric38kHzSignal(); if (Common::didPress(KEY_SELECT, select)) - monitor(); + monitor(b); VBlankIntrWait(); Common::log(output); @@ -174,7 +174,7 @@ void receiveGeneric38kHzSignal() { } } -void monitor() { +void monitor(bool& b) { const u32 WIDTH = 29; const u32 SPEED = 3; const u8 ADDR = 0x04; @@ -185,7 +185,6 @@ void monitor() { int direction = 1; std::string output = ""; u32 count = 0; - bool b = true; while (true) { if (Common::didPress(KEY_B, b)) diff --git a/lib/LinkIR.hpp b/lib/LinkIR.hpp index cd748c3..8bb99ef 100644 --- a/lib/LinkIR.hpp +++ b/lib/LinkIR.hpp @@ -60,17 +60,17 @@ class LinkIR { using u32 = Link::u32; using u16 = Link::u16; using u8 = Link::u8; + using vu32 = Link::vu32; using Pin = LinkGPIO::Pin; using Direction = LinkGPIO::Direction; static constexpr int CYCLES_PER_MICROSECOND = 17; static constexpr int DETECTION_TIMEOUT = 2500; + static constexpr int DEMODULATION_MARK_MIN_TRANSITIONS = 3; static constexpr int DEMODULATION_38KHZ_PERIOD = 1000000 / 38000; - static constexpr int DEMODULATION_WINDOW_FACTOR = 2; - static constexpr int DEMODULATION_SAMPLE_WINDOW_CYCLES = - DEMODULATION_38KHZ_PERIOD * DEMODULATION_WINDOW_FACTOR * - CYCLES_PER_MICROSECOND; - static constexpr int DEMODULATION_MIN_TRANSITIONS = 2; + static constexpr int DEMODULATION_SPACE_PERIODS = 3; + static constexpr int DEMODULATION_SPACE_THRESHOLD = + DEMODULATION_38KHZ_PERIOD * DEMODULATION_SPACE_PERIODS; static constexpr int NEC_TOLERANCE_PERCENTAGE = 15; static constexpr int NEC_TOTAL_PULSES = 68; static constexpr int NEC_LEADER_MARK = 9000; @@ -245,8 +245,7 @@ class LinkIR { /** * Turns the output IR LED ON/OFF through the `SO` pin (HIGH = ON). * @param on Whether the light should be ON. - * \warning The adapter won't keep it ON for more than 10µs. Add some pauses - * after every 10µs. + * \warning Add some pauses after every 10µs. */ void setLight(bool on) { linkGPIO.writePin(Pin::SO, on); } @@ -273,12 +272,7 @@ class LinkIR { * @brief This method is called by the SERIAL interrupt handler. * \warning This is internal API! */ - void _onSerial() { - if (!isEnabled) - return; - - detected = true; - } + void _onSerial(); // defined in `LinkIR.cpp` struct Config { u8 primaryTimerId; @@ -295,6 +289,9 @@ class LinkIR { LinkGPIO linkGPIO; volatile bool isEnabled = false; volatile bool detected = false; + vu32 firstLightTime = 0; + vu32 lastLightTime = 0; + vu32 transitionCount = 0; void addNECByte(u16 pulses[], u32& i, u8 value) { for (u32 b = 0; b < 8; b++) { @@ -311,7 +308,12 @@ class LinkIR { void generate38kHzSignal(u32 microseconds); // defined in ASM (`LinkIR.cpp`) void waitMicroseconds(u32 microseconds); // defined in ASM (`LinkIR.cpp`) - void resetState() { detected = false; } + void resetState() { + detected = false; + firstLightTime = 0; + lastLightTime = 0; + transitionCount = 0; + } void startCount() { Link::_REG_TM[config.primaryTimerId].start = 0; @@ -326,7 +328,7 @@ class LinkIR { Link::_TM_ENABLE | Link::_TM_FREQ_1; } - u32 getCount() { + LINK_INLINE u32 getCount() { return (Link::_REG_TM[config.primaryTimerId].count | (Link::_REG_TM[config.secondaryTimerId].count << 16)); } @@ -349,5 +351,6 @@ inline void LINK_IR_ISR_SERIAL() { } // TODO: C BINDINGS +// TODO: DOCUMENTATION (README) #endif // LINK_IR_H diff --git a/lib/iwram_code/LinkIR.cpp b/lib/iwram_code/LinkIR.cpp index de344b0..87fa8aa 100644 --- a/lib/iwram_code/LinkIR.cpp +++ b/lib/iwram_code/LinkIR.cpp @@ -31,65 +31,84 @@ LINK_CODE_IWRAM bool LinkIR::receive(u16 pulses[], bool hasStarted = false; bool isMark = false; u32 pulseIndex = 0; - u32 initialTime = 0; u32 lastTransitionTime = 0; + firstLightTime = 0; + lastLightTime = 0; + transitionCount = 0; startCount(); - initialTime = getCount(); + u32 initialTime = getCount(); + linkGPIO.setSIInterrupts(true); while (true) { - // begin a fixed demodulation window - u32 windowStart = getCount(); - u32 transitionsCount = 0; - bool previousRaw = isDetectingLight(); + u32 currentLastLightTime = lastLightTime; // (mutated via interrupts) + u32 currentFirstLightTime = firstLightTime; // (mutated via interrupts) + u32 now = getCount(); + u32 timeSinceLastLight = now - currentLastLightTime; - // sample for a fixed window duration - while (getCount() - windowStart < DEMODULATION_SAMPLE_WINDOW_CYCLES) { - bool currentRaw = isDetectingLight(); - if (currentRaw != previousRaw) { - transitionsCount++; - previousRaw = currentRaw; - } - } + // Transitions - bool isCarrierPresent = transitionsCount >= DEMODULATION_MIN_TRANSITIONS; - u32 transitionTime = getCount(); - - // new transition? - if (isCarrierPresent != isMark) { - if (!hasStarted && isCarrierPresent) { - // first mark initializes the capture - hasStarted = true; - lastTransitionTime = transitionTime; - } else if (hasStarted) { - // record the pulse duration in microseconds - if (pulseIndex >= maxEntries - 1) - break; - u32 pulseDuration = - (transitionTime - lastTransitionTime) / CYCLES_PER_MICROSECOND; + if (!isMark && transitionCount > DEMODULATION_MARK_MIN_TRANSITIONS) { + // [space ->] mark + if (hasStarted) { + u32 pulseDuration = (currentFirstLightTime - lastTransitionTime) / + CYCLES_PER_MICROSECOND; pulses[pulseIndex++] = pulseDuration; - lastTransitionTime = transitionTime; + if ((int)pulseIndex >= (int)maxEntries - 2) + break; } - isMark = isCarrierPresent; + isMark = true; + lastTransitionTime = currentFirstLightTime; + hasStarted = true; } + if (hasStarted && isMark && + timeSinceLastLight >= + DEMODULATION_SPACE_THRESHOLD * CYCLES_PER_MICROSECOND) { + // mark -> space + u32 pulseDuration = + (currentLastLightTime - lastTransitionTime) / CYCLES_PER_MICROSECOND; + pulses[pulseIndex++] = pulseDuration; + if ((int)pulseIndex >= (int)maxEntries - 2) + break; + isMark = false; + lastTransitionTime = currentLastLightTime; + transitionCount = 0; + } + + // Timeouts + + u32 timeSinceLastTransition = now - lastTransitionTime; + u32 timeSinceInitialization = now - initialTime; + // if we've started and we're in a space, check for timeout if (hasStarted && !isMark && - ((getCount() - lastTransitionTime) / CYCLES_PER_MICROSECOND >= - signalTimeout)) + timeSinceLastTransition >= signalTimeout * CYCLES_PER_MICROSECOND) break; // if we haven't started and we've waited too long, timeout too if (!hasStarted && - ((getCount() - initialTime) / CYCLES_PER_MICROSECOND >= startTimeout)) + timeSinceInitialization >= startTimeout * CYCLES_PER_MICROSECOND) break; } pulses[pulseIndex] = LINK_IR_SIGNAL_END; stopCount(); + linkGPIO.setSIInterrupts(false); return pulseIndex > 0; } +LINK_CODE_IWRAM void LinkIR::_onSerial() { + if (!isEnabled) + return; + + detected = true; + lastLightTime = getCount(); + if (transitionCount == 0) + firstLightTime = lastLightTime; + transitionCount++; +} + /** * NOTES: * To modulate a signal at 38kHz, we need to stay 13.15µs LOW and 13.15µs HIGH.