From f758c4b3910e8cb4d09c284db245e66de8cb5e5e Mon Sep 17 00:00:00 2001 From: Dion Dokter Date: Fri, 14 Jun 2024 16:55:05 +0200 Subject: Start implementing lcd --- embassy-stm32/Cargo.toml | 4 +- embassy-stm32/build.rs | 18 +++- embassy-stm32/src/lcd.rs | 176 ++++++++++++++++++++++++++++++++++++ embassy-stm32/src/lib.rs | 2 + examples/stm32u0/.cargo/config.toml | 2 +- examples/stm32u0/Cargo.toml | 2 +- examples/stm32u0/src/bin/lcd.rs | 76 ++++++++++++++++ 7 files changed, 275 insertions(+), 5 deletions(-) create mode 100644 embassy-stm32/src/lcd.rs create mode 100644 examples/stm32u0/src/bin/lcd.rs diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index d06d8af03..79d79c5a3 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -72,7 +72,7 @@ rand_core = "0.6.3" sdio-host = "0.5.0" critical-section = "1.1" #stm32-metapac = { version = "15" } -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-cdd0f8e7cb79cbd126e2480f1b747fb01c901910" } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-b5202878509d8d82913bd969ba4137b7958139aa" } vcell = "0.1.3" nb = "1.0.0" @@ -97,7 +97,7 @@ proc-macro2 = "1.0.36" quote = "1.0.15" #stm32-metapac = { version = "15", default-features = false, features = ["metadata"]} -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-cdd0f8e7cb79cbd126e2480f1b747fb01c901910", default-features = false, features = ["metadata"] } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-b5202878509d8d82913bd969ba4137b7958139aa", default-features = false, features = ["metadata"] } [features] default = ["rt"] diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 6aedcc228..b7c00f8f7 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -1072,12 +1072,28 @@ fn main() { (("tsc", "G8_IO2"), quote!(crate::tsc::G8IO2Pin)), (("tsc", "G8_IO3"), quote!(crate::tsc::G8IO3Pin)), (("tsc", "G8_IO4"), quote!(crate::tsc::G8IO4Pin)), + (("lcd", "SEG"), quote!(crate::lcd::SegComPin)), + (("lcd", "COM"), quote!(crate::lcd::SegComPin)), ].into(); + let mut seen_lcd_pins = HashSet::new(); + for p in METADATA.peripherals { if let Some(regs) = &p.registers { for pin in p.pins { - let key = (regs.kind, pin.signal); + let mut key = (regs.kind, pin.signal); + + // LCD is special + if regs.kind == "lcd" { + key.1 = pin.signal.trim_end_matches(char::is_numeric); + + // Some lcd pins have multiple lcd functions + // Dedup so they don't get the trait implemented twice + if !seen_lcd_pins.insert(pin.pin) { + continue; + } + } + if let Some(tr) = signals.get(&key) { let mut peri = format_ident!("{}", p.name); diff --git a/embassy-stm32/src/lcd.rs b/embassy-stm32/src/lcd.rs new file mode 100644 index 000000000..89fca416f --- /dev/null +++ b/embassy-stm32/src/lcd.rs @@ -0,0 +1,176 @@ +//! LCD +use core::marker::PhantomData; + +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use crate::gpio::{AFType, AnyPin, SealedPin}; +use crate::rcc::{self, RccPeripheral}; +use crate::{peripherals, Peripheral}; + +#[non_exhaustive] +#[derive(Debug, Default, Clone, Copy)] +pub struct Config { + pub use_voltage_output_buffer: bool, + pub use_segment_muxing: bool, + pub bias: Bias, + pub duty: Duty, + pub voltage_source: VoltageSource, + pub high_drive: bool, +} + +#[repr(u8)] +#[derive(Debug, Default, Clone, Copy)] +pub enum Bias { + #[default] + Quarter = 0b00, + Half = 0b01, + Third = 0b10, +} + +#[repr(u8)] +#[derive(Debug, Default, Clone, Copy)] +pub enum Duty { + #[default] + Static = 0b000, + Half = 0b001, + Third = 0b010, + Quarter = 0b011, + /// In this mode, `COM[7:4]` outputs are available on `SEG[51:48]`. + /// This allows reducing the number of available segments. + Eigth = 0b100, +} + +#[repr(u8)] +#[derive(Debug, Default, Clone, Copy)] +pub enum VoltageSource { + #[default] + /// Voltage stepup converter + Internal, + /// VLCD pin + External, +} + +/// LCD driver. +pub struct Lcd<'d, T: Instance> { + _peri: PhantomData<&'d mut T>, +} + +impl<'d, T: Instance> Lcd<'d, T> { + /// Initialize the lcd driver + pub fn new(_peri: impl Peripheral

+ 'd, config: Config, pins: [LcdPin<'d, T>; N]) -> Self { + rcc::enable_and_reset::(); + + for pin in pins { + pin.pin.set_as_af(pin.af_num, AFType::OutputPushPull); + } + + T::regs().cr().write(|w| { + w.set_bufen(config.use_voltage_output_buffer); + w.set_mux_seg(config.use_segment_muxing); + w.set_bias(config.bias as u8); + w.set_duty(config.duty as u8); + w.set_vsel(matches!(config.voltage_source, VoltageSource::External)); + }); + + while !T::regs().sr().read().fcrsf() { } + + T::regs().fcr().modify(|w| { + w.set_dead(0); + w.set_pon(0b111); + // w.set_hd(config.high_drive); + }); + while !T::regs().sr().read().fcrsf() { } + + for i in 0..8 { + T::regs().ram_com(i).low().write_value(0); + T::regs().ram_com(i).high().write_value(0); + } + T::regs().sr().write(|w| w.set_udr(true)); + + while !T::regs().sr().read().fcrsf() { } + + T::regs().fcr().modify(|w| { + w.set_ps(2); + w.set_div(4); + }); + while !T::regs().sr().read().fcrsf() { } + + T::regs().fcr().modify(|w| { + w.set_cc(7); + }); + while !T::regs().sr().read().fcrsf() { } + + T::regs().cr().modify(|w| w.set_lcden(true)); + + while !T::regs().sr().read().rdy() { } + + Self { _peri: PhantomData } + } + + pub fn write_frame(&mut self, data: &[u32; 16]) { + defmt::info!("{:06b}", T::regs().sr().read().0); + + // Wait until the last update is done + while T::regs().sr().read().udr() { } + + for i in 0..8 { + T::regs().ram_com(i).low().write_value(data[i * 2]); + T::regs().ram_com(i).low().write_value(data[i * 2 + 1]); + } + T::regs().sr().write(|w| w.set_udr(true)); + } +} + +impl<'d, T: Instance> Drop for Lcd<'d, T> { + fn drop(&mut self) { + rcc::disable::(); + } +} + +pub struct LcdPin<'d, T: Instance> { + pin: PeripheralRef<'d, AnyPin>, + af_num: u8, + _phantom: PhantomData, +} + +impl<'d, T: Instance, Pin: Peripheral> + 'd> From for LcdPin<'d, T> { + fn from(value: Pin) -> Self { + Self::new(value) + } +} + +impl<'d, T: Instance> LcdPin<'d, T> { + pub fn new(pin: impl Peripheral

> + 'd) -> Self { + into_ref!(pin); + + let af = pin.af_num(); + + Self { + pin: pin.map_into(), + af_num: af, + _phantom: PhantomData, + } + } +} + +trait SealedInstance: crate::rcc::SealedRccPeripheral { + fn regs() -> crate::pac::lcd::Lcd; +} + +/// DSI instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + RccPeripheral + 'static {} + +pin_trait!(SegComPin, Instance); + +foreach_peripheral!( + (lcd, $inst:ident) => { + impl crate::lcd::SealedInstance for peripherals::$inst { + fn regs() -> crate::pac::lcd::Lcd { + crate::pac::$inst + } + } + + impl crate::lcd::Instance for peripherals::$inst {} + }; +); diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 95f59360a..334a0d717 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -89,6 +89,8 @@ pub mod i2s; pub mod ipcc; #[cfg(feature = "low-power")] pub mod low_power; +#[cfg(lcd)] +pub mod lcd; #[cfg(ltdc)] pub mod ltdc; #[cfg(opamp)] diff --git a/examples/stm32u0/.cargo/config.toml b/examples/stm32u0/.cargo/config.toml index 688347084..06eed6c8f 100644 --- a/examples/stm32u0/.cargo/config.toml +++ b/examples/stm32u0/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] # replace stm32u083rctx with your chip as listed in `probe-rs chip list` -runner = "probe-rs run --chip stm32u083rctx" +runner = "probe-rs run --chip stm32u083rctx --catch-hardfault" [build] target = "thumbv6m-none-eabi" diff --git a/examples/stm32u0/Cargo.toml b/examples/stm32u0/Cargo.toml index afeb4dc34..6c310b0f6 100644 --- a/examples/stm32u0/Cargo.toml +++ b/examples/stm32u0/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32u083rc to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32u083rc", "memory-x", "unstable-pac", "exti", "chrono"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32u083mc", "memory-x", "unstable-pac", "exti", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } diff --git a/examples/stm32u0/src/bin/lcd.rs b/examples/stm32u0/src/bin/lcd.rs new file mode 100644 index 000000000..8612c3dfc --- /dev/null +++ b/examples/stm32u0/src/bin/lcd.rs @@ -0,0 +1,76 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::lcd::{Bias, Config, Duty, Lcd, VoltageSource}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + // The RTC clock = the LCD clock and must be running + { + use embassy_stm32::rcc::*; + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, // 16 MHz + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL7, // 16 * 7 = 112 MHz + divp: None, + divq: None, + divr: Some(PllRDiv::DIV2), // 112 / 2 = 56 MHz + }); + config.rcc.ls = LsConfig::default(); + } + + let p = embassy_stm32::init(config); + info!("Hello World!"); + + let mut config = Config::default(); + config.bias = Bias::Third; + config.duty = Duty::Quarter; + + let mut lcd = Lcd::new( + p.LCD, + config, + [ + p.PC4.into(), + p.PC5.into(), + p.PB1.into(), + p.PE7.into(), + p.PE8.into(), + p.PE9.into(), + p.PB11.into(), + p.PB14.into(), + p.PB15.into(), + p.PD8.into(), + p.PD9.into(), + p.PD12.into(), + p.PB9.into(), + p.PA10.into(), + p.PA9.into(), + p.PA8.into(), + p.PD13.into(), + p.PC6.into(), + p.PC8.into(), + p.PC9.into(), + p.PC10.into(), + p.PD0.into(), + p.PD1.into(), + p.PD3.into(), + p.PD4.into(), + p.PD5.into(), + p.PD6.into(), + p.PC11.into(), + ], + ); + + loop { + defmt::info!("Writing frame"); + lcd.write_frame(&[0xAAAAAAAA; 16]); + defmt::info!("Writing frame"); + lcd.write_frame(&[!0xAAAAAAAA; 16]); + } +} -- cgit