aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--embassy-stm32/src/lcd.rs129
-rw-r--r--examples/stm32u0/src/bin/lcd.rs39
2 files changed, 130 insertions, 38 deletions
diff --git a/embassy-stm32/src/lcd.rs b/embassy-stm32/src/lcd.rs
index 5c5f46861..15d3e8fbc 100644
--- a/embassy-stm32/src/lcd.rs
+++ b/embassy-stm32/src/lcd.rs
@@ -16,46 +16,61 @@ pub struct Config {
16 pub bias: Bias, 16 pub bias: Bias,
17 pub duty: Duty, 17 pub duty: Duty,
18 pub voltage_source: VoltageSource, 18 pub voltage_source: VoltageSource,
19 pub high_drive: bool,
20 pub target_fps: Hertz, 19 pub target_fps: Hertz,
20 pub drive: Drive,
21} 21}
22 22
23impl Default for Config { 23impl Default for Config {
24 fn default() -> Self { 24 fn default() -> Self {
25 Self { 25 Self {
26 use_voltage_output_buffer: Default::default(), 26 use_voltage_output_buffer: false,
27 use_segment_muxing: Default::default(), 27 use_segment_muxing: false,
28 bias: Default::default(), 28 bias: Default::default(),
29 duty: Default::default(), 29 duty: Default::default(),
30 voltage_source: Default::default(), 30 voltage_source: Default::default(),
31 high_drive: Default::default(),
32 target_fps: Hertz(30), 31 target_fps: Hertz(30),
32 drive: Drive::Medium,
33 } 33 }
34 } 34 }
35} 35}
36 36
37/// The number of voltage levels used when driving an LCD.
38/// Your LCD datasheet should tell you what to use.
37#[repr(u8)] 39#[repr(u8)]
38#[derive(Debug, Default, Clone, Copy)] 40#[derive(Debug, Default, Clone, Copy)]
39pub enum Bias { 41pub enum Bias {
42 /// 1/4 bias
40 #[default] 43 #[default]
41 Quarter = 0b00, 44 Quarter = 0b00,
45 /// 1/2 bias
42 Half = 0b01, 46 Half = 0b01,
47 /// 1/3 bias
43 Third = 0b10, 48 Third = 0b10,
44} 49}
45 50
51/// The duty used by the LCD driver.
52///
53/// This is essentially how many COM pins you're using.
46#[repr(u8)] 54#[repr(u8)]
47#[derive(Debug, Default, Clone, Copy)] 55#[derive(Debug, Default, Clone, Copy)]
48pub enum Duty { 56pub enum Duty {
49 #[default] 57 #[default]
58 /// Use a single COM pin
50 Static = 0b000, 59 Static = 0b000,
60 /// Use two COM pins
51 Half = 0b001, 61 Half = 0b001,
62 /// Use three COM pins
52 Third = 0b010, 63 Third = 0b010,
64 /// Use four COM pins
53 Quarter = 0b011, 65 Quarter = 0b011,
66 /// Use eight COM pins.
67 ///
54 /// In this mode, `COM[7:4]` outputs are available on `SEG[51:48]`. 68 /// In this mode, `COM[7:4]` outputs are available on `SEG[51:48]`.
55 /// This allows reducing the number of available segments. 69 /// This allows reducing the number of available segments.
56 Eigth = 0b100, 70 Eigth = 0b100,
57} 71}
58 72
73/// Whether to use the internal or external voltage source to drive the LCD
59#[repr(u8)] 74#[repr(u8)]
60#[derive(Debug, Default, Clone, Copy)] 75#[derive(Debug, Default, Clone, Copy)]
61pub enum VoltageSource { 76pub enum VoltageSource {
@@ -66,6 +81,40 @@ pub enum VoltageSource {
66 External, 81 External,
67} 82}
68 83
84/// Defines the pulse duration in terms of ck_ps pulses.
85///
86/// A short pulse leads to lower power consumption, but displays with high internal resistance
87/// may need a longer pulse to achieve satisfactory contrast.
88/// Note that the pulse is never longer than one half prescaled LCD clock period.
89///
90/// Displays with high internal resistance may need a longer drive time to achieve satisfactory contrast.
91/// `PermanentHighDrive` is useful in this case if some additional power consumption can be tolerated.
92///
93/// Basically, for power usage, you want this as low as possible while still being able to use the LCD
94/// with a good enough contrast.
95#[repr(u8)]
96#[derive(Debug, Clone, Copy)]
97pub enum Drive {
98 /// Zero clock pulse on duration
99 Lowest = 0x00,
100 /// One clock pulse on duration
101 VeryLow = 0x01,
102 /// Two clock pulse on duration
103 Low = 0x02,
104 /// Three clock pulse on duration
105 Medium = 0x03,
106 /// Four clock pulse on duration
107 MediumHigh = 0x04,
108 /// Five clock pulse on duration
109 High = 0x05,
110 /// Six clock pulse on duration
111 VeryHigh = 0x06,
112 /// Seven clock pulse on duration
113 Highest = 0x07,
114 /// Enables the highdrive bit of the hardware
115 PermanentHighDrive = 0x09,
116}
117
69/// LCD driver. 118/// LCD driver.
70pub struct Lcd<'d, T: Instance> { 119pub struct Lcd<'d, T: Instance> {
71 _peri: PhantomData<&'d mut T>, 120 _peri: PhantomData<&'d mut T>,
@@ -73,9 +122,18 @@ pub struct Lcd<'d, T: Instance> {
73 122
74impl<'d, T: Instance> Lcd<'d, T> { 123impl<'d, T: Instance> Lcd<'d, T> {
75 /// Initialize the lcd driver 124 /// Initialize the lcd driver
76 pub fn new<const N: usize>(_peri: impl Peripheral<P = T> + 'd, config: Config, pins: [LcdPin<'d, T>; N]) -> Self { 125 pub fn new<const N: usize>(
126 _peri: impl Peripheral<P = T> + 'd,
127 config: Config,
128 vlcd_pin: impl Peripheral<P = impl VlcdPin<T>> + 'd,
129 pins: [LcdPin<'d, T>; N],
130 ) -> Self {
77 rcc::enable_and_reset::<T>(); 131 rcc::enable_and_reset::<T>();
78 132
133 into_ref!(vlcd_pin);
134 vlcd_pin.set_as_af(vlcd_pin.af_num(), AFType::OutputPushPull);
135 vlcd_pin.set_speed(crate::gpio::Speed::VeryHigh);
136
79 // Set the pins 137 // Set the pins
80 for pin in pins { 138 for pin in pins {
81 pin.pin.set_as_af(pin.af_num, AFType::OutputPushPull); 139 pin.pin.set_as_af(pin.af_num, AFType::OutputPushPull);
@@ -123,7 +181,13 @@ impl<'d, T: Instance> Lcd<'d, T> {
123 } 181 }
124 } 182 }
125 183
126 trace!("lcd_clk: {}, fps: {}, ps: {}, div: {}", lcd_clk, best_fps_match, ps, div); 184 trace!(
185 "lcd_clk: {}, fps: {}, ps: {}, div: {}",
186 lcd_clk,
187 best_fps_match,
188 ps,
189 div
190 );
127 191
128 if best_fps_match == u32::MAX || ps > 0xF { 192 if best_fps_match == u32::MAX || ps > 0xF {
129 panic!("Lcd clock error"); 193 panic!("Lcd clock error");
@@ -131,13 +195,12 @@ impl<'d, T: Instance> Lcd<'d, T> {
131 195
132 // Set the frame control 196 // Set the frame control
133 T::regs().fcr().modify(|w| { 197 T::regs().fcr().modify(|w| {
134 w.0 = 0x7C5C41; 198 w.set_ps(ps as u8);
135 // w.set_ps(ps as u8); 199 w.set_div(div as u8);
136 // w.set_div(div as u8); 200 w.set_cc(0b100); // Init in the middle-ish
137 // w.set_cc(0); 201 w.set_dead(0b000);
138 // w.set_dead(0); 202 w.set_pon(config.drive as u8 & 0x07);
139 // w.set_pon(0); 203 w.set_hd((config.drive as u8 & !0x07) != 0);
140 // // w.set_hd(config.high_drive);
141 }); 204 });
142 205
143 // Wait for the frame control to synchronize 206 // Wait for the frame control to synchronize
@@ -145,12 +208,11 @@ impl<'d, T: Instance> Lcd<'d, T> {
145 208
146 // Set the control register values 209 // Set the control register values
147 T::regs().cr().modify(|w| { 210 T::regs().cr().modify(|w| {
148 w.0 = 0x4E; 211 w.set_bufen(config.use_voltage_output_buffer);
149 // w.set_bufen(config.use_voltage_output_buffer); 212 w.set_mux_seg(config.use_segment_muxing);
150 // w.set_mux_seg(config.use_segment_muxing); 213 w.set_bias(config.bias as u8);
151 // w.set_bias(config.bias as u8); 214 w.set_duty(config.duty as u8);
152 // w.set_duty(config.duty as u8); 215 w.set_vsel(matches!(config.voltage_source, VoltageSource::External));
153 // w.set_vsel(matches!(config.voltage_source, VoltageSource::External));
154 }); 216 });
155 217
156 // Enable the lcd 218 // Enable the lcd
@@ -165,10 +227,28 @@ impl<'d, T: Instance> Lcd<'d, T> {
165 Self { _peri: PhantomData } 227 Self { _peri: PhantomData }
166 } 228 }
167 229
168 pub fn write_frame(&mut self, data: &[u32; 16]) { 230 /// Change the contrast by changing the voltage being used.
169 defmt::info!("{:06b}", T::regs().sr().read().0); 231 ///
232 /// This from low at 0 to high at 7.
233 pub fn set_contrast_control(&mut self, value: u8) {
234 T::regs().fcr().modify(|w| w.set_cc(value));
235 }
236
237 /// Change the contrast by introducing a deadtime to the signals
238 /// where the voltages are held at 0V.
239 ///
240 /// This from no dead time at 0 to high dead time at 7.
241 pub fn set_dead_time(&mut self, value: u8) {
242 T::regs().fcr().modify(|w: &mut stm32_metapac::lcd::regs::Fcr| w.set_dead(value));
243 }
170 244
171 // Wait until the last update is done 245 /// Write frame data to the peripheral.
246 ///
247 /// What each bit means depends on the exact microcontroller you use,
248 /// which pins are connected to your LCD and also the LCD layout itself.
249 ///
250 /// This function blocks until the last update display request has been processed.
251 pub fn write_frame(&mut self, data: &[u32; 16]) {
172 while T::regs().sr().read().udr() {} 252 while T::regs().sr().read().udr() {}
173 253
174 for i in 0..8 { 254 for i in 0..8 {
@@ -224,6 +304,11 @@ trait SealedInstance: crate::rcc::SealedRccPeripheral {
224pub trait Instance: SealedInstance + RccPeripheral + 'static {} 304pub trait Instance: SealedInstance + RccPeripheral + 'static {}
225 305
226pin_trait!(SegComPin, Instance); 306pin_trait!(SegComPin, Instance);
307pin_trait!(VlcdPin, Instance);
308
309// TODO: pull into build.rs, but the metapack doesn't have this info
310pin_trait_impl!(crate::lcd::VlcdPin, LCD, PC3, 11);
311pin_trait_impl!(crate::lcd::VlcdPin, LCD, PB2, 11);
227 312
228foreach_peripheral!( 313foreach_peripheral!(
229 (lcd, $inst:ident) => { 314 (lcd, $inst:ident) => {
diff --git a/examples/stm32u0/src/bin/lcd.rs b/examples/stm32u0/src/bin/lcd.rs
index 7a7c3a8a1..5551dd819 100644
--- a/examples/stm32u0/src/bin/lcd.rs
+++ b/examples/stm32u0/src/bin/lcd.rs
@@ -3,7 +3,7 @@
3 3
4use defmt::*; 4use defmt::*;
5use embassy_executor::Spawner; 5use embassy_executor::Spawner;
6use embassy_stm32::lcd::{Bias, Config, Duty, Lcd, VoltageSource}; 6use embassy_stm32::{lcd::{Bias, Config, Duty, Lcd, VoltageSource}, time::Hertz};
7use {defmt_rtt as _, panic_probe as _}; 7use {defmt_rtt as _, panic_probe as _};
8 8
9#[embassy_executor::main] 9#[embassy_executor::main]
@@ -22,7 +22,7 @@ async fn main(_spawner: Spawner) {
22 divq: None, 22 divq: None,
23 divr: Some(PllRDiv::DIV2), // 112 / 2 = 56 MHz 23 divr: Some(PllRDiv::DIV2), // 112 / 2 = 56 MHz
24 }); 24 });
25 config.rcc.ls = LsConfig::default_lse(); 25 config.rcc.ls = LsConfig::default_lsi();
26 } 26 }
27 27
28 let p = embassy_stm32::init(config); 28 let p = embassy_stm32::init(config);
@@ -31,46 +31,53 @@ async fn main(_spawner: Spawner) {
31 let mut config = Config::default(); 31 let mut config = Config::default();
32 config.bias = Bias::Third; 32 config.bias = Bias::Third;
33 config.duty = Duty::Quarter; 33 config.duty = Duty::Quarter;
34 config.target_fps = Hertz(60);
34 35
35 let mut lcd = Lcd::new( 36 let mut lcd = Lcd::new(
36 p.LCD, 37 p.LCD,
37 config, 38 config,
39 p.PC3,
38 [ 40 [
39 p.PC4.into(), 41 p.PA8.into(),
40 p.PC5.into(), 42 p.PA9.into(),
43 p.PA10.into(),
41 p.PB1.into(), 44 p.PB1.into(),
42 p.PE7.into(), 45 p.PB9.into(),
43 p.PE8.into(),
44 p.PE9.into(),
45 p.PB11.into(), 46 p.PB11.into(),
46 p.PB14.into(), 47 p.PB14.into(),
47 p.PB15.into(), 48 p.PB15.into(),
48 p.PD8.into(), 49 p.PC4.into(),
49 p.PD9.into(), 50 p.PC5.into(),
50 p.PD12.into(),
51 p.PB9.into(),
52 p.PA10.into(),
53 p.PA9.into(),
54 p.PA8.into(),
55 p.PD13.into(),
56 p.PC6.into(), 51 p.PC6.into(),
57 p.PC8.into(), 52 p.PC8.into(),
58 p.PC9.into(), 53 p.PC9.into(),
59 p.PC10.into(), 54 p.PC10.into(),
55 p.PC11.into(),
56 p.PD8.into(),
57 p.PD9.into(),
58 p.PD12.into(),
59 p.PD13.into(),
60 p.PD0.into(), 60 p.PD0.into(),
61 p.PD1.into(), 61 p.PD1.into(),
62 p.PD3.into(), 62 p.PD3.into(),
63 p.PD4.into(), 63 p.PD4.into(),
64 p.PD5.into(), 64 p.PD5.into(),
65 p.PD6.into(), 65 p.PD6.into(),
66 p.PC11.into(), 66 p.PE7.into(),
67 p.PE8.into(),
68 p.PE9.into(),
67 ], 69 ],
68 ); 70 );
69 71
70 loop { 72 loop {
71 defmt::info!("Writing frame"); 73 defmt::info!("Writing frame");
72 lcd.write_frame(&[0xAAAAAAAA; 16]); 74 lcd.write_frame(&[0xAAAAAAAA; 16]);
75
76 embassy_time::Timer::after_secs(1).await;
77
73 defmt::info!("Writing frame"); 78 defmt::info!("Writing frame");
74 lcd.write_frame(&[!0xAAAAAAAA; 16]); 79 lcd.write_frame(&[!0xAAAAAAAA; 16]);
80
81 embassy_time::Timer::after_secs(1).await;
75 } 82 }
76} 83}