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,
+ 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