diff --git a/.gitignore b/.gitignore index abd34f10a7a..e2de8562fc0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ **/*.rs.bk ulp/ulp_start.o install-rust-toolchain.sh +/.devcontainer +components_esp32.lock diff --git a/Cargo.toml b/Cargo.toml index f32a0e96c5c..6e5c7204c22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,10 @@ embassy-sync = [] # For now, the dependecy on the `embassy-sync` crate is non-op # - When not enabled (default) the code for the new ADC oneshot driver will be compiled; # - Since we don't wrap the legacy _continuous_ ADC driver, the new _continuous_ ADC driver is always compiled. adc-oneshot-legacy = [] +# Similar to adc-oneshot-legacy +# - When enabled (default), the code for the legacy RMT TX/RX driver will be compiled. +# - When disabled the code for the new onewire RMT driver will be compiled. +rmt-legacy = [] # Propagated esp-idf-sys features native = ["esp-idf-sys/native"] pio = ["esp-idf-sys/pio"] diff --git a/examples/rmt_morse_code.rs b/examples/rmt_morse_code.rs index 671cc119ccb..fc85b1f25f8 100644 --- a/examples/rmt_morse_code.rs +++ b/examples/rmt_morse_code.rs @@ -14,150 +14,174 @@ //! * Background sending. //! * Taking a [`Pin`] and [`Channel`] by ref mut, so that they can be used again later. //! -use esp_idf_hal::delay::Ets; -use esp_idf_hal::gpio::*; -use esp_idf_hal::peripheral::*; -use esp_idf_hal::peripherals::Peripherals; -use esp_idf_hal::rmt::config::{CarrierConfig, DutyPercent, Loop, TransmitConfig}; -use esp_idf_hal::rmt::*; -use esp_idf_hal::units::FromValueType; +#[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] fn main() -> anyhow::Result<()> { - esp_idf_hal::sys::link_patches(); - - let peripherals = Peripherals::take()?; - let mut channel = peripherals.rmt.channel0; - let mut led = peripherals.pins.gpio17; - let stop = peripherals.pins.gpio16; - - let carrier = CarrierConfig::new() - .duty_percent(DutyPercent::new(50)?) - .frequency(611.Hz()); - let mut config = TransmitConfig::new() - .carrier(Some(carrier)) - .looping(Loop::Endless) - .clock_divider(255); - - let tx = send_morse_code(&mut channel, &mut led, &config, "HELLO ")?; - - let stop = PinDriver::input(stop)?; + example::main() +} - println!("Keep sending until pin {} is set low.", stop.pin()); +#[cfg(not(any(feature = "rmt-legacy", esp_idf_version_major = "4")))] +fn main() -> anyhow::Result<()> { + println!("This example requires feature `rmt-legacy` enabled or using ESP-IDF v4.4.X"); - while stop.is_high() { - Ets::delay_ms(100); + loop { + std::thread::sleep(std::time::Duration::from_millis(1000)); } +} - println!("Pin {} set to low. Stopped.", stop.pin()); - - // Release pin and channel so we can use them again. - drop(tx); +#[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +mod example { + use esp_idf_hal::units::FromValueType; + use esp_idf_hal::{ + delay::Ets, + gpio::{OutputPin, PinDriver}, + peripheral::Peripheral, + prelude::Peripherals, + rmt::{ + config::{CarrierConfig, DutyPercent, Loop, TransmitConfig}, + PinState, Pulse, PulseTicks, RmtChannel, TxRmtDriver, VariableLengthSignal, + }, + }; + + pub fn main() -> anyhow::Result<()> { + esp_idf_hal::sys::link_patches(); + + let peripherals = Peripherals::take()?; + let mut channel = peripherals.rmt.channel0; + let mut led = peripherals.pins.gpio17; + let stop = peripherals.pins.gpio16; + + let carrier = CarrierConfig::new() + .duty_percent(DutyPercent::new(50)?) + .frequency(611.Hz()); + let mut config = TransmitConfig::new() + .carrier(Some(carrier)) + .looping(Loop::Endless) + .clock_divider(255); + + let tx = send_morse_code(&mut channel, &mut led, &config, "HELLO ")?; + + let stop = PinDriver::input(stop)?; + + println!("Keep sending until pin {} is set low.", stop.pin()); + + while stop.is_high() { + Ets::delay_ms(100); + } - // Wait so the messages don't get garbled. - Ets::delay_ms(3000); + println!("Pin {} set to low. Stopped.", stop.pin()); - // Now send a single message and stop. - println!("Saying GOODBYE!"); - config.looping = Loop::None; - send_morse_code(channel, led, &config, "GOODBYE")?; + // Release pin and channel so we can use them again. + drop(tx); - Ok(()) -} + // Wait so the messages don't get garbled. + Ets::delay_ms(3000); -fn send_morse_code<'d>( - channel: impl Peripheral

+ 'd, - led: impl Peripheral

+ 'd, - config: &TransmitConfig, - message: &str, -) -> anyhow::Result> { - println!("Sending morse message '{message}'."); - - let mut signal = VariableLengthSignal::new(); - let pulses = str_pulses(message); - // We've been collecting `Pulse` but `VariableLengthSignal` needs `&Pulse`: - let pulses: Vec<&Pulse> = pulses.iter().collect(); - signal.push(pulses)?; - - let mut tx = TxRmtDriver::new(channel, led, config)?; - tx.start(signal)?; - - // Return `tx` so we can release the pin and channel later. - Ok(tx) -} + // Now send a single message and stop. + println!("Saying GOODBYE!"); + config.looping = Loop::None; + send_morse_code(channel, led, &config, "GOODBYE")?; -fn high() -> Pulse { - Pulse::new(PinState::High, PulseTicks::max()) -} + Ok(()) + } -fn low() -> Pulse { - Pulse::new(PinState::Low, PulseTicks::max()) -} + fn send_morse_code<'d>( + channel: impl Peripheral

+ 'd, + led: impl Peripheral

+ 'd, + config: &TransmitConfig, + message: &str, + ) -> anyhow::Result> { + println!("Sending morse message '{message}'."); + + let mut signal = VariableLengthSignal::new(); + let pulses = str_pulses(message); + // We've been collecting `Pulse` but `VariableLengthSignal` needs `&Pulse`: + let pulses: Vec<&Pulse> = pulses.iter().collect(); + signal.push(pulses)?; + + let mut tx = TxRmtDriver::new(channel, led, config)?; + tx.start(signal)?; + + // Return `tx` so we can release the pin and channel later. + Ok(tx) + } -enum Code { - Dot, - Dash, - WordGap, -} + enum Code { + Dot, + Dash, + WordGap, + } -impl Code { - pub fn push_pulse(&self, pulses: &mut Vec) { - match &self { - Code::Dot => pulses.extend_from_slice(&[high(), low()]), - Code::Dash => pulses.extend_from_slice(&[high(), high(), high(), low()]), - Code::WordGap => pulses.extend_from_slice(&[low(), low(), low(), low(), low(), low()]), + impl Code { + pub fn push_pulse(&self, pulses: &mut Vec) { + match &self { + Code::Dot => pulses.extend_from_slice(&[high(), low()]), + Code::Dash => pulses.extend_from_slice(&[high(), high(), high(), low()]), + Code::WordGap => { + pulses.extend_from_slice(&[low(), low(), low(), low(), low(), low()]) + } + } } } -} -fn find_codes(c: &char) -> &'static [Code] { - for (found, codes) in CODES.iter() { - if found == c { - return codes; - } + fn high() -> Pulse { + Pulse::new(PinState::High, PulseTicks::max()) } - &[] -} -fn str_pulses(s: &str) -> Vec { - let mut pulses = vec![]; - for c in s.chars() { - for code in find_codes(&c) { - code.push_pulse(&mut pulses); + fn low() -> Pulse { + Pulse::new(PinState::Low, PulseTicks::max()) + } + + fn find_codes(c: &char) -> &'static [Code] { + for (found, codes) in CODES.iter() { + if found == c { + return codes; + } } + &[] + } + + fn str_pulses(s: &str) -> Vec { + let mut pulses = vec![]; + for c in s.chars() { + for code in find_codes(&c) { + code.push_pulse(&mut pulses); + } - // Create a gap after each symbol. - pulses.push(low()); - pulses.push(low()); + // Create a gap after each symbol. + pulses.push(low()); + pulses.push(low()); + } + pulses } - pulses -} -const CODES: &[(char, &[Code])] = &[ - (' ', &[Code::WordGap]), - ('A', &[Code::Dot, Code::Dash]), - ('B', &[Code::Dash, Code::Dot, Code::Dot, Code::Dot]), - ('C', &[Code::Dash, Code::Dot, Code::Dash, Code::Dot]), - ('D', &[Code::Dash, Code::Dot, Code::Dot]), - ('E', &[Code::Dot]), - ('F', &[Code::Dot, Code::Dot, Code::Dash, Code::Dot]), - ('G', &[Code::Dash, Code::Dash, Code::Dot]), - ('H', &[Code::Dot, Code::Dot, Code::Dot, Code::Dot]), - ('I', &[Code::Dot, Code::Dot]), - ('J', &[Code::Dot, Code::Dash, Code::Dash, Code::Dash]), - ('K', &[Code::Dash, Code::Dot, Code::Dash]), - ('L', &[Code::Dot, Code::Dash, Code::Dot, Code::Dot]), - ('M', &[Code::Dash, Code::Dash]), - ('N', &[Code::Dash, Code::Dot]), - ('O', &[Code::Dash, Code::Dash, Code::Dash]), - ('P', &[Code::Dot, Code::Dash, Code::Dash, Code::Dot]), - ('Q', &[Code::Dash, Code::Dash, Code::Dot, Code::Dash]), - ('R', &[Code::Dot, Code::Dash, Code::Dot]), - ('S', &[Code::Dot, Code::Dot, Code::Dot]), - ('T', &[Code::Dash]), - ('U', &[Code::Dot, Code::Dot, Code::Dash]), - ('V', &[Code::Dot, Code::Dot, Code::Dot, Code::Dash]), - ('W', &[Code::Dot, Code::Dash, Code::Dash]), - ('X', &[Code::Dash, Code::Dot, Code::Dot, Code::Dash]), - ('Y', &[Code::Dash, Code::Dot, Code::Dash, Code::Dash]), - ('Z', &[Code::Dash, Code::Dash, Code::Dot, Code::Dot]), -]; + const CODES: &[(char, &[Code])] = &[ + (' ', &[Code::WordGap]), + ('A', &[Code::Dot, Code::Dash]), + ('B', &[Code::Dash, Code::Dot, Code::Dot, Code::Dot]), + ('C', &[Code::Dash, Code::Dot, Code::Dash, Code::Dot]), + ('D', &[Code::Dash, Code::Dot, Code::Dot]), + ('E', &[Code::Dot]), + ('F', &[Code::Dot, Code::Dot, Code::Dash, Code::Dot]), + ('G', &[Code::Dash, Code::Dash, Code::Dot]), + ('H', &[Code::Dot, Code::Dot, Code::Dot, Code::Dot]), + ('I', &[Code::Dot, Code::Dot]), + ('J', &[Code::Dot, Code::Dash, Code::Dash, Code::Dash]), + ('K', &[Code::Dash, Code::Dot, Code::Dash]), + ('L', &[Code::Dot, Code::Dash, Code::Dot, Code::Dot]), + ('M', &[Code::Dash, Code::Dash]), + ('N', &[Code::Dash, Code::Dot]), + ('O', &[Code::Dash, Code::Dash, Code::Dash]), + ('P', &[Code::Dot, Code::Dash, Code::Dash, Code::Dot]), + ('Q', &[Code::Dash, Code::Dash, Code::Dot, Code::Dash]), + ('R', &[Code::Dot, Code::Dash, Code::Dot]), + ('S', &[Code::Dot, Code::Dot, Code::Dot]), + ('T', &[Code::Dash]), + ('U', &[Code::Dot, Code::Dot, Code::Dash]), + ('V', &[Code::Dot, Code::Dot, Code::Dot, Code::Dash]), + ('W', &[Code::Dot, Code::Dash, Code::Dash]), + ('X', &[Code::Dash, Code::Dot, Code::Dot, Code::Dash]), + ('Y', &[Code::Dash, Code::Dot, Code::Dash, Code::Dash]), + ('Z', &[Code::Dash, Code::Dash, Code::Dot, Code::Dot]), + ]; +} diff --git a/examples/rmt_musical_buzzer.rs b/examples/rmt_musical_buzzer.rs index 8e70c15911d..804b2f8260b 100644 --- a/examples/rmt_musical_buzzer.rs +++ b/examples/rmt_musical_buzzer.rs @@ -4,201 +4,227 @@ //! //! Based off the ESP-IDF rmt musical buzzer example: //! https://github.com/espressif/esp-idf/blob/b092fa073047c957545a0ae9504f04972a8c6d74/examples/peripherals/rmt/musical_buzzer/main/musical_buzzer_example_main.c -use core::time::Duration; - -use esp_idf_hal::delay::Ets; -use esp_idf_hal::peripherals::Peripherals; -use esp_idf_hal::rmt::{self, config::TransmitConfig, TxRmtDriver}; - -use esp_idf_hal::units::Hertz; -use notes::*; +#[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] fn main() -> anyhow::Result<()> { - esp_idf_hal::sys::link_patches(); + example::main() +} - let peripherals = Peripherals::take()?; - let led = peripherals.pins.gpio17; - let channel = peripherals.rmt.channel0; - let config = TransmitConfig::new(); - let mut tx: TxRmtDriver<'static> = TxRmtDriver::new(channel, led, &config)?; +#[cfg(not(any(feature = "rmt-legacy", esp_idf_version_major = "4")))] +fn main() -> anyhow::Result<()> { + println!("This example requires feature `rmt-legacy` enabled or using ESP-IDF v4.4.X"); loop { - play_song(&mut tx, ODE_TO_JOY)?; - Ets::delay_ms(3000); + std::thread::sleep(std::time::Duration::from_millis(1000)); } } -pub fn play_song(tx: &mut TxRmtDriver<'static>, song: &[NoteValue]) -> anyhow::Result<()> { - for note_value in song { - note_value.play(tx)?; - } - Ok(()) -} +#[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +mod example { + use std::time::Duration; -pub struct NoteValueIter { - ticks: rmt::PulseTicks, - tone_cycles: u32, - pause_cycles: u32, -} + use esp_idf_hal::delay::Ets; + use esp_idf_hal::{ + prelude::Peripherals, + rmt::{self, config::TransmitConfig, TxRmtDriver}, + units::Hertz, + }; + use notes::*; -impl NoteValueIter { - fn new(ticks_per_sec: Hertz, note: &NoteValue) -> Self { - // Calculate the frequency for a piezo buzzer. - let dur_ms = note.duration.as_millis(); - let cycles_per_sec = note.note.0; // pitch - let ticks_per_cycle = ticks_per_sec.0 as u128 / cycles_per_sec as u128; - let ticks_per_half = (ticks_per_cycle / 2_u128) as u16; - let ticks = rmt::PulseTicks::new(ticks_per_half).unwrap(); - - let total_cycles = (cycles_per_sec as u128 * dur_ms / 1000_u128) as u32; - // Pause for the last 40ms of every note - let pause_cycles = (cycles_per_sec as u128 * 40_u128 / 1000_u128) as u32; - let tone_cycles = total_cycles - pause_cycles; - - Self { - ticks, - tone_cycles, - pause_cycles, + pub fn main() -> anyhow::Result<()> { + esp_idf_hal::sys::link_patches(); + + let peripherals = Peripherals::take()?; + let led = peripherals.pins.gpio17; + let channel = peripherals.rmt.channel0; + let config = TransmitConfig::new(); + let mut tx: TxRmtDriver<'static> = TxRmtDriver::new(channel, led, &config)?; + + loop { + play_song(&mut tx, ODE_TO_JOY)?; + Ets::delay_ms(3000); } } -} -impl std::iter::Iterator for NoteValueIter { - type Item = rmt::Symbol; + pub fn play_song(tx: &mut TxRmtDriver<'static>, song: &[NoteValue]) -> anyhow::Result<()> { + for note_value in song { + note_value.play(tx)?; + } + Ok(()) + } + + pub struct NoteValueIter { + ticks: rmt::PulseTicks, + tone_cycles: u32, + pause_cycles: u32, + } - // runs in ISR - fn next(&mut self) -> Option { - if self.tone_cycles + self.pause_cycles > 0 { - let high_state = if self.tone_cycles > 0 { - self.tone_cycles -= 1; - rmt::PinState::High + impl NoteValueIter { + fn new(ticks_per_sec: Hertz, note: &NoteValue) -> Self { + // Calculate the frequency for a piezo buzzer. + let dur_ms = note.duration.as_millis(); + let cycles_per_sec = note.note.0; // pitch + let ticks_per_cycle = ticks_per_sec.0 as u128 / cycles_per_sec as u128; + let ticks_per_half = (ticks_per_cycle / 2_u128) as u16; + let ticks = rmt::PulseTicks::new(ticks_per_half).unwrap(); + + let total_cycles = (cycles_per_sec as u128 * dur_ms / 1000_u128) as u32; + // Pause for the last 40ms of every note + let pause_cycles = (cycles_per_sec as u128 * 40_u128 / 1000_u128) as u32; + let tone_cycles = total_cycles - pause_cycles; + + Self { + ticks, + tone_cycles, + pause_cycles, + } + } + } + + impl std::iter::Iterator for NoteValueIter { + type Item = rmt::Symbol; + + // runs in ISR + fn next(&mut self) -> Option { + if self.tone_cycles + self.pause_cycles > 0 { + let high_state = if self.tone_cycles > 0 { + self.tone_cycles -= 1; + rmt::PinState::High + } else { + self.pause_cycles -= 1; + rmt::PinState::Low + }; + let level0 = rmt::Pulse::new(high_state, self.ticks); + let level1 = rmt::Pulse::new(rmt::PinState::Low, self.ticks); + Some(rmt::Symbol::new(level0, level1)) } else { - self.pause_cycles -= 1; - rmt::PinState::Low - }; - let level0 = rmt::Pulse::new(high_state, self.ticks); - let level1 = rmt::Pulse::new(rmt::PinState::Low, self.ticks); - Some(rmt::Symbol::new(level0, level1)) - } else { - None + None + } } } -} -#[derive(Debug)] -pub struct Note(u16); - -#[allow(dead_code)] -pub mod notes { - use crate::Note; - - pub const A4: Note = Note(440); - pub const AS4: Note = Note(466); - pub const B4: Note = Note(494); - pub const C5: Note = Note(523); - pub const CS5: Note = Note(554); - pub const D5: Note = Note(587); - pub const DS5: Note = Note(622); - pub const E5: Note = Note(659); - pub const F5: Note = Note(698); - pub const FS5: Note = Note(740); - pub const G5: Note = Note(784); - pub const GS5: Note = Note(831); - pub const A5: Note = Note(880); -} + #[derive(Debug)] + pub struct Note(u16); + + #[allow(dead_code)] + pub mod notes { + use super::Note; + + pub const A4: Note = Note(440); + pub const AS4: Note = Note(466); + pub const B4: Note = Note(494); + pub const C5: Note = Note(523); + pub const CS5: Note = Note(554); + pub const D5: Note = Note(587); + pub const DS5: Note = Note(622); + pub const E5: Note = Note(659); + pub const F5: Note = Note(698); + pub const FS5: Note = Note(740); + pub const G5: Note = Note(784); + pub const GS5: Note = Note(831); + pub const A5: Note = Note(880); + } -#[derive(Debug)] -pub struct NoteValue { - note: Note, - duration: Duration, -} + #[derive(Debug)] + pub struct NoteValue { + note: Note, + duration: Duration, + } -impl NoteValue { - pub fn play(&self, tx: &mut TxRmtDriver<'static>) -> anyhow::Result<()> { - let ticks_hz = tx.counter_clock()?; - tx.start_iter_blocking(self.iter(ticks_hz))?; - Ok(()) + impl NoteValue { + pub fn play(&self, tx: &mut TxRmtDriver<'static>) -> anyhow::Result<()> { + let ticks_hz = tx.counter_clock()?; + tx.start_iter_blocking(self.iter(ticks_hz))?; + Ok(()) + } + + pub fn iter(&self, ticks_hz: Hertz) -> NoteValueIter { + NoteValueIter::new(ticks_hz, self) + } } - pub fn iter(&self, ticks_hz: Hertz) -> NoteValueIter { - NoteValueIter::new(ticks_hz, self) + macro_rules! n { + ($n: expr, $duration: expr) => { + NoteValue { + note: $n, + duration: Duration::from_millis($duration), + } + }; } -} -macro_rules! n { - ($n: expr, $duration: expr) => { - NoteValue { - note: $n, - duration: Duration::from_millis($duration), - } - }; + const ODE_TO_JOY: &[NoteValue] = &[ + n!(FS5, 400), + n!(FS5, 600), + n!(G5, 400), + n!(A5, 400), + n!(A5, 400), + n!(G5, 400), + n!(FS5, 400), + n!(E5, 400), + n!(D5, 400), + n!(D5, 400), + n!(E5, 400), + n!(FS5, 400), + n!(FS5, 400), + n!(FS5, 200), + n!(E5, 200), + n!(E5, 800), + n!(FS5, 400), + n!(FS5, 600), + n!(G5, 400), + n!(A5, 400), + n!(A5, 400), + n!(G5, 400), + n!(FS5, 400), + n!(E5, 400), + n!(D5, 400), + n!(D5, 400), + n!(E5, 400), + n!(FS5, 400), + n!(E5, 400), + n!(E5, 200), + n!(D5, 200), + n!(D5, 800), + n!(E5, 400), + n!(E5, 400), + n!(FS5, 400), + n!(D5, 400), + n!(E5, 400), + n!(FS5, 200), + n!(G5, 200), + n!(FS5, 400), + n!(D5, 400), + n!(E5, 400), + n!(FS5, 200), + n!(G5, 200), + n!(FS5, 400), + n!(E5, 400), + n!(D5, 400), + n!(E5, 400), + n!(A4, 400), + n!(A4, 400), + n!(FS5, 400), + n!(FS5, 600), + n!(G5, 400), + n!(A5, 400), + n!(A5, 400), + n!(G5, 400), + n!(FS5, 400), + n!(E5, 400), + n!(D5, 400), + n!(D5, 400), + n!(E5, 400), + n!(FS5, 400), + n!(E5, 400), + n!(E5, 200), + n!(D5, 200), + n!(D5, 800), + ]; } -const ODE_TO_JOY: &[NoteValue] = &[ - n!(FS5, 400), - n!(FS5, 600), - n!(G5, 400), - n!(A5, 400), - n!(A5, 400), - n!(G5, 400), - n!(FS5, 400), - n!(E5, 400), - n!(D5, 400), - n!(D5, 400), - n!(E5, 400), - n!(FS5, 400), - n!(FS5, 400), - n!(FS5, 200), - n!(E5, 200), - n!(E5, 800), - n!(FS5, 400), - n!(FS5, 600), - n!(G5, 400), - n!(A5, 400), - n!(A5, 400), - n!(G5, 400), - n!(FS5, 400), - n!(E5, 400), - n!(D5, 400), - n!(D5, 400), - n!(E5, 400), - n!(FS5, 400), - n!(E5, 400), - n!(E5, 200), - n!(D5, 200), - n!(D5, 800), - n!(E5, 400), - n!(E5, 400), - n!(FS5, 400), - n!(D5, 400), - n!(E5, 400), - n!(FS5, 200), - n!(G5, 200), - n!(FS5, 400), - n!(D5, 400), - n!(E5, 400), - n!(FS5, 200), - n!(G5, 200), - n!(FS5, 400), - n!(E5, 400), - n!(D5, 400), - n!(E5, 400), - n!(A4, 400), - n!(A4, 400), - n!(FS5, 400), - n!(FS5, 600), - n!(G5, 400), - n!(A5, 400), - n!(A5, 400), - n!(G5, 400), - n!(FS5, 400), - n!(E5, 400), - n!(D5, 400), - n!(D5, 400), - n!(E5, 400), - n!(FS5, 400), - n!(E5, 400), - n!(E5, 200), - n!(D5, 200), - n!(D5, 800), -]; +// #[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +// #[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +// #[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +// #[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +// #[allow(dead_code)] +// #[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] diff --git a/examples/rmt_neopixel.rs b/examples/rmt_neopixel.rs index 0b344d6745e..05510ad40f4 100644 --- a/examples/rmt_neopixel.rs +++ b/examples/rmt_neopixel.rs @@ -9,100 +9,119 @@ //! Datasheet (PDF) for a WS2812, which explains how the pulses are to be sent: //! https://cdn-shop.adafruit.com/datasheets/WS2812.pdf -use anyhow::{bail, Result}; -use core::time::Duration; -use esp_idf_hal::delay::FreeRtos; -use esp_idf_hal::peripherals::Peripherals; -use esp_idf_hal::rmt::config::TransmitConfig; -use esp_idf_hal::rmt::*; +#[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +fn main() -> anyhow::Result<()> { + example::main() +} -fn main() -> Result<()> { - esp_idf_hal::sys::link_patches(); +#[cfg(not(any(feature = "rmt-legacy", esp_idf_version_major = "4")))] +fn main() -> anyhow::Result<()> { + println!("This example requires feature `rmt-legacy` enabled or using ESP-IDF v4.4.X"); - let peripherals = Peripherals::take()?; - // Onboard RGB LED pin - // ESP32-C3-DevKitC-02 gpio8, ESP32-C3-DevKit-RUST-1 gpio2 - let led = peripherals.pins.gpio2; - let channel = peripherals.rmt.channel0; - let config = TransmitConfig::new().clock_divider(1); - let mut tx = TxRmtDriver::new(channel, led, &config)?; + loop { + std::thread::sleep(std::time::Duration::from_millis(1000)); + } +} - // 3 seconds white at 10% brightness - neopixel(Rgb::new(25, 25, 25), &mut tx)?; - FreeRtos::delay_ms(3000); +#[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +mod example { + use std::time::Duration; - // infinite rainbow loop at 20% brightness - (0..360).cycle().try_for_each(|hue| { - FreeRtos::delay_ms(10); - let rgb = Rgb::from_hsv(hue, 100, 20)?; - neopixel(rgb, &mut tx) - }) -} + use anyhow::{bail, Result}; + use esp_idf_hal::{ + delay::FreeRtos, + prelude::Peripherals, + rmt::{config::TransmitConfig, FixedLengthSignal, PinState, Pulse, TxRmtDriver}, + }; + + pub fn main() -> Result<()> { + esp_idf_hal::sys::link_patches(); -fn neopixel(rgb: Rgb, tx: &mut TxRmtDriver) -> Result<()> { - let color: u32 = rgb.into(); - let ticks_hz = tx.counter_clock()?; - let (t0h, t0l, t1h, t1l) = ( - Pulse::new_with_duration(ticks_hz, PinState::High, &Duration::from_nanos(350))?, - Pulse::new_with_duration(ticks_hz, PinState::Low, &Duration::from_nanos(800))?, - Pulse::new_with_duration(ticks_hz, PinState::High, &Duration::from_nanos(700))?, - Pulse::new_with_duration(ticks_hz, PinState::Low, &Duration::from_nanos(600))?, - ); - let mut signal = FixedLengthSignal::<24>::new(); - for i in (0..24).rev() { - let p = 2_u32.pow(i); - let bit: bool = p & color != 0; - let (high_pulse, low_pulse) = if bit { (t1h, t1l) } else { (t0h, t0l) }; - signal.set(23 - i as usize, &(high_pulse, low_pulse))?; + let peripherals = Peripherals::take()?; + // Onboard RGB LED pin + // ESP32-C3-DevKitC-02 gpio8, ESP32-C3-DevKit-RUST-1 gpio2 + let led = peripherals.pins.gpio2; + let channel = peripherals.rmt.channel0; + let config = TransmitConfig::new().clock_divider(1); + let mut tx = TxRmtDriver::new(channel, led, &config)?; + + // 3 seconds white at 10% brightness + neopixel(Rgb::new(25, 25, 25), &mut tx)?; + FreeRtos::delay_ms(3000); + + // infinite rainbow loop at 20% brightness + (0..360).cycle().try_for_each(|hue| { + FreeRtos::delay_ms(10); + let rgb = Rgb::from_hsv(hue, 100, 20)?; + neopixel(rgb, &mut tx) + }) } - tx.start_blocking(&signal)?; - Ok(()) -} -struct Rgb { - r: u8, - g: u8, - b: u8, -} + fn neopixel(rgb: Rgb, tx: &mut TxRmtDriver) -> Result<()> { + let color: u32 = rgb.into(); + let ticks_hz = tx.counter_clock()?; + let (t0h, t0l, t1h, t1l) = ( + Pulse::new_with_duration(ticks_hz, PinState::High, &Duration::from_nanos(350))?, + Pulse::new_with_duration(ticks_hz, PinState::Low, &Duration::from_nanos(800))?, + Pulse::new_with_duration(ticks_hz, PinState::High, &Duration::from_nanos(700))?, + Pulse::new_with_duration(ticks_hz, PinState::Low, &Duration::from_nanos(600))?, + ); + let mut signal = FixedLengthSignal::<24>::new(); + for i in (0..24).rev() { + let p = 2_u32.pow(i); + let bit: bool = p & color != 0; + let (high_pulse, low_pulse) = if bit { (t1h, t1l) } else { (t0h, t0l) }; + signal.set(23 - i as usize, &(high_pulse, low_pulse))?; + } + tx.start_blocking(&signal)?; + Ok(()) + } -impl Rgb { - pub fn new(r: u8, g: u8, b: u8) -> Self { - Self { r, g, b } + struct Rgb { + r: u8, + g: u8, + b: u8, } - /// Converts hue, saturation, value to RGB - pub fn from_hsv(h: u32, s: u32, v: u32) -> Result { - if h > 360 || s > 100 || v > 100 { - bail!("The given HSV values are not in valid range"); + + impl Rgb { + pub fn new(r: u8, g: u8, b: u8) -> Self { + Self { r, g, b } + } + /// Converts hue, saturation, value to RGB + pub fn from_hsv(h: u32, s: u32, v: u32) -> Result { + if h > 360 || s > 100 || v > 100 { + bail!("The given HSV values are not in valid range"); + } + let s = s as f64 / 100.0; + let v = v as f64 / 100.0; + let c = s * v; + let x = c * (1.0 - (((h as f64 / 60.0) % 2.0) - 1.0).abs()); + let m = v - c; + let (r, g, b) = match h { + 0..=59 => (c, x, 0.0), + 60..=119 => (x, c, 0.0), + 120..=179 => (0.0, c, x), + 180..=239 => (0.0, x, c), + 240..=299 => (x, 0.0, c), + _ => (c, 0.0, x), + }; + Ok(Self { + r: ((r + m) * 255.0) as u8, + g: ((g + m) * 255.0) as u8, + b: ((b + m) * 255.0) as u8, + }) } - let s = s as f64 / 100.0; - let v = v as f64 / 100.0; - let c = s * v; - let x = c * (1.0 - (((h as f64 / 60.0) % 2.0) - 1.0).abs()); - let m = v - c; - let (r, g, b) = match h { - 0..=59 => (c, x, 0.0), - 60..=119 => (x, c, 0.0), - 120..=179 => (0.0, c, x), - 180..=239 => (0.0, x, c), - 240..=299 => (x, 0.0, c), - _ => (c, 0.0, x), - }; - Ok(Self { - r: ((r + m) * 255.0) as u8, - g: ((g + m) * 255.0) as u8, - b: ((b + m) * 255.0) as u8, - }) } -} -impl From for u32 { - /// Convert RGB to u32 color value - /// - /// e.g. rgb: (1,2,4) - /// G R B - /// 7 0 7 0 7 0 - /// 00000010 00000001 00000100 - fn from(rgb: Rgb) -> Self { - ((rgb.r as u32) << 16) | ((rgb.g as u32) << 8) | rgb.b as u32 + impl From for u32 { + /// Convert RGB to u32 color value + /// + /// e.g. rgb: (1,2,4) + /// G R B + /// 7 0 7 0 7 0 + /// 00000010 00000001 00000100 + fn from(rgb: Rgb) -> Self { + ((rgb.r as u32) << 16) | ((rgb.g as u32) << 8) | rgb.b as u32 + } } } diff --git a/examples/rmt_onewire_temperature.rs b/examples/rmt_onewire_temperature.rs new file mode 100644 index 00000000000..e0e9ebad645 --- /dev/null +++ b/examples/rmt_onewire_temperature.rs @@ -0,0 +1,172 @@ +//! RMT Onewire Example +//! +//! Example demonstrating the use of the onewire component to measure temperature from the ds18b20 temperature probe. +//! +//! In order to use this example, an overidden `Cargo.toml` must be defined with the following definitions: +//! ``` +//! [[package.metadata.esp-idf-sys.extra_components]] +//! remote_component = { name = "onewire_bus", version = "^1.0.2" } +//! +//! +//! [patch.crates-io] +//! esp-idf-sys = { git = "https://github.com/esp-rs/esp-idf-sys", rev = "2728b85" } +//! +//! ``` +//! +//! The example can then be run with +//! `MCU= cargo run --example rmt_onewire --manifest-path /path/to/other/Cargo.toml` +//! +//! Below is a connection sketch, the signal pin must be externally pulled-up +//! with a 4.7kOhm resistor. +//! This example uses gpio 16, but any pin capable of +//! input AND output is suitable. +//! +//! If the example is successful, it should print the address of each +//! onewire device attached to the bus. +//! +//! ┌──────────────────────────┐ +//! │ 3.3V├───────┬─────────────┬──────────────────────┐ +//! │ │ ┌┴┐ │VDD │VDD +//! │ ESP Board │ 4.7k│ │ ┌──────┴──────┐ ┌──────┴──────┐ +//! │ │ └┬┘ DQ│ │ DQ│ │ +//! │ ONEWIRE_GPIO_PIN├───────┴──┬───┤ DS18B20 │ ┌───┤ DS18B20 │ ...... +//! │ │ └───│-------------│────┴───│-------------│── +//! │ │ └──────┬──────┘ └──────┬──────┘ +//! │ │ │GND │GND +//! │ GND├─────────────────────┴──────────────────────┘ +//! └──────────────────────────┘ +//! +//! +//! This example demonstrates: +//! * A RMT device in both TX and RX mode. +//! * Usage of the onewire bus driver interface. +//! * How to iterate through a device search to discover devices on the bus. + +use std::time::Duration; + +use esp_idf_hal::delay::FreeRtos; +#[cfg(all( + esp_idf_soc_rmt_supported, + not(feature = "rmt-legacy"), + esp_idf_comp_espressif__onewire_bus_enabled, +))] +use esp_idf_hal::onewire::{OWAddress, OWCommand, OWDriver}; +use esp_idf_hal::peripherals::Peripherals; +use esp_idf_sys::EspError; + +#[cfg(all( + esp_idf_soc_rmt_supported, + not(esp_idf_version_major = "4"), + esp_idf_comp_espressif__onewire_bus_enabled, +))] +fn main() -> anyhow::Result<()> { + println!("Starting APP!"); + + let peripherals = Peripherals::take()?; + + let channel = peripherals.rmt.channel0; + let onewire_gpio_pin = peripherals.pins.gpio16; + + let mut onewire_bus: OWDriver = OWDriver::new(onewire_gpio_pin, channel)?; + let device = { + let mut search = onewire_bus.search()?; + search.next() + }; + + if device.is_none() { + println!("No device found"); + return Ok(()); + } + + let device = device.unwrap(); + if let Err(err) = device { + println!("An error occured searching for the device, err = {}", err); + return Err(err.into()); + } + let device = device.unwrap(); + println!( + "Found Device: {:?}, family code = {}", + device, + device.family_code() + ); + + loop { + ds18b20_trigger_temp_conversion(&device, &onewire_bus)?; + let temp = ds18b20_get_temperature(&device, &onewire_bus)?; + println!("Temperature: {}", temp); + FreeRtos::delay_ms(3000); + } +} + +#[cfg(any( + feature = "rmt-legacy", + esp_idf_version_major = "4", + not(esp_idf_comp_espressif__onewire_bus_enabled), + not(esp_idf_soc_rmt_supported), +))] +fn main() -> anyhow::Result<()> { + println!("This example requires feature `rmt-legacy` disabled, using ESP-IDF > v4.4.X, the component included in `Cargo.toml`, or is not supported on this MCU"); + + loop { + std::thread::sleep(std::time::Duration::from_millis(1000)); + } +} + +#[cfg(all( + esp_idf_soc_rmt_supported, + not(esp_idf_version_major = "4"), + esp_idf_comp_espressif__onewire_bus_enabled, +))] +fn ds18b20_send_command<'a>(addr: &OWAddress, bus: &OWDriver, cmd: u8) -> Result<(), EspError> { + let mut buf = [0; 10]; + buf[0] = OWCommand::MatchRom as _; + let addr = addr.address().to_le_bytes(); + buf[1..9].copy_from_slice(&addr); + buf[9] = cmd; + + bus.write(&buf) +} + +#[allow(dead_code)] +#[repr(u8)] +enum Ds18b20Command { + ConvertTemp = 0x44, + WriteScratch = 0x4E, + ReadScratch = 0xBE, +} +#[cfg(all( + esp_idf_soc_rmt_supported, + not(esp_idf_version_major = "4"), + esp_idf_comp_espressif__onewire_bus_enabled, +))] +fn ds18b20_trigger_temp_conversion<'a>(addr: &OWAddress, bus: &OWDriver) -> Result<(), EspError> { + // reset bus and check if the ds18b20 is present + bus.reset()?; + + ds18b20_send_command(addr, bus, Ds18b20Command::ConvertTemp as u8)?; + + // delay proper time for temp conversion, + // assume max resolution (12-bits) + std::thread::sleep(Duration::from_millis(800)); + + Ok(()) +} +#[cfg(all( + esp_idf_soc_rmt_supported, + not(esp_idf_version_major = "4"), + esp_idf_comp_espressif__onewire_bus_enabled, +))] +fn ds18b20_get_temperature<'a>(addr: &OWAddress, bus: &OWDriver) -> Result { + bus.reset()?; + + ds18b20_send_command(addr, bus, Ds18b20Command::ReadScratch as u8)?; + + let mut buf = [0u8; 10]; + bus.read(&mut buf)?; + let lsb = buf[0]; + let msb = buf[1]; + + let temp_raw: u16 = (u16::from(msb) << 8) | u16::from(lsb); + + Ok(f32::from(temp_raw) / 16.0) +} diff --git a/examples/rmt_transceiver.rs b/examples/rmt_transceiver.rs index 33466bb6fb3..4efdec3c375 100644 --- a/examples/rmt_transceiver.rs +++ b/examples/rmt_transceiver.rs @@ -18,90 +18,109 @@ //! level0 = High dur0 = PulseTicks(210) level1 = Low dur1 = PulseTicks(0) //! Tx Loop -use esp_idf_hal::delay::FreeRtos; -use esp_idf_hal::peripherals::Peripherals; -use esp_idf_hal::rmt::{ - FixedLengthSignal, PinState, Pulse, PulseTicks, Receive, RmtReceiveConfig, RmtTransmitConfig, - RxRmtDriver, TxRmtDriver, -}; - +#[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] fn main() -> anyhow::Result<()> { - println!("Starting APP!"); - - let peripherals = Peripherals::take()?; - - /* - *********************** SET UP RMT RECEIVER ****************************** - */ - let mut rx = RxRmtDriver::new( - peripherals.rmt.channel2, - peripherals.pins.gpio2, - &RmtReceiveConfig::new().idle_threshold(700u16), - 250, - )?; - - rx.start()?; - - let _ = std::thread::Builder::new() - .stack_size(10000) - .spawn(move || loop { - println!("Rx Loop"); - - let mut pulses = [(Pulse::zero(), Pulse::zero()); 250]; + example::main() +} - // See sdkconfig.defaults to determine the tick time value ( default is one tick = 10 milliseconds) - // Set ticks_to_wait to 0 for non-blocking - let receive = rx.receive(&mut pulses, 0).unwrap(); +#[cfg(not(any(feature = "rmt-legacy", esp_idf_version_major = "4")))] +fn main() -> anyhow::Result<()> { + println!("This example requires feature `rmt-legacy` enabled or using ESP-IDF v4.4.X"); - if let Receive::Read(length) = receive { - let pulses = &pulses[..length]; + loop { + std::thread::sleep(std::time::Duration::from_millis(1000)); + } +} - for (pulse0, pulse1) in pulses { - println!("0={pulse0:?}, 1={pulse1:?}"); +#[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +mod example { + use esp_idf_hal::{ + delay::FreeRtos, + prelude::Peripherals, + rmt::{ + FixedLengthSignal, PinState, Pulse, PulseTicks, Receive, RmtReceiveConfig, + RmtTransmitConfig, RxRmtDriver, TxRmtDriver, + }, + }; + + pub fn main() -> anyhow::Result<()> { + println!("Starting APP!"); + + let peripherals = Peripherals::take()?; + + /* + *********************** SET UP RMT RECEIVER ****************************** + */ + let mut rx = RxRmtDriver::new( + peripherals.rmt.channel2, + peripherals.pins.gpio2, + &RmtReceiveConfig::new().idle_threshold(700u16), + 250, + )?; + + rx.start()?; + + let _ = std::thread::Builder::new() + .stack_size(10000) + .spawn(move || loop { + println!("Rx Loop"); + + let mut pulses = [(Pulse::zero(), Pulse::zero()); 250]; + + // See sdkconfig.defaults to determine the tick time value ( default is one tick = 10 milliseconds) + // Set ticks_to_wait to 0 for non-blocking + let receive = rx.receive(&mut pulses, 0).unwrap(); + + if let Receive::Read(length) = receive { + let pulses = &pulses[..length]; + + for (pulse0, pulse1) in pulses { + println!("0={pulse0:?}, 1={pulse1:?}"); + } } - } - FreeRtos::delay_ms(500); + FreeRtos::delay_ms(500); + }); + + /* + *********************** SET UP RMT TRANSMITTER ****************************** + */ + + // Prepare the tx_config + // The default uses one memory block or 64 signals and clock divider set to 80 (1us tick) + let mut tx = TxRmtDriver::new( + peripherals.rmt.channel0, + peripherals.pins.gpio4, + &RmtTransmitConfig::new(), + )?; + + // Prepare signal pulse signal to be sent. + let one_low = Pulse::new(PinState::Low, PulseTicks::new(410)?); + let one_high = Pulse::new(PinState::High, PulseTicks::new(210)?); + let zero_low = Pulse::new(PinState::Low, PulseTicks::new(210)?); + let zero_high = Pulse::new(PinState::High, PulseTicks::new(410)?); + let sync_low = Pulse::new(PinState::Low, PulseTicks::new(620)?); + let sync_high = Pulse::new(PinState::High, PulseTicks::new(620)?); + + let _ = std::thread::spawn(move || loop { + println!("Tx Loop"); + + // Create a sequence + let mut signal = FixedLengthSignal::<5>::new(); + signal.set(0, &(sync_high, sync_low)).unwrap(); + signal.set(1, &(sync_high, sync_low)).unwrap(); + signal.set(2, &(one_high, one_low)).unwrap(); + signal.set(3, &(zero_high, zero_low)).unwrap(); + signal.set(4, &(one_high, one_low)).unwrap(); + + // Transmit the signal (send sequence) + tx.start(signal).unwrap(); + + FreeRtos::delay_ms(1000); }); - /* - *********************** SET UP RMT TRANSMITTER ****************************** - */ - - // Prepare the tx_config - // The default uses one memory block or 64 signals and clock divider set to 80 (1us tick) - let mut tx = TxRmtDriver::new( - peripherals.rmt.channel0, - peripherals.pins.gpio4, - &RmtTransmitConfig::new(), - )?; - - // Prepare signal pulse signal to be sent. - let one_low = Pulse::new(PinState::Low, PulseTicks::new(410)?); - let one_high = Pulse::new(PinState::High, PulseTicks::new(210)?); - let zero_low = Pulse::new(PinState::Low, PulseTicks::new(210)?); - let zero_high = Pulse::new(PinState::High, PulseTicks::new(410)?); - let sync_low = Pulse::new(PinState::Low, PulseTicks::new(620)?); - let sync_high = Pulse::new(PinState::High, PulseTicks::new(620)?); - - let _ = std::thread::spawn(move || loop { - println!("Tx Loop"); - - // Create a sequence - let mut signal = FixedLengthSignal::<5>::new(); - signal.set(0, &(sync_high, sync_low)).unwrap(); - signal.set(1, &(sync_high, sync_low)).unwrap(); - signal.set(2, &(one_high, one_low)).unwrap(); - signal.set(3, &(zero_high, zero_low)).unwrap(); - signal.set(4, &(one_high, one_low)).unwrap(); - - // Transmit the signal (send sequence) - tx.start(signal).unwrap(); - - FreeRtos::delay_ms(1000); - }); - - loop { - FreeRtos::delay_ms(3000); + loop { + FreeRtos::delay_ms(3000); + } } } diff --git a/src/lib.rs b/src/lib.rs index 921666272f2..da00c543380 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,12 +40,23 @@ pub mod ledc; #[cfg(any(all(esp32, esp_idf_eth_use_esp32_emac), esp_idf_eth_use_openeth))] pub mod mac; pub mod modem; +#[cfg(all( + esp_idf_soc_rmt_supported, + not(esp_idf_version_major = "4"), + esp_idf_comp_espressif__onewire_bus_enabled, +))] +pub mod onewire; #[cfg(any(esp32, esp32s2, esp32s3, esp32c6))] pub mod pcnt; pub mod peripheral; pub mod peripherals; pub mod prelude; pub mod reset; + +// mutually exclusive features assert +#[cfg(all(feature = "rmt-legacy", esp_idf_comp_espressif__onewire_bus_enabled))] +compile_error!("the onewire component cannot be used with the legacy rmt peripheral"); + pub mod rmt; pub mod rom; #[cfg(feature = "experimental")] diff --git a/src/onewire.rs b/src/onewire.rs new file mode 100644 index 00000000000..f086868a1e2 --- /dev/null +++ b/src/onewire.rs @@ -0,0 +1,161 @@ +//! RMT-based Onewire Implementation +//! +//! The Onewire module driver can be used to communicate with onewire (1-Wire) +//! devices. +//! +//! This module is an abstraction around the esp-idf component [onewire_bus](https://components.espressif.com/components/espressif/onewire_bus) +//! implementation. It is recommended to read the usage of the C API in this [example](https://github.com/espressif/esp-idf/tree/v5.2.2/examples/peripherals/rmt/onewire) +//! +//! +//! This implementation currently supports the one-wire API from the new (v5) esp-idf API. +//! +//! The pin this peripheral is attached to must be +//! externally pulled-up with a 4.7kOhm resistor. +//! +//! todo: +//! - crc checking on messages +//! - helper methods on the driver for executing commands +//! +//! See the `examples/` folder of this repository for more. + +use core::marker::PhantomData; +use core::ptr; + +use esp_idf_sys::*; + +use crate::peripheral::Peripheral; +use crate::rmt::RmtChannel; + +/// Onewire Address type +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct OWAddress(u64); + +impl OWAddress { + pub fn address(&self) -> u64 { + self.0 + } + + pub fn family_code(&self) -> u8 { + (self.0 & u64::from(0xffu8)) as u8 + } +} + +/// Wrapper around a device iterator to search for available devices on the bus +pub struct DeviceSearch<'a, 'b> { + search: onewire_device_iter_handle_t, + _bus: &'a mut OWDriver<'b>, +} + +impl<'a, 'b> DeviceSearch<'a, 'b> { + fn new(bus: &'a mut OWDriver<'b>) -> Result { + let mut my_iter: onewire_device_iter_handle_t = ptr::null_mut(); + + esp!(unsafe { onewire_new_device_iter(bus.bus, &mut my_iter) })?; + + Ok(Self { + search: my_iter, + _bus: bus, + }) + } + + /// Search for the next device on the bus and yield it. + fn next_device(&mut self) -> Result { + let mut next_onewire_device = onewire_device_t::default(); + esp!(unsafe { onewire_device_iter_get_next(self.search, &mut next_onewire_device) })?; + Ok(OWAddress(next_onewire_device.address)) + } +} + +impl<'a, 'b> Iterator for DeviceSearch<'a, 'b> { + type Item = Result; + + fn next(&mut self) -> Option { + match self.next_device() { + Ok(addr) => Some(Ok(addr)), + Err(err) if err.code() == ESP_ERR_NOT_FOUND => None, + Err(err) => Some(Err(err)), + } + } +} + +impl<'a, 'b> Drop for DeviceSearch<'a, 'b> { + fn drop(&mut self) { + esp!(unsafe { onewire_del_device_iter(self.search) }).unwrap(); + } +} + +#[derive(Debug)] +pub struct OWDriver<'a> { + bus: onewire_bus_handle_t, + _channel: u8, + _p: PhantomData<&'a mut ()>, +} + +impl<'a> OWDriver<'a> { + /// Create a new One Wire driver on the allocated pin. + /// + /// The pin will be used as an open drain output. + pub fn new( + pin: impl Peripheral

+ 'a, + _channel: impl Peripheral

+ 'a, + ) -> Result { + let mut bus: onewire_bus_handle_t = ptr::null_mut(); + + let pin = pin.into_ref().pin(); + let bus_config = esp_idf_sys::onewire_bus_config_t { bus_gpio_num: pin }; + + let rmt_config = esp_idf_sys::onewire_bus_rmt_config_t { max_rx_bytes: 10 }; + + esp!(unsafe { onewire_new_bus_rmt(&bus_config, &rmt_config, &mut bus as _) })?; + + Ok(Self { + bus, + _channel: C::channel() as _, + _p: PhantomData, + }) + } + + pub fn read(&self, buff: &mut [u8]) -> Result<(), EspError> { + esp!(unsafe { onewire_bus_read_bytes(self.bus, buff.as_mut_ptr() as *mut _, buff.len()) })?; + + Ok(()) + } + + pub fn write(&self, data: &[u8]) -> Result<(), EspError> { + esp!(unsafe { onewire_bus_write_bytes(self.bus, data.as_ptr(), data.len() as u8) })?; + + Ok(()) + } + + /// Send reset pulse to the bus, and check if there are devices attached to the bus + /// + /// If there are no devices on the bus, this will result in an error. + pub fn reset(&self) -> Result<(), EspError> { + esp!(unsafe { onewire_bus_reset(self.bus) }) + } + + /// Start a search for devices attached to the OneWire bus. + pub fn search(&mut self) -> Result, EspError> { + DeviceSearch::new(self) + } +} + +impl<'d> Drop for OWDriver<'d> { + fn drop(&mut self) { + esp!(unsafe { onewire_bus_del(self.bus) }).unwrap(); + } +} + +unsafe impl<'d> Send for OWDriver<'d> {} + +/// Command codes +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[repr(u8)] +pub enum OWCommand { + Search = 0xF0, //Obtain IDs of all devices on the bus + MatchRom = 0x55, //Address specific device + SkipRom = 0xCC, //Skip addressing + ReadRom = 0x33, //Identification + SearchAlarm = 0xEC, // Conditional search for all devices in an alarm state. + ReadPowerSupply = 0xB4, +} diff --git a/src/rmt.rs b/src/rmt.rs index 286fd94f690..a98edfdfb1d 100644 --- a/src/rmt.rs +++ b/src/rmt.rs @@ -11,7 +11,6 @@ //! //! Not supported: //! * Interrupts. -//! * Receiving. //! * Change of config after initialisation. //! //! # Example @@ -51,26 +50,19 @@ //! [VariableLengthSignal] allows you to use the heap and incrementally add pulse items without knowing the size //! ahead of time. -use core::cell::UnsafeCell; -use core::marker::PhantomData; +use core::slice; use core::time::Duration; -use core::{ptr, slice}; #[cfg(feature = "alloc")] extern crate alloc; use esp_idf_sys::*; -use crate::gpio::InputPin; -use crate::gpio::OutputPin; -use crate::interrupt::InterruptType; -use crate::peripheral::Peripheral; use crate::units::Hertz; -use config::ReceiveConfig; -use config::TransmitConfig; - pub use chip::*; +#[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +pub use driver::*; // Might not always be available in the generated `esp-idf-sys` bindings const ERR_ERANGE: esp_err_t = 34; @@ -495,282 +487,6 @@ pub mod config { } } -/// The RMT transmitter driver. -/// -/// Use [`TxRmtDriver::start()`] or [`TxRmtDriver::start_blocking()`] to transmit pulses. -/// -/// See the [rmt module][crate::rmt] for more information. - -pub struct TxRmtDriver<'d> { - channel: u8, - _p: PhantomData<&'d mut ()>, -} - -impl<'d> TxRmtDriver<'d> { - /// Initialise the rmt module with the specified pin, channel and configuration. - /// - /// To uninstall the driver just drop it. - /// - /// Internally this calls `rmt_config()` and `rmt_driver_install()`. - pub fn new( - _channel: impl Peripheral

+ 'd, - pin: impl Peripheral

+ 'd, - config: &TransmitConfig, - ) -> Result { - crate::into_ref!(pin); - - let mut flags = 0; - if config.aware_dfs { - flags |= RMT_CHANNEL_FLAGS_AWARE_DFS; - } - - let carrier_en = config.carrier.is_some(); - let carrier = config.carrier.unwrap_or_default(); - - let sys_config = rmt_config_t { - rmt_mode: rmt_mode_t_RMT_MODE_TX, - channel: C::channel(), - gpio_num: pin.pin(), - clk_div: config.clock_divider, - mem_block_num: config.mem_block_num, - flags, - __bindgen_anon_1: rmt_config_t__bindgen_ty_1 { - tx_config: rmt_tx_config_t { - carrier_en, - carrier_freq_hz: carrier.frequency.into(), - carrier_level: carrier.carrier_level as u32, - carrier_duty_percent: carrier.duty_percent.0, - idle_output_en: config.idle.is_some(), - idle_level: config.idle.map(|i| i as u32).unwrap_or(0), - loop_en: config.looping != config::Loop::None, - #[cfg(any( - all(not(esp_idf_version_major = "4"), not(esp_idf_version_major = "5")), - all(esp_idf_version_major = "5", not(esp_idf_version_minor = "0")), - not(esp32) - ))] - loop_count: match config.looping { - config::Loop::Count(count) if count > 0 && count < 1024 => count, - _ => 0, - }, - }, - }, - }; - - unsafe { - esp!(rmt_config(&sys_config))?; - esp!(rmt_driver_install( - C::channel(), - 0, - InterruptType::to_native(config.intr_flags) as _ - ))?; - } - - Ok(Self { - channel: C::channel() as _, - _p: PhantomData, - }) - } - - /// Get speed of the channel’s internal counter clock. - /// - /// This calls [rmt_get_counter_clock()][rmt_get_counter_clock] - /// internally. It is used for calculating the number of ticks per second for pulses. - /// - /// See [Pulse::new_with_duration()]. - /// - /// [rmt_get_counter_clock]: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/rmt.html#_CPPv421rmt_get_counter_clock13rmt_channel_tP8uint32_t - pub fn counter_clock(&self) -> Result { - let mut ticks_hz: u32 = 0; - esp!(unsafe { rmt_get_counter_clock(self.channel(), &mut ticks_hz) })?; - Ok(ticks_hz.into()) - } - - /// Start sending the given signal without blocking. - /// - /// `signal` is captured for safety so that the user can't change the data while transmitting. - pub fn start(&mut self, signal: S) -> Result<(), EspError> - where - S: Signal, - { - self.write_items(&signal, false) - } - - /// Start sending the given signal while blocking. - pub fn start_blocking(&mut self, signal: &S) -> Result<(), EspError> - where - S: Signal + ?Sized, - { - self.write_items(signal, true) - } - - fn write_items(&mut self, signal: &S, block: bool) -> Result<(), EspError> - where - S: Signal + ?Sized, - { - let items = signal.as_slice(); - esp!(unsafe { rmt_write_items(self.channel(), items.as_ptr(), items.len() as i32, block) }) - } - - /// Transmit all items in `iter` without blocking. - /// - /// Note that this requires `iter` to be [`Box`]ed for an allocation free version see [`Self::start_iter_blocking`]. - /// - /// ### Warning - /// - /// Iteration of `iter` happens inside an interrupt handler so beware of side-effects - /// that don't work in interrupt handlers. Iteration must also be fast so that there - /// are no time-gaps between successive transmissions where the perhipheral has to - /// wait for items. This can cause weird behavior and can be counteracted with - /// increasing [`Config::mem_block_num`] or making iteration more efficient. - #[cfg(feature = "alloc")] - pub fn start_iter(&mut self, iter: T) -> Result<(), EspError> - where - T: Iterator + Send + 'static, - { - let iter = alloc::boxed::Box::new(UnsafeCell::new(iter)); - unsafe { - esp!(rmt_translator_init( - self.channel(), - Some(Self::translate_iterator::), - ))?; - - esp!(rmt_write_sample( - self.channel(), - alloc::boxed::Box::leak(iter) as *const _ as _, - 1, - false - )) - } - } - - /// Transmit all items in `iter`, blocking until all items are transmitted. - /// - /// This method does not require any allocations since the thread is paused until all - /// items are transmitted. The iterator lives on the stack and will be dropped after - /// all items are written and before this method returns. - /// - /// ### Warning - /// - /// Iteration of `iter` happens inside an interrupt handler so beware of side-effects - /// that don't work in interrupt handlers. Iteration must also be fast so that there - /// are no time-gaps between successive transmissions where the perhipheral has to - /// wait for items. This can cause weird behavior and can be counteracted with - /// increasing [`Config::mem_block_num`] or making iteration more efficient. - - pub fn start_iter_blocking(&mut self, iter: T) -> Result<(), EspError> - where - T: Iterator + Send, - { - let iter = UnsafeCell::new(iter); - unsafe { - // TODO: maybe use a separate struct so that we don't have to do this when - // transmitting the same iterator type. - esp!(rmt_translator_init( - self.channel(), - Some(Self::translate_iterator::), - ))?; - esp!(rmt_write_sample( - self.channel(), - &iter as *const _ as _, - 24, - true - )) - } - } - - /// The translator that turns an iterator into `rmt_item32_t` elements. Most of the - /// magic happens here. - /// - /// The general idea is that we can fill a buffer (`dest`) of `rmt_item32_t` items of - /// length `wanted_num` with the items that we get from the iterator. Then we can tell - /// the peripheral driver how many items we filled in by setting `item_num`. The - /// driver will call this function over-and-over until `translated_size` is equal to - /// `src_size` so when the iterator returns [`None`] we set `translated_size` to - /// `src_size` to signal that there are no more items to translate. - /// - /// The compiler will generate this function for every different call to - /// [`Self::start_iter_blocking`] and [`Self::start_iter`] with different iterator - /// types because of the type parameter. This is done to avoid the double indirection - /// that we'd have to do when using a trait object since references to trait objects - /// are fat-pointers (2 `usize` wide) and we only get a narrow pointer (`src`). - /// Using a trait object has the addional overhead that every call to `Iterator::next` - /// would also be indirect (through the `vtable`) and couldn't be inlined. - unsafe extern "C" fn translate_iterator( - src: *const core::ffi::c_void, - mut dest: *mut rmt_item32_t, - src_size: usize, - wanted_num: usize, - translated_size: *mut usize, - item_num: *mut usize, - ) where - T: Iterator, - { - // An `UnsafeCell` is needed here because we're casting a `*const` to a `*mut`. - // Safe because this is the only existing reference. - let iter = &mut *UnsafeCell::raw_get(src as *const UnsafeCell); - - let mut i = 0; - let finished = loop { - if i >= wanted_num { - break 0; - } - - if let Some(item) = iter.next() { - *dest = item.0; - dest = dest.add(1); - i += 1; - } else { - // Only deallocate the iter if the const generics argument is `true` - // otherwise we could be deallocating stack memory. - #[cfg(feature = "alloc")] - if DEALLOC_ITER { - drop(alloc::boxed::Box::from_raw(iter)); - } - break src_size; - } - }; - - *item_num = i; - *translated_size = finished; - } - - /// Stop transmitting. - pub fn stop(&mut self) -> Result<(), EspError> { - esp!(unsafe { rmt_tx_stop(self.channel()) }) - } - - pub fn set_looping(&mut self, looping: config::Loop) -> Result<(), EspError> { - esp!(unsafe { rmt_set_tx_loop_mode(self.channel(), looping != config::Loop::None) })?; - - #[cfg(not(any(esp32, esp32c2)))] - esp!(unsafe { - rmt_set_tx_loop_count( - self.channel(), - match looping { - config::Loop::Count(count) if count > 0 && count < 1024 => count, - _ => 0, - }, - ) - })?; - - Ok(()) - } - - pub fn channel(&self) -> rmt_channel_t { - self.channel as _ - } -} - -impl<'d> Drop for TxRmtDriver<'d> { - /// Stop transmitting and release the driver. - fn drop(&mut self) { - self.stop().unwrap(); - esp!(unsafe { rmt_driver_uninstall(self.channel()) }).unwrap(); - } -} - -unsafe impl<'d> Send for TxRmtDriver<'d> {} - /// Symbols /// /// Represents a single pulse cycle symbol comprised of mark (high) @@ -992,182 +708,486 @@ pub enum Receive { Timeout, } -/// The RMT receiver. -/// -/// Use [`RxRmtDriver::start()`] to receive pulses. -/// -/// See the [rmt module][crate::rmt] for more information. -pub struct RxRmtDriver<'d> { - channel: u8, - next_ringbuf_item: Option<(*mut rmt_item32_t, usize)>, - _p: PhantomData<&'d mut ()>, -} +#[cfg(any(feature = "rmt-legacy", esp_idf_version_major = "4"))] +mod driver { + use core::cell::UnsafeCell; + use core::marker::PhantomData; + use core::ptr; + + use config::{ReceiveConfig, TransmitConfig}; + use esp_idf_sys::{ + esp, rmt_config_t, rmt_config_t__bindgen_ty_1, rmt_item32_t, rmt_mode_t_RMT_MODE_TX, + rmt_set_tx_loop_mode, rmt_tx_config_t, rmt_tx_stop, vRingbufferReturnItem, EspError, + RMT_CHANNEL_FLAGS_AWARE_DFS, + }; + use esp_idf_sys::{rmt_channel_t, rmt_driver_uninstall}; + + use crate::gpio::InputPin; + use crate::interrupt::InterruptType; + use crate::{gpio::OutputPin, peripheral::Peripheral}; -impl<'d> RxRmtDriver<'d> { - /// Initialise the rmt module with the specified pin, channel and configuration. + use super::RmtChannel; + + use super::*; + + /// The RMT transmitter driver. /// - /// To uninstall the driver just drop it. + /// Use [`TxRmtDriver::start()`] or [`TxRmtDriver::start_blocking()`] to transmit pulses. /// - /// Internally this calls `rmt_config()` and `rmt_driver_install()`. + /// See the [rmt module][crate::rmt] for more information. - pub fn new( - _channel: impl Peripheral

+ 'd, - pin: impl Peripheral

+ 'd, - config: &ReceiveConfig, - ring_buf_size: usize, - ) -> Result { - crate::into_ref!(pin); - - #[cfg(not(any(esp32, esp32c2)))] - let carrier_en = config.carrier.is_some(); - - #[cfg(not(any(esp32, esp32c2)))] - let carrier = config.carrier.unwrap_or_default(); - - let sys_config = rmt_config_t { - rmt_mode: rmt_mode_t_RMT_MODE_RX, - channel: C::channel(), - gpio_num: pin.pin(), - clk_div: config.clock_divider, - mem_block_num: config.mem_block_num, - flags: 0, - __bindgen_anon_1: rmt_config_t__bindgen_ty_1 { - rx_config: rmt_rx_config_t { - idle_threshold: config.idle_threshold, - filter_ticks_thresh: config.filter_ticks_thresh, - filter_en: config.filter_en, - #[cfg(not(any(esp32, esp32c2)))] - rm_carrier: carrier_en, - #[cfg(not(any(esp32, esp32c2)))] - carrier_freq_hz: carrier.frequency.into(), - #[cfg(not(any(esp32, esp32c2)))] - carrier_level: carrier.carrier_level as u32, - #[cfg(not(any(esp32, esp32c2)))] - carrier_duty_percent: carrier.duty_percent.0, + pub struct TxRmtDriver<'d> { + channel: u8, + _p: PhantomData<&'d mut ()>, + } + + impl<'d> TxRmtDriver<'d> { + /// Initialise the rmt module with the specified pin, channel and configuration. + /// + /// To uninstall the driver just drop it. + /// + /// Internally this calls `rmt_config()` and `rmt_driver_install()`. + pub fn new( + _channel: impl Peripheral

+ 'd, + pin: impl Peripheral

+ 'd, + config: &TransmitConfig, + ) -> Result { + crate::into_ref!(pin); + + let mut flags = 0; + if config.aware_dfs { + flags |= RMT_CHANNEL_FLAGS_AWARE_DFS; + } + + let carrier_en = config.carrier.is_some(); + let carrier = config.carrier.unwrap_or_default(); + + let sys_config = rmt_config_t { + rmt_mode: rmt_mode_t_RMT_MODE_TX, + channel: C::channel(), + gpio_num: pin.pin(), + clk_div: config.clock_divider, + mem_block_num: config.mem_block_num, + flags, + __bindgen_anon_1: rmt_config_t__bindgen_ty_1 { + tx_config: rmt_tx_config_t { + carrier_en, + carrier_freq_hz: carrier.frequency.into(), + carrier_level: carrier.carrier_level as u32, + carrier_duty_percent: carrier.duty_percent.0, + idle_output_en: config.idle.is_some(), + idle_level: config.idle.map(|i| i as u32).unwrap_or(0), + loop_en: config.looping != config::Loop::None, + #[cfg(any( + all( + not(esp_idf_version_major = "4"), + not(esp_idf_version_major = "5") + ), + all(esp_idf_version_major = "5", not(esp_idf_version_minor = "0")), + not(esp32) + ))] + loop_count: match config.looping { + config::Loop::Count(count) if count > 0 && count < 1024 => count, + _ => 0, + }, + }, }, - }, - }; + }; - unsafe { - esp!(rmt_config(&sys_config))?; - esp!(rmt_driver_install( - C::channel(), - ring_buf_size * 4, - InterruptType::to_native(config.intr_flags) as _ - ))?; + unsafe { + esp!(rmt_config(&sys_config))?; + esp!(rmt_driver_install( + C::channel(), + 0, + InterruptType::to_native(config.intr_flags) as _ + ))?; + } + + Ok(Self { + channel: C::channel() as _, + _p: PhantomData, + }) } - Ok(Self { - channel: C::channel() as _, - next_ringbuf_item: None, - _p: PhantomData, - }) - } + /// Get speed of the channel’s internal counter clock. + /// + /// This calls [rmt_get_counter_clock()][rmt_get_counter_clock] + /// internally. It is used for calculating the number of ticks per second for pulses. + /// + /// See [Pulse::new_with_duration()]. + /// + /// [rmt_get_counter_clock]: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/rmt.html#_CPPv421rmt_get_counter_clock13rmt_channel_tP8uint32_t + pub fn counter_clock(&self) -> Result { + let mut ticks_hz: u32 = 0; + esp!(unsafe { rmt_get_counter_clock(self.channel(), &mut ticks_hz) })?; + Ok(ticks_hz.into()) + } - pub fn channel(&self) -> rmt_channel_t { - self.channel as _ - } + /// Start sending the given signal without blocking. + /// + /// `signal` is captured for safety so that the user can't change the data while transmitting. + pub fn start(&mut self, signal: S) -> Result<(), EspError> + where + S: Signal, + { + self.write_items(&signal, false) + } - /// Start receiving - pub fn start(&self) -> Result<(), EspError> { - esp!(unsafe { rmt_rx_start(self.channel(), true) }) - } + /// Start sending the given signal while blocking. + pub fn start_blocking(&mut self, signal: &S) -> Result<(), EspError> + where + S: Signal + ?Sized, + { + self.write_items(signal, true) + } - /// Stop receiving - pub fn stop(&self) -> Result<(), EspError> { - esp!(unsafe { rmt_rx_stop(self.channel()) }) - } + fn write_items(&mut self, signal: &S, block: bool) -> Result<(), EspError> + where + S: Signal + ?Sized, + { + let items = signal.as_slice(); + esp!(unsafe { + rmt_write_items(self.channel(), items.as_ptr(), items.len() as i32, block) + }) + } + + /// Transmit all items in `iter` without blocking. + /// + /// Note that this requires `iter` to be [`Box`]ed for an allocation free version see [`Self::start_iter_blocking`]. + /// + /// ### Warning + /// + /// Iteration of `iter` happens inside an interrupt handler so beware of side-effects + /// that don't work in interrupt handlers. Iteration must also be fast so that there + /// are no time-gaps between successive transmissions where the perhipheral has to + /// wait for items. This can cause weird behavior and can be counteracted with + /// increasing [`Config::mem_block_num`] or making iteration more efficient. + #[cfg(feature = "alloc")] + pub fn start_iter(&mut self, iter: T) -> Result<(), EspError> + where + T: Iterator + Send + 'static, + { + let iter = alloc::boxed::Box::new(UnsafeCell::new(iter)); + unsafe { + esp!(rmt_translator_init( + self.channel(), + Some(Self::translate_iterator::), + ))?; + + esp!(rmt_write_sample( + self.channel(), + alloc::boxed::Box::leak(iter) as *const _ as _, + 1, + false + )) + } + } - pub fn receive( - &mut self, - buf: &mut [(Pulse, Pulse)], - ticks_to_wait: TickType_t, - ) -> Result { - if let Some(items) = self.fetch_ringbuf_next_item(ticks_to_wait)? { - if items.len() <= buf.len() { - for (index, item) in items.iter().enumerate() { - let item = unsafe { item.__bindgen_anon_1.__bindgen_anon_1 }; - - buf[index] = ( - Pulse::new( - item.level0().into(), - PulseTicks::new(item.duration0().try_into().unwrap()).unwrap(), - ), - Pulse::new( - item.level1().into(), - PulseTicks::new(item.duration1().try_into().unwrap()).unwrap(), - ), - ); + /// Transmit all items in `iter`, blocking until all items are transmitted. + /// + /// This method does not require any allocations since the thread is paused until all + /// items are transmitted. The iterator lives on the stack and will be dropped after + /// all items are written and before this method returns. + /// + /// ### Warning + /// + /// Iteration of `iter` happens inside an interrupt handler so beware of side-effects + /// that don't work in interrupt handlers. Iteration must also be fast so that there + /// are no time-gaps between successive transmissions where the perhipheral has to + /// wait for items. This can cause weird behavior and can be counteracted with + /// increasing [`Config::mem_block_num`] or making iteration more efficient. + + pub fn start_iter_blocking(&mut self, iter: T) -> Result<(), EspError> + where + T: Iterator + Send, + { + let iter = UnsafeCell::new(iter); + unsafe { + // TODO: maybe use a separate struct so that we don't have to do this when + // transmitting the same iterator type. + esp!(rmt_translator_init( + self.channel(), + Some(Self::translate_iterator::), + ))?; + esp!(rmt_write_sample( + self.channel(), + &iter as *const _ as _, + 24, + true + )) + } + } + + /// The translator that turns an iterator into `rmt_item32_t` elements. Most of the + /// magic happens here. + /// + /// The general idea is that we can fill a buffer (`dest`) of `rmt_item32_t` items of + /// length `wanted_num` with the items that we get from the iterator. Then we can tell + /// the peripheral driver how many items we filled in by setting `item_num`. The + /// driver will call this function over-and-over until `translated_size` is equal to + /// `src_size` so when the iterator returns [`None`] we set `translated_size` to + /// `src_size` to signal that there are no more items to translate. + /// + /// The compiler will generate this function for every different call to + /// [`Self::start_iter_blocking`] and [`Self::start_iter`] with different iterator + /// types because of the type parameter. This is done to avoid the double indirection + /// that we'd have to do when using a trait object since references to trait objects + /// are fat-pointers (2 `usize` wide) and we only get a narrow pointer (`src`). + /// Using a trait object has the addional overhead that every call to `Iterator::next` + /// would also be indirect (through the `vtable`) and couldn't be inlined. + unsafe extern "C" fn translate_iterator( + src: *const core::ffi::c_void, + mut dest: *mut rmt_item32_t, + src_size: usize, + wanted_num: usize, + translated_size: *mut usize, + item_num: *mut usize, + ) where + T: Iterator, + { + // An `UnsafeCell` is needed here because we're casting a `*const` to a `*mut`. + // Safe because this is the only existing reference. + let iter = &mut *UnsafeCell::raw_get(src as *const UnsafeCell); + + let mut i = 0; + let finished = loop { + if i >= wanted_num { + break 0; } - let len = items.len(); + if let Some(item) = iter.next() { + *dest = item.0; + dest = dest.add(1); + i += 1; + } else { + // Only deallocate the iter if the const generics argument is `true` + // otherwise we could be deallocating stack memory. + #[cfg(feature = "alloc")] + if DEALLOC_ITER { + drop(alloc::boxed::Box::from_raw(iter)); + } + break src_size; + } + }; + + *item_num = i; + *translated_size = finished; + } - self.return_ringbuf_item()?; + /// Stop transmitting. + pub fn stop(&mut self) -> Result<(), EspError> { + esp!(unsafe { rmt_tx_stop(self.channel()) }) + } - Ok(Receive::Read(len)) - } else { - Ok(Receive::Overflow(items.len())) - } - } else { - Ok(Receive::Timeout) + pub fn set_looping(&mut self, looping: config::Loop) -> Result<(), EspError> { + esp!(unsafe { rmt_set_tx_loop_mode(self.channel(), looping != config::Loop::None) })?; + + #[cfg(not(any(esp32, esp32c2)))] + esp!(unsafe { + rmt_set_tx_loop_count( + self.channel(), + match looping { + config::Loop::Count(count) if count > 0 && count < 1024 => count, + _ => 0, + }, + ) + })?; + + Ok(()) + } + + pub fn channel(&self) -> rmt_channel_t { + self.channel as _ } } - fn fetch_ringbuf_next_item( - &mut self, - ticks_to_wait: TickType_t, - ) -> Result, EspError> { - if let Some((rmt_items, length)) = self.next_ringbuf_item { - Ok(Some(unsafe { - core::slice::from_raw_parts(rmt_items, length) - })) - } else { - let mut ringbuf_handle = ptr::null_mut(); - esp!(unsafe { rmt_get_ringbuf_handle(self.channel(), &mut ringbuf_handle) })?; + impl<'d> Drop for TxRmtDriver<'d> { + /// Stop transmitting and release the driver. + fn drop(&mut self) { + self.stop().unwrap(); + esp!(unsafe { rmt_driver_uninstall(self.channel()) }).unwrap(); + } + } + + unsafe impl<'d> Send for TxRmtDriver<'d> {} - let mut length = 0; - let rmt_items: *mut rmt_item32_t = unsafe { - xRingbufferReceive(ringbuf_handle.cast(), &mut length, ticks_to_wait).cast() + /// The RMT receiver. + /// + /// Use [`RxRmtDriver::start()`] to receive pulses. + /// + /// See the [rmt module][crate::rmt] for more information. + pub struct RxRmtDriver<'d> { + channel: u8, + next_ringbuf_item: Option<(*mut rmt_item32_t, usize)>, + _p: PhantomData<&'d mut ()>, + } + + impl<'d> RxRmtDriver<'d> { + /// Initialise the rmt module with the specified pin, channel and configuration. + /// + /// To uninstall the driver just drop it. + /// + /// Internally this calls `rmt_config()` and `rmt_driver_install()`. + + pub fn new( + _channel: impl Peripheral

+ 'd, + pin: impl Peripheral

+ 'd, + config: &ReceiveConfig, + ring_buf_size: usize, + ) -> Result { + crate::into_ref!(pin); + + #[cfg(not(any(esp32, esp32c2)))] + let carrier_en = config.carrier.is_some(); + + #[cfg(not(any(esp32, esp32c2)))] + let carrier = config.carrier.unwrap_or_default(); + + let sys_config = rmt_config_t { + rmt_mode: rmt_mode_t_RMT_MODE_RX, + channel: C::channel(), + gpio_num: pin.pin(), + clk_div: config.clock_divider, + mem_block_num: config.mem_block_num, + flags: 0, + __bindgen_anon_1: rmt_config_t__bindgen_ty_1 { + rx_config: rmt_rx_config_t { + idle_threshold: config.idle_threshold, + filter_ticks_thresh: config.filter_ticks_thresh, + filter_en: config.filter_en, + #[cfg(not(any(esp32, esp32c2)))] + rm_carrier: carrier_en, + #[cfg(not(any(esp32, esp32c2)))] + carrier_freq_hz: carrier.frequency.into(), + #[cfg(not(any(esp32, esp32c2)))] + carrier_level: carrier.carrier_level as u32, + #[cfg(not(any(esp32, esp32c2)))] + carrier_duty_percent: carrier.duty_percent.0, + }, + }, }; - if rmt_items.is_null() { - Ok(None) + unsafe { + esp!(rmt_config(&sys_config))?; + esp!(rmt_driver_install( + C::channel(), + ring_buf_size * 4, + InterruptType::to_native(config.intr_flags) as _ + ))?; + } + + Ok(Self { + channel: C::channel() as _, + next_ringbuf_item: None, + _p: PhantomData, + }) + } + + pub fn channel(&self) -> rmt_channel_t { + self.channel as _ + } + + /// Start receiving + pub fn start(&self) -> Result<(), EspError> { + esp!(unsafe { rmt_rx_start(self.channel(), true) }) + } + + /// Stop receiving + pub fn stop(&self) -> Result<(), EspError> { + esp!(unsafe { rmt_rx_stop(self.channel()) }) + } + + pub fn receive( + &mut self, + buf: &mut [(Pulse, Pulse)], + ticks_to_wait: TickType_t, + ) -> Result { + if let Some(items) = self.fetch_ringbuf_next_item(ticks_to_wait)? { + if items.len() <= buf.len() { + for (index, item) in items.iter().enumerate() { + let item = unsafe { item.__bindgen_anon_1.__bindgen_anon_1 }; + + buf[index] = ( + Pulse::new( + item.level0().into(), + PulseTicks::new(item.duration0().try_into().unwrap()).unwrap(), + ), + Pulse::new( + item.level1().into(), + PulseTicks::new(item.duration1().try_into().unwrap()).unwrap(), + ), + ); + } + + let len = items.len(); + + self.return_ringbuf_item()?; + + Ok(Receive::Read(len)) + } else { + Ok(Receive::Overflow(items.len())) + } } else { - let length = length / 4; - self.next_ringbuf_item = Some((rmt_items, length)); + Ok(Receive::Timeout) + } + } + fn fetch_ringbuf_next_item( + &mut self, + ticks_to_wait: TickType_t, + ) -> Result, EspError> { + if let Some((rmt_items, length)) = self.next_ringbuf_item { Ok(Some(unsafe { core::slice::from_raw_parts(rmt_items, length) })) + } else { + let mut ringbuf_handle = ptr::null_mut(); + esp!(unsafe { rmt_get_ringbuf_handle(self.channel(), &mut ringbuf_handle) })?; + + let mut length = 0; + let rmt_items: *mut rmt_item32_t = unsafe { + xRingbufferReceive(ringbuf_handle.cast(), &mut length, ticks_to_wait).cast() + }; + + if rmt_items.is_null() { + Ok(None) + } else { + let length = length / 4; + self.next_ringbuf_item = Some((rmt_items, length)); + + Ok(Some(unsafe { + core::slice::from_raw_parts(rmt_items, length) + })) + } } } - } - fn return_ringbuf_item(&mut self) -> Result<(), EspError> { - let mut ringbuf_handle = ptr::null_mut(); - esp!(unsafe { rmt_get_ringbuf_handle(self.channel(), &mut ringbuf_handle) })?; + fn return_ringbuf_item(&mut self) -> Result<(), EspError> { + let mut ringbuf_handle = ptr::null_mut(); + esp!(unsafe { rmt_get_ringbuf_handle(self.channel(), &mut ringbuf_handle) })?; - if let Some((rmt_items, _)) = self.next_ringbuf_item.take() { - unsafe { - vRingbufferReturnItem(ringbuf_handle, rmt_items.cast()); + if let Some((rmt_items, _)) = self.next_ringbuf_item.take() { + unsafe { + vRingbufferReturnItem(ringbuf_handle, rmt_items.cast()); + } + } else { + unreachable!(); } - } else { - unreachable!(); - } - Ok(()) + Ok(()) + } } -} -impl<'d> Drop for RxRmtDriver<'d> { - /// Stop receiving and release the driver. - fn drop(&mut self) { - self.stop().unwrap(); - esp!(unsafe { rmt_driver_uninstall(self.channel()) }).unwrap(); + impl<'d> Drop for RxRmtDriver<'d> { + /// Stop receiving and release the driver. + fn drop(&mut self) { + self.stop().unwrap(); + esp!(unsafe { rmt_driver_uninstall(self.channel()) }).unwrap(); + } } -} -unsafe impl<'d> Send for RxRmtDriver<'d> {} + unsafe impl<'d> Send for RxRmtDriver<'d> {} +} mod chip { use esp_idf_sys::*;