aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/stm32f4/src/bin/ws2812_pwm_dma.rs76
-rw-r--r--examples/stm32f4/src/bin/ws2812_spi.rs95
2 files changed, 136 insertions, 35 deletions
diff --git a/examples/stm32f4/src/bin/ws2812_pwm_dma.rs b/examples/stm32f4/src/bin/ws2812_pwm_dma.rs
index 52cc665c7..dccd639ac 100644
--- a/examples/stm32f4/src/bin/ws2812_pwm_dma.rs
+++ b/examples/stm32f4/src/bin/ws2812_pwm_dma.rs
@@ -1,7 +1,16 @@
1// Configure TIM3 in PWM mode, and start DMA Transfer(s) to send color data into ws2812. 1// Configure TIM3 in PWM mode, and start DMA Transfer(s) to send color data into ws2812.
2// We assume the DIN pin of ws2812 connect to GPIO PB4, and ws2812 is properly powered. 2// We assume the DIN pin of ws2812 connect to GPIO PB4, and ws2812 is properly powered.
3// 3//
4// This demo is a combination of HAL, PAC, and manually invoke `dma::Transfer` 4// The idea is that the data rate of ws2812 is 800 kHz, and it use different duty ratio to represent bit 0 and bit 1.
5// Thus we can set TIM overflow at 800 kHz, and let TIM Update Event trigger a DMA transfer, then let DMA change CCR value,
6// such that pwm duty ratio meet the bit representation of ws2812.
7//
8// You may want to modify TIM CCR with Cortex core directly,
9// but according to my test, Cortex core will need to run far more than 100 MHz to catch up with TIM.
10// Thus we need to use a DMA.
11//
12// This demo is a combination of HAL, PAC, and manually invoke `dma::Transfer`.
13// If you need a simpler way to control ws2812, you may want to take a look at `ws2812_spi.rs` file, which make use of SPI.
5// 14//
6// Warning: 15// Warning:
7// DO NOT stare at ws2812 directy (especially after each MCU Reset), its (max) brightness could easily make your eyes feel burn. 16// DO NOT stare at ws2812 directy (especially after each MCU Reset), its (max) brightness could easily make your eyes feel burn.
@@ -16,7 +25,7 @@ use embassy_stm32::pac;
16use embassy_stm32::time::khz; 25use embassy_stm32::time::khz;
17use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; 26use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
18use embassy_stm32::timer::{Channel, CountingMode}; 27use embassy_stm32::timer::{Channel, CountingMode};
19use embassy_time::Timer; 28use embassy_time::{Duration, Ticker, Timer};
20use {defmt_rtt as _, panic_probe as _}; 29use {defmt_rtt as _, panic_probe as _};
21 30
22#[embassy_executor::main] 31#[embassy_executor::main]
@@ -33,7 +42,6 @@ async fn main(_spawner: Spawner) {
33 freq: mhz(12), 42 freq: mhz(12),
34 mode: HseMode::Oscillator, 43 mode: HseMode::Oscillator,
35 }); 44 });
36 device_config.rcc.sys = Sysclk::PLL1_P;
37 device_config.rcc.pll_src = PllSource::HSE; 45 device_config.rcc.pll_src = PllSource::HSE;
38 device_config.rcc.pll = Some(Pll { 46 device_config.rcc.pll = Some(Pll {
39 prediv: PllPreDiv::DIV6, 47 prediv: PllPreDiv::DIV6,
@@ -42,6 +50,7 @@ async fn main(_spawner: Spawner) {
42 divq: None, 50 divq: None,
43 divr: None, 51 divr: None,
44 }); 52 });
53 device_config.rcc.sys = Sysclk::PLL1_P;
45 } 54 }
46 55
47 let mut dp = embassy_stm32::init(device_config); 56 let mut dp = embassy_stm32::init(device_config);
@@ -56,14 +65,11 @@ async fn main(_spawner: Spawner) {
56 CountingMode::EdgeAlignedUp, 65 CountingMode::EdgeAlignedUp,
57 ); 66 );
58 67
59 // PAC level hacking, 68 // PAC level hacking, enable timer-update-event trigger DMA
60 // enable auto-reload preload, and enable timer-update-event trigger DMA 69 pac::TIM3.dier().modify(|v| v.set_ude(true));
61 {
62 pac::TIM3.cr1().modify(|v| v.set_arpe(true));
63 pac::TIM3.dier().modify(|v| v.set_ude(true));
64 }
65 70
66 // construct ws2812 non-return-to-zero (NRZ) code bit by bit 71 // construct ws2812 non-return-to-zero (NRZ) code bit by bit
72 // ws2812 only need 24 bits for each LED, but we add one bit more to keep PWM output low
67 73
68 let max_duty = ws2812_pwm.get_max_duty(); 74 let max_duty = ws2812_pwm.get_max_duty();
69 let n0 = 8 * max_duty / 25; // ws2812 Bit 0 high level timing 75 let n0 = 8 * max_duty / 25; // ws2812 Bit 0 high level timing
@@ -83,7 +89,7 @@ async fn main(_spawner: Spawner) {
83 0, // keep PWM output low after a transfer 89 0, // keep PWM output low after a transfer
84 ]; 90 ];
85 91
86 let color_list = [&turn_off, &dim_white]; 92 let color_list = &[&turn_off, &dim_white];
87 93
88 let pwm_channel = Channel::Ch1; 94 let pwm_channel = Channel::Ch1;
89 95
@@ -98,34 +104,34 @@ async fn main(_spawner: Spawner) {
98 dma_transfer_option.fifo_threshold = Some(FifoThreshold::Full); 104 dma_transfer_option.fifo_threshold = Some(FifoThreshold::Full);
99 dma_transfer_option.mburst = Burst::Incr8; 105 dma_transfer_option.mburst = Burst::Incr8;
100 106
101 let mut color_list_index = 0; 107 // flip color at 2 Hz
108 let mut ticker = Ticker::every(Duration::from_micros(500));
102 109
103 loop { 110 loop {
104 // start PWM output 111 for &color in color_list {
105 ws2812_pwm.enable(pwm_channel); 112 // start PWM output
106 113 ws2812_pwm.enable(pwm_channel);
107 unsafe { 114
108 Transfer::new_write( 115 unsafe {
109 // with &mut, we can easily reuse same DMA channel multiple times 116 Transfer::new_write(
110 &mut dp.DMA1_CH2, 117 // with &mut, we can easily reuse same DMA channel multiple times
111 5, 118 &mut dp.DMA1_CH2,
112 color_list[color_list_index], 119 5,
113 pac::TIM3.ccr(pwm_channel.raw()).as_ptr() as *mut _, 120 color,
114 dma_transfer_option, 121 pac::TIM3.ccr(pwm_channel.raw()).as_ptr() as *mut _,
115 ) 122 dma_transfer_option,
116 .await; 123 )
117 // ws2812 need at least 50 us low level input to confirm the input data and change it's state 124 .await;
118 Timer::after_micros(50).await; 125 // ws2812 need at least 50 us low level input to confirm the input data and change it's state
126 Timer::after_micros(50).await;
127 }
128
129 // stop PWM output for saving some energy
130 ws2812_pwm.disable(pwm_channel);
131
132 // wait until ticker tick
133 ticker.next().await;
119 } 134 }
120
121 // stop PWM output for saving some energy
122 ws2812_pwm.disable(pwm_channel);
123
124 // wait another half second, so that we can see color change
125 Timer::after_millis(500).await;
126
127 // flip the index bit so that next round DMA transfer the other color data
128 color_list_index ^= 1;
129 } 135 }
130 } 136 }
131} 137}
diff --git a/examples/stm32f4/src/bin/ws2812_spi.rs b/examples/stm32f4/src/bin/ws2812_spi.rs
new file mode 100644
index 000000000..e0d28af7f
--- /dev/null
+++ b/examples/stm32f4/src/bin/ws2812_spi.rs
@@ -0,0 +1,95 @@
1// Mimic PWM with SPI, to control ws2812
2// We assume the DIN pin of ws2812 connect to GPIO PB5, and ws2812 is properly powered.
3//
4// The idea is that the data rate of ws2812 is 800 kHz, and it use different duty ratio to represent bit 0 and bit 1.
5// Thus we can adjust SPI to send each *round* of data at 800 kHz, and in each *round*, we can adjust each *bit* to mimic 2 different PWM waveform.
6// such that the output waveform meet the bit representation of ws2812.
7//
8// If you want to save SPI for other purpose, you may want to take a look at `ws2812_tim_dma.rs` file, which make use of TIM and DMA.
9//
10// Warning:
11// DO NOT stare at ws2812 directy (especially after each MCU Reset), its (max) brightness could easily make your eyes feel burn.
12
13#![no_std]
14#![no_main]
15#![feature(type_alias_impl_trait)]
16
17use embassy_stm32::time::khz;
18use embassy_stm32::{dma, spi};
19use embassy_time::{Duration, Ticker, Timer};
20use {defmt_rtt as _, panic_probe as _};
21
22// we use 16 bit data frame format of SPI, to let timing as accurate as possible.
23// thanks to loose tolerance of ws2812 timing, you can also use 8 bit data frame format, thus you need want to adjust the bit representation.
24const N0: u16 = 0b1111100000000000u16; // ws2812 Bit 0 high level timing
25const N1: u16 = 0b1111111111000000u16; // ws2812 Bit 1 high level timing
26
27// ws2812 only need 24 bits for each LED, but we add one bit more to keep SPI output low
28
29static TURN_OFF: [u16; 25] = [
30 N0, N0, N0, N0, N0, N0, N0, N0, // Green
31 N0, N0, N0, N0, N0, N0, N0, N0, // Red
32 N0, N0, N0, N0, N0, N0, N0, N0, // Blue
33 0, // keep SPI output low after last bit
34];
35
36static DIM_WHITE: [u16; 25] = [
37 N0, N0, N0, N0, N0, N0, N1, N0, // Green
38 N0, N0, N0, N0, N0, N0, N1, N0, // Red
39 N0, N0, N0, N0, N0, N0, N1, N0, // Blue
40 0, // keep SPI output low after last bit
41];
42
43static COLOR_LIST: &[&[u16]] = &[&TURN_OFF, &DIM_WHITE];
44
45#[embassy_executor::main]
46async fn main(_spawner: embassy_executor::Spawner) {
47 let mut device_config = embassy_stm32::Config::default();
48
49 // Since we use 16 bit SPI, and we need each round 800 kHz,
50 // thus SPI output speed should be 800 kHz * 16 = 12.8 MHz, and APB clock should be 2 * 12.8 MHz = 25.6 MHz.
51 //
52 // As for my setup, with 12 MHz HSE, I got 25.5 MHz SYSCLK, which is slightly slower, but it's ok for ws2812.
53 {
54 use embassy_stm32::rcc::{Hse, HseMode, Pll, PllMul, PllPDiv, PllPreDiv, PllSource, Sysclk};
55 use embassy_stm32::time::mhz;
56 device_config.enable_debug_during_sleep = true;
57 device_config.rcc.hse = Some(Hse {
58 freq: mhz(12),
59 mode: HseMode::Oscillator,
60 });
61 device_config.rcc.pll_src = PllSource::HSE;
62 device_config.rcc.pll = Some(Pll {
63 prediv: PllPreDiv::DIV6,
64 mul: PllMul::MUL102,
65 divp: Some(PllPDiv::DIV8),
66 divq: None,
67 divr: None,
68 });
69 device_config.rcc.sys = Sysclk::PLL1_P;
70 }
71
72 let dp = embassy_stm32::init(device_config);
73
74 // Set SPI output speed.
75 // It's ok to blindly set frequency to 12800 kHz, the hal crate will take care of the SPI CR1 BR field.
76 // And in my case, the real bit rate will be 25.5 MHz / 2 = 12_750 kHz
77 let mut spi_config = spi::Config::default();
78 spi_config.frequency = khz(12_800);
79
80 // Since we only output waveform, then the Rx and Sck it is not considered
81 let mut ws2812_spi = spi::Spi::new_txonly_nosck(dp.SPI1, dp.PB5, dp.DMA2_CH2, dma::NoDma, spi_config);
82
83 // flip color at 2 Hz
84 let mut ticker = Ticker::every(Duration::from_millis(500));
85
86 loop {
87 for &color in COLOR_LIST {
88 ws2812_spi.write(color).await.unwrap();
89 // ws2812 need at least 50 us low level input to confirm the input data and change it's state
90 Timer::after_micros(50).await;
91 // wait until ticker tick
92 ticker.next().await;
93 }
94 }
95}