-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathlib.rs
More file actions
324 lines (301 loc) · 9.51 KB
/
lib.rs
File metadata and controls
324 lines (301 loc) · 9.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
#![no_std]
//! WS2812 PIO Driver for the RP2040
//!
//! This driver implements driving a WS2812 RGB LED strip from
//! a PIO device of the RP2040 chip.
//!
//! You should reach to [Ws2812] if you run the main loop
//! of your controller yourself and you want [Ws2812] to take
//! a hold of your timer.
//!
//! In case you use `cortex-m-rtic` and can't afford this crate
//! to wait blocking for you, you should try [Ws2812Direct].
//! Bear in mind that you will have to take care of timing requirements
//! yourself then.
use embedded_hal::timer::CountDown;
use fugit::{ExtU32, HertzU32, MicrosDurationU32};
use rp2040_hal::{
gpio::AnyPin,
pio::{PIOExt, StateMachineIndex, Tx, UninitStateMachine, PIO},
};
use smart_leds_trait::SmartLedsWrite;
/// This is the WS2812 PIO Driver.
///
/// For blocking applications is recommended to use
/// the [Ws2812] struct instead of this raw driver.
///
/// If you use this driver directly, you will need to
/// take care of the timing expectations of the [Ws2812Direct::write]
/// method.
///
/// Typical usage example:
///```ignore
/// use rp2040_hal::clocks::init_clocks_and_plls;
/// let clocks = init_clocks_and_plls(...);
/// let pins = rp2040_hal::gpio::pin::bank0::Pins::new(...);
///
/// let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS);
/// let mut ws = Ws2812Direct::new(
/// pins.gpio4.into_mode(),
/// &mut pio,
/// sm0,
/// clocks.peripheral_clock.freq(),
/// );
///
/// // Then you will make sure yourself to not write too frequently:
/// loop {
/// use smart_leds::{SmartLedsWrite, RGB8};
/// let color : RGB8 = (255, 0, 255).into();
///
/// ws.write([color]).unwrap();
/// delay_for_at_least_60_microseconds();
/// };
///```
///
/// // It is possible to change the color ordering by using
/// // the constructor ```new_with_chip_config``` along with the
/// // ordering enum ```Ws2812ColorOrder```:
///```ignore
/// let mut ws = Ws2812Direct::new_with_chip_config(
/// pins.gpio4.into_mode(),
/// &mut pio,
/// sm0,
/// clocks.peripheral_clock.freq(),
/// Ws2812ColorOrder::RGB,
/// );
/// ```
mod ws2812_color_order;
pub use ws2812_color_order::Ws2812ColorOrder;
pub struct Ws2812Direct<P, SM, I>
where
I: AnyPin<Function = P::PinFunction>,
P: PIOExt,
SM: StateMachineIndex,
{
tx: Tx<(P, SM)>,
_pin: I,
_converter: Ws2812ColorOrder,
}
impl<P, SM, I> Ws2812Direct<P, SM, I>
where
I: AnyPin<Function = P::PinFunction>,
P: PIOExt,
SM: StateMachineIndex,
{
/// Creates a new instance of this driver.
pub fn new(
pin: I,
pio: &mut PIO<P>,
sm: UninitStateMachine<(P, SM)>,
clock_freq: fugit::HertzU32,
) -> Self {
Ws2812Direct::new_with_chip_config(pin, pio, sm, clock_freq, Ws2812ColorOrder::GRB)
}
/// Creates a new instance of this driver.
pub fn new_with_chip_config(
pin: I,
pio: &mut PIO<P>,
sm: UninitStateMachine<(P, SM)>,
clock_freq: fugit::HertzU32,
chip_config: Ws2812ColorOrder,
) -> Self {
// prepare the PIO program
let side_set = pio::SideSet::new(false, 1, false);
let mut a = pio::Assembler::new_with_side_set(side_set);
const T1: u8 = 2; // start bit
const T2: u8 = 5; // data bit
const T3: u8 = 3; // stop bit
const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32;
const FREQ: HertzU32 = HertzU32::kHz(800);
let mut wrap_target = a.label();
let mut wrap_source = a.label();
let mut do_zero = a.label();
a.bind(&mut wrap_target);
// Do stop bit
a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0);
// Do start bit
a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1);
// Do data bit = 1
a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1);
a.bind(&mut do_zero);
// Do data bit = 0
a.nop_with_delay_and_side_set(T2 - 1, 0);
a.bind(&mut wrap_source);
let program = a.assemble_with_wrap(wrap_source, wrap_target);
// Install the program into PIO instruction memory.
let installed = pio.install(&program).unwrap();
// Configure the PIO state machine.
let bit_freq = FREQ * CYCLES_PER_BIT;
let mut int = clock_freq / bit_freq;
let rem = clock_freq - (int * bit_freq);
let frac = (rem * 256) / bit_freq;
assert!(
(1..=65536).contains(&int) && (int != 65536 || frac == 0),
"(System Clock / {}) must be within [1.0, 65536.0].",
bit_freq.to_kHz()
);
// 65536.0 is represented as 0 in the pio's clock divider
if int == 65536 {
int = 0;
}
// Using lossy conversion because range have been checked
let int: u16 = int as u16;
let frac: u8 = frac as u8;
let pin = pin.into();
let (mut sm, _, tx) = rp2040_hal::pio::PIOBuilder::from_installed_program(installed)
// only use TX FIFO
.buffers(rp2040_hal::pio::Buffers::OnlyTx)
// Pin configuration
.side_set_pin_base(pin.id().num)
// OSR config
.out_shift_direction(rp2040_hal::pio::ShiftDirection::Left)
.autopull(true)
.pull_threshold(24)
.clock_divisor_fixed_point(int, frac)
.build(sm);
// Prepare pin's direction.
sm.set_pindirs([(pin.id().num, rp2040_hal::pio::PinDir::Output)]);
sm.start();
Self {
tx,
_pin: I::from(pin),
_converter: chip_config,
}
}
}
impl<P, SM, I> SmartLedsWrite for Ws2812Direct<P, SM, I>
where
I: AnyPin<Function = P::PinFunction>,
P: PIOExt,
SM: StateMachineIndex,
{
type Color = smart_leds_trait::RGB8;
type Error = ();
/// If you call this function, be advised that you will have to wait
/// at least 60 microseconds between calls of this function!
/// That means, either you get hold on a timer and the timing
/// requirements right your self, or rather use [Ws2812].
///
/// Please bear in mind, that it still blocks when writing into the
/// PIO FIFO until all data has been transmitted to the LED chain.
fn write<T, J>(&mut self, iterator: T) -> Result<(), ()>
where
T: IntoIterator<Item = J>,
J: Into<Self::Color>,
{
for item in iterator {
let color: Self::Color = item.into();
while !self.tx.write(self._converter._get_color_value(&color)) {
cortex_m::asm::nop();
}
}
Ok(())
}
}
/// Instance of a WS2812 LED chain.
///
/// Use the [Ws2812::write] method to update the WS2812 LED chain.
///
/// Typical usage example:
///```ignore
/// use rp2040_hal::clocks::init_clocks_and_plls;
/// let clocks = init_clocks_and_plls(...);
/// let pins = rp2040_hal::gpio::pin::bank0::Pins::new(...);
///
/// let timer = Timer::new(pac.TIMER, &mut pac.RESETS);
///
/// let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS);
/// let mut ws = Ws2812::new(
/// pins.gpio4.into_mode(),
/// &mut pio,
/// sm0,
/// clocks.peripheral_clock.freq(),
/// timer.count_down(),
/// );
///
/// loop {
/// use smart_leds::{SmartLedsWrite, RGB8};
/// let color : RGB8 = (255, 0, 255).into();
///
/// ws.write([color].iter().copied()).unwrap();
///
/// // Do other stuff here...
/// };
///```
///
/// // It is possible to change the color ordering by using
/// // the constructor ```new_with_chip_config``` along with the
/// // ordering enum ```Ws2812ColorOrder```:
///```ignore
/// let mut ws = Ws2812::new_with_chip_config(
/// pins.gpio4.into_mode(),
/// &mut pio,
/// sm0,
/// clocks.peripheral_clock.freq(),
/// timer.count_down(),
/// Ws2812ColorOrder::RGB,
/// );
/// ```
pub struct Ws2812<P, SM, C, I>
where
C: CountDown,
I: AnyPin<Function = P::PinFunction>,
P: PIOExt,
SM: StateMachineIndex,
{
driver: Ws2812Direct<P, SM, I>,
cd: C,
}
impl<P, SM, C, I> Ws2812<P, SM, C, I>
where
C: CountDown,
I: AnyPin<Function = P::PinFunction>,
P: PIOExt,
SM: StateMachineIndex,
{
/// Creates a new instance of this driver.
pub fn new(
pin: I,
pio: &mut PIO<P>,
sm: UninitStateMachine<(P, SM)>,
clock_freq: fugit::HertzU32,
cd: C,
) -> Ws2812<P, SM, C, I> {
let driver = Ws2812Direct::new(pin, pio, sm, clock_freq);
Self { driver, cd }
}
/// Creates a new instance of this driver.
pub fn new_with_chip_config(
pin: I,
pio: &mut PIO<P>,
sm: UninitStateMachine<(P, SM)>,
clock_freq: fugit::HertzU32,
cd: C,
chip_config: Ws2812ColorOrder,
) -> Ws2812<P, SM, C, I> {
let driver = Ws2812Direct::new_with_chip_config(pin, pio, sm, clock_freq, chip_config);
Self { driver, cd }
}
}
impl<P, SM, I, C> SmartLedsWrite for Ws2812<P, SM, C, I>
where
C: CountDown,
C::Time: From<MicrosDurationU32>,
I: AnyPin<Function = P::PinFunction>,
P: PIOExt,
SM: StateMachineIndex,
{
type Color = smart_leds_trait::RGB8;
type Error = ();
fn write<T, J>(&mut self, iterator: T) -> Result<(), ()>
where
T: IntoIterator<Item = J>,
J: Into<Self::Color>,
{
self.driver.tx.clear_stalled_flag();
while !self.driver.tx.is_empty() && !self.driver.tx.has_stalled() {}
self.cd.start(60u32.micros());
let _ = nb::block!(self.cd.wait());
self.driver.write(iterator)
}
}