jkristell / infrared

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

embassy example #104

Open n1ght-hunter opened 7 months ago

n1ght-hunter commented 7 months ago

it would be nice to have an embassy example

jkristell commented 7 months ago

I totally agree. Do you want to give it a try?

rbomze commented 5 months ago

i got it working on embassy-rs with an esp32, running wifi and other tasks. will post some example in a day or two. ok, here my asap version, will make a proper pull request with an working example soon. i ran the following code on embassy-executor 0.5.0 and esp-hal 0.16.1.

in async fn main(spawner: Spawner):

    let io = esp_hal::IO::new(peripherals.GPIO, peripherals.IO_MUX);
    let ir_pin = io.pins.gpio27.into_floating_input().into();
    interrupt::enable(Interrupt::GPIO, Priority::Priority2).unwrap();
    let ir_recv: Receiver<Nec> = infrared::Receiver::new(1_000_000);
    spawner.spawn(ir_processor(ir_pin, ir_recv)).unwrap();

the async task handling ir processing:

#[embassy_executor::task]
async fn ir_processor(mut ir_pin: AnyPin<Input<Floating>>, mut ir_recv:  Receiver<Nec>) {
    let mut lastedge = Instant::now();
    loop {
        ir_pin.wait_for_any_edge().await.unwrap();
        let rising = ir_pin.is_high().unwrap();
        let now = Instant::now();
        let dur = now.checked_duration_since(lastedge).unwrap();
        if let Ok(Some(cmd)) = ir_recv.event(dur.as_micros().try_into().unwrap(), !rising) {
            println!("cmd: {:?}", cmd);
        }
        lastedge = now;
    }
}

i initialized the receiver with a frequency of 1mhz as i use embassy's time in microseconds later for comparison.

akriese commented 3 weeks ago

For anyone trying to make it work with the ESP32's RMT module, this code is working for me:

#[embassy_executor::task]
pub async fn ir_receive(mut channel: Channel<Async, 1>) {
    let mut ir_receiver: Receiver<Nec> = infrared::Receiver::new(80_000_000);

    let mut data = [PulseCode {
        level1: true,
        length1: 1,
        level2: false,
        length2: 1,
    }; 48];

    loop {
        channel.receive(&mut data).await.unwrap();

        for entry in data {
            if entry.length1 == 0 {
                break;
            }

            let res = ir_receiver.event(entry.length1 * 255, entry.level1);

            if res.is_err() {
                log::info!("{:?}", res);
                break;
            }

            if let Some(cmd) = res.unwrap() {
                // execute command
                log::info!("{:?}", cmd);
                break;
            }

            if entry.length2 == 0 {
                break;
            }

            let res = ir_receiver.event(entry.length2 * 255, entry.level2);

            if res.is_err() {
                log::info!("{:?}", res);
                break;
            }

            if let Some(cmd) = res.unwrap() {
                // execute command
                log::info!("{:?}", cmd);
                break;
            }
        }
    }
}

Usage and initialization in main:

#[main]
async fn main(spawner: Spawner) {
    let peripherals = Peripherals::take();
    let system = SystemControl::new(peripherals.SYSTEM);
    let clocks = ClockControl::max(system.clock_control).freeze();
    let timg0 = TimerGroup::new_async(peripherals.TIMG0, &clocks);
    esp_hal_embassy::init(&clocks, timg0);

    let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);

    let rx_config = RxChannelConfig {
        clk_divider: 255,
        idle_threshold: 10000,
        ..RxChannelConfig::default()
    };
    let rmt_async = Rmt::new_async(
        unsafe { Peripherals::steal() }.RMT,
        HertzU32::MHz(80),
        &clocks,
    )
    .unwrap();
    let recv_channel =
        RxChannelCreatorAsync::configure(rmt_async.channel1, io.pins.gpio26, rx_config).unwrap();

    spawner.spawn(ir_receive(recv_channel)).ok();
}

It is adapting this example from the esp-hal repo. One advantage of this over rbomze's solution would be, that one only awaits the receiving of the whole ir signal block instead of awaiting each edge. So, it would be less prone to timing errors with other tasks running simultaneously. Here, the decoder is expecting a frequency of 80 Mhz, which is the RMT channel frequency. We need to use a clock divider for the channel (I used 255) and have to multiply the signal lengths with the same value. At least, that worked for me :)