infrared icon indicating copy to clipboard operation
infrared copied to clipboard

Investigate how to make tolerances configurable

Open jkristell opened this issue 4 years ago • 3 comments

jkristell avatar Sep 11 '20 07:09 jkristell

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.

riskable avatar Oct 07 '22 14:10 riskable

Hi!

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

jkristell avatar Oct 26 '22 03:10 jkristell

@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.

dotcypress avatar Nov 07 '22 21:11 dotcypress