1049 lines
38 KiB
Rust
1049 lines
38 KiB
Rust
//! # Remote Control Peripheral (RMT)
|
|
//!
|
|
//! ### Summary
|
|
//! The ESP32 variants include a remote control peripheral (RMT) that
|
|
//! is designed to handle infrared remote control signals. For that
|
|
//! purpose, it can convert bitstreams of data (from the RAM) into
|
|
//! pulse codes and even modulate those codes into a carrier wave.
|
|
//!
|
|
//! It can also convert received pulse codes (again, with carrier
|
|
//! wave support) into data bits.
|
|
//!
|
|
//! A secondary use case for this peripheral is to drive RGB(W) LEDs
|
|
//! that bear an internal IC and use a pulse code protocol.
|
|
//!
|
|
//! ### Channels
|
|
//! The RMT peripheral has the following channels available
|
|
//! on individual chips:
|
|
//!
|
|
//! * The **ESP32** has 8 channels, each of them can be either receiver or
|
|
//! transmitter
|
|
//! * The **ESP32-C3** has 4 channels, `Channel0` and `Channel1` hardcoded for
|
|
//! transmitting signals and `Channel2` and `Channel3` hardcoded for receiving
|
|
//! signals.
|
|
//! * The **ESP32-S2** has 4 channels, each of them can be either receiver or
|
|
//! transmitter.
|
|
//! * The **ESP32-S3** has 8 channels, `Channel0`-`Channel3` hardcdoded for
|
|
//! transmitting signals and `Channel4`-`Channel7` hardcoded for receiving
|
|
//! signals.
|
|
//!
|
|
//! ### Implementation State
|
|
//! * FIFO mode is not supported (there appear to be some issues with FIFO mode
|
|
//! in some variants and for consistency all variants therefore we use
|
|
//! NON-FIFO mode everywhere)
|
|
//! * Non-blocking mode is currently not supported!
|
|
//! * Input channels are currently not supported!
|
|
//!
|
|
//! ### Example (for ESP32-C3)
|
|
//! ```
|
|
//! let mut peripherals = pac::Peripherals::take().unwrap();
|
|
//!
|
|
//! // Configure RMT peripheral globally
|
|
//! let pulse = PulseControl::new(
|
|
//! peripherals.RMT,
|
|
//! &mut peripherals.SYSTEM,
|
|
//! ClockSource::APB,
|
|
//! 0, // Integer part of the RMT-wide clock divider
|
|
//! 0, // Numerator part of the RMT-wide clock divider
|
|
//! 0, // Denominator part of the RMT-wide clock divider
|
|
//! )
|
|
//! .unwrap();
|
|
//!
|
|
//! // Get reference to channel
|
|
//! let mut rmt_channel0 = pulse.channel0;
|
|
//!
|
|
//! // Set up channel
|
|
//! rmt_channel0
|
|
//! .set_idle_output_level(false)
|
|
//! .set_carrier_modulation(false)
|
|
//! .set_channel_divider(1)
|
|
//! .set_idle_output(true);
|
|
//!
|
|
//! // Assign GPIO pin where pulses should be sent to
|
|
//! let mut rmt_channel0 = rmt_channel0.assign_pin(io.pins.gpio8);
|
|
//!
|
|
//! // Create pulse sequence
|
|
//! let mut seq = [PulseCode {
|
|
//! level1: true,
|
|
//! length1: 10u32.nanos(),
|
|
//! level2: false,
|
|
//! length2: 90u32.nanos(),
|
|
//! }; 288];
|
|
//!
|
|
//! // Send sequence
|
|
//! rmt_channel0
|
|
//! .send_pulse_sequence(RepeatMode::SingleShot, &seq)
|
|
//! .unwrap();
|
|
//! ```
|
|
|
|
#![deny(missing_docs)]
|
|
|
|
use core::slice::Iter;
|
|
|
|
use fugit::NanosDurationU32;
|
|
pub use paste::paste;
|
|
|
|
use crate::{
|
|
gpio::{types::OutputSignal, OutputPin},
|
|
pac::RMT,
|
|
system::PeripheralClockControl,
|
|
};
|
|
|
|
/// Errors that can occur when the peripheral is configured
|
|
#[derive(Debug)]
|
|
pub enum SetupError {
|
|
/// The global configuration for the RMT peripheral is invalid
|
|
/// (e.g. the fractional parameters are outOfBound)
|
|
InvalidGlobalConfig,
|
|
}
|
|
|
|
/// Errors that can occur during a transmission attempt
|
|
#[derive(Debug)]
|
|
pub enum TransmissionError {
|
|
/// Generic Transmission Error
|
|
Failure(bool, bool, bool, bool),
|
|
/// The maximum number of transmissions (`=(2^10)-1`) was exceeded
|
|
RepetitionOverflow,
|
|
/// The `RepeatNtimes` and `Forever` modesl are only feasible if the
|
|
/// sequence fits into the RAM in one go. If the sequence has > 48
|
|
/// elements, the `RepeatNtimes` and `Forever` modes cannot be used.
|
|
IncompatibleRepeatMode,
|
|
}
|
|
|
|
/// Specifies the mode with which pulses are sent out in transmitter channels
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
pub enum RepeatMode {
|
|
/// Send sequence once
|
|
SingleShot,
|
|
/// Send sequence N times (`N < (2^10)`)
|
|
#[cfg(not(esp32))]
|
|
RepeatNtimes(u16),
|
|
/// Repeat sequence until stopped by additional function call
|
|
Forever,
|
|
}
|
|
|
|
/// Specify the clock source for the RMT peripheral
|
|
#[cfg(any(esp32c3, esp32s3))]
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub enum ClockSource {
|
|
/// Application-level clock
|
|
APB = 1,
|
|
/// 20 MHz internal oscillator
|
|
RTC20M = 2,
|
|
/// External clock source
|
|
XTAL = 3,
|
|
}
|
|
|
|
/// Specify the clock source for the RMT peripheral on the ESP32 and ESP32-S3
|
|
/// variants
|
|
#[cfg(any(esp32s2, esp32))]
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub enum ClockSource {
|
|
/// Reference Tick (usually configured to 1 us)
|
|
RefTick = 0,
|
|
/// Application-level clock
|
|
APB = 1,
|
|
}
|
|
|
|
// Specifies how many entries we can store in the RAM section that is allocated
|
|
// to the RMT channel
|
|
#[cfg(any(esp32s2, esp32))]
|
|
const CHANNEL_RAM_SIZE: u8 = 64;
|
|
#[cfg(any(esp32c3, esp32s3))]
|
|
const CHANNEL_RAM_SIZE: u8 = 48;
|
|
|
|
// Specifies where the RMT RAM section starts for the particular ESP32 variant
|
|
#[cfg(esp32s2)]
|
|
const RMT_RAM_START: usize = 0x3f416400;
|
|
#[cfg(esp32c3)]
|
|
const RMT_RAM_START: usize = 0x60016400;
|
|
#[cfg(esp32)]
|
|
const RMT_RAM_START: usize = 0x3ff56800;
|
|
#[cfg(esp32s3)]
|
|
const RMT_RAM_START: usize = 0x60016800;
|
|
|
|
/// Object representing the state of one pulse code per ESP32-C3 TRM
|
|
///
|
|
/// Allows for the assignment of two levels and their lenghts
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct PulseCode {
|
|
/// Logical output level in the first pulse code interval
|
|
pub level1: bool,
|
|
/// Length of the first pulse code interval (in clock cycles)
|
|
pub length1: NanosDurationU32,
|
|
/// Logical output level in the second pulse code interval
|
|
pub level2: bool,
|
|
/// Length of the second pulse code interval (in clock cycles)
|
|
pub length2: NanosDurationU32,
|
|
}
|
|
|
|
/// Convert a pulse code structure into a u32 value that can be written
|
|
/// into the data registers
|
|
impl From<PulseCode> for u32 {
|
|
#[inline(always)]
|
|
fn from(p: PulseCode) -> u32 {
|
|
// The Pulse Code format in the RAM appears to be
|
|
// little-endian
|
|
|
|
// The length1 value resides in bits [14:0]
|
|
let mut entry: u32 = p.length1.ticks() as u32;
|
|
|
|
// If level1 is high, set bit 15, otherwise clear it
|
|
if p.level1 {
|
|
entry |= 1 << 15;
|
|
} else {
|
|
entry &= !(1 << 15);
|
|
}
|
|
|
|
// If level2 is high, set bit 31, otherwise clear it
|
|
if p.level2 {
|
|
entry |= 1 << 31;
|
|
} else {
|
|
entry &= !(1 << 31);
|
|
}
|
|
|
|
// The length2 value resides in bits [30:16]
|
|
entry |= (p.length2.ticks() as u32) << 16;
|
|
|
|
entry
|
|
}
|
|
}
|
|
|
|
/// Functionality that every OutputChannel must support
|
|
pub trait OutputChannel<CC> {
|
|
/// Set the logical level that the connected pin is pulled to
|
|
/// while the channel is idle
|
|
fn set_idle_output_level(&mut self, level: bool) -> &mut Self;
|
|
|
|
/// Enable/Disable the output while the channel is idle
|
|
fn set_idle_output(&mut self, state: bool) -> &mut Self;
|
|
|
|
/// Set channel clock divider value
|
|
fn set_channel_divider(&mut self, divider: u8) -> &mut Self;
|
|
|
|
/// Enable/Disable carrier modulation
|
|
fn set_carrier_modulation(&mut self, state: bool) -> &mut Self;
|
|
|
|
/// Set the clock source (for the ESP32-S2 abd ESP32 this can be done on a
|
|
/// channel level)
|
|
#[cfg(any(esp32s2, esp32))]
|
|
fn set_clock_source(&mut self, source: ClockSource) -> &mut Self;
|
|
|
|
/// Assign a pin that should be driven by this channel
|
|
///
|
|
/// (Note that we only take a reference here, so the ownership remains with
|
|
/// the calling entity. The configured pin thus can be re-configured
|
|
/// independently.)
|
|
fn assign_pin<RmtPin: OutputPin>(self, pin: RmtPin) -> CC;
|
|
}
|
|
|
|
/// Functionality that is allowed only on `ConfiguredChannel`
|
|
pub trait ConfiguredChannel {
|
|
/// Send a pulse sequence in a blocking fashion
|
|
fn send_pulse_sequence<const N: usize>(
|
|
&mut self,
|
|
repeat_mode: RepeatMode,
|
|
sequence: &[PulseCode; N],
|
|
) -> Result<(), TransmissionError>;
|
|
|
|
/// Send a raw pulse sequence in a blocking fashion
|
|
///
|
|
/// In this function we expect the `sequence` elements to be already
|
|
/// in the correct u32 format that is understood by the RMT.
|
|
/// Please refer to the reference manual or use the variant which
|
|
/// accepts `PulseCode` objects instead.
|
|
fn send_pulse_sequence_raw<const N: usize>(
|
|
&mut self,
|
|
repeat_mode: RepeatMode,
|
|
sequence: &[u32; N],
|
|
) -> Result<(), TransmissionError>;
|
|
|
|
/// Stop any ongoing (repetitive) transmission
|
|
///
|
|
/// This function needs to be called to stop sending when
|
|
/// previously a sequence was sent with `RepeatMode::Forever`.
|
|
fn stop_transmission(&self);
|
|
}
|
|
|
|
macro_rules! channel_instance {
|
|
($num:literal, $cxi:ident, $output_signal:path
|
|
) => {
|
|
/// RX/TX Input/Output Channel
|
|
pub struct $cxi {
|
|
mem_offset: usize,
|
|
}
|
|
impl $cxi {
|
|
/// Create a new channel instance
|
|
pub fn new() -> Self {
|
|
let mut channel = $cxi { mem_offset: 0 };
|
|
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(any(esp32c3, esp32s3))] {
|
|
// Apply default configuration
|
|
unsafe { &*RMT::PTR }.ch_tx_conf0[$num].modify(|_, w| unsafe {
|
|
// Configure memory block size
|
|
w.mem_size()
|
|
.bits(1)
|
|
});
|
|
}
|
|
else {
|
|
conf0!($num).modify(|_, w| unsafe {
|
|
// Configure memory block size
|
|
w.mem_size()
|
|
.bits(1)
|
|
});
|
|
conf1!($num).modify(|_, w|
|
|
// Configure memory block size
|
|
w.mem_owner()
|
|
.clear_bit()
|
|
);
|
|
|
|
}
|
|
};
|
|
|
|
#[cfg(esp32)]
|
|
conf0!($num).modify(|_, w|
|
|
// Enable clock
|
|
w.clk_en()
|
|
.set_bit()
|
|
// Disable forced power down of the peripheral (just to be sure)
|
|
.mem_pd()
|
|
.clear_bit()
|
|
);
|
|
|
|
channel.set_carrier_modulation(false);
|
|
channel.set_idle_output_level(false);
|
|
channel.set_idle_output(false);
|
|
channel.set_channel_divider(1);
|
|
|
|
channel
|
|
}
|
|
|
|
/// Write a sequence of pulse codes into the RMT fifo buffer
|
|
#[inline(always)]
|
|
fn write_sequence(
|
|
&mut self,
|
|
seq_iter: &mut Iter<u32>,
|
|
max_inserted_elements: u8,
|
|
){
|
|
for _ in 0..max_inserted_elements {
|
|
match seq_iter.next() {
|
|
None => {
|
|
break;
|
|
}
|
|
Some(pulse) => self.load_fifo(*pulse),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn load_fifo(&mut self, value: u32) {
|
|
let base_ptr: usize = RMT_RAM_START + ($num * CHANNEL_RAM_SIZE as usize * 4);
|
|
let ram_ptr = (base_ptr + self.mem_offset) as *mut u32;
|
|
unsafe {
|
|
ram_ptr.write_volatile(value);
|
|
}
|
|
|
|
self.mem_offset += 4;
|
|
if self.mem_offset >= CHANNEL_RAM_SIZE as usize * 4 {
|
|
self.mem_offset = 0;
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn reset_fifo(&mut self) {
|
|
self.mem_offset = 0;
|
|
}
|
|
}
|
|
|
|
paste!(
|
|
#[doc = "Wrapper for`" $cxi "` object."]
|
|
pub struct [<Configured $cxi>] {
|
|
channel: $cxi,
|
|
}
|
|
|
|
impl ConfiguredChannel for [<Configured $cxi>] {
|
|
/// Send a pulse sequence in a blocking fashion
|
|
fn send_pulse_sequence<const N: usize>(
|
|
&mut self,
|
|
repeat_mode: RepeatMode,
|
|
sequence: &[PulseCode; N],
|
|
) -> Result<(), TransmissionError> {
|
|
let precomputed_sequence = sequence.map(|x| u32::from(x));
|
|
|
|
self.send_pulse_sequence_raw(repeat_mode, &precomputed_sequence)
|
|
}
|
|
|
|
/// Send a raw pulse sequence in a blocking fashion
|
|
///
|
|
/// In this function we expect the `sequence` elements to be already
|
|
/// in the correct u32 format that is understood by the RMT.
|
|
/// Please refer to the reference manual or use the variant which
|
|
/// accepts `PulseCode` objects instead.
|
|
///
|
|
/// We expect that the end marker is already part of the provided
|
|
/// sequence and to be provided in all modes!
|
|
fn send_pulse_sequence_raw<const N: usize>(
|
|
&mut self,
|
|
repeat_mode: RepeatMode,
|
|
sequence: &[u32; N],
|
|
) -> Result<(), TransmissionError> {
|
|
// Check for any configuration error states
|
|
match repeat_mode {
|
|
#[cfg(not(esp32))]
|
|
RepeatMode::RepeatNtimes(val) => {
|
|
if val >= 1024 {
|
|
return Err(TransmissionError::RepetitionOverflow);
|
|
}
|
|
if sequence.len() > CHANNEL_RAM_SIZE as usize {
|
|
return Err(TransmissionError::IncompatibleRepeatMode);
|
|
}
|
|
}
|
|
RepeatMode::Forever => {
|
|
if sequence.len() > CHANNEL_RAM_SIZE as usize {
|
|
return Err(TransmissionError::IncompatibleRepeatMode);
|
|
}
|
|
}
|
|
_ => (),
|
|
};
|
|
|
|
// Depending on the variant, other registers have to be used here
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(any(esp32, esp32s2))] {
|
|
let conf_reg = & conf1!($num);
|
|
} else {
|
|
let conf_reg = & unsafe{ &*RMT::PTR }.ch_tx_conf0[$num];
|
|
}
|
|
}
|
|
|
|
// The ESP32 does not support loop/count modes, as such we have to
|
|
// only configure a subset of registers
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(esp32)] {
|
|
// Configure counting mode and repetitions
|
|
unsafe { &*RMT::PTR }.ch_tx_lim[$num].modify(|_, w| unsafe {
|
|
// Set the interrupt threshold for sent pulse codes to
|
|
// half the size of the RAM in case we use wrap mode
|
|
w.tx_lim()
|
|
.bits(CHANNEL_RAM_SIZE as u16 /2)
|
|
});
|
|
} else {
|
|
// Extract repetition value
|
|
let mut reps = 0;
|
|
if let RepeatMode::RepeatNtimes(val) = repeat_mode {
|
|
reps = val;
|
|
}
|
|
|
|
// Configure counting mode and repetitions
|
|
unsafe { &*RMT::PTR }.ch_tx_lim[$num].modify(|_, w| unsafe {
|
|
// Set number of repetitions
|
|
w.tx_loop_num()
|
|
.bits(reps)
|
|
// Enable loop counting
|
|
.tx_loop_cnt_en()
|
|
.bit(reps != 0)
|
|
// Reset any pre-existing counting value
|
|
.loop_count_reset()
|
|
.set_bit()
|
|
// Set the interrupt threshold for sent pulse codes to 24
|
|
// (= half the size of the RAM) in case we use wrap mode
|
|
.tx_lim()
|
|
.bits(CHANNEL_RAM_SIZE as u16/2)
|
|
});
|
|
}
|
|
}
|
|
|
|
#[cfg(any(esp32c3, esp32s3))]
|
|
conf_reg.modify(|_, w| {
|
|
// Set config update bit
|
|
w.conf_update().set_bit()
|
|
});
|
|
|
|
// Setup configuration
|
|
conf_reg.modify(|_, w| {
|
|
// Set configure continuous
|
|
// (also reset FIFO buffer pointers)
|
|
w.tx_conti_mode()
|
|
.bit(repeat_mode != RepeatMode::SingleShot)
|
|
.mem_rd_rst()
|
|
.set_bit()
|
|
.apb_mem_rst()
|
|
.set_bit()
|
|
});
|
|
|
|
self.channel.reset_fifo();
|
|
let mut sequence_iter = sequence.iter();
|
|
|
|
// We have to differentiate here if we can fit the whole sequence
|
|
// in the RAM in one go or if we have to use the wrap mode to split
|
|
// the sequence into chuncks.
|
|
if sequence.len() >= CHANNEL_RAM_SIZE as usize {
|
|
// Write the first 48 entries
|
|
self.channel.write_sequence(&mut sequence_iter, CHANNEL_RAM_SIZE);
|
|
} else {
|
|
// Write whole sequence to FIFO RAM
|
|
self.channel.write_sequence(&mut sequence_iter, CHANNEL_RAM_SIZE);
|
|
}
|
|
|
|
// Clear the relevant interrupts
|
|
//
|
|
// (since this is a write-through register, we can do this
|
|
// safely for multiple separate channel instances without
|
|
// having concurrency issues)
|
|
// Depending on the variant, other registers have to be used here
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(esp32)] {
|
|
unsafe { &*RMT::PTR }.int_clr.write(|w| {
|
|
// The ESP32 variant does not have the loop functionality
|
|
paste!(
|
|
w.[<ch $num _tx_end_int_clr>]()
|
|
.set_bit()
|
|
.[<ch $num _err_int_clr>]()
|
|
.set_bit()
|
|
.[<ch $num _tx_thr_event_int_clr>]()
|
|
.set_bit()
|
|
)
|
|
});
|
|
} else if #[cfg(esp32s2)] {
|
|
unsafe { &*RMT::PTR }.int_clr.write(|w| {
|
|
paste!(
|
|
w.[<ch $num _tx_end_int_clr>]()
|
|
.set_bit()
|
|
.[<ch $num _tx_loop_int_clr>]()
|
|
.set_bit()
|
|
.[<ch $num _err_int_clr>]()
|
|
.set_bit()
|
|
.[<ch $num _tx_thr_event_int_clr>]()
|
|
.set_bit()
|
|
)
|
|
});
|
|
} else {
|
|
unsafe { &*RMT::PTR }.int_clr.write(|w| {
|
|
paste!(
|
|
w.[<ch $num _tx_end_int_clr>]()
|
|
.set_bit()
|
|
.[<ch $num _tx_loop_int_clr>]()
|
|
.set_bit()
|
|
.[<ch $num _tx_err_int_clr>]()
|
|
.set_bit()
|
|
.[<ch $num _tx_thr_event_int_clr>]()
|
|
.set_bit()
|
|
)
|
|
});
|
|
}
|
|
}
|
|
|
|
// always enable tx wrap
|
|
#[cfg(any(esp32c3, esp32s3))]
|
|
unsafe { &*RMT::PTR }.ch_tx_conf0[$num].modify(|_, w| {
|
|
w.mem_tx_wrap_en()
|
|
.set_bit()
|
|
});
|
|
|
|
// apply configuration updates
|
|
#[cfg(any(esp32c3, esp32s3))]
|
|
unsafe { &*RMT::PTR }.ch_tx_conf0[$num].modify(|_, w| {
|
|
w.conf_update()
|
|
.set_bit()
|
|
});
|
|
|
|
// Depending on the variant, other registers have to be used here
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(any(esp32, esp32s2))] {
|
|
conf1!($num).modify(|_, w| w.tx_start().set_bit());
|
|
} else {
|
|
unsafe{ &*RMT::PTR }.ch_tx_conf0[$num].modify(|_, w| w.tx_start().set_bit());
|
|
}
|
|
}
|
|
|
|
// If we're in forever mode, we return right away, otherwise we wait
|
|
// for completion
|
|
if repeat_mode != RepeatMode::Forever {
|
|
// Wait for interrupt being raised, either completion or error
|
|
loop {
|
|
let interrupts = unsafe { &*RMT::PTR }.int_raw.read();
|
|
|
|
match (
|
|
unsafe { interrupts.ch_tx_end_int_raw($num).bit() },
|
|
// The ESP32 variant does not support the loop functionality
|
|
#[cfg(not(esp32))]
|
|
unsafe {interrupts.ch_tx_loop_int_raw($num).bit()},
|
|
#[cfg(esp32)]
|
|
false,
|
|
// The C3/S3 have a slightly different interrupt naming scheme
|
|
#[cfg(any(esp32, feature= "esp32s2"))]
|
|
unsafe { interrupts.ch_err_int_raw($num).bit() },
|
|
#[cfg(any(esp32c3, feature= "esp32s3"))]
|
|
unsafe { interrupts.ch_tx_err_int_raw($num).bit() },
|
|
unsafe { interrupts.ch_tx_thr_event_int_raw($num).bit() },
|
|
) {
|
|
// SingleShot completed and no error -> success
|
|
(true, false, false, _) => break,
|
|
// Sequence completed and no error -> success
|
|
(false, true, false, _) => {
|
|
// Stop transmitting (only necessary in sequence case)
|
|
self.stop_transmission();
|
|
break;
|
|
}
|
|
// Refill the buffer
|
|
(false, false, false, true) => {
|
|
self.channel.write_sequence(&mut sequence_iter, CHANNEL_RAM_SIZE / 2);
|
|
|
|
// Clear the threshold interrupt (write-through)
|
|
unsafe { &*RMT::PTR }.int_clr.write(|w| {
|
|
paste!(w.[<ch $num _tx_thr_event_int_clr>]().set_bit())
|
|
});
|
|
}
|
|
// Neither completed nor error -> continue busy waiting
|
|
(false, false, false, false) => (),
|
|
// Anything else constitutes an error state
|
|
_ => {
|
|
return Err(TransmissionError::Failure(
|
|
unsafe { interrupts.ch_tx_end_int_raw($num).bit() },
|
|
// The ESP32 variant does not support the loop functionality
|
|
#[cfg(not(esp32))]
|
|
unsafe {interrupts.ch_tx_loop_int_raw($num).bit()},
|
|
#[cfg(esp32)]
|
|
false,
|
|
// The C3/S3 have a slightly different interrupt naming scheme
|
|
#[cfg(any(esp32, feature= "esp32s2"))]
|
|
unsafe { interrupts.ch_err_int_raw($num).bit() },
|
|
#[cfg(any(esp32c3, feature= "esp32s3"))]
|
|
unsafe { interrupts.ch_tx_err_int_raw($num).bit() },
|
|
unsafe { interrupts.ch_tx_thr_event_int_raw($num).bit() },
|
|
))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Stop any ongoing (repetitive) transmission
|
|
///
|
|
/// This function needs to be called to stop sending when
|
|
/// previously a sequence was sent with `RepeatMode::Forever`.
|
|
fn stop_transmission(&self) {
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(any(esp32c3, esp32s3))] {
|
|
unsafe { &*RMT::PTR }
|
|
.ch_tx_conf0[$num]
|
|
.modify(|_, w| w.tx_stop().set_bit());
|
|
}
|
|
else if #[cfg(esp32s2)] {
|
|
conf1!($num)
|
|
.modify(|_, w| w.tx_stop().set_bit());
|
|
}
|
|
// The ESP32 variant does not have any way to stop a
|
|
// transmission once it has been started!
|
|
};
|
|
}
|
|
}
|
|
|
|
);
|
|
};
|
|
}
|
|
|
|
macro_rules! output_channel {
|
|
($num:literal, $cxi:ident, $output_signal:path
|
|
) => {
|
|
paste!(
|
|
|
|
impl OutputChannel<[<Configured $cxi>]> for $cxi {
|
|
/// Set the logical level that the connected pin is pulled to
|
|
/// while the channel is idle
|
|
#[inline(always)]
|
|
fn set_idle_output_level(&mut self, level: bool) -> &mut Self {
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(any(esp32c3, esp32s3))] {
|
|
unsafe { &*RMT::PTR }
|
|
.ch_tx_conf0[$num]
|
|
.modify(|_, w| w.idle_out_lv().bit(level));
|
|
}
|
|
else {
|
|
conf1!($num)
|
|
.modify(|_, w| w.idle_out_lv().bit(level));
|
|
}
|
|
};
|
|
self
|
|
}
|
|
|
|
/// Enable/Disable the output while the channel is idle
|
|
#[inline(always)]
|
|
fn set_idle_output(&mut self, state: bool) -> &mut Self {
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(any(esp32c3, esp32s3))] {
|
|
unsafe { &*RMT::PTR }
|
|
.ch_tx_conf0[$num]
|
|
.modify(|_, w| w.idle_out_en().bit(state));
|
|
}
|
|
else {
|
|
conf1!($num)
|
|
.modify(|_, w| w.idle_out_en().bit(state));
|
|
}
|
|
};
|
|
self
|
|
}
|
|
|
|
/// Set channel clock divider value
|
|
#[inline(always)]
|
|
fn set_channel_divider(&mut self, divider: u8) -> &mut Self {
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(any(esp32c3, esp32s3))] {
|
|
unsafe { &*RMT::PTR }
|
|
.ch_tx_conf0[$num]
|
|
.modify(|_, w| unsafe { w.div_cnt().bits(divider) });
|
|
}
|
|
else {
|
|
conf0!($num)
|
|
.modify(|_, w| unsafe { w.div_cnt().bits(divider) });
|
|
}
|
|
};
|
|
self
|
|
}
|
|
|
|
/// Enable/Disable carrier modulation
|
|
#[inline(always)]
|
|
fn set_carrier_modulation(&mut self, state: bool) -> &mut Self {
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(any(esp32c3, esp32s3))] {
|
|
unsafe { &*RMT::PTR }
|
|
.ch_tx_conf0[$num]
|
|
.modify(|_, w| w.carrier_en().bit(state));
|
|
}
|
|
else {
|
|
conf0!($num)
|
|
.modify(|_, w| w.carrier_en().bit(state));
|
|
}
|
|
};
|
|
self
|
|
}
|
|
|
|
/// Set the clock source (for the ESP32-S2 and ESP32 this can be done on a
|
|
/// channel level)
|
|
#[cfg(any(esp32s2, esp32))]
|
|
#[inline(always)]
|
|
fn set_clock_source(&mut self, source: ClockSource) -> &mut Self {
|
|
let bit_value = match source {
|
|
ClockSource::RefTick => false,
|
|
ClockSource::APB => true,
|
|
};
|
|
|
|
conf1!($num)
|
|
.modify(|_, w| w.ref_always_on().bit(bit_value));
|
|
self
|
|
}
|
|
|
|
/// Assign a pin that should be driven by this channel
|
|
fn assign_pin<RmtPin: OutputPin >(
|
|
self,
|
|
mut pin: RmtPin,
|
|
) -> [<Configured $cxi>] {
|
|
// Configure Pin as output anc connect to signal
|
|
pin.set_to_push_pull_output()
|
|
.connect_peripheral_to_output($output_signal);
|
|
|
|
[<Configured $cxi>] {
|
|
channel: self,
|
|
}
|
|
}
|
|
}
|
|
);
|
|
};
|
|
}
|
|
|
|
#[cfg(esp32)]
|
|
macro_rules! conf0 {
|
|
($channel: literal) => {
|
|
match $channel {
|
|
0 => &unsafe { &*RMT::PTR }.ch0conf0,
|
|
1 => &unsafe { &*RMT::PTR }.ch1conf0,
|
|
2 => &unsafe { &*RMT::PTR }.ch2conf0,
|
|
3 => &unsafe { &*RMT::PTR }.ch3conf0,
|
|
4 => &unsafe { &*RMT::PTR }.ch4conf0,
|
|
5 => &unsafe { &*RMT::PTR }.ch5conf0,
|
|
6 => &unsafe { &*RMT::PTR }.ch6conf0,
|
|
7 => &unsafe { &*RMT::PTR }.ch7conf0,
|
|
_ => panic!("Attempted access to non-existing channel!"),
|
|
}
|
|
};
|
|
}
|
|
|
|
#[cfg(esp32)]
|
|
macro_rules! conf1 {
|
|
($channel: literal) => {
|
|
match $channel {
|
|
0 => &unsafe { &*RMT::PTR }.ch0conf1,
|
|
1 => &unsafe { &*RMT::PTR }.ch1conf1,
|
|
2 => &unsafe { &*RMT::PTR }.ch2conf1,
|
|
3 => &unsafe { &*RMT::PTR }.ch3conf1,
|
|
4 => &unsafe { &*RMT::PTR }.ch4conf1,
|
|
5 => &unsafe { &*RMT::PTR }.ch5conf1,
|
|
6 => &unsafe { &*RMT::PTR }.ch6conf1,
|
|
7 => &unsafe { &*RMT::PTR }.ch7conf1,
|
|
_ => panic!("Attempted access to non-existing channel!"),
|
|
}
|
|
};
|
|
}
|
|
|
|
#[cfg(esp32s2)]
|
|
macro_rules! conf0 {
|
|
($channel: literal) => {
|
|
match $channel {
|
|
0 => &unsafe { &*RMT::PTR }.ch0conf0,
|
|
1 => &unsafe { &*RMT::PTR }.ch1conf0,
|
|
2 => &unsafe { &*RMT::PTR }.ch2conf0,
|
|
3 => &unsafe { &*RMT::PTR }.ch3conf0,
|
|
_ => panic!("Attempted access to non-existing channel!"),
|
|
}
|
|
};
|
|
}
|
|
|
|
#[cfg(esp32s2)]
|
|
macro_rules! conf1 {
|
|
($channel: literal) => {
|
|
match $channel {
|
|
0 => &unsafe { &*RMT::PTR }.ch0conf1,
|
|
1 => &unsafe { &*RMT::PTR }.ch1conf1,
|
|
2 => &unsafe { &*RMT::PTR }.ch2conf1,
|
|
3 => &unsafe { &*RMT::PTR }.ch3conf1,
|
|
_ => panic!("Attempted access to non-existing channel!"),
|
|
}
|
|
};
|
|
}
|
|
|
|
macro_rules! rmt {
|
|
(
|
|
$global_conf_reg:ident,
|
|
$(
|
|
($num:literal, $cxi:ident, $obj_name:ident, $output_signal:path),
|
|
)+
|
|
)
|
|
=> {
|
|
/// RMT peripheral (RMT)
|
|
pub struct PulseControl {
|
|
/// The underlying register block
|
|
reg: RMT,
|
|
$(
|
|
/// RMT channel $cxi
|
|
pub $obj_name: $cxi,
|
|
)+
|
|
}
|
|
|
|
impl PulseControl {
|
|
/// Create a new pulse controller instance
|
|
#[cfg(any(esp32c3, esp32s3))]
|
|
pub fn new(
|
|
instance: RMT,
|
|
peripheral_clock_control: &mut PeripheralClockControl,
|
|
clk_source: ClockSource,
|
|
div_abs: u8,
|
|
div_frac_a: u8,
|
|
div_frac_b: u8,
|
|
) -> Result<Self, SetupError> {
|
|
|
|
let pc = PulseControl {
|
|
reg: instance,
|
|
$(
|
|
$obj_name: $cxi::new(),
|
|
)+
|
|
};
|
|
|
|
pc.enable_peripheral(peripheral_clock_control);
|
|
pc.config_global(clk_source, div_abs, div_frac_a, div_frac_b)?;
|
|
|
|
Ok(pc)
|
|
}
|
|
|
|
/// Create a new pulse controller instance
|
|
#[cfg(any(esp32, esp32s2))]
|
|
pub fn new(
|
|
instance: RMT,
|
|
peripheral_clock_control: &mut PeripheralClockControl,
|
|
) -> Result<Self, SetupError> {
|
|
|
|
let pc = PulseControl {
|
|
reg: instance,
|
|
$(
|
|
$obj_name: $cxi::new(),
|
|
)+
|
|
};
|
|
|
|
pc.enable_peripheral(peripheral_clock_control);
|
|
pc.config_global()?;
|
|
|
|
Ok(pc)
|
|
}
|
|
|
|
/// Return the raw interface to the underlying RMT peripheral
|
|
pub fn free(self) -> RMT {
|
|
self.reg
|
|
}
|
|
|
|
// Enable the RMT peripherals clock in the system peripheral
|
|
fn enable_peripheral(&self, peripheral_clock_control: &mut PeripheralClockControl) {
|
|
peripheral_clock_control.enable(crate::system::Peripheral::Rmt);
|
|
}
|
|
|
|
/// Assign the global (peripheral-wide) configuration. This
|
|
/// is mostly the divider setup and the clock source selection
|
|
///
|
|
/// The dividing factor for the source
|
|
/// clock is calculated as follows:
|
|
///
|
|
/// divider = absolute_part + 1 + (fractional_part_a / fractional_part_b)
|
|
#[cfg(any(esp32c3, esp32s3))]
|
|
fn config_global(
|
|
&self,
|
|
clk_source: ClockSource,
|
|
div_abs: u8,
|
|
div_frac_a: u8,
|
|
div_frac_b: u8,
|
|
) -> Result<(), SetupError> {
|
|
// Before assigning, confirm that the fractional parameters for
|
|
// the divider are within bounds
|
|
if div_frac_a > 64 || div_frac_b > 64 {
|
|
return Err(SetupError::InvalidGlobalConfig);
|
|
}
|
|
|
|
// TODO: Confirm that the selected clock source is enabled in the
|
|
// system / rtc_cntl peripheral? Particularly relevant for clock sources
|
|
// other than APB_CLK, this needs to be revisited once #24 and $44 have been
|
|
// addressed!
|
|
|
|
// Configure peripheral
|
|
self.reg.sys_conf.modify(|_, w| unsafe {
|
|
// Enable clock
|
|
w.clk_en()
|
|
.set_bit()
|
|
// Force Clock on
|
|
.mem_clk_force_on()
|
|
.set_bit()
|
|
// Enable Source clock
|
|
.sclk_active()
|
|
.set_bit()
|
|
// Disable forced power down of the peripheral (just to be sure)
|
|
.mem_force_pd()
|
|
.clear_bit()
|
|
// Disable FIFO mode
|
|
.apb_fifo_mask()
|
|
.set_bit()
|
|
// Select clock source
|
|
.sclk_sel()
|
|
.bits(clk_source as u8)
|
|
// Set absolute part of divider
|
|
.sclk_div_num()
|
|
.bits(div_abs)
|
|
// Set fractional parts of divider to 0
|
|
.sclk_div_a()
|
|
.bits(div_frac_a)
|
|
.sclk_div_b()
|
|
.bits(div_frac_b)
|
|
});
|
|
|
|
// Disable all interrupts
|
|
self.reg.int_ena.write(|w| unsafe { w.bits(0) });
|
|
|
|
// Clear all interrupts
|
|
self.reg.int_clr.write(|w| unsafe { w.bits(0xffffffff) });
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Assign the global (peripheral-wide) configuration.
|
|
#[cfg(any(esp32s2, esp32))]
|
|
fn config_global(&self) -> Result<(), SetupError> {
|
|
// TODO: Confirm that the selected clock source is enabled in the
|
|
// system / rtc_cntl peripheral? Particularly relevant for clock sources
|
|
// other than APB_CLK, this needs to be revisited once #24 and $44 have been
|
|
// addressed!
|
|
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(esp32)] {
|
|
// Configure peripheral
|
|
self.reg.apb_conf.modify(|_, w|
|
|
// Disable FIFO mode
|
|
w.apb_fifo_mask()
|
|
.set_bit()
|
|
// Enable wrap mode (globally for the ESP32 and ESP32-S2 variants)
|
|
.mem_tx_wrap_en()
|
|
.set_bit()
|
|
);
|
|
}
|
|
else {
|
|
// Configure peripheral
|
|
self.reg.apb_conf.modify(|_, w|
|
|
// Enable clock
|
|
w.clk_en()
|
|
.set_bit()
|
|
// Force Clock on
|
|
.mem_clk_force_on()
|
|
.set_bit()
|
|
// Disable forced power down of the peripheral (just to be sure)
|
|
.mem_force_pd()
|
|
.clear_bit()
|
|
// Disable FIFO mode
|
|
.apb_fifo_mask()
|
|
.set_bit()
|
|
// Enable wrap mode (globally for the ESP32 and ESP32-S2 variants)
|
|
.mem_tx_wrap_en()
|
|
.set_bit()
|
|
);
|
|
}
|
|
};
|
|
|
|
// Disable all interrupts
|
|
self.reg.int_ena.write(|w| unsafe { w.bits(0) });
|
|
|
|
// Clear all interrupts
|
|
self.reg.int_clr.write(|w| unsafe { w.bits(0) });
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
$(
|
|
channel_instance!($num, $cxi, $output_signal);
|
|
output_channel!($num, $cxi, $output_signal);
|
|
)+
|
|
};
|
|
}
|
|
|
|
#[cfg(esp32c3)]
|
|
rmt!(
|
|
sys_conf,
|
|
(0, Channel0, channel0, OutputSignal::RMT_SIG_0),
|
|
(1, Channel1, channel1, OutputSignal::RMT_SIG_1),
|
|
);
|
|
|
|
#[cfg(esp32s2)]
|
|
rmt!(
|
|
apb_conf,
|
|
(0, Channel0, channel0, OutputSignal::RMT_SIG_OUT0),
|
|
(1, Channel1, channel1, OutputSignal::RMT_SIG_OUT1),
|
|
(2, Channel2, channel2, OutputSignal::RMT_SIG_OUT2),
|
|
(3, Channel3, channel3, OutputSignal::RMT_SIG_OUT3),
|
|
);
|
|
|
|
#[cfg(esp32)]
|
|
rmt!(
|
|
apb_conf,
|
|
(0, Channel0, channel0, OutputSignal::RMT_SIG_0),
|
|
(1, Channel1, channel1, OutputSignal::RMT_SIG_1),
|
|
(2, Channel2, channel2, OutputSignal::RMT_SIG_2),
|
|
(3, Channel3, channel3, OutputSignal::RMT_SIG_3),
|
|
(4, Channel4, channel4, OutputSignal::RMT_SIG_4),
|
|
(5, Channel5, channel5, OutputSignal::RMT_SIG_5),
|
|
(6, Channel6, channel6, OutputSignal::RMT_SIG_6),
|
|
(7, Channel7, channel7, OutputSignal::RMT_SIG_7),
|
|
);
|
|
|
|
#[cfg(esp32s3)]
|
|
rmt!(
|
|
sys_conf,
|
|
(0, Channel0, channel0, OutputSignal::RMT_SIG_OUT0),
|
|
(1, Channel1, channel1, OutputSignal::RMT_SIG_OUT1),
|
|
(2, Channel2, channel2, OutputSignal::RMT_SIG_OUT2),
|
|
(3, Channel3, channel3, OutputSignal::RMT_SIG_OUT3),
|
|
);
|