Skip to content

Commit f3c5c6d

Browse files
committed
add support for non-blocking smart LED driver
1 parent 8a65dcf commit f3c5c6d

File tree

3 files changed

+180
-67
lines changed

3 files changed

+180
-67
lines changed

esp-hal-smartled/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
#![no_main]
2424

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

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

7070
let delay = Delay::new();

esp-hal-smartled/src/lib.rs

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

36+
// Required RMT RAM to drive one LED.
37+
// number of channels (r,g,b -> 3) * pulses per channel 8)
38+
const RMT_RAM_ONE_LED: usize = 3 * 8;
39+
3640
const SK68XX_CODE_PERIOD: u32 = 1250; // 800kHz
3741
const SK68XX_T0H_NS: u32 = 400; // 300ns per SK6812 datasheet, 400 per WS2812. Some require >350ns for T0H. Others <500ns for T0H.
3842
const SK68XX_T0L_NS: u32 = SK68XX_CODE_PERIOD - SK68XX_T0H_NS;
@@ -56,21 +60,86 @@ impl From<RmtError> for LedAdapterError {
5660
}
5761
}
5862

63+
fn led_pulses_for_clock(src_clock: u32) -> (u32, u32) {
64+
(
65+
u32::from(PulseCode {
66+
level1: true,
67+
length1: ((SK68XX_T0H_NS * src_clock) / 1000) as u16,
68+
level2: false,
69+
length2: ((SK68XX_T0L_NS * src_clock) / 1000) as u16,
70+
}),
71+
u32::from(PulseCode {
72+
level1: true,
73+
length1: ((SK68XX_T1H_NS * src_clock) / 1000) as u16,
74+
level2: false,
75+
length2: ((SK68XX_T1L_NS * src_clock) / 1000) as u16,
76+
}),
77+
)
78+
}
79+
80+
fn led_config() -> TxChannelConfig {
81+
TxChannelConfig {
82+
clk_divider: 1,
83+
idle_output_level: false,
84+
carrier_modulation: false,
85+
idle_output: true,
86+
87+
..TxChannelConfig::default()
88+
}
89+
}
90+
91+
fn convert_rgb_to_pulses(
92+
value: RGB8,
93+
mut_iter: &mut IterMut<u32>,
94+
pulses: (u32, u32),
95+
) -> Result<(), LedAdapterError> {
96+
convert_rgb_channel_to_pulses(value.g, mut_iter, pulses)?;
97+
convert_rgb_channel_to_pulses(value.r, mut_iter, pulses)?;
98+
convert_rgb_channel_to_pulses(value.b, mut_iter, pulses)?;
99+
Ok(())
100+
}
101+
102+
fn convert_rgb_channel_to_pulses(
103+
channel_value: u8,
104+
mut_iter: &mut IterMut<u32>,
105+
pulses: (u32, u32),
106+
) -> Result<(), LedAdapterError> {
107+
for position in [128, 64, 32, 16, 8, 4, 2, 1] {
108+
*mut_iter.next().ok_or(LedAdapterError::BufferSizeExceeded)? =
109+
match channel_value & position {
110+
0 => pulses.0,
111+
_ => pulses.1,
112+
}
113+
}
114+
115+
Ok(())
116+
}
117+
118+
/// Function to calculate the required RMT buffer size for a given number of LEDs when using
119+
/// the blocking API.
120+
pub const fn buffer_size(num_leds: usize) -> usize {
121+
// 1 additional pulse for the end delimiter
122+
num_leds * RMT_RAM_ONE_LED + 1
123+
}
124+
59125
/// Macro to allocate a buffer sized for a specific number of LEDs to be
60126
/// addressed.
61127
///
62128
/// Attempting to use more LEDs that the buffer is configured for will result in
63129
/// an `LedAdapterError:BufferSizeExceeded` error.
64130
#[macro_export]
131+
macro_rules! smart_led_buffer {
132+
( $num_leds: expr ) => {
133+
[0u32; $crate::buffer_size($num_leds)]
134+
};
135+
}
136+
137+
/// Deprecated alias for [smart_led_buffer] macro.
138+
#[macro_export]
139+
#[deprecated]
65140
macro_rules! smartLedBuffer {
66-
( $buffer_size: literal ) => {
67-
// The size we're assigning here is calculated as following
68-
// (
69-
// Nr. of LEDs
70-
// * channels (r,g,b -> 3)
71-
// * pulses per channel 8)
72-
// ) + 1 additional pulse for the end delimiter
73-
[0u32; $buffer_size * 24 + 1]
141+
( $num_leds: expr ) => {
142+
smart_led_buffer!($num_leds);
74143
};
75144
}
76145

@@ -99,67 +168,16 @@ where
99168
O: OutputPin + 'd,
100169
C: TxChannelCreator<'d, TX, O>,
101170
{
102-
let config = TxChannelConfig {
103-
clk_divider: 1,
104-
idle_output_level: false,
105-
carrier_modulation: false,
106-
idle_output: true,
107-
108-
..TxChannelConfig::default()
109-
};
110-
111-
let channel = channel.configure(pin, config).unwrap();
171+
let channel = channel.configure(pin, led_config()).unwrap();
112172

113173
// Assume the RMT peripheral is set up to use the APB clock
114-
let clocks = Clocks::get();
115-
let src_clock = clocks.apb_clock.to_MHz();
174+
let src_clock = Clocks::get().apb_clock.to_MHz();
116175

117176
Self {
118177
channel: Some(channel),
119178
rmt_buffer,
120-
pulses: (
121-
PulseCode::new (
122-
true,
123-
((SK68XX_T0H_NS * src_clock) / 1000) as u16,
124-
false,
125-
((SK68XX_T0L_NS * src_clock) / 1000) as u16,
126-
),
127-
PulseCode::new (
128-
true,
129-
((SK68XX_T1H_NS * src_clock) / 1000) as u16,
130-
false,
131-
((SK68XX_T1L_NS * src_clock) / 1000) as u16,
132-
),
133-
),
134-
}
135-
}
136-
137-
fn convert_rgb_to_pulse(
138-
value: RGB8,
139-
mut_iter: &mut IterMut<u32>,
140-
pulses: (u32, u32),
141-
) -> Result<(), LedAdapterError> {
142-
Self::convert_rgb_channel_to_pulses(value.g, mut_iter, pulses)?;
143-
Self::convert_rgb_channel_to_pulses(value.r, mut_iter, pulses)?;
144-
Self::convert_rgb_channel_to_pulses(value.b, mut_iter, pulses)?;
145-
146-
Ok(())
147-
}
148-
149-
fn convert_rgb_channel_to_pulses(
150-
channel_value: u8,
151-
mut_iter: &mut IterMut<u32>,
152-
pulses: (u32, u32),
153-
) -> Result<(), LedAdapterError> {
154-
for position in [128, 64, 32, 16, 8, 4, 2, 1] {
155-
*mut_iter.next().ok_or(LedAdapterError::BufferSizeExceeded)? =
156-
match channel_value & position {
157-
0 => pulses.0,
158-
_ => pulses.1,
159-
}
179+
pulses: led_pulses_for_clock(src_clock),
160180
}
161-
162-
Ok(())
163181
}
164182
}
165183

@@ -185,7 +203,7 @@ where
185203
// This will result in an `BufferSizeExceeded` error in case
186204
// the iterator provides more elements than the buffer can take.
187205
for item in iterator {
188-
Self::convert_rgb_to_pulse(item.into(), &mut seq_iter, self.pulses)?;
206+
convert_rgb_to_pulses(item.into(), &mut seq_iter, self.pulses)?;
189207
}
190208

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

0 commit comments

Comments
 (0)