Skip to content

Commit 374e8a2

Browse files
committed
add support for non-blocking smart LED driver
1 parent 58014bf commit 374e8a2

File tree

2 files changed

+177
-64
lines changed

2 files changed

+177
-64
lines changed

esp-hal-smartled/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "esp-hal-smartled"
3-
version = "0.13.1"
3+
version = "0.14.0"
44
edition = "2021"
55
rust-version = "1.76.0"
66
description = "RMT peripheral adapter for smart LEDs"

esp-hal-smartled/src/lib.rs

Lines changed: 176 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ use esp_hal::{
3434
};
3535
use smart_leds_trait::{SmartLedsWrite, RGB8};
3636

37+
// Required RMT RAM to drive one LED.
38+
// number of channels (r,g,b -> 3) * pulses per channel 8)
39+
const RMT_RAM_ONE_LED: usize = 3 * 8;
40+
3741
const SK68XX_CODE_PERIOD: u32 = 1200;
3842
const SK68XX_T0H_NS: u32 = 320;
3943
const SK68XX_T0L_NS: u32 = SK68XX_CODE_PERIOD - SK68XX_T0H_NS;
@@ -51,24 +55,89 @@ pub enum LedAdapterError {
5155
TransmissionError(RmtError),
5256
}
5357

58+
fn led_pulses_for_clock(src_clock: u32) -> (u32, u32) {
59+
(
60+
u32::from(PulseCode {
61+
level1: true,
62+
length1: ((SK68XX_T0H_NS * src_clock) / 1000) as u16,
63+
level2: false,
64+
length2: ((SK68XX_T0L_NS * src_clock) / 1000) as u16,
65+
}),
66+
u32::from(PulseCode {
67+
level1: true,
68+
length1: ((SK68XX_T1H_NS * src_clock) / 1000) as u16,
69+
level2: false,
70+
length2: ((SK68XX_T1L_NS * src_clock) / 1000) as u16,
71+
}),
72+
)
73+
}
74+
75+
fn led_config() -> TxChannelConfig {
76+
TxChannelConfig {
77+
clk_divider: 1,
78+
idle_output_level: false,
79+
carrier_modulation: false,
80+
idle_output: true,
81+
82+
..TxChannelConfig::default()
83+
}
84+
}
85+
86+
fn convert_rgb_to_pulses(
87+
value: RGB8,
88+
mut_iter: &mut IterMut<u32>,
89+
pulses: (u32, u32),
90+
) -> Result<(), LedAdapterError> {
91+
convert_rgb_channel_to_pulses(value.g, mut_iter, pulses)?;
92+
convert_rgb_channel_to_pulses(value.r, mut_iter, pulses)?;
93+
convert_rgb_channel_to_pulses(value.b, mut_iter, pulses)?;
94+
Ok(())
95+
}
96+
97+
fn convert_rgb_channel_to_pulses(
98+
channel_value: u8,
99+
mut_iter: &mut IterMut<u32>,
100+
pulses: (u32, u32),
101+
) -> Result<(), LedAdapterError> {
102+
for position in [128, 64, 32, 16, 8, 4, 2, 1] {
103+
*mut_iter.next().ok_or(LedAdapterError::BufferSizeExceeded)? =
104+
match channel_value & position {
105+
0 => pulses.0,
106+
_ => pulses.1,
107+
}
108+
}
109+
110+
Ok(())
111+
}
112+
54113
/// Macro to allocate a buffer sized for a specific number of LEDs to be
55114
/// addressed.
56115
///
57116
/// Attempting to use more LEDs that the buffer is configured for will result in
58117
/// an `LedAdapterError:BufferSizeExceeded` error.
59118
#[macro_export]
119+
macro_rules! smart_led_buffer {
120+
( $num_leds: ident ) => {
121+
[0u32; buffer_size($num_leds)]
122+
};
123+
}
124+
125+
/// Deprecated alias for [smart_led_buffer] macro.
126+
#[macro_export]
127+
#[deprecated]
60128
macro_rules! smartLedBuffer {
61-
( $buffer_size: literal ) => {
62-
// The size we're assigning here is calculated as following
63-
// (
64-
// Nr. of LEDs
65-
// * channels (r,g,b -> 3)
66-
// * pulses per channel 8)
67-
// ) + 1 additional pulse for the end delimiter
68-
[0u32; $buffer_size * 24 + 1]
129+
( $num_leds: ident ) => {
130+
smart_led_buffer!($num_leds);
69131
};
70132
}
71133

134+
/// Function to calculate the required RMT buffer size for a given number of LEDs when using
135+
/// the blocking API.
136+
pub const fn buffer_size(num_leds: usize) -> usize {
137+
// 1 additional pulse for the end delimiter
138+
num_leds * RMT_RAM_ONE_LED + 1
139+
}
140+
72141
/// Adapter taking an RMT channel and a specific pin and providing RGB LED
73142
/// interaction functionality using the `smart-leds` crate
74143
pub struct SmartLedsAdapter<TX, const BUFFER_SIZE: usize>
@@ -94,67 +163,16 @@ where
94163
O: OutputPin + 'd,
95164
C: TxChannelCreator<'d, TX, O>,
96165
{
97-
let config = TxChannelConfig {
98-
clk_divider: 1,
99-
idle_output_level: false,
100-
carrier_modulation: false,
101-
idle_output: true,
102-
103-
..TxChannelConfig::default()
104-
};
105-
106-
let channel = channel.configure(pin, config).unwrap();
166+
let channel = channel.configure(pin, led_config()).unwrap();
107167

108168
// Assume the RMT peripheral is set up to use the APB clock
109-
let clocks = Clocks::get();
110-
let src_clock = clocks.apb_clock.to_MHz();
169+
let src_clock = Clocks::get().apb_clock.to_MHz();
111170

112171
Self {
113172
channel: Some(channel),
114173
rmt_buffer,
115-
pulses: (
116-
u32::from(PulseCode {
117-
level1: true,
118-
length1: ((SK68XX_T0H_NS * src_clock) / 1000) as u16,
119-
level2: false,
120-
length2: ((SK68XX_T0L_NS * src_clock) / 1000) as u16,
121-
}),
122-
u32::from(PulseCode {
123-
level1: true,
124-
length1: ((SK68XX_T1H_NS * src_clock) / 1000) as u16,
125-
level2: false,
126-
length2: ((SK68XX_T1L_NS * src_clock) / 1000) as u16,
127-
}),
128-
),
129-
}
130-
}
131-
132-
fn convert_rgb_to_pulse(
133-
value: RGB8,
134-
mut_iter: &mut IterMut<u32>,
135-
pulses: (u32, u32),
136-
) -> Result<(), LedAdapterError> {
137-
Self::convert_rgb_channel_to_pulses(value.g, mut_iter, pulses)?;
138-
Self::convert_rgb_channel_to_pulses(value.r, mut_iter, pulses)?;
139-
Self::convert_rgb_channel_to_pulses(value.b, mut_iter, pulses)?;
140-
141-
Ok(())
142-
}
143-
144-
fn convert_rgb_channel_to_pulses(
145-
channel_value: u8,
146-
mut_iter: &mut IterMut<u32>,
147-
pulses: (u32, u32),
148-
) -> Result<(), LedAdapterError> {
149-
for position in [128, 64, 32, 16, 8, 4, 2, 1] {
150-
*mut_iter.next().ok_or(LedAdapterError::BufferSizeExceeded)? =
151-
match channel_value & position {
152-
0 => pulses.0,
153-
_ => pulses.1,
154-
}
174+
pulses: led_pulses_for_clock(src_clock),
155175
}
156-
157-
Ok(())
158176
}
159177
}
160178

@@ -180,7 +198,7 @@ where
180198
// This will result in an `BufferSizeExceeded` error in case
181199
// the iterator provides more elements than the buffer can take.
182200
for item in iterator {
183-
Self::convert_rgb_to_pulse(item.into(), &mut seq_iter, self.pulses)?;
201+
convert_rgb_to_pulses(item.into(), &mut seq_iter, self.pulses)?;
184202
}
185203

186204
// Finally, add an end element.
@@ -200,3 +218,98 @@ where
200218
}
201219
}
202220
}
221+
222+
/// Support for asynchronous and non-blocking use of the RMT peripheral to drive smart LEDs.
223+
pub mod asynch {
224+
use super::*;
225+
use esp_hal::{
226+
clock::Clocks,
227+
gpio::PeripheralOutput,
228+
peripheral::Peripheral,
229+
rmt::{asynch::TxChannelAsync, TxChannelCreatorAsync},
230+
};
231+
232+
/// Function to calculate the required RMT buffer size for a given number of LEDs when using
233+
/// the asynchronous API.
234+
pub const fn buffer_size(num_leds: usize) -> usize {
235+
// 1 byte end delimiter for each transfer.
236+
num_leds * (RMT_RAM_ONE_LED + 1)
237+
}
238+
239+
/// Adapter taking an RMT channel and a specific pin and providing RGB LED
240+
/// interaction functionality.
241+
pub struct SmartLedAdapterAsync<Tx, const BUFFER_SIZE: usize> {
242+
channel: Tx,
243+
rmt_buffer: [u32; BUFFER_SIZE],
244+
pulses: (u32, u32),
245+
}
246+
247+
impl<'d, Tx: TxChannelAsync, const BUFFER_SIZE: usize> SmartLedAdapterAsync<Tx, BUFFER_SIZE> {
248+
/// Create a new adapter object that drives the pin using the RMT channel.
249+
pub fn new<C, O>(
250+
channel: C,
251+
pin: impl Peripheral<P = O> + 'd,
252+
rmt_buffer: [u32; BUFFER_SIZE],
253+
) -> SmartLedAdapterAsync<Tx, BUFFER_SIZE>
254+
where
255+
O: PeripheralOutput + 'd,
256+
C: TxChannelCreatorAsync<'d, Tx, O>,
257+
{
258+
let channel = channel.configure(pin, led_config()).unwrap();
259+
260+
// Assume the RMT peripheral is set up to use the APB clock
261+
let src_clock = Clocks::get().apb_clock.to_MHz();
262+
263+
Self {
264+
channel,
265+
rmt_buffer,
266+
pulses: led_pulses_for_clock(src_clock),
267+
}
268+
}
269+
270+
/// Convert all RGB8 items of the iterator to the RMT format and
271+
/// add them to internal buffer, then start perform all asynchronous operations based on
272+
/// that buffer.
273+
pub async fn write(
274+
&mut self,
275+
led: impl IntoIterator<Item = RGB8>,
276+
) -> Result<(), LedAdapterError> {
277+
self.prepare_rmt_buffer(led)?;
278+
for chunk in self.rmt_buffer.chunks(RMT_RAM_ONE_LED + 1) {
279+
self.channel
280+
.transmit(chunk)
281+
.await
282+
.map_err(LedAdapterError::TransmissionError)?;
283+
}
284+
Ok(())
285+
}
286+
287+
fn prepare_rmt_buffer(
288+
&mut self,
289+
iterator: impl IntoIterator<Item = RGB8>,
290+
) -> Result<(), LedAdapterError> {
291+
// We always start from the beginning of the buffer
292+
let mut seq_iter = self.rmt_buffer.iter_mut();
293+
294+
// Add all converted iterator items to the buffer.
295+
// This will result in an `BufferSizeExceeded` error in case
296+
// the iterator provides more elements than the buffer can take.
297+
for item in iterator {
298+
Self::convert_rgb_to_pulse(item, &mut seq_iter, self.pulses)?;
299+
}
300+
Ok(())
301+
}
302+
303+
/// Converts a RGB value to the correspodnign pulse value.
304+
fn convert_rgb_to_pulse(
305+
value: RGB8,
306+
mut_iter: &mut IterMut<u32>,
307+
pulses: (u32, u32),
308+
) -> Result<(), LedAdapterError> {
309+
convert_rgb_to_pulses(value, mut_iter, pulses)?;
310+
*mut_iter.next().ok_or(LedAdapterError::BufferSizeExceeded)? = 0;
311+
312+
Ok(())
313+
}
314+
}
315+
}

0 commit comments

Comments
 (0)