aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxoviat <[email protected]>2025-11-22 09:46:52 -0600
committerxoviat <[email protected]>2025-11-22 09:46:52 -0600
commit454c6276507999b49f2dea6bb6abdae48d495713 (patch)
treef0623c7755b100f7f0593424333742541208150a
parent7e768e63a60e1e583a679b8cf9705c2604fdb28d (diff)
parenteb4e4100acbe03ee1d3726c948f91b6927a18125 (diff)
Merge branch 'main' of https://github.com/embassy-rs/embassy into lp-dma
-rw-r--r--embassy-stm32/CHANGELOG.md1
-rw-r--r--embassy-stm32/Cargo.toml4
-rw-r--r--embassy-stm32/build.rs18
-rw-r--r--embassy-stm32/src/adc/adc4.rs35
-rw-r--r--embassy-stm32/src/adc/mod.rs2
-rw-r--r--embassy-stm32/src/adc/v2.rs17
-rw-r--r--embassy-stm32/src/lcd.rs510
-rw-r--r--embassy-stm32/src/lib.rs2
-rw-r--r--examples/stm32u0/.cargo/config.toml4
-rw-r--r--examples/stm32u0/Cargo.toml6
-rw-r--r--examples/stm32u0/src/bin/lcd.rs412
-rw-r--r--tests/stm32/Cargo.toml8
-rw-r--r--tests/stm32/src/bin/adc.rs39
-rw-r--r--tests/stm32/src/common.rs1
14 files changed, 1018 insertions, 41 deletions
diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md
index 6e1381925..da2ea5c79 100644
--- a/embassy-stm32/CHANGELOG.md
+++ b/embassy-stm32/CHANGELOG.md
@@ -75,6 +75,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
75- fix: fixing channel numbers on vbat and vddcore for adc on adc 75- fix: fixing channel numbers on vbat and vddcore for adc on adc
76- adc: adding disable to vbat 76- adc: adding disable to vbat
77- feat: stm32/flash: add async support for h7 family 77- feat: stm32/flash: add async support for h7 family
78- feat: stm32/lcd: added implementation
78 79
79## 0.4.0 - 2025-08-26 80## 0.4.0 - 2025-08-26
80 81
diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml
index 2f4f2ce51..fbd00895d 100644
--- a/embassy-stm32/Cargo.toml
+++ b/embassy-stm32/Cargo.toml
@@ -200,11 +200,11 @@ aligned = "0.4.1"
200heapless = "0.9.1" 200heapless = "0.9.1"
201 201
202#stm32-metapac = { version = "18" } 202#stm32-metapac = { version = "18" }
203stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-b77c8d968f53b18d6bdcd052e354b5070ec2bbc2" } 203stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-61abfb115273dc66ed99bf14545779a2aa0722c8" }
204 204
205[build-dependencies] 205[build-dependencies]
206#stm32-metapac = { version = "18", default-features = false, features = ["metadata"]} 206#stm32-metapac = { version = "18", default-features = false, features = ["metadata"]}
207stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-b77c8d968f53b18d6bdcd052e354b5070ec2bbc2", default-features = false, features = ["metadata"] } 207stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-61abfb115273dc66ed99bf14545779a2aa0722c8", default-features = false, features = ["metadata"] }
208 208
209proc-macro2 = "1.0.36" 209proc-macro2 = "1.0.36"
210quote = "1.0.15" 210quote = "1.0.15"
diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs
index 3277ba440..e218e2492 100644
--- a/embassy-stm32/build.rs
+++ b/embassy-stm32/build.rs
@@ -1352,6 +1352,9 @@ fn main() {
1352 (("tsc", "G8_IO2"), quote!(crate::tsc::G8IO2Pin)), 1352 (("tsc", "G8_IO2"), quote!(crate::tsc::G8IO2Pin)),
1353 (("tsc", "G8_IO3"), quote!(crate::tsc::G8IO3Pin)), 1353 (("tsc", "G8_IO3"), quote!(crate::tsc::G8IO3Pin)),
1354 (("tsc", "G8_IO4"), quote!(crate::tsc::G8IO4Pin)), 1354 (("tsc", "G8_IO4"), quote!(crate::tsc::G8IO4Pin)),
1355 (("lcd", "SEG"), quote!(crate::lcd::SegPin)),
1356 (("lcd", "COM"), quote!(crate::lcd::ComPin)),
1357 (("lcd", "VLCD"), quote!(crate::lcd::VlcdPin)),
1355 (("dac", "OUT1"), quote!(crate::dac::DacPin<Ch1>)), 1358 (("dac", "OUT1"), quote!(crate::dac::DacPin<Ch1>)),
1356 (("dac", "OUT2"), quote!(crate::dac::DacPin<Ch2>)), 1359 (("dac", "OUT2"), quote!(crate::dac::DacPin<Ch2>)),
1357 ].into(); 1360 ].into();
@@ -1359,9 +1362,22 @@ fn main() {
1359 for p in METADATA.peripherals { 1362 for p in METADATA.peripherals {
1360 if let Some(regs) = &p.registers { 1363 if let Some(regs) = &p.registers {
1361 let mut adc_pairs: BTreeMap<u8, (Option<Ident>, Option<Ident>)> = BTreeMap::new(); 1364 let mut adc_pairs: BTreeMap<u8, (Option<Ident>, Option<Ident>)> = BTreeMap::new();
1365 let mut seen_lcd_seg_pins = HashSet::new();
1362 1366
1363 for pin in p.pins { 1367 for pin in p.pins {
1364 let key = (regs.kind, pin.signal); 1368 let mut key = (regs.kind, pin.signal);
1369
1370 // LCD is special. There are so many pins!
1371 if regs.kind == "lcd" {
1372 key.1 = pin.signal.trim_end_matches(char::is_numeric);
1373
1374 if key.1 == "SEG" && !seen_lcd_seg_pins.insert(pin.pin) {
1375 // LCD has SEG pins multiplexed in the peripheral
1376 // This means we can see them twice. We need to skip those so we're not impl'ing the trait twice
1377 continue;
1378 }
1379 }
1380
1365 if let Some(tr) = signals.get(&key) { 1381 if let Some(tr) = signals.get(&key) {
1366 let mut peri = format_ident!("{}", p.name); 1382 let mut peri = format_ident!("{}", p.name);
1367 1383
diff --git a/embassy-stm32/src/adc/adc4.rs b/embassy-stm32/src/adc/adc4.rs
index 472eb46fd..453513309 100644
--- a/embassy-stm32/src/adc/adc4.rs
+++ b/embassy-stm32/src/adc/adc4.rs
@@ -113,26 +113,11 @@ foreach_adc!(
113 } 113 }
114 114
115 fn enable() { 115 fn enable() {
116 let cr_initial = ADC4::regs().cr().read(); 116 if !ADC4::regs().cr().read().aden() || !ADC4::regs().isr().read().adrdy() {
117 let isr_initial = ADC4::regs().isr().read(); 117 ADC4::regs().isr().write(|w| w.set_adrdy(true));
118 118 ADC4::regs().cr().modify(|w| w.set_aden(true));
119 if cr_initial.aden() && isr_initial.adrdy() { 119 while !ADC4::regs().isr().read().adrdy() {}
120 return;
121 }
122
123 if cr_initial.aden() || cr_initial.adstart() {
124 if cr_initial.adstart() {
125 ADC4::regs().cr().modify(|w| w.set_adstp(true));
126 while ADC4::regs().cr().read().adstart() {}
127 }
128
129 ADC4::regs().cr().modify(|w| w.set_addis(true));
130 while ADC4::regs().cr().read().aden() {}
131 } 120 }
132
133 ADC4::regs().isr().write(|w| w.set_adrdy(true));
134 ADC4::regs().cr().modify(|w| w.set_aden(true));
135 while !ADC4::regs().isr().read().adrdy() {}
136 } 121 }
137 122
138 fn start() { 123 fn start() {
@@ -143,13 +128,17 @@ foreach_adc!(
143 } 128 }
144 129
145 fn stop() { 130 fn stop() {
146 if ADC4::regs().cr().read().adstart() && !ADC4::regs().cr().read().addis() { 131 let cr = ADC4::regs().cr().read();
147 ADC4::regs().cr().modify(|reg| { 132 if cr.adstart() {
148 reg.set_adstp(true); 133 ADC4::regs().cr().modify(|w| w.set_adstp(true));
149 });
150 while ADC4::regs().cr().read().adstart() {} 134 while ADC4::regs().cr().read().adstart() {}
151 } 135 }
152 136
137 if cr.aden() || cr.adstart() {
138 ADC4::regs().cr().modify(|w| w.set_addis(true));
139 while ADC4::regs().cr().read().aden() {}
140 }
141
153 // Reset configuration. 142 // Reset configuration.
154 ADC4::regs().cfgr1().modify(|reg| { 143 ADC4::regs().cfgr1().modify(|reg| {
155 reg.set_dmaen(false); 144 reg.set_dmaen(false);
diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs
index 755cb78c2..6d53d9b91 100644
--- a/embassy-stm32/src/adc/mod.rs
+++ b/embassy-stm32/src/adc/mod.rs
@@ -192,6 +192,8 @@ impl<'d, T: AnyInstance> Adc<'d, T> {
192 #[cfg(any(adc_v1, adc_c0, adc_l0, adc_v2, adc_g4, adc_v4, adc_u5, adc_wba))] 192 #[cfg(any(adc_v1, adc_c0, adc_l0, adc_v2, adc_g4, adc_v4, adc_u5, adc_wba))]
193 channel.setup(); 193 channel.setup();
194 194
195 // Ensure no conversions are ongoing
196 T::stop();
195 #[cfg(any(adc_v2, adc_v3, adc_g0, adc_h7rs, adc_u0, adc_u5, adc_wba, adc_c0))] 197 #[cfg(any(adc_v2, adc_v3, adc_g0, adc_h7rs, adc_u0, adc_u5, adc_wba, adc_c0))]
196 T::enable(); 198 T::enable();
197 T::configure_sequence([((channel.channel(), channel.is_differential()), sample_time)].into_iter()); 199 T::configure_sequence([((channel.channel(), channel.is_differential()), sample_time)].into_iter());
diff --git a/embassy-stm32/src/adc/v2.rs b/embassy-stm32/src/adc/v2.rs
index 341b15674..3c4431ae0 100644
--- a/embassy-stm32/src/adc/v2.rs
+++ b/embassy-stm32/src/adc/v2.rs
@@ -79,12 +79,17 @@ impl<T: Instance> super::SealedAnyInstance for T {
79 T::regs().dr().as_ptr() as *mut u16 79 T::regs().dr().as_ptr() as *mut u16
80 } 80 }
81 81
82 fn enable() {} 82 fn enable() {
83 T::regs().cr2().modify(|reg| {
84 reg.set_adon(true);
85 });
86
87 blocking_delay_us(3);
88 }
83 89
84 fn start() { 90 fn start() {
85 // Begin ADC conversions 91 // Begin ADC conversions
86 T::regs().cr2().modify(|reg| { 92 T::regs().cr2().modify(|reg| {
87 reg.set_adon(true);
88 reg.set_swstart(true); 93 reg.set_swstart(true);
89 }); 94 });
90 } 95 }
@@ -198,7 +203,7 @@ impl<T: Instance> super::SealedAnyInstance for T {
198 203
199impl<'d, T> Adc<'d, T> 204impl<'d, T> Adc<'d, T>
200where 205where
201 T: Instance, 206 T: Instance + super::AnyInstance,
202{ 207{
203 pub fn new(adc: Peri<'d, T>) -> Self { 208 pub fn new(adc: Peri<'d, T>) -> Self {
204 Self::new_with_config(adc, Default::default()) 209 Self::new_with_config(adc, Default::default())
@@ -209,11 +214,7 @@ where
209 214
210 let presc = from_pclk2(T::frequency()); 215 let presc = from_pclk2(T::frequency());
211 T::common_regs().ccr().modify(|w| w.set_adcpre(presc)); 216 T::common_regs().ccr().modify(|w| w.set_adcpre(presc));
212 T::regs().cr2().modify(|reg| { 217 T::enable();
213 reg.set_adon(true);
214 });
215
216 blocking_delay_us(3);
217 218
218 if let Some(resolution) = config.resolution { 219 if let Some(resolution) = config.resolution {
219 T::regs().cr1().modify(|reg| reg.set_res(resolution.into())); 220 T::regs().cr1().modify(|reg| reg.set_res(resolution.into()));
diff --git a/embassy-stm32/src/lcd.rs b/embassy-stm32/src/lcd.rs
new file mode 100644
index 000000000..ea29f1398
--- /dev/null
+++ b/embassy-stm32/src/lcd.rs
@@ -0,0 +1,510 @@
1//! LCD
2use core::marker::PhantomData;
3
4use embassy_hal_internal::{Peri, PeripheralType};
5
6use crate::gpio::{AfType, AnyPin, SealedPin};
7use crate::peripherals;
8use crate::rcc::{self, RccPeripheral};
9use crate::time::Hertz;
10
11#[cfg(any(stm32u0, stm32l073, stm32l083))]
12const NUM_SEGMENTS: u8 = 52;
13#[cfg(any(stm32wb, stm32l4x6, stm32l15x, stm32l162, stm32l4x3, stm32l4x6))]
14const NUM_SEGMENTS: u8 = 44;
15#[cfg(any(stm32l053, stm32l063, stm32l100))]
16const NUM_SEGMENTS: u8 = 32;
17
18/// LCD configuration struct
19#[non_exhaustive]
20#[derive(Debug, Clone, Copy)]
21pub struct Config {
22 #[cfg(lcd_v2)]
23 /// Enable the voltage output buffer for higher driving capability.
24 ///
25 /// The LCD driving capability is improved as buffers prevent the LCD capacitive loads from loading the resistor
26 /// bridge unacceptably and interfering with its voltage generation.
27 pub use_voltage_output_buffer: bool,
28 /// Enable SEG pin remapping. SEG[31:28] multiplexed with SEG[43:40]
29 pub use_segment_muxing: bool,
30 /// Bias selector
31 pub bias: Bias,
32 /// Duty selector
33 pub duty: Duty,
34 /// Internal or external voltage source
35 pub voltage_source: VoltageSource,
36 /// The frequency used to update the LCD with.
37 /// Should be between ~30 and ~100. Lower is better for power consumption, but has lower visual fidelity.
38 pub target_fps: Hertz,
39 /// LCD driver selector
40 pub drive: Drive,
41}
42
43impl Default for Config {
44 fn default() -> Self {
45 Self {
46 #[cfg(lcd_v2)]
47 use_voltage_output_buffer: false,
48 use_segment_muxing: false,
49 bias: Default::default(),
50 duty: Default::default(),
51 voltage_source: Default::default(),
52 target_fps: Hertz(60),
53 drive: Drive::Medium,
54 }
55 }
56}
57
58/// The number of voltage levels used when driving an LCD.
59/// Your LCD datasheet should tell you what to use.
60#[repr(u8)]
61#[derive(Debug, Default, Clone, Copy)]
62pub enum Bias {
63 /// 1/4 bias
64 #[default]
65 Quarter = 0b00,
66 /// 1/2 bias
67 Half = 0b01,
68 /// 1/3 bias
69 Third = 0b10,
70}
71
72/// The duty used by the LCD driver.
73///
74/// This is essentially how many COM pins you're using.
75#[repr(u8)]
76#[derive(Debug, Default, Clone, Copy)]
77pub enum Duty {
78 #[default]
79 /// Use a single COM pin
80 Static = 0b000,
81 /// Use two COM pins
82 Half = 0b001,
83 /// Use three COM pins
84 Third = 0b010,
85 /// Use four COM pins
86 Quarter = 0b011,
87 /// Use eight COM pins.
88 ///
89 /// In this mode, `COM[7:4]` outputs are available on `SEG[51:48]`.
90 /// This allows reducing the number of available segments.
91 Eigth = 0b100,
92}
93
94impl Duty {
95 fn num_com_pins(&self) -> u8 {
96 match self {
97 Duty::Static => 1,
98 Duty::Half => 2,
99 Duty::Third => 3,
100 Duty::Quarter => 4,
101 Duty::Eigth => 8,
102 }
103 }
104}
105
106/// Whether to use the internal or external voltage source to drive the LCD
107#[repr(u8)]
108#[derive(Debug, Default, Clone, Copy)]
109pub enum VoltageSource {
110 #[default]
111 /// Voltage stepup converter
112 Internal,
113 /// VLCD pin
114 External,
115}
116
117/// Defines the pulse duration in terms of ck_ps pulses.
118///
119/// A short pulse leads to lower power consumption, but displays with high internal resistance
120/// may need a longer pulse to achieve satisfactory contrast.
121/// Note that the pulse is never longer than one half prescaled LCD clock period.
122///
123/// Displays with high internal resistance may need a longer drive time to achieve satisfactory contrast.
124/// `PermanentHighDrive` is useful in this case if some additional power consumption can be tolerated.
125///
126/// Basically, for power usage, you want this as low as possible while still being able to use the LCD
127/// with a good enough contrast.
128#[repr(u8)]
129#[derive(Debug, Clone, Copy)]
130pub enum Drive {
131 /// Zero clock pulse on duration
132 Lowest = 0x00,
133 /// One clock pulse on duration
134 VeryLow = 0x01,
135 /// Two clock pulse on duration
136 Low = 0x02,
137 /// Three clock pulse on duration
138 Medium = 0x03,
139 /// Four clock pulse on duration
140 MediumHigh = 0x04,
141 /// Five clock pulse on duration
142 High = 0x05,
143 /// Six clock pulse on duration
144 VeryHigh = 0x06,
145 /// Seven clock pulse on duration
146 Highest = 0x07,
147 /// Enables the highdrive bit of the hardware
148 PermanentHighDrive = 0x09,
149}
150
151/// LCD driver.
152pub struct Lcd<'d, T: Instance> {
153 _peri: PhantomData<&'d mut T>,
154 duty: Duty,
155 ck_div: u32,
156}
157
158impl<'d, T: Instance> Lcd<'d, T> {
159 /// Initialize the lcd driver.
160 ///
161 /// The `pins` parameter must contain *all* segment and com pins that are connected to the LCD.
162 /// This is not further checked by this driver. Pins not routed to the LCD can be used for other purposes.
163 pub fn new<const N: usize>(
164 _peripheral: Peri<'d, T>,
165 config: Config,
166 vlcd_pin: Peri<'_, impl VlcdPin<T>>,
167 pins: [LcdPin<'d, T>; N],
168 ) -> Self {
169 rcc::enable_and_reset::<T>();
170
171 vlcd_pin.set_as_af(
172 vlcd_pin.af_num(),
173 AfType::output(crate::gpio::OutputType::PushPull, crate::gpio::Speed::VeryHigh),
174 );
175
176 assert_eq!(
177 pins.iter().filter(|pin| !pin.is_seg).count(),
178 config.duty.num_com_pins() as usize,
179 "The number of provided COM pins is not the same as the duty configures"
180 );
181
182 // Set the pins
183 for pin in pins {
184 pin.pin.set_as_af(
185 pin.af_num,
186 AfType::output(crate::gpio::OutputType::PushPull, crate::gpio::Speed::VeryHigh),
187 );
188 }
189
190 // Initialize the display ram to 0
191 for i in 0..8 {
192 T::regs().ram_com(i).low().write_value(0);
193 T::regs().ram_com(i).high().write_value(0);
194 }
195
196 // Calculate the clock dividers
197 let Some(lcd_clk) = (unsafe { rcc::get_freqs().rtc.to_hertz() }) else {
198 panic!("The LCD driver needs the RTC/LCD clock to be running");
199 };
200 let duty_divider = match config.duty {
201 Duty::Static => 1,
202 Duty::Half => 2,
203 Duty::Third => 3,
204 Duty::Quarter => 4,
205 Duty::Eigth => 8,
206 };
207 let target_clock = config.target_fps.0 * duty_divider;
208 let target_division = lcd_clk.0 / target_clock;
209
210 let mut ps = 0;
211 let mut div = 0;
212 let mut best_fps_match = u32::MAX;
213
214 for trial_div in 0..0xF {
215 let trial_ps = (target_division / (trial_div + 16))
216 .next_power_of_two()
217 .trailing_zeros();
218 let fps = lcd_clk.0 / ((1 << trial_ps) * (trial_div + 16)) / duty_divider;
219
220 if fps < config.target_fps.0 {
221 continue;
222 }
223
224 if fps < best_fps_match {
225 ps = trial_ps;
226 div = trial_div;
227 best_fps_match = fps;
228 }
229 }
230
231 let ck_div = lcd_clk.0 / ((1 << ps) * (div + 16));
232
233 trace!(
234 "lcd_clk: {}, fps: {}, ps: {}, div: {}, ck_div: {}",
235 lcd_clk, best_fps_match, ps, div, ck_div
236 );
237
238 if best_fps_match == u32::MAX || ps > 0xF {
239 panic!("Lcd clock error");
240 }
241
242 // Set the frame control
243 T::regs().fcr().modify(|w| {
244 w.set_ps(ps as u8);
245 w.set_div(div as u8);
246 w.set_cc(0b100); // Init in the middle-ish
247 w.set_dead(0b000);
248 w.set_pon(config.drive as u8 & 0x07);
249 w.set_hd((config.drive as u8 & !0x07) != 0);
250 });
251
252 // Wait for the frame control to synchronize
253 while !T::regs().sr().read().fcrsf() {}
254
255 // Set the control register values
256 T::regs().cr().modify(|w| {
257 #[cfg(lcd_v2)]
258 w.set_bufen(config.use_voltage_output_buffer);
259 w.set_mux_seg(config.use_segment_muxing);
260 w.set_bias(config.bias as u8);
261 w.set_duty(config.duty as u8);
262 w.set_vsel(matches!(config.voltage_source, VoltageSource::External));
263 });
264
265 // Enable the lcd
266 T::regs().cr().modify(|w| w.set_lcden(true));
267
268 // Wait for the lcd to be enabled
269 while !T::regs().sr().read().ens() {}
270
271 // Wait for the stepup converter to be ready
272 while !T::regs().sr().read().rdy() {}
273
274 Self {
275 _peri: PhantomData,
276 duty: config.duty,
277 ck_div,
278 }
279 }
280
281 /// Change the contrast by changing the voltage being used.
282 ///
283 /// This is from low at 0 to high at 7.
284 pub fn set_contrast_control(&mut self, value: u8) {
285 assert!((0..=7).contains(&value));
286 T::regs().fcr().modify(|w| w.set_cc(value));
287 }
288
289 /// Change the contrast by introducing a deadtime to the signals
290 /// where the voltages are held at 0V.
291 ///
292 /// This is from no dead time at 0 to high dead time at 7.
293 pub fn set_dead_time(&mut self, value: u8) {
294 assert!((0..=7).contains(&value));
295 T::regs()
296 .fcr()
297 .modify(|w: &mut stm32_metapac::lcd::regs::Fcr| w.set_dead(value));
298 }
299
300 /// Write data into the display RAM. This overwrites the data already in it for the specified com index.
301 ///
302 /// The `com_index` value determines which part of the RAM is written to.
303 /// The `segments` value is a bitmap where each bit represents whether a pixel is turned on or off.
304 ///
305 /// This function waits last update request to be finished, but does not submit the buffer to the LCD with a new request.
306 /// Submission has to be done manually using [Self::submit_frame].
307 pub fn write_com_segments(&mut self, com_index: u8, segments: u64) {
308 while T::regs().sr().read().udr() {}
309
310 assert!(
311 com_index < self.duty.num_com_pins(),
312 "Com index cannot be higher than number of configured com pins (through the Duty setting in the config)"
313 );
314
315 assert!(
316 segments.leading_zeros() >= 64 - self.num_segments() as u32,
317 "Invalid segment pixel set",
318 );
319
320 T::regs()
321 .ram_com(com_index as usize)
322 .low()
323 .write_value((segments & 0xFFFF_FFFF) as u32);
324 T::regs()
325 .ram_com(com_index as usize)
326 .high()
327 .write_value(((segments >> 32) & 0xFFFF_FFFF) as u32);
328 }
329
330 /// Read the data from the display RAM.
331 ///
332 /// The `com_index` value determines which part of the RAM is read from.
333 ///
334 /// This function waits for the last update request to be finished.
335 pub fn read_com_segments(&self, com_index: u8) -> u64 {
336 while T::regs().sr().read().udr() {}
337
338 assert!(
339 com_index < self.duty.num_com_pins(),
340 "Com index cannot be higher than number of configured com pins (through the Duty setting in the config)"
341 );
342
343 let low = T::regs().ram_com(com_index as usize).low().read();
344 let high = T::regs().ram_com(com_index as usize).high().read();
345
346 ((high as u64) << 32) | low as u64
347 }
348
349 /// Submit the current RAM data to the LCD.
350 ///
351 /// This function waits until the RAM is writable, but does not wait for the frame to be drawn.
352 pub fn submit_frame(&mut self) {
353 while T::regs().sr().read().udr() {}
354 // Clear the update done flag
355 T::regs().sr().write(|w| w.set_udd(true));
356 // Set the update request flag
357 T::regs().sr().write(|w| w.set_udr(true));
358 }
359
360 /// Get the number of segments that are supported on this LCD
361 pub fn num_segments(&self) -> u8 {
362 match self.duty {
363 Duty::Eigth => NUM_SEGMENTS - 4, // With 8 coms, 4 of the segment pins turn into com pins
364 _ => NUM_SEGMENTS,
365 }
366 }
367
368 /// Get the pixel mask for the current LCD setup.
369 /// This is a mask of all bits that are allowed to be set in the [Self::write_com_segments] function.
370 pub fn segment_pixel_mask(&self) -> u64 {
371 (1 << self.num_segments()) - 1
372 }
373
374 /// Get the number of COM pins that were configured through the Drive config
375 pub fn num_com_pins(&self) -> u8 {
376 self.duty.num_com_pins()
377 }
378
379 /// Set the blink behavior on some pixels.
380 ///
381 /// The blink frequency is an approximation. It's divided from the clock selected by the FPS.
382 /// Play with the FPS value if you want the blink frequency to be more accurate.
383 ///
384 /// If a blink frequency cannot be attained, this function will panic.
385 pub fn set_blink(&mut self, selector: BlinkSelector, freq: BlinkFreq) {
386 // Freq * 100 to be able to do integer math
387 let scaled_blink_freq = match freq {
388 BlinkFreq::Hz0_25 => 25,
389 BlinkFreq::Hz0_5 => 50,
390 BlinkFreq::Hz1 => 100,
391 BlinkFreq::Hz2 => 200,
392 BlinkFreq::Hz4 => 400,
393 };
394
395 let desired_divider = self.ck_div * 100 / scaled_blink_freq;
396 let target_divider = desired_divider.next_power_of_two();
397 let power_divisions = target_divider.trailing_zeros();
398
399 trace!(
400 "Setting LCD blink frequency -> desired_divider: {}, target_divider: {}",
401 desired_divider, target_divider
402 );
403
404 assert!(
405 (8..=1024).contains(&target_divider),
406 "LCD blink frequency cannot be attained"
407 );
408
409 T::regs().fcr().modify(|reg| {
410 reg.set_blinkf((power_divisions - 3) as u8);
411 reg.set_blink(selector as u8);
412 })
413 }
414}
415
416impl<'d, T: Instance> Drop for Lcd<'d, T> {
417 fn drop(&mut self) {
418 // Disable the lcd
419 T::regs().cr().modify(|w| w.set_lcden(false));
420 rcc::disable::<T>();
421 }
422}
423
424/// Blink frequency
425#[derive(Debug, Clone, Copy, PartialEq, Eq)]
426pub enum BlinkFreq {
427 /// 0.25 hz
428 Hz0_25,
429 /// 0.5 hz
430 Hz0_5,
431 /// 1 hz
432 Hz1,
433 /// 2 hz
434 Hz2,
435 /// 4 hz
436 Hz4,
437}
438
439/// Blink pixel selector
440#[derive(Debug, Clone, Copy, PartialEq, Eq)]
441#[repr(u8)]
442pub enum BlinkSelector {
443 /// No pixels blink
444 None = 0b00,
445 /// The SEG0, COM0 pixel blinks if the pixel is set
446 Seg0Com0 = 0b01,
447 /// The SEG0 pixel of all COMs blinks if the pixel is set
448 Seg0ComAll = 0b10,
449 /// All pixels blink if the pixel is set
450 All = 0b11,
451}
452
453/// A type-erased pin that can be configured as an LCD pin.
454/// This is used for passing pins to the new function in the array.
455pub struct LcdPin<'d, T: Instance> {
456 pin: Peri<'d, AnyPin>,
457 af_num: u8,
458 is_seg: bool,
459 _phantom: PhantomData<T>,
460}
461
462impl<'d, T: Instance> LcdPin<'d, T> {
463 /// Construct an LCD pin from any pin that supports it
464 pub fn new_seg(pin: Peri<'d, impl SegPin<T>>) -> Self {
465 let af = pin.af_num();
466
467 Self {
468 pin: pin.into(),
469 af_num: af,
470 is_seg: true,
471 _phantom: PhantomData,
472 }
473 }
474
475 /// Construct an LCD pin from any pin that supports it
476 pub fn new_com(pin: Peri<'d, impl ComPin<T>>) -> Self {
477 let af = pin.af_num();
478
479 Self {
480 pin: pin.into(),
481 af_num: af,
482 is_seg: false,
483 _phantom: PhantomData,
484 }
485 }
486}
487
488trait SealedInstance: crate::rcc::SealedRccPeripheral + PeripheralType {
489 fn regs() -> crate::pac::lcd::Lcd;
490}
491
492/// DSI instance trait.
493#[allow(private_bounds)]
494pub trait Instance: SealedInstance + RccPeripheral + 'static {}
495
496pin_trait!(SegPin, Instance);
497pin_trait!(ComPin, Instance);
498pin_trait!(VlcdPin, Instance);
499
500foreach_peripheral!(
501 (lcd, $inst:ident) => {
502 impl crate::lcd::SealedInstance for peripherals::$inst {
503 fn regs() -> crate::pac::lcd::Lcd {
504 crate::pac::$inst
505 }
506 }
507
508 impl crate::lcd::Instance for peripherals::$inst {}
509 };
510);
diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs
index ef6f1d6dc..9e6ba1f34 100644
--- a/embassy-stm32/src/lib.rs
+++ b/embassy-stm32/src/lib.rs
@@ -95,6 +95,8 @@ pub mod i2c;
95pub mod i2s; 95pub mod i2s;
96#[cfg(stm32wb)] 96#[cfg(stm32wb)]
97pub mod ipcc; 97pub mod ipcc;
98#[cfg(lcd)]
99pub mod lcd;
98#[cfg(feature = "low-power")] 100#[cfg(feature = "low-power")]
99pub mod low_power; 101pub mod low_power;
100#[cfg(lptim)] 102#[cfg(lptim)]
diff --git a/examples/stm32u0/.cargo/config.toml b/examples/stm32u0/.cargo/config.toml
index 688347084..e9212cacb 100644
--- a/examples/stm32u0/.cargo/config.toml
+++ b/examples/stm32u0/.cargo/config.toml
@@ -1,6 +1,6 @@
1[target.'cfg(all(target_arch = "arm", target_os = "none"))'] 1[target.'cfg(all(target_arch = "arm", target_os = "none"))']
2# replace stm32u083rctx with your chip as listed in `probe-rs chip list` 2# replace stm32u083mctx with your chip as listed in `probe-rs chip list`
3runner = "probe-rs run --chip stm32u083rctx" 3runner = "probe-rs run --chip stm32u083mctx"
4 4
5[build] 5[build]
6target = "thumbv6m-none-eabi" 6target = "thumbv6m-none-eabi"
diff --git a/examples/stm32u0/Cargo.toml b/examples/stm32u0/Cargo.toml
index 9f5227e3f..8cc894cb3 100644
--- a/examples/stm32u0/Cargo.toml
+++ b/examples/stm32u0/Cargo.toml
@@ -6,8 +6,8 @@ license = "MIT OR Apache-2.0"
6publish = false 6publish = false
7 7
8[dependencies] 8[dependencies]
9# Change stm32u083rc to your chip name, if necessary. 9# Change stm32u083mc to your chip name, if necessary.
10embassy-stm32 = { version = "0.4.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32u083rc", "memory-x", "unstable-pac", "exti", "chrono"] } 10embassy-stm32 = { version = "0.4.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32u083mc", "memory-x", "unstable-pac", "exti", "chrono"] }
11embassy-sync = { version = "0.7.2", path = "../../embassy-sync", features = ["defmt"] } 11embassy-sync = { version = "0.7.2", path = "../../embassy-sync", features = ["defmt"] }
12embassy-executor = { version = "0.9.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } 12embassy-executor = { version = "0.9.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] }
13embassy-time = { version = "0.5.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } 13embassy-time = { version = "0.5.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
@@ -19,9 +19,7 @@ defmt-rtt = "1.0.0"
19 19
20cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } 20cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] }
21cortex-m-rt = "0.7.0" 21cortex-m-rt = "0.7.0"
22embedded-hal = "0.2.6"
23panic-probe = { version = "1.0.0", features = ["print-defmt"] } 22panic-probe = { version = "1.0.0", features = ["print-defmt"] }
24heapless = { version = "0.8", default-features = false }
25 23
26micromath = "2.0.0" 24micromath = "2.0.0"
27chrono = { version = "0.4.38", default-features = false } 25chrono = { version = "0.4.38", default-features = false }
diff --git a/examples/stm32u0/src/bin/lcd.rs b/examples/stm32u0/src/bin/lcd.rs
new file mode 100644
index 000000000..2b34d4ef1
--- /dev/null
+++ b/examples/stm32u0/src/bin/lcd.rs
@@ -0,0 +1,412 @@
1#![no_std]
2#![no_main]
3
4use defmt::*;
5use embassy_executor::Spawner;
6use embassy_stm32::lcd::{Bias, BlinkFreq, BlinkSelector, Config, Duty, Lcd, LcdPin};
7use embassy_stm32::peripherals::LCD;
8use embassy_stm32::time::Hertz;
9use embassy_time::Duration;
10use {defmt_rtt as _, panic_probe as _};
11
12#[embassy_executor::main]
13async fn main(_spawner: Spawner) {
14 let mut config = embassy_stm32::Config::default();
15 // The RTC clock = the LCD clock and must be running
16 {
17 use embassy_stm32::rcc::*;
18 config.rcc.sys = Sysclk::PLL1_R;
19 config.rcc.hsi = true;
20 config.rcc.pll = Some(Pll {
21 source: PllSource::HSI, // 16 MHz
22 prediv: PllPreDiv::DIV1,
23 mul: PllMul::MUL7, // 16 * 7 = 112 MHz
24 divp: None,
25 divq: None,
26 divr: Some(PllRDiv::DIV2), // 112 / 2 = 56 MHz
27 });
28 config.rcc.ls = LsConfig::default_lsi();
29 }
30
31 let p = embassy_stm32::init(config);
32 info!("Hello World!");
33
34 let mut config = Config::default();
35 config.bias = Bias::Third;
36 config.duty = Duty::Quarter;
37 config.target_fps = Hertz(100);
38
39 let mut lcd = Lcd::new(
40 p.LCD,
41 config,
42 p.PC3,
43 [
44 LcdPin::new_com(p.PA8),
45 LcdPin::new_com(p.PA9),
46 LcdPin::new_com(p.PA10),
47 LcdPin::new_seg(p.PB1),
48 LcdPin::new_com(p.PB9),
49 LcdPin::new_seg(p.PB11),
50 LcdPin::new_seg(p.PB14),
51 LcdPin::new_seg(p.PB15),
52 LcdPin::new_seg(p.PC4),
53 LcdPin::new_seg(p.PC5),
54 LcdPin::new_seg(p.PC6),
55 LcdPin::new_seg(p.PC8),
56 LcdPin::new_seg(p.PC9),
57 LcdPin::new_seg(p.PC10),
58 LcdPin::new_seg(p.PC11),
59 LcdPin::new_seg(p.PD8),
60 LcdPin::new_seg(p.PD9),
61 LcdPin::new_seg(p.PD12),
62 LcdPin::new_seg(p.PD13),
63 LcdPin::new_seg(p.PD0),
64 LcdPin::new_seg(p.PD1),
65 LcdPin::new_seg(p.PD3),
66 LcdPin::new_seg(p.PD4),
67 LcdPin::new_seg(p.PD5),
68 LcdPin::new_seg(p.PD6),
69 LcdPin::new_seg(p.PE7),
70 LcdPin::new_seg(p.PE8),
71 LcdPin::new_seg(p.PE9),
72 ],
73 );
74
75 lcd.set_blink(BlinkSelector::All, BlinkFreq::Hz4);
76 {
77 let mut buffer = DisplayBuffer::new();
78 for i in 0..4 {
79 buffer.write_colon(i);
80 buffer.write(&mut lcd);
81 embassy_time::Timer::after_millis(200).await;
82 buffer.write_dot(i);
83 buffer.write(&mut lcd);
84 embassy_time::Timer::after_millis(200).await;
85 }
86 for i in 0..4 {
87 buffer.write_bar(i);
88 buffer.write(&mut lcd);
89 embassy_time::Timer::after_millis(200).await;
90 }
91 }
92
93 embassy_time::Timer::after_millis(1000).await;
94
95 lcd.set_blink(BlinkSelector::None, BlinkFreq::Hz4);
96
97 const MESSAGE: &str = "Hello embassy people. Hope you like this LCD demo :} ";
98 loop {
99 print_message(MESSAGE, &mut lcd, Duration::from_millis(250)).await;
100 print_message(characters::ALL_CHARS, &mut lcd, Duration::from_millis(500)).await;
101 }
102}
103
104async fn print_message(message: &str, lcd: &mut Lcd<'_, LCD>, delay: Duration) {
105 let mut display_buffer = DisplayBuffer::new();
106
107 let mut char_buffer = [' '; 6];
108 for char in message.chars() {
109 char_buffer.copy_within(1.., 0);
110 char_buffer[5] = char;
111
112 display_buffer.clear();
113 for (i, char) in char_buffer.iter().enumerate() {
114 display_buffer.write_char(i, *char);
115 }
116 display_buffer.write(lcd);
117
118 embassy_time::Timer::after(delay).await;
119 }
120}
121
122/// Display layout for the U0-DK
123mod display_layout {
124 // Character layout. There are 6 characters, left-to-right
125 // T
126 // ─────────
127 // │ N │
128 // │ │ │ │ │
129 // TL │ └┐ │ ┌┘ │ TR
130 // │NW│ │ │NE│
131 // │ │ │
132 // W─── ───E
133 // │ │ │
134 // │SW│ │ │SE│
135 // BL │ ┌┘ │ └┐ │ BR
136 // │ │ │ │ │
137 // │ S │
138 // ─────────
139 // B
140
141 pub const CHAR_N_COM: u8 = 3;
142 pub const CHAR_N_SEG: [u8; 6] = [39, 37, 35, 48, 26, 33];
143 pub const CHAR_NW_COM: u8 = 3;
144 pub const CHAR_NW_SEG: [u8; 6] = [49, 38, 36, 34, 27, 24];
145 pub const CHAR_W_COM: u8 = 0;
146 pub const CHAR_W_SEG: [u8; 6] = CHAR_NW_SEG;
147 pub const CHAR_SW_COM: u8 = 2;
148 pub const CHAR_SW_SEG: [u8; 6] = CHAR_NW_SEG;
149 pub const CHAR_S_COM: u8 = 2;
150 pub const CHAR_S_SEG: [u8; 6] = [22, 6, 46, 11, 15, 29];
151 pub const CHAR_SE_COM: u8 = 3;
152 pub const CHAR_SE_SEG: [u8; 6] = CHAR_S_SEG;
153 pub const CHAR_E_COM: u8 = 0;
154 pub const CHAR_E_SEG: [u8; 6] = [23, 45, 47, 14, 28, 32];
155 pub const CHAR_NE_COM: u8 = 2;
156 pub const CHAR_NE_SEG: [u8; 6] = CHAR_N_SEG;
157 pub const CHAR_T_COM: u8 = 1;
158 pub const CHAR_T_SEG: [u8; 6] = CHAR_N_SEG;
159 pub const CHAR_TL_COM: u8 = 1;
160 pub const CHAR_TL_SEG: [u8; 6] = CHAR_NW_SEG;
161 pub const CHAR_BL_COM: u8 = 0;
162 pub const CHAR_BL_SEG: [u8; 6] = CHAR_S_SEG;
163 pub const CHAR_B_COM: u8 = 1;
164 pub const CHAR_B_SEG: [u8; 6] = CHAR_S_SEG;
165 pub const CHAR_BR_COM: u8 = 1;
166 pub const CHAR_BR_SEG: [u8; 6] = CHAR_E_SEG;
167 pub const CHAR_TR_COM: u8 = 0;
168 pub const CHAR_TR_SEG: [u8; 6] = CHAR_N_SEG;
169
170 pub const COLON_COM: u8 = 2;
171 pub const COLON_SEG: [u8; 4] = [23, 45, 47, 14];
172 pub const DOT_COM: u8 = 3;
173 pub const DOT_SEG: [u8; 4] = COLON_SEG;
174 /// COM + SEG, bar from top to bottom
175 pub const BAR: [(u8, u8); 4] = [(2, 28), (3, 28), (2, 32), (3, 32)];
176}
177
178mod characters {
179 use super::CharSegment::{self, *};
180
181 pub const CHAR_0: &[CharSegment] = &[T, TL, BL, B, BR, TR, NW, SE];
182 pub const CHAR_1: &[CharSegment] = &[NE, TR, BR];
183 pub const CHAR_2: &[CharSegment] = &[T, BL, B, TR, E, W];
184 pub const CHAR_3: &[CharSegment] = &[T, B, BR, TR, E];
185 pub const CHAR_4: &[CharSegment] = &[TL, BR, TR, E, W];
186 pub const CHAR_5: &[CharSegment] = &[T, TL, B, BR, E, W];
187 pub const CHAR_6: &[CharSegment] = &[T, TL, BL, B, BR, E, W];
188 pub const CHAR_7: &[CharSegment] = &[T, NE, S];
189 pub const CHAR_8: &[CharSegment] = &[T, TL, BL, B, BR, TR, E, W];
190 pub const CHAR_9: &[CharSegment] = &[T, TL, BR, TR, E, W];
191
192 pub const CHAR_COLON: &[CharSegment] = &[N, S];
193 pub const CHAR_SEMICOLON: &[CharSegment] = &[N, SW];
194 pub const CHAR_EQUALS: &[CharSegment] = &[E, W, B];
195 pub const CHAR_SLASH: &[CharSegment] = &[SW, NE];
196 pub const CHAR_BACKSLASH: &[CharSegment] = &[SE, NW];
197 pub const CHAR_PLUS: &[CharSegment] = &[N, E, S, W];
198 pub const CHAR_STAR: &[CharSegment] = &[NE, N, NW, SE, S, SW];
199 pub const CHAR_QUOTE: &[CharSegment] = &[N];
200 pub const CHAR_BACKTICK: &[CharSegment] = &[NW];
201 pub const CHAR_DASH: &[CharSegment] = &[W, E];
202 pub const CHAR_COMMA: &[CharSegment] = &[SW];
203 pub const CHAR_DOT: &[CharSegment] = &[S];
204 pub const CHAR_CURLYOPEN: &[CharSegment] = &[T, NW, W, SW, B];
205 pub const CHAR_CURLYCLOSE: &[CharSegment] = &[T, NE, E, SE, B];
206 pub const CHAR_AMPERSAND: &[CharSegment] = &[T, NE, NW, W, BL, B, SE];
207
208 pub const CHAR_A: &[CharSegment] = &[T, TL, TR, E, W, BL, BR];
209 pub const CHAR_B: &[CharSegment] = &[T, TR, BR, B, N, S, E];
210 pub const CHAR_C: &[CharSegment] = &[T, TL, BL, B];
211 pub const CHAR_D: &[CharSegment] = &[T, TR, BR, B, N, S];
212 pub const CHAR_E: &[CharSegment] = &[T, TL, BL, B, W];
213 pub const CHAR_F: &[CharSegment] = &[T, TL, BL, W];
214 pub const CHAR_G: &[CharSegment] = &[T, TL, BL, B, BR, E];
215 pub const CHAR_H: &[CharSegment] = &[TL, BL, E, W, TR, BR];
216 pub const CHAR_I: &[CharSegment] = &[T, N, S, B];
217 pub const CHAR_J: &[CharSegment] = &[TR, BR, B, BL];
218 pub const CHAR_K: &[CharSegment] = &[TL, BL, W, NE, SE];
219 pub const CHAR_L: &[CharSegment] = &[TL, BL, B];
220 pub const CHAR_M: &[CharSegment] = &[BL, TL, NW, NE, TR, BR];
221 pub const CHAR_N: &[CharSegment] = &[BL, TL, NW, SE, BR, TR];
222 pub const CHAR_O: &[CharSegment] = &[T, TL, BL, B, BR, TR];
223 pub const CHAR_P: &[CharSegment] = &[BL, TL, T, TR, E, W];
224 pub const CHAR_Q: &[CharSegment] = &[T, TL, BL, B, BR, TR, SE];
225 pub const CHAR_R: &[CharSegment] = &[BL, TL, T, TR, E, W, SE];
226 pub const CHAR_S: &[CharSegment] = &[T, NW, E, BR, B];
227 pub const CHAR_T: &[CharSegment] = &[T, N, S];
228 pub const CHAR_U: &[CharSegment] = &[TL, BL, B, BR, TR];
229 pub const CHAR_V: &[CharSegment] = &[TL, BL, SW, NE];
230 pub const CHAR_W: &[CharSegment] = &[TL, BL, SW, SE, BR, TR];
231 pub const CHAR_X: &[CharSegment] = &[NE, NW, SE, SW];
232 pub const CHAR_Y: &[CharSegment] = &[NE, NW, S];
233 pub const CHAR_Z: &[CharSegment] = &[T, NE, SW, B];
234
235 pub const CHAR_UNKNOWN: &[CharSegment] = &[N, NW, W, SW, S, SE, E, NE, T, TL, BL, B, BR, TR];
236
237 pub const ALL_CHARS: &str =
238 "0 1 2 3 4 5 6 7 8 9 : ; = / \\ + * ' ` - , . { } & A B C D E F G H I J K L M N O P Q R S T U V W X Y Z � ";
239
240 pub fn get_char_segments(val: char) -> &'static [CharSegment] {
241 match val {
242 val if val.is_whitespace() => &[],
243
244 '0' => CHAR_0,
245 '1' => CHAR_1,
246 '2' => CHAR_2,
247 '3' => CHAR_3,
248 '4' => CHAR_4,
249 '5' => CHAR_5,
250 '6' => CHAR_6,
251 '7' => CHAR_7,
252 '8' => CHAR_8,
253 '9' => CHAR_9,
254
255 ':' => CHAR_COLON,
256 ';' => CHAR_SEMICOLON,
257 '=' => CHAR_EQUALS,
258 '/' => CHAR_SLASH,
259 '\\' => CHAR_BACKSLASH,
260 '+' => CHAR_PLUS,
261 '*' => CHAR_STAR,
262 '\'' => CHAR_QUOTE,
263 '`' => CHAR_BACKTICK,
264 '-' => CHAR_DASH,
265 ',' => CHAR_COMMA,
266 '.' => CHAR_DOT,
267 '{' => CHAR_CURLYOPEN,
268 '}' => CHAR_CURLYCLOSE,
269 '&' => CHAR_AMPERSAND,
270
271 'A' | 'a' => CHAR_A,
272 'B' | 'b' => CHAR_B,
273 'C' | 'c' => CHAR_C,
274 'D' | 'd' => CHAR_D,
275 'E' | 'e' => CHAR_E,
276 'F' | 'f' => CHAR_F,
277 'G' | 'g' => CHAR_G,
278 'H' | 'h' => CHAR_H,
279 'I' | 'i' => CHAR_I,
280 'J' | 'j' => CHAR_J,
281 'K' | 'k' => CHAR_K,
282 'L' | 'l' => CHAR_L,
283 'M' | 'm' => CHAR_M,
284 'N' | 'n' => CHAR_N,
285 'O' | 'o' => CHAR_O,
286 'P' | 'p' => CHAR_P,
287 'Q' | 'q' => CHAR_Q,
288 'R' | 'r' => CHAR_R,
289 'S' | 's' => CHAR_S,
290 'T' | 't' => CHAR_T,
291 'U' | 'u' => CHAR_U,
292 'V' | 'v' => CHAR_V,
293 'W' | 'w' => CHAR_W,
294 'X' | 'x' => CHAR_X,
295 'Y' | 'y' => CHAR_Y,
296 'Z' | 'z' => CHAR_Z,
297
298 _ => CHAR_UNKNOWN,
299 }
300 }
301}
302
303pub struct DisplayBuffer {
304 pixels: [u64; 4],
305}
306
307impl DisplayBuffer {
308 pub const fn new() -> Self {
309 Self { pixels: [0; 4] }
310 }
311
312 pub fn clear(&mut self) {
313 *self = Self::new();
314 }
315
316 fn write_char_segment(&mut self, index: usize, value: CharSegment) {
317 defmt::assert!(index < 6);
318 let (com, segments) = value.get_com_seg();
319 self.pixels[com as usize] |= 1 << segments[index];
320 }
321
322 pub fn write_char(&mut self, index: usize, val: char) {
323 let segments = characters::get_char_segments(val);
324
325 for segment in segments {
326 self.write_char_segment(index, *segment);
327 }
328 }
329
330 pub fn write(&self, lcd: &mut Lcd<'_, LCD>) {
331 lcd.write_com_segments(0, self.pixels[0]);
332 lcd.write_com_segments(1, self.pixels[1]);
333 lcd.write_com_segments(2, self.pixels[2]);
334 lcd.write_com_segments(3, self.pixels[3]);
335 lcd.submit_frame();
336 }
337
338 pub fn write_colon(&mut self, index: usize) {
339 defmt::assert!(index < 4);
340 self.pixels[display_layout::COLON_COM as usize] |= 1 << display_layout::COLON_SEG[index];
341 }
342
343 pub fn write_dot(&mut self, index: usize) {
344 defmt::assert!(index < 4);
345 self.pixels[display_layout::DOT_COM as usize] |= 1 << display_layout::DOT_SEG[index];
346 }
347
348 pub fn write_bar(&mut self, index: usize) {
349 defmt::assert!(index < 4);
350 let (bar_com, bar_seg) = display_layout::BAR[index];
351 self.pixels[bar_com as usize] |= 1 << bar_seg;
352 }
353}
354
355impl Default for DisplayBuffer {
356 fn default() -> Self {
357 Self::new()
358 }
359}
360
361#[derive(Debug, Clone, Copy)]
362enum CharSegment {
363 /// North
364 N,
365 /// North west
366 NW,
367 /// West
368 W,
369 /// South west
370 SW,
371 /// South
372 S,
373 /// South East
374 SE,
375 /// East
376 E,
377 /// North East
378 NE,
379 /// Top
380 T,
381 /// Top left
382 TL,
383 /// Bottom left
384 BL,
385 /// Bottom
386 B,
387 /// Bottom right
388 BR,
389 /// Top right
390 TR,
391}
392
393impl CharSegment {
394 fn get_com_seg(&self) -> (u8, [u8; 6]) {
395 match self {
396 CharSegment::N => (display_layout::CHAR_N_COM, display_layout::CHAR_N_SEG),
397 CharSegment::NW => (display_layout::CHAR_NW_COM, display_layout::CHAR_NW_SEG),
398 CharSegment::W => (display_layout::CHAR_W_COM, display_layout::CHAR_W_SEG),
399 CharSegment::SW => (display_layout::CHAR_SW_COM, display_layout::CHAR_SW_SEG),
400 CharSegment::S => (display_layout::CHAR_S_COM, display_layout::CHAR_S_SEG),
401 CharSegment::SE => (display_layout::CHAR_SE_COM, display_layout::CHAR_SE_SEG),
402 CharSegment::E => (display_layout::CHAR_E_COM, display_layout::CHAR_E_SEG),
403 CharSegment::NE => (display_layout::CHAR_NE_COM, display_layout::CHAR_NE_SEG),
404 CharSegment::T => (display_layout::CHAR_T_COM, display_layout::CHAR_T_SEG),
405 CharSegment::TL => (display_layout::CHAR_TL_COM, display_layout::CHAR_TL_SEG),
406 CharSegment::BL => (display_layout::CHAR_BL_COM, display_layout::CHAR_BL_SEG),
407 CharSegment::B => (display_layout::CHAR_B_COM, display_layout::CHAR_B_SEG),
408 CharSegment::BR => (display_layout::CHAR_BR_COM, display_layout::CHAR_BR_SEG),
409 CharSegment::TR => (display_layout::CHAR_TR_COM, display_layout::CHAR_TR_SEG),
410 }
411 }
412}
diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml
index 8fcb6b2b4..496a9de18 100644
--- a/tests/stm32/Cargo.toml
+++ b/tests/stm32/Cargo.toml
@@ -32,7 +32,7 @@ stm32l552ze = ["embassy-stm32/stm32l552ze", "not-gpdma", "rng", "hash", "dual-ba
32stm32u585ai = ["embassy-stm32/stm32u585ai", "spi-v345", "chrono", "rng", "hash", "cordic"] 32stm32u585ai = ["embassy-stm32/stm32u585ai", "spi-v345", "chrono", "rng", "hash", "cordic"]
33stm32u5a5zj = ["embassy-stm32/stm32u5a5zj", "spi-v345", "chrono", "rng", "hash"] # FIXME: cordic test cause it crash 33stm32u5a5zj = ["embassy-stm32/stm32u5a5zj", "spi-v345", "chrono", "rng", "hash"] # FIXME: cordic test cause it crash
34stm32wb55rg = ["embassy-stm32/stm32wb55rg", "chrono", "not-gpdma", "ble", "mac" , "rng", "hsem", "stop"] 34stm32wb55rg = ["embassy-stm32/stm32wb55rg", "chrono", "not-gpdma", "ble", "mac" , "rng", "hsem", "stop"]
35stm32wba52cg = ["embassy-stm32/stm32wba52cg", "spi-v345", "chrono", "rng", "hash"] 35stm32wba52cg = ["embassy-stm32/stm32wba52cg", "spi-v345", "chrono", "rng", "hash", "adc"]
36stm32wl55jc = ["embassy-stm32/stm32wl55jc-cm4", "not-gpdma", "rng", "chrono", "hsem"] 36stm32wl55jc = ["embassy-stm32/stm32wl55jc-cm4", "not-gpdma", "rng", "chrono", "hsem"]
37stm32f091rc = ["embassy-stm32/stm32f091rc", "cm0", "not-gpdma", "chrono"] 37stm32f091rc = ["embassy-stm32/stm32f091rc", "cm0", "not-gpdma", "chrono"]
38stm32h503rb = ["embassy-stm32/stm32h503rb", "spi-v345", "rng", "stop"] 38stm32h503rb = ["embassy-stm32/stm32h503rb", "spi-v345", "rng", "stop"]
@@ -56,6 +56,7 @@ mac = ["dep:embassy-stm32-wpan", "embassy-stm32-wpan/mac"]
56embassy-stm32-wpan = [] 56embassy-stm32-wpan = []
57not-gpdma = [] 57not-gpdma = []
58dac = [] 58dac = []
59adc = []
59ucpd = [] 60ucpd = []
60cordic = ["dep:num-traits"] 61cordic = ["dep:num-traits"]
61hsem = [] 62hsem = []
@@ -111,6 +112,11 @@ path = "src/bin/afio.rs"
111required-features = [ "afio",] 112required-features = [ "afio",]
112 113
113[[bin]] 114[[bin]]
115name = "adc"
116path = "src/bin/adc.rs"
117required-features = [ "adc",]
118
119[[bin]]
114name = "can" 120name = "can"
115path = "src/bin/can.rs" 121path = "src/bin/can.rs"
116required-features = [ "can",] 122required-features = [ "can",]
diff --git a/tests/stm32/src/bin/adc.rs b/tests/stm32/src/bin/adc.rs
new file mode 100644
index 000000000..6cedc6498
--- /dev/null
+++ b/tests/stm32/src/bin/adc.rs
@@ -0,0 +1,39 @@
1#![no_std]
2#![no_main]
3
4// required-features: dac
5
6#[path = "../common.rs"]
7mod common;
8
9use common::*;
10use embassy_executor::Spawner;
11use embassy_stm32::adc::{Adc, SampleTime};
12use embassy_time::Timer;
13use {defmt_rtt as _, panic_probe as _};
14
15#[embassy_executor::main]
16async fn main(_spawner: Spawner) {
17 // Initialize the board and obtain a Peripherals instance
18 let p: embassy_stm32::Peripherals = init();
19
20 let adc = peri!(p, ADC);
21 let mut adc_pin = peri!(p, DAC_PIN);
22
23 let mut adc = Adc::new_adc4(adc);
24
25 // Now wait a little to obtain a stable value
26 Timer::after_millis(30).await;
27 let _ = adc.blocking_read(&mut adc_pin, SampleTime::from_bits(0));
28
29 for _ in 0..=255 {
30 // Now wait a little to obtain a stable value
31 Timer::after_millis(30).await;
32
33 // Need to steal the peripherals here because PA4 is obviously in use already
34 let _ = adc.blocking_read(&mut adc_pin, SampleTime::from_bits(0));
35 }
36
37 info!("Test OK");
38 cortex_m::asm::bkpt();
39}
diff --git a/tests/stm32/src/common.rs b/tests/stm32/src/common.rs
index 096cce947..9f88b182a 100644
--- a/tests/stm32/src/common.rs
+++ b/tests/stm32/src/common.rs
@@ -259,6 +259,7 @@ define_peris!(
259define_peris!( 259define_peris!(
260 UART = LPUART1, UART_TX = PB5, UART_RX = PA10, UART_TX_DMA = GPDMA1_CH0, UART_RX_DMA = GPDMA1_CH1, 260 UART = LPUART1, UART_TX = PB5, UART_RX = PA10, UART_TX_DMA = GPDMA1_CH0, UART_RX_DMA = GPDMA1_CH1,
261 SPI = SPI1, SPI_SCK = PB4, SPI_MOSI = PA15, SPI_MISO = PB3, SPI_TX_DMA = GPDMA1_CH0, SPI_RX_DMA = GPDMA1_CH1, 261 SPI = SPI1, SPI_SCK = PB4, SPI_MOSI = PA15, SPI_MISO = PB3, SPI_TX_DMA = GPDMA1_CH0, SPI_RX_DMA = GPDMA1_CH1,
262 ADC = ADC4, DAC_PIN = PA0,
262 @irq UART = {LPUART1 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::LPUART1>;}, 263 @irq UART = {LPUART1 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::LPUART1>;},
263); 264);
264#[cfg(feature = "stm32h7s3l8")] 265#[cfg(feature = "stm32h7s3l8")]