diff options
| -rw-r--r-- | embassy-stm32/src/lcd.rs | 129 | ||||
| -rw-r--r-- | examples/stm32u0/src/bin/lcd.rs | 39 |
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 | ||
| 23 | impl Default for Config { | 23 | impl 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)] |
| 39 | pub enum Bias { | 41 | pub 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)] |
| 48 | pub enum Duty { | 56 | pub 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)] |
| 61 | pub enum VoltageSource { | 76 | pub 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)] | ||
| 97 | pub 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. |
| 70 | pub struct Lcd<'d, T: Instance> { | 119 | pub 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 | ||
| 74 | impl<'d, T: Instance> Lcd<'d, T> { | 123 | impl<'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 { | |||
| 224 | pub trait Instance: SealedInstance + RccPeripheral + 'static {} | 304 | pub trait Instance: SealedInstance + RccPeripheral + 'static {} |
| 225 | 305 | ||
| 226 | pin_trait!(SegComPin, Instance); | 306 | pin_trait!(SegComPin, Instance); |
| 307 | pin_trait!(VlcdPin, Instance); | ||
| 308 | |||
| 309 | // TODO: pull into build.rs, but the metapack doesn't have this info | ||
| 310 | pin_trait_impl!(crate::lcd::VlcdPin, LCD, PC3, 11); | ||
| 311 | pin_trait_impl!(crate::lcd::VlcdPin, LCD, PB2, 11); | ||
| 227 | 312 | ||
| 228 | foreach_peripheral!( | 313 | foreach_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 | ||
| 4 | use defmt::*; | 4 | use defmt::*; |
| 5 | use embassy_executor::Spawner; | 5 | use embassy_executor::Spawner; |
| 6 | use embassy_stm32::lcd::{Bias, Config, Duty, Lcd, VoltageSource}; | 6 | use embassy_stm32::{lcd::{Bias, Config, Duty, Lcd, VoltageSource}, time::Hertz}; |
| 7 | use {defmt_rtt as _, panic_probe as _}; | 7 | use {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 | } |
