Skip to content

Commit 94369e8

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

File tree

3 files changed

+181
-67
lines changed

3 files changed

+181
-67
lines changed

esp-hal-smartled/Cargo.toml

Lines changed: 2 additions & 2 deletions
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"
@@ -25,7 +25,7 @@ esp-backtrace = { version = "0.14.2", features = [
2525
"panic-handler",
2626
"println",
2727
] }
28-
esp-println = "0.12.0"
28+
esp-println = { version = "0.12.0"}
2929
smart-leds = "0.4.0"
3030

3131
[features]

esp-hal-smartled/examples/hello_rgb.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
use esp_backtrace as _;
2626
use esp_hal::{delay::Delay, gpio::Io, prelude::*, rmt::Rmt};
27-
use esp_hal_smartled::{smartLedBuffer, SmartLedsAdapter};
27+
use esp_hal_smartled::{smart_led_buffer, SmartLedsAdapter};
2828
use smart_leds::{
2929
brightness, gamma,
3030
hsv::{hsv2rgb, Hsv},
@@ -66,7 +66,7 @@ fn main() -> ! {
6666

6767
// We use one of the RMT channels to instantiate a `SmartLedsAdapter` which can
6868
// be used directly with all `smart_led` implementations
69-
let rmt_buffer = smartLedBuffer!(1);
69+
let rmt_buffer = smart_led_buffer!(1);
7070
let mut led = SmartLedsAdapter::new(rmt.channel0, led_pin, rmt_buffer);
7171

7272
let delay = Delay::new();

esp-hal-smartled/src/lib.rs

Lines changed: 177 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,90 @@ 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+
113+
/// Function to calculate the required RMT buffer size for a given number of LEDs when using
114+
/// the blocking API.
115+
pub const fn buffer_size(num_leds: usize) -> usize {
116+
// 1 additional pulse for the end delimiter
117+
num_leds * RMT_RAM_ONE_LED + 1
118+
}
119+
54120
/// Macro to allocate a buffer sized for a specific number of LEDs to be
55121
/// addressed.
56122
///
57123
/// Attempting to use more LEDs that the buffer is configured for will result in
58124
/// an `LedAdapterError:BufferSizeExceeded` error.
59125
#[macro_export]
126+
macro_rules! smart_led_buffer {
127+
( $num_leds: expr ) => {
128+
[0u32; $crate::buffer_size($num_leds)]
129+
};
130+
}
131+
132+
/// Deprecated alias for [smart_led_buffer] macro.
133+
#[macro_export]
134+
#[deprecated]
60135
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]
136+
( $num_leds: expr ) => {
137+
smart_led_buffer!($num_leds);
69138
};
70139
}
71140

141+
72142
/// Adapter taking an RMT channel and a specific pin and providing RGB LED
73143
/// interaction functionality using the `smart-leds` crate
74144
pub struct SmartLedsAdapter<TX, const BUFFER_SIZE: usize>
@@ -94,68 +164,17 @@ where
94164
O: OutputPin + 'd,
95165
C: TxChannelCreator<'d, TX, O>,
96166
{
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();
167+
let channel = channel.configure(pin, led_config()).unwrap();
107168

108169
// 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();
170+
let src_clock = Clocks::get().apb_clock.to_MHz();
111171

112172
Self {
113173
channel: Some(channel),
114174
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-
),
175+
pulses: led_pulses_for_clock(src_clock),
129176
}
130177
}
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-
}
155-
}
156-
157-
Ok(())
158-
}
159178
}
160179

161180
impl<TX, const BUFFER_SIZE: usize> SmartLedsWrite for SmartLedsAdapter<TX, BUFFER_SIZE>
@@ -180,7 +199,7 @@ where
180199
// This will result in an `BufferSizeExceeded` error in case
181200
// the iterator provides more elements than the buffer can take.
182201
for item in iterator {
183-
Self::convert_rgb_to_pulse(item.into(), &mut seq_iter, self.pulses)?;
202+
convert_rgb_to_pulses(item.into(), &mut seq_iter, self.pulses)?;
184203
}
185204

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

0 commit comments

Comments
 (0)