infrared
infrared copied to clipboard
Investigate how to make tolerances configurable
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.
Hi!
Interesting. I have to look into this a bit more. Could you send me a log of the raw elapsed_us values?
@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.