Skip to content

Commit f083668

Browse files
committed
Add non-blocking asynch smart LED driver
1 parent ad75112 commit f083668

File tree

4 files changed

+198
-69
lines changed

4 files changed

+198
-69
lines changed

esp-hal-smartled/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
## 0.15.0
11+
12+
### Added
13+
14+
- New `SmartLedsAdapterAsync` which is an asynchronous, non-blocking version of the driver.
15+
16+
## 0.14.0
17+
18+
## 0.13.1
19+
1020
### Added
1121

1222
### 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.14.0"
3+
version = "0.15.0"
44
edition = "2021"
55
rust-version = "1.84.0"
66
description = "RMT peripheral adapter for smart LEDs"
@@ -16,7 +16,7 @@ defmt = { version = "0.3.10", optional = true }
1616
document-features = "0.2.10"
1717
esp-hal = "0.23.1"
1818
fugit = "0.3.7"
19-
smart-leds-trait = "0.3.0"
19+
smart-leds-trait = "0.3.1"
2020

2121
[dev-dependencies]
2222
cfg-if = "1.0.0"

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, main, rmt::Rmt, time::RateExtU32};
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},
@@ -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: 184 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,16 @@ use esp_hal::{
2929
clock::Clocks,
3030
gpio::OutputPin,
3131
peripheral::Peripheral,
32-
rmt::{Error as RmtError, PulseCode, TxChannel, TxChannelConfig, TxChannelCreator},
32+
rmt::{
33+
Error as RmtError, PulseCode, TxChannel, TxChannelAsync, TxChannelConfig, TxChannelCreator,
34+
TxChannelCreatorAsync,
35+
},
3336
};
34-
use smart_leds_trait::{SmartLedsWrite, RGB8};
37+
use smart_leds_trait::{SmartLedsWrite, SmartLedsWriteAsync, RGB8};
38+
39+
// Required RMT RAM to drive one LED.
40+
// number of channels (r,g,b -> 3) * pulses per channel 8)
41+
const RMT_RAM_ONE_LED: usize = 3 * 8;
3542

3643
const SK68XX_CODE_PERIOD: u32 = 1250; // 800kHz
3744
const SK68XX_T0H_NS: u32 = 400; // 300ns per SK6812 datasheet, 400 per WS2812. Some require >350ns for T0H. Others <500ns for T0H.
@@ -56,21 +63,89 @@ impl From<RmtError> for LedAdapterError {
5663
}
5764
}
5865

66+
fn led_pulses_for_clock(src_clock: u32) -> (u32, u32) {
67+
(
68+
PulseCode::new(
69+
true,
70+
((SK68XX_T0H_NS * src_clock) / 1000) as u16,
71+
false,
72+
((SK68XX_T0L_NS * src_clock) / 1000) as u16,
73+
),
74+
PulseCode::new(
75+
true,
76+
((SK68XX_T1H_NS * src_clock) / 1000) as u16,
77+
false,
78+
((SK68XX_T1L_NS * src_clock) / 1000) as u16,
79+
),
80+
)
81+
}
82+
83+
fn led_config() -> TxChannelConfig {
84+
TxChannelConfig {
85+
clk_divider: 1,
86+
idle_output_level: false,
87+
carrier_modulation: false,
88+
idle_output: true,
89+
90+
..TxChannelConfig::default()
91+
}
92+
}
93+
94+
fn convert_rgb_to_pulses(
95+
value: RGB8,
96+
mut_iter: &mut IterMut<u32>,
97+
pulses: (u32, u32),
98+
) -> Result<(), LedAdapterError> {
99+
convert_rgb_channel_to_pulses(value.g, mut_iter, pulses)?;
100+
convert_rgb_channel_to_pulses(value.r, mut_iter, pulses)?;
101+
convert_rgb_channel_to_pulses(value.b, mut_iter, pulses)?;
102+
Ok(())
103+
}
104+
105+
fn convert_rgb_channel_to_pulses(
106+
channel_value: u8,
107+
mut_iter: &mut IterMut<u32>,
108+
pulses: (u32, u32),
109+
) -> Result<(), LedAdapterError> {
110+
for position in [128, 64, 32, 16, 8, 4, 2, 1] {
111+
*mut_iter.next().ok_or(LedAdapterError::BufferSizeExceeded)? =
112+
match channel_value & position {
113+
0 => pulses.0,
114+
_ => pulses.1,
115+
}
116+
}
117+
118+
Ok(())
119+
}
120+
121+
/// Function to calculate the required RMT buffer size for a given number of LEDs when using
122+
/// the blocking API.
123+
///
124+
/// This buffer size is calculated for the synchronous API provided by the [SmartLedsAdapter].
125+
/// [buffer_size_async] should be used for the asynchronous API.
126+
pub const fn buffer_size(num_leds: usize) -> usize {
127+
// 1 additional pulse for the end delimiter
128+
num_leds * RMT_RAM_ONE_LED + 1
129+
}
130+
59131
/// Macro to allocate a buffer sized for a specific number of LEDs to be
60132
/// addressed.
61133
///
62134
/// Attempting to use more LEDs that the buffer is configured for will result in
63135
/// an `LedAdapterError:BufferSizeExceeded` error.
64136
#[macro_export]
137+
macro_rules! smart_led_buffer {
138+
( $num_leds: expr ) => {
139+
[0u32; $crate::buffer_size($num_leds)]
140+
};
141+
}
142+
143+
/// Deprecated alias for [smart_led_buffer] macro.
144+
#[macro_export]
145+
#[deprecated]
65146
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]
147+
( $num_leds: expr ) => {
148+
smart_led_buffer!($num_leds);
74149
};
75150
}
76151

@@ -99,67 +174,16 @@ where
99174
O: OutputPin + 'd,
100175
C: TxChannelCreator<'d, TX, O>,
101176
{
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();
177+
let channel = channel.configure(pin, led_config()).unwrap();
112178

113179
// 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();
180+
let src_clock = Clocks::get().apb_clock.to_MHz();
116181

117182
Self {
118183
channel: Some(channel),
119184
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-
}
185+
pulses: led_pulses_for_clock(src_clock),
160186
}
161-
162-
Ok(())
163187
}
164188
}
165189

@@ -185,7 +209,7 @@ where
185209
// This will result in an `BufferSizeExceeded` error in case
186210
// the iterator provides more elements than the buffer can take.
187211
for item in iterator {
188-
Self::convert_rgb_to_pulse(item.into(), &mut seq_iter, self.pulses)?;
212+
convert_rgb_to_pulses(item.into(), &mut seq_iter, self.pulses)?;
189213
}
190214

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

0 commit comments

Comments
 (0)