jkristell / infrared

Infrared remote control library for embedded Rust
Apache License 2.0
56 stars 10 forks source link

Investigate how to make tolerances configurable #37

Open jkristell opened 4 years ago

riskable commented 2 years ago

FYI: I was looking for a workaround to the inability to control this very thing (tolerances) and came up with this:

    // The infrared module is a bit too picky about timing so we fudge it a bit to make the IR
    // receiver *much* more reliable (at least with NEC remotes):
    if elapsed_us < 850 { // No idea how close these values are to their theoretical exact timings
        elapsed_us = 550;
    } else if elapsed_us > 1400 && elapsed_us < 2000 {
        elapsed_us = 1600;
    }

...and it works surprisingly well! Like, flawlessly. Basically, I've got the IR pin tied to IO_IRQ_BANK0 and triggering on High and Low (just like in the RP2040 example I wrote a while back). The value that gets passed to ir_recv.event_iter() seems to be the key part.

Basically, when I first started this code the IR remote worked OK-ish... Had to re-press a button about 25% of the time. However, as time went on and the project grew the IR events became more and more unreliable to the point where they would only work like 10% of the time. I knew why: I had too much simultaneous crap going on! haha. This was messing with the very precise timing required by infrared. I had defmt print out the elapsed_us value with each call of my interrupt-bound function and did the export DEFMT_LOG=trace; cargo run --release thing and that's when I had my "Aha!" moment.

Here's the full function code in case you need more context:

// NOTE: This needs to be bound to the IO_IRQ_BANK0 interrupt
#[inline(never)]
#[link_section = ".data.ram_func"]
pub(crate) fn handle_ir_event(mut c: crate::app::handle_ir_event::Context) {
    // static mut LAST: Option<Instant<u64, 1, 1000000>> = None;
    let ir_edge = c.local.ir_edge;
    let ir_recv = c.local.ir_recv;

    let now = monotonics::now();
    let elapsed = now.checked_duration_since(*ir_edge).unwrap();
    let mut elapsed_us: u32 = elapsed.to_micros() as u32;
    // debug!("IR elapsed_us: {}", elapsed_us);

    // The infrared module is a bit too picky about timing so we fudge it a bit to make the IR
    // receiver *much* more reliable (at least with NEC remotes):
    if elapsed_us < 850 {
        elapsed_us = 550;
    } else if elapsed_us > 1400 && elapsed_us < 2000 {
        elapsed_us = 1600;
    }

    if let Ok(cmds) = ir_recv.event_iter(elapsed_us) {
        for cmd in cmds {
            // debug!("IR: {:?}", cmd); // TEMP
            match cmd {
                MultiReceiverCommand::Nec(nec) => {
                    debug!("Nec cmd: {:?}, elapsed_us: {}", nec, elapsed_us);
                    let since_last_cmd = (now
                        .checked_duration_since(*c.local.ir_last_cmd_time)
                        .unwrap())
                    .to_millis();
                    if let Some(button) = IRRemote::decode(&nec) {
                        // debug!("Button decoded: {}", button);
                        if since_last_cmd > MAX_REPEAT_TIME {
                            // No commands for a while so reset the repeat counter
                            *c.local.ir_last_cmd_time = now;
                            *c.local.ir_repeat_counter = 0;
                        }
                        c.shared.states.lock(|states| {
                            if !nec.repeat
                                || *c.local.ir_repeat_counter == 0
                                || *c.local.ir_repeat_counter >= REPEAT_INTERVAL
                            {
                                let index = IRRemote::index(button) as u8;
                                let (_code, _action) = IRRemote::BUTTONS[index as usize];
                                *states.ir_button = index; // This is how keeb_scanner() can know what was pressed
                                *c.local.ir_repeat_counter = 0; // Reset the repeat counter
                            }
                        });
                        *c.local.ir_repeat_counter += 1;
                    }
                }
                MultiReceiverCommand::NecSamsung(nec_samsung) => {
                    debug!("nec_samsung cmd: {:?}", nec_samsung);
                }
                MultiReceiverCommand::NecApple(nec_apple) => {
                    debug!("nec_apple cmd: {:?}", nec_apple);
                }
                MultiReceiverCommand::Rc5(rc5) => {
                    debug!("rc5 cmd: {:?}", rc5);
                }
                MultiReceiverCommand::Rc6(rc6) => {
                    debug!("rc6 cmd: {:?}", rc6);
                }
                MultiReceiverCommand::Denon(denon) => {
                    debug!("denon cmd: {:?}", denon);
                }
                _ => {} // Just ignore other protocols (since we can only support 6 at a time)
            }
        }
    }

    // Clear the interrupts (won't work unless we do this)
    ir_recv.pin().clear_interrupt(Interrupt::EdgeLow); // Does the order matter?
    ir_recv.pin().clear_interrupt(Interrupt::EdgeHigh); // I don't know!

    // let elapsed_since_processing = monotonics::now().checked_duration_since(now).unwrap();
    *ir_edge = now;
}

I haven't had the chance to test it with other kinds of remotes yet. NEC is the one I'm planning on putting my full support behind regardless.

jkristell commented 1 year ago

Hi!

Interesting. I have to look into this a bit more. Could you send me a log of the raw elapsed_us values?

dotcypress commented 1 year ago

@riskable I found this issue a hard way. In my device I using internal MCU oscillator(too much ppm's) as system clocks and NEC command parsing become really unstable.