diff --git a/lib/LinkIR.hpp b/lib/LinkIR.hpp index fac8690..0047a35 100644 --- a/lib/LinkIR.hpp +++ b/lib/LinkIR.hpp @@ -14,16 +14,24 @@ // interrupt_add(INTR_TIMER3, []() {}); // - 3) Initialize the library with: // linkIR->activate(); -// - 4) Send IR signals: -// linkIR->send({21, 2, 40, 4, 32, 0}); +// - 4) Send NEC signals: +// linkIR->sendNec(0x04, 0x03); +// - 5) Receive NEC signals: +// // TODO: IMPLEMENT +// - 6) Send 38kHz signals: +// linkIR->send({9000, 4500, 560, 560, 560, 1690, 0}); // // u16 array, numbers are microseconds // // even indices: marks (IR on) // // odd indices: spaces (IR off) // // 0 = EOS -// - 5) Receive IR signals; +// - 7) Receive 38kHz signals; // u16 pulses[2000]; // linkIR->receive(pulses, 2000, 30000); // // out array, max entries, timeout (in microseconds) +// - 8) Bitbang the LED manually: +// linkIR->setLight(true); +// - 9) Receive manually: +// bool ledOn = linkIR->isDetectingLight(); // -------------------------------------------------------------------------- #ifndef LINK_DEVELOPMENT @@ -37,13 +45,12 @@ LINK_VERSION_TAG LINK_IR_VERSION = "vLinkIR/v8.0.0"; #define LINK_IR_SIGNAL_END 0 -#define LINK_IR_DEFAULT_TOLERANCE_PERCENTAGE 15 #define LINK_IR_DEFAULT_PRIMARY_TIMER_ID 2 #define LINK_IR_DEFAULT_SECONDARY_TIMER_ID 3 /** * @brief A driver for the Infrared Adapter (AGB-006). - * \warning If you enable this, make sure that `lib/iwram_code/LinkIR.cpp` gets + * \warning To use this, make sure that `lib/iwram_code/LinkIR.cpp` gets * compiled! For example, in a Makefile-based project, verify that the directory * is in your `SRCDIRS` list. */ @@ -117,12 +124,14 @@ class LinkIR { setLight(false); linkGPIO.setSIInterrupts(false); - bool success = irq; - irq = false; - - return success; + return detected; } + /** + * Sends a NEC signal. + * @param address An 8-bit address, to specify the device. + * @param command An 8-bit command, to specify the action. + */ void sendNEC(u8 address, u8 command) { u16 pulses[NEC_TOTAL_PULSES]; u32 i = 0; @@ -148,10 +157,23 @@ class LinkIR { * other frequencies, you'll have to bitbang the `SO` pin yourself with * `setLight(...)`. */ - void send(u16 pulses[]); + void send(u16 pulses[]); // defined in `LinkIR.cpp` - // TODO: DOCUMENT - bool receive(u16 pulses[], u32 maxEntries, u32 timeout, u32 startTimeout); + /** + * Receives a generic IR signal modulated at standard 38kHz. + * @param pulses An array to be filled with u16 numbers describing the signal. + * Even indices are *marks* (IR on), odd indices are *spaces* (IR off), and + * `0` ends the signal. + * @param maxEntries Maximum capacity of the `pulses` array. + * @param timeout Number of microseconds inside a *space* after terminating + * the reception. It doesn't start to count until the first *mark*. + * @param startTimeout Number of microseconds before the first *mark* to abort + * the reception. + */ + bool receive(u16 pulses[], + u32 maxEntries, + u32 timeout, + u32 startTimeout = 0xFFFFFFFF); // defined in `LinkIR.cpp` /** * Turns the output IR LED ON/OFF through the `SO` pin (HIGH = ON). @@ -188,11 +210,10 @@ class LinkIR { if (!isEnabled) return; - irq = true; + detected = true; } struct Config { - u8 tolerancePercentage; u8 primaryTimerId; u8 secondaryTimerId; }; @@ -203,10 +224,10 @@ class LinkIR { */ Config config; - // private: // TODO: REMOVE + private: LinkGPIO linkGPIO; volatile bool isEnabled = false; - volatile bool irq = false; + volatile bool detected = false; void addNECByte(u16 pulses[], u32& i, u8 value) { for (u32 b = 0; b < 8; b++) { @@ -218,7 +239,7 @@ class LinkIR { void generate38kHzSignal(u32 microseconds); // defined in ASM (`LinkIR.cpp`) void waitMicroseconds(u32 microseconds); // defined in ASM (`LinkIR.cpp`) - void resetState() { irq = false; } + void resetState() { detected = false; } void startCount() { Link::_REG_TM[config.primaryTimerId].start = 0; diff --git a/lib/iwram_code/LinkIR.cpp b/lib/iwram_code/LinkIR.cpp index ab5e14e..67ffb6b 100644 --- a/lib/iwram_code/LinkIR.cpp +++ b/lib/iwram_code/LinkIR.cpp @@ -1,64 +1,5 @@ #include "../LinkIR.hpp" -// To modulate a signal at 38kHz, we need to stay 13.15µs LOW and 13.15µs HIGH. -// 38kHz signal => 38000/second => -// period = 1000000µs / 38000 = 26.31µs -// halfPeriod = 13.15µs -// LED ON => RCNT = 0x80BA (GPIO mode, SC, SD, SO as OUTPUT, SD=HIGH, SO=HIGH) -// LED OFF => RCNT = 0x80B2 (GPIO mode, SC, SD, SO as OUTPUT, SD=HIGH, SO=LOW) - -LINK_CODE_IWRAM void LinkIR::generate38kHzSignal(u32 microseconds) { - // halfPeriods = ceil(microseconds / 13.15 µs) (in fixed-point math) - u32 halfPeriods = Link::_max((microseconds * 100 + 1315) / 1316, 1); - - // the GBA is 16.776MHz => 13.15 µs ~= 220 cycles per half-period - - asm volatile( - "mov r0, %0 \n" // r0 = address of REG_RCNT - "ldr r1, =0x80BA \n" // r1 = initial value 0x80BA (LED ON) - "mov r2, %1 \n" // r2 = main loop count (halfPeriods) - "1: \n" // --- main loop --- - "strh r1, [r0] \n" // write current value to REG_RCNT - "mov r3, #54 \n" // r3 = inner loop count (54) (*) - "2: \n" // --- inner loop --- - "subs r3, r3, #1 \n" // decrement inner loop count - // [1 cycle] - "bne 2b \n" // repeat inner loop if needed - // [taken: ~3 cycles, final: ~1 cycles] - // (*) the we need to wait ~220 cycles between
iterations: - // [first 53 iterations (branch taken): 53 * ~4 cycles = ~212 cycles] - // [final iteration (branch not taken): ~2 cycles] - // [overhead: ~6 cycles] - "eor r1, r1, #8 \n" // toggle r1: 0x80BA^8 = 0x80B2 (& viceversa) - "subs r2, r2, #1 \n" // decrement main loop count - "bne 1b \n" // repeat main loop if needed - "ldr r1, =0x80B2 \n" // ensure we end with 0x80B2 - "strh r1, [r0] \n" // write REG_RCNT = 0x80B2 (LED OFF) - : - : "r"(&Link::_REG_RCNT), "r"(halfPeriods) - : "r0", "r1", "r2", "r3"); -} - -LINK_CODE_IWRAM void LinkIR::waitMicroseconds(u32 microseconds) { - if (!microseconds) - return; - - asm volatile( - "mov r1, %0 \n" // r1 = main loop count (microseconds) - "1: \n" // --- main loop --- - "mov r2, #3 \n" // r2 = inner loop count (3) - "nop \n" // extra cycle - "nop \n" // extra cycle - "2: \n" // --- inner loop --- - "subs r2, r2, #1 \n" // decrement inner loop count - "bne 2b \n" // repeat inner loop if needed - "subs r1, r1, #1 \n" // decrement main loop count - "bne 1b \n" // repeat main loop if needed - : - : "r"(microseconds) - : "r1", "r2"); -} - LINK_CODE_IWRAM void LinkIR::send(u16 pulses[]) { setLight(false); @@ -67,8 +8,10 @@ LINK_CODE_IWRAM void LinkIR::send(u16 pulses[]) { bool isMark = i % 2 == 0; if (isMark) { + // even index: mark generate38kHzSignal(microseconds); } else { + // odd index: space setLight(false); waitMicroseconds(microseconds); } @@ -142,3 +85,65 @@ LINK_CODE_IWRAM bool LinkIR::receive(u16 pulses[], stopCount(); return pulseIndex > 0; } + +/** + * NOTES: + * To modulate a signal at 38kHz, we need to stay 13.15µs LOW and 13.15µs HIGH. + * 38kHz signal => 38000/second => + * period = 1000000µs / 38000 = 26.31µs + * halfPeriod = 13.15µs + * LED ON => RCNT = 0x80BA (GPIO mode, SC, SD, SO as OUTPUT, SD=HIGH, SO=HIGH) + * LED OFF => RCNT = 0x80B2 (GPIO mode, SC, SD, SO as OUTPUT, SD=HIGH, SO=LOW) + */ + +LINK_CODE_IWRAM void LinkIR::generate38kHzSignal(u32 microseconds) { + // halfPeriods = ceil(microseconds / 13.15 µs) (in fixed-point math) + u32 halfPeriods = Link::_max((microseconds * 100 + 1315) / 1316, 1); + + // the GBA is 16.776MHz => 13.15 µs ~= 220 cycles per half-period + + asm volatile( + "mov r0, %0 \n" // r0 = address of REG_RCNT + "ldr r1, =0x80BA \n" // r1 = initial value 0x80BA (LED ON) + "mov r2, %1 \n" // r2 = main loop count (halfPeriods) + "1: \n" // --- main loop --- + "strh r1, [r0] \n" // write current value to REG_RCNT + "mov r3, #54 \n" // r3 = inner loop count (54) (*) + "2: \n" // --- inner loop --- + "subs r3, r3, #1 \n" // decrement inner loop count + // [1 cycle] + "bne 2b \n" // repeat inner loop if needed + // [taken: ~3 cycles, final: ~1 cycles] + // (*) the we need to wait ~220 cycles between
iterations: + // [first 53 iterations (branch taken): 53 * ~4 cycles = ~212 cycles] + // [final iteration (branch not taken): ~2 cycles] + // [overhead: ~6 cycles] + "eor r1, r1, #8 \n" // toggle r1: 0x80BA^8 = 0x80B2 (& viceversa) + "subs r2, r2, #1 \n" // decrement main loop count + "bne 1b \n" // repeat main loop if needed + "ldr r1, =0x80B2 \n" // ensure we end with 0x80B2 + "strh r1, [r0] \n" // write REG_RCNT = 0x80B2 (LED OFF) + : + : "r"(&Link::_REG_RCNT), "r"(halfPeriods) + : "r0", "r1", "r2", "r3"); +} + +LINK_CODE_IWRAM void LinkIR::waitMicroseconds(u32 microseconds) { + if (!microseconds) + return; + + asm volatile( + "mov r1, %0 \n" // r1 = main loop count (microseconds) + "1: \n" // --- main loop --- + "mov r2, #3 \n" // r2 = inner loop count (3) + "nop \n" // extra cycle + "nop \n" // extra cycle + "2: \n" // --- inner loop --- + "subs r2, r2, #1 \n" // decrement inner loop count + "bne 2b \n" // repeat inner loop if needed + "subs r1, r1, #1 \n" // decrement main loop count + "bne 1b \n" // repeat main loop if needed + : + : "r"(microseconds) + : "r1", "r2"); +} diff --git a/lib/iwram_code/LinkWireless.cpp b/lib/iwram_code/LinkWireless.cpp index 873802a..7a99dea 100644 --- a/lib/iwram_code/LinkWireless.cpp +++ b/lib/iwram_code/LinkWireless.cpp @@ -39,6 +39,8 @@ _LINK_SERIAL_ISR void LinkWireless::processMessage(u32 playerId, * NOTES: * When using `LINK_WIRELESS_ENABLE_NESTED_IRQ`: * - Any user ISR can interrupt the library ISRs. + * * The SERIAL ISR only enables nested interrupts after completing the + * acknowledge with the Wireless Adapter. * - SERIAL ISR can interrupt TIMER ISR. * -> This doesn't cause data races since TIMER ISR only works when * there is no active async task.