jkristell / infrared

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

Always the same code #80

Open luiswirth opened 2 years ago

luiswirth commented 2 years ago

First off, thanks for this crate!

I'm trying to decode a signal of a generic "Car MP3" remote, which apparently uses the NEC protocol. When using the NecDebug protocol type, I always get the same bits for every button I press. Am I doing something wrong?

I always get NecCommand { addr: 0, cmd: 0, repeat: true } or respectively NecDebugCmd { bits: 0 } for every button on my remote.

On rare occasions I also get a non-zero cmd, but it seems random.

Here is a condensed version of my code

type IrReceiver = infrared::Receiver<
  infrared::protocol::Nec,
  infrared::receiver::Poll,
  infrared::receiver::PinInput<Pin<Gpio3, Input<Floating>>>,
  Button<CarMp3>
>;

pub static IR_RECEIVER: Mutex<RefCell<Option<IrReceiver>>> = Mutex::new(RefCell::new(None));

pub static UART: Mutex<RefCell<Option<UartPeripheral<hal::uart::Enabled, pac::UART0>>>> =
  Mutex::new(RefCell::new(None));

pub static TIMER: Mutex<RefCell<Option<hal::Timer>>> = Mutex::new(RefCell::new(None));
pub static ALARM: Mutex<RefCell<Option<timer::Alarm0>>> = Mutex::new(RefCell::new(None));

const TIMER_FREQ: u32 = 20_000;
const TIMER_DURATION_US: u32 = 1_000_000 / TIMER_FREQ;

fn main() {
    // ...omitted

    let ir_pin: IrReceiverPin = pins.gpio3.into_floating_input();
    let receiver = IrReceiver::with_pin(TIMER_FREQ, ir_pin);
    let mut timer = Timer::new(p.TIMER, &mut p.RESETS);
    let alarm = timer.alarm_0().unwrap();

    cortex_m::interrupt::free(|cs| {
      IR_RECEIVER.borrow(cs).replace(Some(receiver));
      TIMER.borrow(cs).replace(Some(timer));
      ALARM.borrow(cs).replace(Some(alarm));

      let mut alarm = ALARM.borrow(cs).borrow_mut();
      let alarm = alarm.as_mut().unwrap();
      let mut timer = TIMER.borrow(cs).borrow_mut();
      let timer = timer.as_mut().unwrap();

      alarm
        .schedule(Microseconds::new(TIMER_DURATION_US))
        .unwrap();
      alarm.enable_interrupt(timer);
    });

    unsafe {
      pac::NVIC::unmask(pac::Interrupt::TIMER_IRQ_0);
    }
}

#[allow(non_snake_case)]
#[interrupt]
fn TIMER_IRQ_0() {
  cortex_m::interrupt::free(|cs| {
    let mut alarm = ALARM.borrow(cs).borrow_mut();
    let alarm = alarm.as_mut().unwrap();
    let mut timer = TIMER.borrow(cs).borrow_mut();
    let timer = timer.as_mut().unwrap();
    let mut receiver = IR_RECEIVER.borrow(cs).borrow_mut();
    let receiver = receiver.as_mut().unwrap();

    if let Ok(Some(cmd)) = receiver.poll() {
      let action = cmd.action();
      let mut uart = UART.borrow(cs).borrow_mut();
      let uart = uart.as_mut().unwrap();
      let string = format!(" {}|{:?} ", cmd.command().cmd != 0, action);
      uart.write_full_blocking(string.as_bytes());
    }

    alarm.clear_interrupt(timer);
    alarm
      .schedule(Microseconds::new(TIMER_DURATION_US))
      .unwrap();
  });
}

#[derive(Default, Debug)]
pub struct CarMp3;
impl RemoteControlModel for CarMp3 {
    const MODEL: &'static str = "Car Mp3";

    const DEVTYPE: infrared::remotecontrol::DeviceType = infrared::remotecontrol::DeviceType::Generic;

    const PROTOCOL: infrared::ProtocolId = ProtocolId::Nec;

    const ADDRESS: u32 = 0;

    type Cmd = NecCommand;

    const BUTTONS: &'static [(u32, infrared::remotecontrol::Action)] = &[
        (0xFFA25D, infrared::remotecontrol::Action::ChannelListPrev),
        (0xFF629D, infrared::remotecontrol::Action::ChannelList),
        (0xFFE21D, infrared::remotecontrol::Action::ChannelListNext),
        (0xFF22DD, infrared::remotecontrol::Action::Prev),
        (0xFF02FD, infrared::remotecontrol::Action::Next),
        (0xFFC23D, infrared::remotecontrol::Action::Play_Pause),
        (0xFFE01F, infrared::remotecontrol::Action::VolumeDown),
        (0xFFA857, infrared::remotecontrol::Action::VolumeUp),
        (0xFF906F, infrared::remotecontrol::Action::Eq),
        (0xFF6897, infrared::remotecontrol::Action::Zero),
        //(0xFF9867, infrared::remotecontrol::Action::?),
        //(0xFFB04F, infrared::remotecontrol::Action::?),
        (0xFF30CF, infrared::remotecontrol::Action::One),
        (0xFF18E7, infrared::remotecontrol::Action::Two),
        (0xFF7A85, infrared::remotecontrol::Action::Three),
        (0xFF10EF, infrared::remotecontrol::Action::Four),
        (0xFF38C7, infrared::remotecontrol::Action::Five),
        (0xFF5AA5, infrared::remotecontrol::Action::Six),
        (0xFF42BD, infrared::remotecontrol::Action::Seven),
        (0xFF4AB5, infrared::remotecontrol::Action::Eight),
        (0xFF52AD, infrared::remotecontrol::Action::Nine),

    ];
}

I'm using a raspberry pi pico with the rp_pico board crate.

jkristell commented 2 years ago

Hi, some quick thoughts:

The symptoms indicate that the receiver recognizes the Repeat command, but haven't received any complete commands yet, so that's why it looks like it received a zero command.

My guess is that probably has something to do with the timing of the interrupt. If you do a match on receiver.poll() and see if there is any error signaled by the Receiver?

You could also try the CaptureReceiver to get raw pulse data. I'm not sure if I have any example available, but I can write one during the weekend.

luiswirth commented 2 years ago

The symptoms indicate that the receiver recognizes the Repeat command, but haven't received any complete commands yet, so that's why it looks like it received a zero command.

Ah, that would make a lot of sense.

My guess is that probably has something to do with the timing of the interrupt. If you do a match on receiver.poll() and see if there is any error signaled by the Receiver?

Hmm could be... Is the timer frequency of 20'000 correct? It doesn't depend on the protocol or the remote, right? Yes, I do get a Data error sometimes.

You could also try the CaptureReceiver to get raw pulse data. I'm not sure if I have any example available, but I can write one during the weekend.

I'm going to take a look at it. I'd appreciate an example!


How important is this configuration?

let mut flash = device.FLASH.constrain();
let mut rcc = device.RCC.constrain();

let clocks = rcc
    .cfgr
    .use_hse(8.mhz())
    .sysclk(48.mhz())
    .pclk1(24.mhz())
    .freeze(&mut flash.acr);

I don't quite understand what it is doing and was unable to mirror it in my code. I just took some Alarm which triggers an interrupt after the specified amount of time.

jkristell commented 2 years ago

Hi, I didn't find the time to finish writting a example for the Pico during the weekend. But I will try to do it this week.

jkristell commented 2 years ago

Got it working!

https://github.com/jkristell/rp-hal/blob/solderparty-stamp-infrared-example/boards/solderparty-rp2040-stamp/examples/ir.rs

This is a really, really, really messy example right now :-). Will clean it up later and put it permanent somewhere.

jkristell commented 2 years ago

Now it's cleaned up and should be able to use it as a start for your own app!

luiswirth commented 2 years ago

Thanks for the example! It's nice to now have event-driven decoding.

However this didn't help with my problem... I'm still getting the same zero commands.

I believe it has something to do with my remote. I have found the datasheet for the IR component in my remote. I didn't get too much from it, but I saw that it is using Pulse Position Modulation (PPM) instead of Pulse Width Modulation (PWM). I thought this might be the problem, because so far I only heard of PWM for NEC.

Could this be it?

luiswirth commented 2 years ago

I'm now using a different NEC remote, which works fine. This is no longer an urgent problem for me. I've you're still interested in investigating it, feel free to reach out. Thanks for your help :).

jkristell commented 2 years ago

It would be interesting to look at the at the pulses of the mystery remote to see how it encodes the data.

I can post another example on how to do that later this week.

riskable commented 2 years ago

I'm having this exact same problem with an RP2040 chip. It happens in both polled and event mode. If I keep pressing buttons every now and again it detects it correctly but 90% of the time it just shows up as an empty repeat command. It only does this on the RP2040... I have very similar code working just fine on the Black Pill board.

Can I see the example of how to use CaptureReceiver?

jkristell commented 2 years ago

What protocol? Running in release mode?

I got the external interrupt based version running very reliable.

I have some fixes in a soon to be merged branch https://github.com/jkristell/infrared/tree/tempus-fugit you could try that, but the API has changed slightly, but you could look at the examples in the examples/bluepill folder and adjust your code.

riskable commented 2 years ago

What protocol? Running in release mode?

I'm using NEC protocol but I have a universal remote to test with that can send basically anything (if you want me to test something in particular). Also yes: cargo build --release

Another thing I'm using that might be causing trouble is RTIC (1.0). I'm going to test some things and if those don't work out I'll try to put together a minimal (reproducible) test case and post it here.

I've also got three different IR receivers I'm testing with: TSOP38238, EK8460 (which I've verified works great with your code--on STM32F4), and IRM-H638T/TR2 which is a new one I'm testing out (it's the only SMD part at JLCPCB I could find that looks like it uses the same exact mechanism/protocol).

riskable commented 2 years ago

Just tested out your tempus-fugit branch and it seems to help a little bit. I'm getting buttons recognized more often (like 1 in 25 presses instead of 1 in 100). They still always show up with repeat: true though. That was testing with the interrupt-driven method. I'm going to try timer-based next.

riskable commented 2 years ago

Just tested the polling way (20Khz) and it doesn't recognize any buttons at all. Every button press is NecCommand { addr: 0, cmd: 0, repeat: true }.

I guess now I need to get to work building those minimum test cases...

riskable commented 2 years ago

Hah: I made a minimum example using RTIC after forking your tempus-fugit repo and it actually works fine! It works great, in fact. I even submitted PR #82 with the working code so you can have an RP2040 example when you're done with that branch 👍

I tested it (the example) with different remotes on the IRM-H638T/TR2 SMD IR receiver and the EK8460. I figure if it works with those two it'll also work fine with the TSOP38238.

Now I need to figure out what it is (the difference) in my regular code that causes the problem...

riskable commented 2 years ago

OK I've narrowed it down: If I read from the ADC that seems to screw it all up. Not sure what's going on there... I tried making everything lock-free and that didn't make any difference. Still investigating.