diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 710b8f79..afa23dbc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,4 +46,4 @@ jobs: - uses: actions-rs/cargo@v1 with: command: check - args: --features=${{ matrix.mcu }},rt,usb_fs,sdio,can,i2s --examples + args: --features=${{ matrix.mcu }},rt,usb_fs,sdio,can,i2s,fsmc_lcd --examples diff --git a/CHANGELOG.md b/CHANGELOG.md index 639f1713..16bf9110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added internal pullup configuaration for the AlternateOD pin type - Added USART support for sending and receiving 9-bit words [#299] - Added support for I2S communication using SPI peripherals, and two examples [#265] +- Added support for some LCD controllers using the Flexible Static Memory + Controller / Flexible Memory Controller [#297] [#265]: https://github.com/stm32-rs/stm32f4xx-hal/pull/265 +[#297]: https://github.com/stm32-rs/stm32f4xx-hal/pull/297 ### Changed diff --git a/Cargo.toml b/Cargo.toml index 62a060d0..2f287d28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ repository = "https://github.com/stm32-rs/stm32f4xx-hal" version = "0.9.0" [package.metadata.docs.rs] -features = ["stm32f429", "rt", "usb_fs", "can", "i2s"] +features = ["stm32f429", "rt", "usb_fs", "can", "i2s", "fsmc_lcd"] targets = ["thumbv7em-none-eabihf"] [dependencies] @@ -40,6 +40,7 @@ bare-metal = { version = "1" } cast = { default-features = false, version = "0.2.2" } void = { default-features = false, version = "1.0.2" } embedded-hal = { features = ["unproven"], version = "0.2.3" } +display-interface = { version = "0.4.0", optional = true } [dependencies.stm32_i2s_v12x] version = "0.2.0" @@ -89,6 +90,8 @@ sdio = ["sdio-host"] i2s = ["stm32_i2s_v12x"] +fsmc_lcd = ["display-interface"] + [profile.dev] debug = true lto = true @@ -161,3 +164,7 @@ required-features = ["rt", "stm32f401"] [[example]] name = "serial-9bit" required-features = ["rt", "stm32f411"] + +[[example]] +name = "st7789-lcd" +required-features = ["rt", "stm32f412", "fsmc_lcd"] diff --git a/examples/st7789-lcd.rs b/examples/st7789-lcd.rs new file mode 100644 index 00000000..2d9bd758 --- /dev/null +++ b/examples/st7789-lcd.rs @@ -0,0 +1,367 @@ +//! +//! Demonstrates use of the Flexible Static Memory Controller to interface with an ST7789 LCD +//! controller +//! +//! Hardware required: an STM32F412G-DISCO board +//! +//! Procedure: Compile this example, load it onto the microcontroller, and run it. +//! +//! Example run command: `cargo run --release --features stm32f412,rt,fsmc_lcd --example st7789-lcd` +//! +//! Expected behavior: The display shows a black background with four colored circles. Periodically, +//! the color of each circle changes. +//! +//! Each circle takes a noticeable amount of time to draw, from top to bottom. Because +//! embedded-graphics by default does not buffer anything in memory, it sends one pixel at a time +//! to the LCD controller. The LCD interface can transfer rectangular blocks of pixels more quickly. +//! + +#![no_std] +#![no_main] + +use core::convert::{Infallible, TryInto}; +use core::iter; +use core::iter::{Cloned, Cycle}; +use core::ops::RangeInclusive; +use core::slice::Iter; + +use cortex_m_rt::entry; +use panic_semihosting as _; + +use embedded_graphics::drawable::Pixel; +use embedded_graphics::geometry::Size; +use embedded_graphics::pixelcolor::Rgb565; +use embedded_graphics::prelude::*; + +use embedded_graphics::primitives::Circle; +use embedded_graphics::style::PrimitiveStyle; +use stm32f4xx_hal::delay::Delay; +use stm32f4xx_hal::fsmc_lcd::{ChipSelect1, FsmcLcd, Lcd, LcdPins, SubBank, Timing}; +use stm32f4xx_hal::pac::{CorePeripherals, Peripherals}; +use stm32f4xx_hal::prelude::*; + +#[entry] +fn main() -> ! { + let cp = CorePeripherals::take().unwrap(); + let dp = Peripherals::take().unwrap(); + + let rcc = dp.RCC.constrain(); + // Make HCLK faster to allow updating the display more quickly + let clocks = rcc.cfgr.hclk(100.mhz()).freeze(); + + let mut delay = Delay::new(cp.SYST, clocks); + + let gpiod = dp.GPIOD.split(); + let gpioe = dp.GPIOE.split(); + let gpiof = dp.GPIOF.split(); + + // Pins connected to the LCD on the 32F412GDISCOVERY board + let lcd_pins = LcdPins { + data: ( + gpiod.pd14.into_alternate_af12(), + gpiod.pd15.into_alternate_af12(), + gpiod.pd0.into_alternate_af12(), + gpiod.pd1.into_alternate_af12(), + gpioe.pe7.into_alternate_af12(), + gpioe.pe8.into_alternate_af12(), + gpioe.pe9.into_alternate_af12(), + gpioe.pe10.into_alternate_af12(), + gpioe.pe11.into_alternate_af12(), + gpioe.pe12.into_alternate_af12(), + gpioe.pe13.into_alternate_af12(), + gpioe.pe14.into_alternate_af12(), + gpioe.pe15.into_alternate_af12(), + gpiod.pd8.into_alternate_af12(), + gpiod.pd9.into_alternate_af12(), + gpiod.pd10.into_alternate_af12(), + ), + address: gpiof.pf0.into_alternate_af12(), + read_enable: gpiod.pd4.into_alternate_af12(), + write_enable: gpiod.pd5.into_alternate_af12(), + chip_select: ChipSelect1(gpiod.pd7.into_alternate_af12()), + }; + let mut lcd_reset = gpiod.pd11.into_push_pull_output(); + let mut backlight_control = gpiof.pf5.into_push_pull_output(); + + // Speed up timing settings, assuming HCLK is 100 MHz (1 cycle = 10 nanoseconds) + // These read timings work to read settings, but slower timings are needed to read from the + // frame memory. + // Read timing: RD can go low at the same time as D/C changes and CS goes low. + // RD must be low for at least 45 ns -> DATAST=8 + // Also, a read cycle must take at least 160 nanoseconds, so set ADDSET=8. This means + // that a whole read takes 16 HCLK cycles (160 nanoseconds). + // Bus turnaround time is zero, because no particular interval is required between transactions. + let read_timing = Timing::default().data(8).address_setup(8).bus_turnaround(0); + // Write timing: Minimum 10 nanoseconds from when WR goes high to CS goes high, so + // HCLK can't be faster than 100 MHz. + // NWE must be low for at least 15 ns -> DATAST=3 + // A write cycle must take at least 66 nanoseconds, so ADDSET=3. This means that a whole + // write cycle takes 7 HCLK cycles (70 nanoseconds) (an extra HCLK cycle is added after NWE + // goes high). + // Bus turnaround time is zero, because no particular interval is required between transactions. + let write_timing = Timing::default().data(3).address_setup(3).bus_turnaround(0); + + let (_fsmc, lcd) = FsmcLcd::new(dp.FSMC, lcd_pins, &read_timing, &write_timing); + + // The 32F412GDISCOVERY board has an FRD154BP2902-CTP LCD. There is no easily available + // datasheet, so the behavior of this code is based on the working demonstration C code: + // https://github.com/STMicroelectronics/STM32CubeF4/blob/e084518f363e04344dc37822210a75e87377b200/Drivers/BSP/STM32412G-Discovery/stm32412g_discovery_lcd.c + // https://github.com/STMicroelectronics/STM32CubeF4/blob/e084518f363e04344dc37822210a75e87377b200/Drivers/BSP/Components/st7789h2/st7789h2.c + + // Reset LCD controller + lcd_reset.set_low().unwrap(); + delay.delay_ms(5u16); + lcd_reset.set_high().unwrap(); + delay.delay_ms(10u16); + lcd_reset.set_low().unwrap(); + delay.delay_ms(20u16); + // Release from reset + lcd_reset.set_high().unwrap(); + delay.delay_ms(10u16); + + // Add LCD controller driver + let mut lcd = St7789::new(lcd, 240, 240); + let mut id = [0u8; 3]; + lcd.read(0x04, &mut id); + if id != [0x85, 0x85, 0x52] { + panic!( + "Unexpected LCD controller ID: {:#x} {:#x} {:#x}", + id[0], id[1], id[2] + ); + } + + // LCD controller setup + configure_lcd(&mut lcd, &mut delay); + + // Clear + lcd.clear(Rgb565::BLACK).unwrap(); + + // Turn on display + lcd.write(0x29, &[]); + + // Turn on backlight + backlight_control.set_high().unwrap(); + + // Draw some circles + let test_colors = [ + Rgb565::new(0x4e >> 3, 0x79 >> 2, 0xa7 >> 3), + Rgb565::new(0xf2 >> 3, 0x8e >> 2, 0x2b >> 3), + Rgb565::new(0xe1 >> 3, 0x57 >> 2, 0x59 >> 3), + Rgb565::new(0x76 >> 3, 0xb7 >> 2, 0xb2 >> 3), + Rgb565::new(0x59 >> 3, 0xa1 >> 2, 0x4f >> 3), + Rgb565::new(0xed >> 3, 0xc9 >> 2, 0x48 >> 3), + Rgb565::new(0xb0 >> 3, 0x7a >> 2, 0xa1 >> 3), + Rgb565::new(0xff >> 3, 0x9d >> 2, 0xa7 >> 3), + Rgb565::new(0x9c >> 3, 0x75 >> 2, 0x5f >> 3), + Rgb565::new(0xba >> 3, 0xb0 >> 2, 0xac >> 3), + ]; + let center_points = [ + Point::new(70, 70), + Point::new(170, 70), + Point::new(170, 170), + Point::new(70, 170), + ]; + let mut drawer = ColoredCircleDrawer::new(¢er_points, &test_colors); + loop { + drawer.draw(&mut lcd).unwrap(); + delay.delay_ms(100u16); + } +} + +fn configure_lcd(lcd: &mut St7789, delay: &mut Delay) +where + S: SubBank, +{ + // Initialize LCD controller + // Sleep in + lcd.write(0x10, &[]); + delay.delay_ms(10u16); + // Software reset + lcd.write(0x1, &[]); + delay.delay_ms(200u16); + // Sleep out + lcd.write(0x11, &[]); + delay.delay_ms(120u16); + // Memory data access control: + // Page address order top to bottom + // Column address order left to right + // Normal order + // Refresh top to bottom + // RGB, not BGR + // Refresh left to right + lcd.write(0x36, &[0x0]); + // Color mode 16 bits/pixel + lcd.write(0x3a, &[0x5]); + // Display inversion on + lcd.write(0x21, &[]); + // Display resolution is 240x240 pixels + // Column address range 0 through 239 + lcd.write(0x2a, &[0x0, 0x0, 0x0, 0xef]); + // Row address range 0 through 239 + lcd.write(0x2b, &[0x0, 0x0, 0x0, 0xef]); + // Porch control + lcd.write(0xb2, &[0x0c, 0x0c, 0x00, 0x33, 0x33]); + // Gate control + lcd.write(0xb7, &[0x35]); + // VCOM + lcd.write(0xbb, &[0x1f]); + // LCM control + lcd.write(0xc0, &[0x2c]); + // VDV and VRH enable + lcd.write(0xc2, &[0x01, 0xc3]); + // VDV set + lcd.write(0xc4, &[0x20]); + // Normal mode frame rate control + lcd.write(0xc6, &[0x0f]); + // Power control + lcd.write(0xd0, &[0xa4, 0xa1]); + // Positive gamma + lcd.write( + 0xe0, + &[ + 0xd0, 0x08, 0x11, 0x08, 0x0c, 0x15, 0x39, 0x33, 0x50, 0x36, 0x13, 0x14, 0x29, 0x2d, + ], + ); + // Negative gamma + lcd.write( + 0xe0, + &[ + 0xd0, 0x08, 0x10, 0x08, 0x06, 0x06, 0x39, 0x44, 0x51, 0x0b, 0x16, 0x14, 0x2f, 0x31, + ], + ); +} + +/// Draws colored circles of various locations and colors +struct ColoredCircleDrawer<'a> { + /// Infinite iterator over circle center points + centers: Cloned>>, + /// Infinite iterator over Rgb565 colors + colors: Cloned>>, +} + +impl<'a> ColoredCircleDrawer<'a> { + pub fn new(centers: &'a [Point], colors: &'a [Rgb565]) -> Self { + ColoredCircleDrawer { + centers: centers.iter().cycle().cloned(), + colors: colors.iter().cycle().cloned(), + } + } + + /// Draws one circle onto a target + pub fn draw(&mut self, target: &mut T) -> Result<(), T::Error> + where + T: DrawTarget, + { + let center = self.centers.next().unwrap(); + let color = self.colors.next().unwrap(); + + Circle::new(center, 50) + .into_styled(PrimitiveStyle::with_fill(color)) + .draw(target) + } +} + +/// A simple driver for ST7789-series LCD controllers +struct St7789 { + inner: Lcd, + width: u16, + height: u16, +} + +impl St7789 +where + S: SubBank, +{ + /// Creates a driver object, but does not perform any initialization + pub fn new(inner: Lcd, width: u16, height: u16) -> Self { + St7789 { + inner, + width, + height, + } + } + + pub fn write(&mut self, command: u16, arguments: &[u8]) { + // Write the command code + self.inner.write_command(command); + // Set data/command high to write parameters + for &argument in arguments { + // Extend argument to 16 bits (the 8 higher bits are ignored) + let argument: u16 = argument.into(); + self.inner.write_data(argument); + } + } + + pub fn read(&mut self, parameter: u16, buffer: &mut [u8]) { + // Write the parameter to read (as a command) + self.inner.write_command(parameter); + // Dummy read + let _ = self.inner.read_data(); + // Read results + for result in buffer { + // Read as 16 bits + let result_16: u16 = self.inner.read_data(); + // Truncate to 8 bits + *result = result_16 as u8; + } + } + + fn write_frame_memory(&mut self, data: D) + where + D: IntoIterator, + { + let ramwr_command = 0x2c; + self.inner.write_command(ramwr_command); + // Set data/command high to write data + for argument in data.into_iter() { + self.inner.write_data(argument); + } + } + + /// Sets the ranges of rows and columns to be written by subsequent memory write operations + pub fn set_pixel_ranges(&mut self, columns: RangeInclusive, rows: RangeInclusive) { + // CASET + self.write(0x2a, &range_to_args(columns)); + // RASET + self.write(0x2b, &range_to_args(rows)); + } +} + +/// Converts a range of u16s into 4 bytes of arguments in the form expected by the RASET and +/// CASET commands +fn range_to_args(range: RangeInclusive) -> [u8; 4] { + let (min, max) = range.into_inner(); + // Min high byte, min low byte, max high byte, max low byte + [(min >> 8) as u8, min as u8, (max >> 8) as u8, max as u8] +} + +// embedded-graphics compatibility +impl DrawTarget for St7789 +where + S: SubBank, +{ + type Error = Infallible; + + fn draw_pixel(&mut self, Pixel(point, color): Pixel) -> Result<(), Self::Error> { + let x: u16 = point.x.try_into().expect("Pixel X too large"); + let y: u16 = point.y.try_into().expect("Pixel Y too large"); + self.set_pixel_ranges(x..=x, y..=y); + self.write_frame_memory(iter::once(color.into_storage())); + Ok(()) + } + + fn size(&self) -> Size { + Size::new(u32::from(self.width), u32::from(self.height)) + } + + fn clear(&mut self, color: Rgb565) -> Result<(), Self::Error> + where + Self: Sized, + { + self.set_pixel_ranges(0..=(self.width - 1), 0..=(self.height - 1)); + // Cover the whole display in width * height pixels of the same color + let total_pixels = usize::from(self.width) * usize::from(self.height); + self.write_frame_memory(iter::repeat(color.into_storage()).take(total_pixels)); + Ok(()) + } +} diff --git a/src/fsmc_lcd/display_interface_impl.rs b/src/fsmc_lcd/display_interface_impl.rs new file mode 100644 index 00000000..e9d13143 --- /dev/null +++ b/src/fsmc_lcd/display_interface_impl.rs @@ -0,0 +1,76 @@ +use display_interface::{DataFormat, DisplayError, WriteOnlyDataCommand}; + +use super::{Lcd, SubBank}; + +impl WriteOnlyDataCommand for Lcd +where + S: SubBank, +{ + fn send_commands(&mut self, cmd: DataFormat<'_>) -> Result<(), DisplayError> { + match cmd { + DataFormat::U8(slice) => { + for value in slice { + self.write_command(u16::from(*value)); + } + } + DataFormat::U16(slice) => { + for value in slice { + self.write_command(*value); + } + } + DataFormat::U16BE(slice) | DataFormat::U16LE(slice) => { + // As long as the data bus is 16 bits wide, the byte order doesn't matter. + for value in slice { + self.write_command(*value); + } + } + DataFormat::U8Iter(iter) => { + for value in iter { + self.write_command(u16::from(value)); + } + } + DataFormat::U16BEIter(iter) | DataFormat::U16LEIter(iter) => { + // As long as the data bus is 16 bits wide, the byte order doesn't matter. + for value in iter { + self.write_command(value); + } + } + _ => return Err(DisplayError::DataFormatNotImplemented), + } + Ok(()) + } + + fn send_data(&mut self, buf: DataFormat<'_>) -> Result<(), DisplayError> { + match buf { + DataFormat::U8(slice) => { + for value in slice { + self.write_data(u16::from(*value)); + } + } + DataFormat::U16(slice) => { + for value in slice { + self.write_data(*value); + } + } + DataFormat::U16BE(slice) | DataFormat::U16LE(slice) => { + // As long as the data bus is 16 bits wide, the byte order doesn't matter. + for value in slice { + self.write_data(*value); + } + } + DataFormat::U8Iter(iter) => { + for value in iter { + self.write_data(u16::from(value)); + } + } + DataFormat::U16BEIter(iter) | DataFormat::U16LEIter(iter) => { + // As long as the data bus is 16 bits wide, the byte order doesn't matter. + for value in iter { + self.write_data(value); + } + } + _ => return Err(DisplayError::DataFormatNotImplemented), + } + Ok(()) + } +} diff --git a/src/fsmc_lcd/mod.rs b/src/fsmc_lcd/mod.rs new file mode 100644 index 00000000..6c184d94 --- /dev/null +++ b/src/fsmc_lcd/mod.rs @@ -0,0 +1,463 @@ +//! +//! LCD interface using the Flexible Memory Controller (FMC) / Flexible Static Memory Controller (FSMC) +//! +//! This module is only available if the `fsmc_lcd` feature is enabled and the target +//! microcontroller has an FMC or FSMC +//! +//! This driver is compatible with many LCD driver chips that support the Intel 8080 interface +//! (also called Display Bus Interface (DBI) type B) with a 16-bit data bus. +//! +//! Here are some examples of compatible LCD drivers: +//! * Sitronix ST7735S +//! * Sitronix ST7789VI +//! * Ilitek ILI9327 +//! * Ilitek ILI9320 +//! * Himax HX8357-B +//! +//! Higher-level driver code can add support for specific LCD driver +//! integrated circuits. +//! +//! For an overview of how this interface works, see [application note AN2790](https://www.st.com/content/ccc/resource/technical/document/application_note/85/ad/ef/0f/a3/a6/49/9a/CD00201397.pdf/files/CD00201397.pdf/jcr:content/translations/en.CD00201397.pdf). +//! +//! # Pins +//! +//! To interface with 1-4 LCDs, you will need: +//! * 16 data pins (0 through 15), shared among all the LCDs +//! * One NEx (chip select) pin per LCD (up to 4) +//! * One Ax (address) pin, shared among all the LCDs. This pin is used to select data or command +//! mode. +//! * You can also supply up to 4 address pins, but they will always have the same output. +//! * Caution: Due to hardware limitations, address line A25 cannot be used. +//! * One NOE (read enable) pin, shared among all the LCDs +//! * One NWE (write enable) pin, shared among all the LCDs +//! +//! # Timing +//! +//! Because the correct timing depends on the specific LCD controller and the wiring between the +//! microcontroller and LCD controller, this driver does not try to calculate the correct +//! timing settings. Instead, it exposes the access modes and timing options that the STM32F4 +//! hardware supports. +//! +//! The default access mode is mode C. For an example timing diagram, refer to reference manual +//! [RM0090](https://www.st.com/resource/en/reference_manual/dm00031020.pdf), +//! figures 443 and 444 (on page 1562), or your microcontroller reference manual. +//! +//! Access modes A, B, and D are also supported. +//! +//! # Basic operation +//! +//! 1. Create an `LcdPins` object containing the pins used to communicate with the LCD +//! +//! 2. Create default `Timing` objects for the write and read timing +//! +//! a. (Optional) Adjust the timing to make read and write operations faster, within the limits +//! of the wiring and LCD controller +//! +//! 3. Pass the FSMC peripheral object, pins, read timing, and write timing to `FsmcLcd::new`. +//! This function will return an `FsmcLcd` and one or more `Lcd` objects. +//! +//! 4. Use the returned `Lcd` object(s) to configure the controller(s) and display graphics + +mod display_interface_impl; +mod pins; +mod sealed; +mod timing; + +use core::marker::PhantomData; + +pub use self::pins::{ + AddressPins, ChipSelect1, ChipSelect2, ChipSelect3, ChipSelect4, ChipSelectPins, DataPins, + LcdPins, PinAddress, PinChipSelect1, PinChipSelect2, PinChipSelect3, PinChipSelect4, PinD0, + PinD1, PinD10, PinD11, PinD12, PinD13, PinD14, PinD15, PinD2, PinD3, PinD4, PinD5, PinD6, + PinD7, PinD8, PinD9, PinReadEnable, PinWriteEnable, Pins, +}; +pub use self::timing::{AccessMode, Timing}; + +use crate::bb; +use crate::pac::RCC; + +// Use the FMC or FSMC, whichever is available, and treat it like an FSMC +#[cfg(any( + feature = "stm32f429", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479" +))] +use crate::pac::fmc as fsmc; +#[cfg(not(any( + feature = "stm32f429", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479" +)))] +use crate::pac::fsmc; +#[cfg(any( + feature = "stm32f429", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479" +))] +use crate::pac::FMC as FSMC; +#[cfg(not(any( + feature = "stm32f429", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479" +)))] +use crate::pac::FSMC; + +/// A sub-bank of bank 1, with its own chip select output +pub trait SubBank: sealed::SealedSubBank {} +/// Sub-bank 1 +pub struct SubBank1(()); +impl sealed::SealedSubBank for SubBank1 { + const BASE_ADDRESS: usize = 0x6000_0000; +} +impl SubBank for SubBank1 {} +/// Sub-bank 2 +pub struct SubBank2(()); +impl sealed::SealedSubBank for SubBank2 { + const BASE_ADDRESS: usize = 0x6400_0000; +} +impl SubBank for SubBank2 {} +/// Sub-bank 3 +pub struct SubBank3(()); +impl sealed::SealedSubBank for SubBank3 { + const BASE_ADDRESS: usize = 0x6800_0000; +} +impl SubBank for SubBank3 {} +/// Sub-bank 4 +pub struct SubBank4(()); +impl sealed::SealedSubBank for SubBank4 { + const BASE_ADDRESS: usize = 0x6c00_0000; +} +impl SubBank for SubBank4 {} + +/// An FMC or FSMC configured as an LCD interface +pub struct FsmcLcd { + pins: PINS, + fsmc: FSMC, +} + +impl FsmcLcd +where + PINS: Pins, +{ + /// Configures the FSMC/FMC to interface with an LCD using the provided pins + /// + /// The same timings will be used for all connected LCDs. + /// + /// The return type includes an `FsmcLcd` and either an `Lcd` or a tuple of up to four `Lcd`s, + /// depending on the pins given. + /// + /// The returned `FsmcLcd` can be used later to release the FSMC and pins for other uses, + /// or it can be ignored. + /// + /// # Return type examples + /// + /// ## One enable/chip select pin + /// + /// If you pass an `LcdPins` object with the `enable` field containing a single `ChipSelect1` + /// object with a pin, this function will return an `FsmcLcd` and an `Lcd`. + /// + /// ### Multiple enable/chip select pins + /// + /// If you pass an `LcdPins` object with the `enable` field containing a tuple of 2-4 + /// `ChipSelectX` objects, this function will return an `FsmcLcd` and a tuple of `Lcd<_>` + /// objects. Each `Lcd` is associated with one chip select pin, and can be controlled + /// independently. + /// + /// # Examples + /// + /// The `stm32f4xx-hal` repository has a `st7789-lcd` example that draws graphics on an LCD + /// and runs on the STM32F412G-DISCO board. + /// + /// Up to four LCDs can be controlled separately using four chip select pins + /// + /// ```ignore + /// let lcd_pins = LcdPins { + /// data: ( + /// gpiod.pd14.into_alternate_af12(), + /// gpiod.pd15.into_alternate_af12(), + /// gpiod.pd0.into_alternate_af12(), + /// gpiod.pd1.into_alternate_af12(), + /// gpioe.pe7.into_alternate_af12(), + /// gpioe.pe8.into_alternate_af12(), + /// gpioe.pe9.into_alternate_af12(), + /// gpioe.pe10.into_alternate_af12(), + /// gpioe.pe11.into_alternate_af12(), + /// gpioe.pe12.into_alternate_af12(), + /// gpioe.pe13.into_alternate_af12(), + /// gpioe.pe14.into_alternate_af12(), + /// gpioe.pe15.into_alternate_af12(), + /// gpiod.pd8.into_alternate_af12(), + /// gpiod.pd9.into_alternate_af12(), + /// gpiod.pd10.into_alternate_af12(), + /// ), + /// // Four address pins, one for each LCD + /// // All of them will have the same output + /// address: ( + /// gpiof.pf0.into_alternate_af12(), + /// gpioe.pe2.into_alternate_af12(), + /// gpioe.pe3.into_alternate_af12(), + /// gpiof.pf14.into_alternate_af12(), + /// ), + /// read_enable: gpiod.pd4.into_alternate_af12(), + /// write_enable: gpiod.pd5.into_alternate_af12(), + /// // Four chip select pins, one for each LCD, controlled independently + /// chip_select: ( + /// ChipSelect1(gpiod.pd7.into_alternate_af12()), + /// ChipSelect2(gpiog.pg9.into_alternate_af12()), + /// ChipSelect3(gpiog.pg10.into_alternate_af12()), + /// ChipSelect4(gpiog.pg12.into_alternate_af12()), + /// ), + /// }; + /// + /// let (_fsmc, mut lcds) = FsmcLcd::new(dp.FSMC, lcd_pins, &Timing::default(), &Timing::default()); + /// // lcds is a tuple of four `Lcd` objects. Each one can be accessed independently. + /// // This is just a basic example of some things that can be done. + /// lcds.0.write_command(37); + /// lcds.1.write_command(38); + /// lcds.2.write_command(39); + /// lcds.3.write_command(40); + /// ``` + pub fn new( + fsmc: FSMC, + pins: PINS, + read_timing: &Timing, + write_timing: &Timing, + ) -> (Self, PINS::Lcds) { + use self::sealed::Conjure; + unsafe { + //NOTE(unsafe) this reference will only be used for atomic writes with no side effects + let rcc = &(*RCC::ptr()); + // Enable the FSMC/FMC peripheral + // All STM32F4 models with an FSMC or FMC use bit 0 in AHB3ENR and AHB3RSTR. + bb::set(&rcc.ahb3enr, 0); + + // Stall the pipeline to work around erratum 2.1.13 (DM00037591) + cortex_m::asm::dsb(); + // Reset FSMC/FMC + bb::set(&rcc.ahb3rstr, 0); + bb::clear(&rcc.ahb3rstr, 0); + } + + // Configure memory type and basic interface settings + // The reference manuals are sometimes unclear on the distinction between banks + // and sub-banks of bank 1. This driver uses addresses in the different sub-banks of + // bank 1. The configuration registers for "bank x" (like FMC_BCRx) actually refer to + // sub-banks, not banks. We need to configure and enable all four of them. + configure_bcr1(&fsmc.bcr1); + configure_bcr(&fsmc.bcr2); + configure_bcr(&fsmc.bcr3); + configure_bcr(&fsmc.bcr4); + configure_btr(&fsmc.btr1, read_timing); + configure_btr(&fsmc.btr2, read_timing); + configure_btr(&fsmc.btr3, read_timing); + configure_btr(&fsmc.btr4, read_timing); + configure_bwtr(&fsmc.bwtr1, write_timing); + configure_bwtr(&fsmc.bwtr2, write_timing); + configure_bwtr(&fsmc.bwtr3, write_timing); + configure_bwtr(&fsmc.bwtr4, write_timing); + + (FsmcLcd { pins, fsmc }, PINS::Lcds::conjure()) + } + + /// Reunites this FsmcLcd and all its associated LCDs, and returns the FSMC and pins for other + /// uses + /// + /// This function also resets and disables the FSMC. + pub fn release(self, _lcds: PINS::Lcds) -> (FSMC, PINS) { + unsafe { + //NOTE(unsafe) this reference will only be used for atomic writes with no side effects + let rcc = &(*RCC::ptr()); + // All STM32F4 models with an FSMC or FMC use bit 0 in AHB3ENR and AHB3RSTR. + // Reset FSMC/FMC + bb::set(&rcc.ahb3rstr, 0); + bb::clear(&rcc.ahb3rstr, 0); + // Disable the FSMC/FMC peripheral + bb::clear(&rcc.ahb3enr, 0); + } + + (self.fsmc, self.pins) + } +} + +/// Configures an SRAM/NOR-Flash chip-select control register for LCD interface use +fn configure_bcr1(bcr: &fsmc::BCR1) { + bcr.write(|w| { + w + // The write fifo and WFDIS bit are missing from some models. + // Where present, the FIFO is enabled by default. + // ------------ + // Disable synchronous writes + .cburstrw() + .disabled() + // Don't split burst transactions (doesn't matter for LCD mode) + .cpsize() + .no_burst_split() + // Ignore wait signal (asynchronous mode) + .asyncwait() + .disabled() + // Enable extended mode, for different read and write timings + .extmod() + .enabled() + // Ignore wait signal (synchronous mode) + .waiten() + .disabled() + // Allow write operations + .wren() + .enabled() + // Default wait timing + .waitcfg() + .before_wait_state() + // Default wait polarity + .waitpol() + .active_low() + // Disable burst reads + .bursten() + .disabled() + // Enable NOR flash operations + .faccen() + .enabled() + // 16-bit bus width + .mwid() + .bits16() + // NOR flash mode (compatible with LCD controllers) + .mtyp() + .flash() + // Address and data not multiplexed + .muxen() + .disabled() + // Enable this memory bank + .mbken() + .enabled() + }) +} + +/// Configures an SRAM/NOR-Flash chip-select control register for LCD interface use +/// +/// This is equivalent to `configure_bcr1`, but without the `WFDIS` and `CCLKEN` bits that are +/// present in BCR1 only. +fn configure_bcr(bcr: &fsmc::BCR) { + bcr.write(|w| { + w + // Disable synchronous writes + .cburstrw() + .disabled() + // Don't split burst transactions (doesn't matter for LCD mode) + .cpsize() + .no_burst_split() + // Ignore wait signal (asynchronous mode) + .asyncwait() + .disabled() + // Enable extended mode, for different read and write timings + .extmod() + .enabled() + // Ignore wait signal (synchronous mode) + .waiten() + .disabled() + // Allow write operations + .wren() + .enabled() + // Default wait timing + .waitcfg() + .before_wait_state() + // Default wait polarity + .waitpol() + .active_low() + // Disable burst reads + .bursten() + .disabled() + // Enable NOR flash operations + .faccen() + .enabled() + // 16-bit bus width + .mwid() + .bits16() + // NOR flash mode (compatible with LCD controllers) + .mtyp() + .flash() + // Address and data not multiplexed + .muxen() + .disabled() + // Enable this memory bank + .mbken() + .enabled() + }) +} + +/// Configures a read timing register +fn configure_btr(btr: &fsmc::BTR, read_timing: &Timing) { + btr.write(|w| unsafe { + w.accmod() + .variant(read_timing.access_mode.as_read_variant()) + .busturn() + .bits(read_timing.bus_turnaround) + .datast() + .bits(read_timing.data) + .addhld() + .bits(read_timing.address_hold) + .addset() + .bits(read_timing.address_setup) + }) +} +/// Configures a write timing register +fn configure_bwtr(bwtr: &fsmc::BWTR, write_timing: &Timing) { + bwtr.write(|w| unsafe { + w.accmod() + .variant(write_timing.access_mode.as_write_variant()) + .busturn() + .bits(write_timing.bus_turnaround) + .datast() + .bits(write_timing.data) + .addhld() + .bits(write_timing.address_hold) + .addset() + .bits(write_timing.address_setup) + }) +} + +/// An interface to an LCD controller using one sub-bank +/// +/// This struct provides low-level read and write commands that can be used to implement +/// drivers for LCD controllers. Each function corresponds to exactly one transaction on the bus. +pub struct Lcd { + /// Phantom S + /// + /// S determines the chip select signal to use, and the addresses used with that signal. + _sub_bank: PhantomData, +} + +impl Lcd +where + S: SubBank, +{ + /// Writes a value with the data/command (address) signals set high + pub fn write_data(&mut self, value: u16) { + unsafe { + core::ptr::write_volatile(S::DATA_ADDRESS as *mut u16, value); + } + } + + /// Writes a value with the data/command (address) signals set low + pub fn write_command(&mut self, value: u16) { + unsafe { + core::ptr::write_volatile(S::COMMAND_ADDRESS as *mut u16, value); + } + } + + /// Reads a value with the data/command (address) signals set high + pub fn read_data(&self) -> u16 { + unsafe { core::ptr::read_volatile(S::DATA_ADDRESS as *const u16) } + } + + /// Reads a value with the data/command (address) signals set low + pub fn read_command(&self) -> u16 { + unsafe { core::ptr::read_volatile(S::COMMAND_ADDRESS as *const u16) } + } +} diff --git a/src/fsmc_lcd/pins.rs b/src/fsmc_lcd/pins.rs new file mode 100644 index 00000000..bd2ed34d --- /dev/null +++ b/src/fsmc_lcd/pins.rs @@ -0,0 +1,760 @@ +//! Pin definitions for the Flexible Static Memory Controller / Flexible Memory Controller +//! +//! Note: This file only includes pins for these functions: +//! * NOE (read enable) +//! * NWE (write enable) +//! * NEx (chip select) +//! * Ax (address) +//! * Dx (data 0 through 15) +//! +//! # Naming conventions +//! +//! For signal names, this module uses: +//! * Chip select instead of enable +//! * Address instead of data/command +//! * Read enable instead of output enable +//! * Write enable + +use core::marker::PhantomData; + +use super::sealed; +use super::{Lcd, SubBank1}; +use crate::fsmc_lcd::{SubBank2, SubBank3, SubBank4}; + +/// A pin that can be used for data bus 0 +pub trait PinD0: sealed::Sealed {} +/// A pin that can be used for data bus 1 +pub trait PinD1: sealed::Sealed {} +/// A pin that can be used for data bus 2 +pub trait PinD2: sealed::Sealed {} +/// A pin that can be used for data bus 3 +pub trait PinD3: sealed::Sealed {} +/// A pin that can be used for data bus 4 +pub trait PinD4: sealed::Sealed {} +/// A pin that can be used for data bus 5 +pub trait PinD5: sealed::Sealed {} +/// A pin that can be used for data bus 6 +pub trait PinD6: sealed::Sealed {} +/// A pin that can be used for data bus 7 +pub trait PinD7: sealed::Sealed {} +/// A pin that can be used for data bus 8 +pub trait PinD8: sealed::Sealed {} +/// A pin that can be used for data bus 9 +pub trait PinD9: sealed::Sealed {} +/// A pin that can be used for data bus 10 +pub trait PinD10: sealed::Sealed {} +/// A pin that can be used for data bus 11 +pub trait PinD11: sealed::Sealed {} +/// A pin that can be used for data bus 12 +pub trait PinD12: sealed::Sealed {} +/// A pin that can be used for data bus 13 +pub trait PinD13: sealed::Sealed {} +/// A pin that can be used for data bus 14 +pub trait PinD14: sealed::Sealed {} +/// A pin that can be used for data bus 15 +pub trait PinD15: sealed::Sealed {} + +/// A pin that can be used for the output enable (read enable, NOE) signal +pub trait PinReadEnable: sealed::Sealed {} +/// A pin that can be used for the write enable (NOE) signal +pub trait PinWriteEnable: sealed::Sealed {} +/// A pin that can be used as one bit of the memory address +/// +/// This is used to switch between data and command mode. +pub trait PinAddress: sealed::Sealed {} + +/// A pin that can be used to enable a memory device on sub-bank 1 +pub trait PinChipSelect1: sealed::Sealed {} +/// A pin that can be used to enable a memory device on sub-bank 2 +pub trait PinChipSelect2: sealed::Sealed {} +/// A pin that can be used to enable a memory device on sub-bank 3 +pub trait PinChipSelect3: sealed::Sealed {} +/// A pin that can be used to enable a memory device on sub-bank 4 +pub trait PinChipSelect4: sealed::Sealed {} + +/// One, two, three, or four address pins +pub trait AddressPins: sealed::Sealed {} + +// Implement AddressPins for one address pin and tuples of two, three, and four +impl AddressPins for A where A: PinAddress {} +impl AddressPins for (A1, A2) {} +impl sealed::Sealed for (A1, A2) {} +impl AddressPins for (A1, A2, A3) {} +impl sealed::Sealed for (A1, A2, A3) {} +impl AddressPins + for (A1, A2, A3, A4) +{ +} +impl sealed::Sealed + for (A1, A2, A3, A4) +{ +} + +// Implement Conjure for all non-empty subsets of Lcds +impl sealed::Conjure for Lcd { + fn conjure() -> Self { + Lcd { + _sub_bank: PhantomData, + } + } +} +impl sealed::Conjure for Lcd { + fn conjure() -> Self { + Lcd { + _sub_bank: PhantomData, + } + } +} +impl sealed::Conjure for Lcd { + fn conjure() -> Self { + Lcd { + _sub_bank: PhantomData, + } + } +} +impl sealed::Conjure for Lcd { + fn conjure() -> Self { + Lcd { + _sub_bank: PhantomData, + } + } +} +impl sealed::Conjure for (Lcd, Lcd) { + fn conjure() -> Self { + ( + Lcd { + _sub_bank: PhantomData, + }, + Lcd { + _sub_bank: PhantomData, + }, + ) + } +} +impl sealed::Conjure for (Lcd, Lcd) { + fn conjure() -> Self { + ( + Lcd { + _sub_bank: PhantomData, + }, + Lcd { + _sub_bank: PhantomData, + }, + ) + } +} +impl sealed::Conjure for (Lcd, Lcd) { + fn conjure() -> Self { + ( + Lcd { + _sub_bank: PhantomData, + }, + Lcd { + _sub_bank: PhantomData, + }, + ) + } +} +impl sealed::Conjure for (Lcd, Lcd) { + fn conjure() -> Self { + ( + Lcd { + _sub_bank: PhantomData, + }, + Lcd { + _sub_bank: PhantomData, + }, + ) + } +} +impl sealed::Conjure for (Lcd, Lcd) { + fn conjure() -> Self { + ( + Lcd { + _sub_bank: PhantomData, + }, + Lcd { + _sub_bank: PhantomData, + }, + ) + } +} +impl sealed::Conjure for (Lcd, Lcd) { + fn conjure() -> Self { + ( + Lcd { + _sub_bank: PhantomData, + }, + Lcd { + _sub_bank: PhantomData, + }, + ) + } +} +impl sealed::Conjure for (Lcd, Lcd, Lcd) { + fn conjure() -> Self { + ( + Lcd { + _sub_bank: PhantomData, + }, + Lcd { + _sub_bank: PhantomData, + }, + Lcd { + _sub_bank: PhantomData, + }, + ) + } +} +impl sealed::Conjure for (Lcd, Lcd, Lcd) { + fn conjure() -> Self { + ( + Lcd { + _sub_bank: PhantomData, + }, + Lcd { + _sub_bank: PhantomData, + }, + Lcd { + _sub_bank: PhantomData, + }, + ) + } +} +impl sealed::Conjure for (Lcd, Lcd, Lcd) { + fn conjure() -> Self { + ( + Lcd { + _sub_bank: PhantomData, + }, + Lcd { + _sub_bank: PhantomData, + }, + Lcd { + _sub_bank: PhantomData, + }, + ) + } +} +impl sealed::Conjure for (Lcd, Lcd, Lcd) { + fn conjure() -> Self { + ( + Lcd { + _sub_bank: PhantomData, + }, + Lcd { + _sub_bank: PhantomData, + }, + Lcd { + _sub_bank: PhantomData, + }, + ) + } +} +impl sealed::Conjure for (Lcd, Lcd, Lcd, Lcd) { + fn conjure() -> Self { + ( + Lcd { + _sub_bank: PhantomData, + }, + Lcd { + _sub_bank: PhantomData, + }, + Lcd { + _sub_bank: PhantomData, + }, + Lcd { + _sub_bank: PhantomData, + }, + ) + } +} + +/// One, two, three, or four chip select pins +/// +/// Due to trait system limitations, this trait is only implemented for pins wrapped in the +/// `ChipSelect1`, `ChipSelect2`, `ChipSelect3`, and `ChipSelect4` wrappers. +/// +/// This trait is implemented for all non-empty subsets of the 4 possible chip select signals. +/// The pins must be in order. +/// +/// # Example types that implement `ChipSelectPins` +/// +/// Wrapped single pins: +/// * `ChipSelect1>>` +/// * `ChipSelect2>>` +/// * `ChipSelect3>>` +/// * `ChipSelect4>>` +/// +/// Tuples of wrapped pins: +/// * `(ChipSelect1>>, ChipSelect2>>)` +/// * `(ChipSelect1>>, ChipSelect4>>)` +/// * `(ChipSelect1>>, ChipSelect2>>, ChipSelect3>>, ChipSelect4>>)` +pub trait ChipSelectPins: sealed::Sealed { + /// One, two, three, or four `Lcd<_>` objects associated with the sub-bank(s) that these pin(s) + /// control + type Lcds: sealed::Conjure; +} + +// The set of 4 chip selects has 15 subsets (excluding the empty set): +// 1 +// 2 +// 3 +// 4 +// 1, 2 +// 1, 3 +// 1, 4 +// 2, 3 +// 2, 4 +// 3, 4 +// 1, 2, 3 +// 1, 2, 4 +// 1, 3, 4 +// 2, 3, 4 +// 1, 2, 3, 4 + +/// Wrapper for a pin that implements PinChipSelect1 +/// +/// This is required to avoid conflicting trait implementations. +pub struct ChipSelect1

(pub P); +/// Wrapper for a pin that implements PinChipSelect2 +/// +/// This is required to avoid conflicting trait implementations. +pub struct ChipSelect2

(pub P); +/// Wrapper for a pin that implements PinChipSelect3 +/// +/// This is required to avoid conflicting trait implementations. +pub struct ChipSelect3

(pub P); +/// Wrapper for a pin that implements PinChipSelect4 +/// +/// This is required to avoid conflicting trait implementations. +pub struct ChipSelect4

(pub P); + +impl ChipSelectPins for ChipSelect1 { + type Lcds = Lcd; +} +impl sealed::Sealed for ChipSelect1 {} +impl ChipSelectPins for ChipSelect2 { + type Lcds = Lcd; +} +impl sealed::Sealed for ChipSelect2 {} +impl ChipSelectPins for ChipSelect3 { + type Lcds = Lcd; +} +impl sealed::Sealed for ChipSelect3 {} +impl ChipSelectPins for ChipSelect4 { + type Lcds = Lcd; +} +impl sealed::Sealed for ChipSelect4 {} +impl ChipSelectPins + for (ChipSelect1, ChipSelect2) +{ + type Lcds = (Lcd, Lcd); +} +impl sealed::Sealed + for (ChipSelect1, ChipSelect2) +{ +} +impl ChipSelectPins + for (ChipSelect1, ChipSelect3) +{ + type Lcds = (Lcd, Lcd); +} +impl sealed::Sealed + for (ChipSelect1, ChipSelect3) +{ +} +impl ChipSelectPins + for (ChipSelect1, ChipSelect4) +{ + type Lcds = (Lcd, Lcd); +} +impl sealed::Sealed + for (ChipSelect1, ChipSelect4) +{ +} +impl ChipSelectPins + for (ChipSelect2, ChipSelect3) +{ + type Lcds = (Lcd, Lcd); +} +impl sealed::Sealed + for (ChipSelect2, ChipSelect3) +{ +} +impl ChipSelectPins + for (ChipSelect2, ChipSelect4) +{ + type Lcds = (Lcd, Lcd); +} +impl sealed::Sealed + for (ChipSelect2, ChipSelect4) +{ +} +impl ChipSelectPins + for (ChipSelect3, ChipSelect4) +{ + type Lcds = (Lcd, Lcd); +} +impl sealed::Sealed + for (ChipSelect3, ChipSelect4) +{ +} +impl ChipSelectPins + for (ChipSelect1, ChipSelect2, ChipSelect3) +{ + type Lcds = (Lcd, Lcd, Lcd); +} +impl sealed::Sealed + for (ChipSelect1, ChipSelect2, ChipSelect3) +{ +} +impl ChipSelectPins + for (ChipSelect1, ChipSelect2, ChipSelect4) +{ + type Lcds = (Lcd, Lcd, Lcd); +} +impl sealed::Sealed + for (ChipSelect1, ChipSelect2, ChipSelect4) +{ +} +impl ChipSelectPins + for (ChipSelect1, ChipSelect3, ChipSelect4) +{ + type Lcds = (Lcd, Lcd, Lcd); +} +impl sealed::Sealed + for (ChipSelect1, ChipSelect3, ChipSelect4) +{ +} +impl ChipSelectPins + for (ChipSelect2, ChipSelect3, ChipSelect4) +{ + type Lcds = (Lcd, Lcd, Lcd); +} +impl sealed::Sealed + for (ChipSelect2, ChipSelect3, ChipSelect4) +{ +} +impl + ChipSelectPins + for ( + ChipSelect1, + ChipSelect2, + ChipSelect3, + ChipSelect4, + ) +{ + type Lcds = (Lcd, Lcd, Lcd, Lcd); +} +impl + sealed::Sealed + for ( + ChipSelect1, + ChipSelect2, + ChipSelect3, + ChipSelect4, + ) +{ +} + +/// A set of data pins +/// +/// Currently this trait is only implemented for tuples of 16 data pins. In the future, +/// this driver may support 8-bit mode using 8 data pins. +pub trait DataPins: sealed::Sealed {} + +impl DataPins + for ( + D0, + D1, + D2, + D3, + D4, + D5, + D6, + D7, + D8, + D9, + D10, + D11, + D12, + D13, + D14, + D15, + ) +where + D0: PinD0, + D1: PinD1, + D2: PinD2, + D3: PinD3, + D4: PinD4, + D5: PinD5, + D6: PinD6, + D7: PinD7, + D8: PinD8, + D9: PinD9, + D10: PinD10, + D11: PinD11, + D12: PinD12, + D13: PinD13, + D14: PinD14, + D15: PinD15, +{ +} +impl sealed::Sealed + for ( + D0, + D1, + D2, + D3, + D4, + D5, + D6, + D7, + D8, + D9, + D10, + D11, + D12, + D13, + D14, + D15, + ) +where + D0: PinD0, + D1: PinD1, + D2: PinD2, + D3: PinD3, + D4: PinD4, + D5: PinD5, + D6: PinD6, + D7: PinD7, + D8: PinD8, + D9: PinD9, + D10: PinD10, + D11: PinD11, + D12: PinD12, + D13: PinD13, + D14: PinD14, + D15: PinD15, +{ +} + +/// A set of pins used to interface with an LCD +/// +/// The `address` and `enable` fields can be individual pins, or tuples of 2, 3, or 4 pins. +pub struct LcdPins { + /// The 16-bit data bus + pub data: D, + /// Address pin(s) (data/command) + pub address: AD, + /// Output enable (read enable) + pub read_enable: NOE, + /// Write enable + pub write_enable: NWE, + /// Chip select / bank enable pin(s) + pub chip_select: NE, +} + +/// A set of pins that can be used with the FSMC +/// +/// This trait is implemented for the `LcdPins` struct that contains 16 data pins, 1 through 4 +/// address pins, 1 through 4 chip select / bank enable pins, an output enable pin, and a write +/// enable pin. +pub trait Pins: sealed::Sealed { + /// One, two, three, or four `Lcd<_>` objects associated with the sub-bank(s) that the chip + /// select pin pin(s) control + type Lcds: sealed::Conjure; +} + +impl Pins for LcdPins +where + D: DataPins, + AD: AddressPins, + NOE: PinReadEnable, + NWE: PinWriteEnable, + NE: ChipSelectPins, +{ + type Lcds = NE::Lcds; +} + +impl sealed::Sealed for LcdPins +where + D: DataPins, + AD: AddressPins, + NOE: PinReadEnable, + NWE: PinWriteEnable, + NE: ChipSelectPins, +{ +} + +/// Pins available on all STM32F4 models that have an FSMC/FMC +mod common_pins { + use super::sealed::Sealed; + use super::{ + PinAddress, PinChipSelect1, PinChipSelect2, PinChipSelect3, PinChipSelect4, PinD0, PinD1, + PinD10, PinD11, PinD12, PinD13, PinD14, PinD15, PinD2, PinD3, PinD4, PinD5, PinD6, PinD7, + PinD8, PinD9, PinReadEnable, PinWriteEnable, + }; + use crate::gpio::gpiod::{ + PD0, PD1, PD10, PD11, PD12, PD13, PD14, PD15, PD4, PD5, PD7, PD8, PD9, + }; + use crate::gpio::gpioe::{ + PE10, PE11, PE12, PE13, PE14, PE15, PE2, PE3, PE4, PE5, PE6, PE7, PE8, PE9, + }; + use crate::gpio::gpiof::{PF0, PF1, PF12, PF13, PF14, PF15, PF2, PF3, PF4, PF5}; + use crate::gpio::gpiog::{PG0, PG1, PG10, PG12, PG13, PG2, PG3, PG4, PG5, PG9}; + use crate::gpio::{Alternate, AF12}; + + // All FSMC/FMC pins use AF12 + type FmcAlternate = Alternate; + + impl PinD2 for PD0 {} + impl PinD3 for PD1 {} + impl PinReadEnable for PD4 {} + impl PinWriteEnable for PD5 {} + impl PinChipSelect1 for PD7 {} + impl Sealed for PD7 {} + impl PinD13 for PD8 {} + impl PinD14 for PD9 {} + impl PinD15 for PD10 {} + impl PinAddress for PD11 {} + impl Sealed for PD11 {} + impl PinAddress for PD12 {} + impl Sealed for PD12 {} + impl PinAddress for PD13 {} + impl Sealed for PD13 {} + impl PinD0 for PD14 {} + impl PinD1 for PD15 {} + impl PinAddress for PE2 {} + impl Sealed for PE2 {} + impl PinAddress for PE3 {} + impl Sealed for PE3 {} + impl PinAddress for PE4 {} + impl Sealed for PE4 {} + impl PinAddress for PE5 {} + impl Sealed for PE5 {} + impl PinAddress for PE6 {} + impl Sealed for PE6 {} + impl PinD4 for PE7 {} + impl PinD5 for PE8 {} + impl PinD6 for PE9 {} + impl PinD7 for PE10 {} + impl PinD8 for PE11 {} + impl PinD9 for PE12 {} + impl PinD10 for PE13 {} + impl PinD11 for PE14 {} + impl PinD12 for PE15 {} + + impl PinAddress for PF0 {} + impl Sealed for PF0 {} + impl PinAddress for PF1 {} + impl Sealed for PF1 {} + impl PinAddress for PF2 {} + impl Sealed for PF2 {} + impl PinAddress for PF3 {} + impl Sealed for PF3 {} + impl PinAddress for PF4 {} + impl Sealed for PF4 {} + impl PinAddress for PF5 {} + impl Sealed for PF5 {} + impl PinAddress for PF12 {} + impl Sealed for PF12 {} + impl PinAddress for PF13 {} + impl Sealed for PF13 {} + impl PinAddress for PF14 {} + impl Sealed for PF14 {} + impl PinAddress for PF15 {} + impl Sealed for PF15 {} + impl PinAddress for PG0 {} + impl Sealed for PG0 {} + impl PinAddress for PG1 {} + impl Sealed for PG1 {} + impl PinAddress for PG2 {} + impl Sealed for PG2 {} + impl PinAddress for PG3 {} + impl Sealed for PG3 {} + impl PinAddress for PG4 {} + impl Sealed for PG4 {} + impl PinAddress for PG5 {} + impl Sealed for PG5 {} + impl PinChipSelect2 for PG9 {} + impl Sealed for PG9 {} + impl PinChipSelect3 for PG10 {} + impl Sealed for PG10 {} + impl PinChipSelect4 for PG12 {} + impl Sealed for PG12 {} + impl PinAddress for PG13 {} + impl Sealed for PG13 {} + // PG14 can be used as address 25 (A25), but that pin is not available here. + // Because external addresses are in units of 16 bits, external address line 25 can never + // be high. The internal memory address would overflow into the next sub-bank. + + // Sealed trait boilerplate + impl Sealed for PD0 {} + impl Sealed for PD1 {} + impl Sealed for PD4 {} + impl Sealed for PD5 {} + impl Sealed for PD8 {} + impl Sealed for PD9 {} + impl Sealed for PD10 {} + impl Sealed for PD14 {} + impl Sealed for PD15 {} + + impl Sealed for PE7 {} + impl Sealed for PE8 {} + impl Sealed for PE9 {} + impl Sealed for PE10 {} + impl Sealed for PE11 {} + impl Sealed for PE12 {} + impl Sealed for PE13 {} + impl Sealed for PE14 {} + impl Sealed for PE15 {} +} + +/// Additional pins available on some models +#[cfg(any(feature = "stm32f412", feature = "stm32f413", feature = "stm32f423"))] +mod extra_pins { + use super::sealed::Sealed; + use super::{ + PinAddress, PinChipSelect4, PinD0, PinD1, PinD13, PinD2, PinD3, PinD4, PinD5, PinD6, PinD7, + PinReadEnable, PinWriteEnable, + }; + use crate::gpio::gpioa::{PA2, PA3, PA4, PA5}; + use crate::gpio::gpiob::{PB12, PB14}; + use crate::gpio::gpioc::{PC11, PC12, PC2, PC3, PC4, PC5, PC6}; + use crate::gpio::{Alternate, AF12}; + + // All FSMC/FMC pins use AF12 + type FmcAlternate = Alternate; + + impl PinD4 for PA2 {} + impl PinD5 for PA3 {} + impl PinD6 for PA4 {} + impl PinD7 for PA5 {} + impl PinD13 for PB12 {} + impl PinD0 for PB14 {} + impl PinWriteEnable for PC2 {} + impl PinAddress for PC3 {} + impl Sealed for PC3 {} + impl PinChipSelect4 for PC4 {} + impl Sealed for PC4 {} + impl PinReadEnable for PC5 {} + impl PinD1 for PC6 {} + impl PinD2 for PC11 {} + impl PinD3 for PC12 {} + + // Sealed trait boilerplate + impl Sealed for PA2 {} + impl Sealed for PA3 {} + impl Sealed for PA4 {} + impl Sealed for PA5 {} + impl Sealed for PB12 {} + impl Sealed for PB14 {} + impl Sealed for PC2 {} + impl Sealed for PC5 {} + impl Sealed for PC6 {} + impl Sealed for PC11 {} + impl Sealed for PC12 {} +} diff --git a/src/fsmc_lcd/sealed.rs b/src/fsmc_lcd/sealed.rs new file mode 100644 index 00000000..98e6b75a --- /dev/null +++ b/src/fsmc_lcd/sealed.rs @@ -0,0 +1,33 @@ +/// Private implementation details used in the fsmc_lcd module and the pins submodule + +pub trait Sealed {} + +/// Private supertrait of SubBank +pub trait SealedSubBank { + /// The address of the beginning of this sub-bank's address space + const BASE_ADDRESS: usize; + /// The address in memory used to communicate with the LCD controller with the data/command + /// signal set to command (low) + const COMMAND_ADDRESS: usize = Self::BASE_ADDRESS; + /// The address in memory used to communicate with the LCD controller with the data/command + /// signal set to data (high) + const DATA_ADDRESS: usize = make_data_address(Self::BASE_ADDRESS); +} + +/// A trait similar to Default, but private to this crate +/// +/// This is used to create `Lcd` objects and tuples of `Lcd`s. +pub trait Conjure { + /// Creates something out of thin air + fn conjure() -> Self; +} + +/// Converts a command address into a data address +/// +/// The data address will result in all external address signals being set high. +const fn make_data_address(base: usize) -> usize { + // Bits 26 and 27 select the sub-bank, don't change them. + // Bits 25 through 1 become address signals 24 through 0, set these high. + // Bit 0 is not used with 16-bit addressing. + base | 0x3fffffe +} diff --git a/src/fsmc_lcd/timing.rs b/src/fsmc_lcd/timing.rs new file mode 100644 index 00000000..77acf8a2 --- /dev/null +++ b/src/fsmc_lcd/timing.rs @@ -0,0 +1,144 @@ +//! FMC/FSMC timing + +use super::fsmc; + +/// Memory access modes +/// +/// These define the general shape of a transaction and the meanings of some of the time fields. +/// Refer to the microcontroller reference manual for more details. +#[derive(Debug, Clone)] +pub enum AccessMode { + ModeA, + ModeB, + ModeC, + ModeD, +} + +impl AccessMode { + pub(crate) fn as_read_variant(&self) -> fsmc::btr::ACCMOD_A { + use fsmc::btr::ACCMOD_A; + match *self { + AccessMode::ModeA => ACCMOD_A::A, + AccessMode::ModeB => ACCMOD_A::B, + AccessMode::ModeC => ACCMOD_A::C, + AccessMode::ModeD => ACCMOD_A::D, + } + } + pub(crate) fn as_write_variant(&self) -> fsmc::bwtr::ACCMOD_A { + use fsmc::bwtr::ACCMOD_A; + match *self { + AccessMode::ModeA => ACCMOD_A::A, + AccessMode::ModeB => ACCMOD_A::B, + AccessMode::ModeC => ACCMOD_A::C, + AccessMode::ModeD => ACCMOD_A::D, + } + } +} + +/// Timing configuration for reading or writing +/// +/// A `Timing` object can be created using `Timing::default()` or `Default::default()`. +/// +/// The default timing uses access mode C and the slowest possible timings, for maximum +/// compatibility. +/// +/// If the LCD controller and wiring allow, you can reduce the times to make transactions faster. +/// +/// All time fields are in units of HCLK cycles. +#[derive(Debug, Clone)] +pub struct Timing { + pub(crate) access_mode: AccessMode, + pub(crate) bus_turnaround: u8, + pub(crate) data: u8, + pub(crate) address_hold: u8, + pub(crate) address_setup: u8, +} + +impl Default for Timing { + /// Returns a conservative (slow) timing configuration with access mode C + fn default() -> Self { + Timing { + access_mode: AccessMode::ModeC, + bus_turnaround: Timing::BUS_TURNAROUND_MAX, + data: 255, + address_hold: Timing::ADDRESS_HOLD_MAX, + address_setup: Timing::ADDRESS_SETUP_MAX, + } + } +} + +impl Timing { + /// Maximum allowed value of the bus turnaround time + pub const BUS_TURNAROUND_MAX: u8 = 15; + /// Minimum allowed value of the data phase time + pub const DATA_MIN: u8 = 1; + /// Maximum allowed value of the address hold time + pub const ADDRESS_HOLD_MIN: u8 = 1; + /// Maximum allowed value of the address hold time + pub const ADDRESS_HOLD_MAX: u8 = 15; + /// Maximum allowed value of the address setup time + pub const ADDRESS_SETUP_MAX: u8 = 15; + + /// Sets the access mode + pub fn access_mode(self, access_mode: AccessMode) -> Self { + Timing { + access_mode, + ..self + } + } + /// Sets the bus turnaround time, in units of HCLK cycles + /// + /// This corresponds to the BUSTURN field of FSMC_BTR or FSMC_BWTR. + /// + /// # Panics + /// + /// This function panics if bus_turnaround is greater than Timing::BUS_TURNAROUND_MAX. + pub fn bus_turnaround(self, bus_turnaround: u8) -> Self { + assert!(bus_turnaround <= Timing::BUS_TURNAROUND_MAX); + Timing { + bus_turnaround, + ..self + } + } + /// Sets the data phase time, in units of HCLK cycles + /// + /// This corresponds to the DATAST field of FSMC_BTR or FSMC_BWTR. + /// + /// # Panics + /// + /// This function panics if data is less than Timing::DATA_MIN. + pub fn data(self, data: u8) -> Self { + assert!(data >= Timing::DATA_MIN); + Timing { data, ..self } + } + /// Sets the address hold phase time, in units of HCLK cycles + /// + /// This corresponds to the ADDHLD field of FSMC_BTR or FSMC_BWTR. + /// + /// # Panics + /// + /// This function panics if address_hold is less than Timing::ADDRESS_HOLD_MIN or greater than + /// Timing::ADDRESS_HOLD_MAX. + pub fn address_hold(self, address_hold: u8) -> Self { + assert!(address_hold >= Timing::ADDRESS_HOLD_MIN); + assert!(address_hold <= Timing::ADDRESS_HOLD_MAX); + Timing { + address_hold, + ..self + } + } + /// Sets the address setup phase time, in units of HCLK cycles + /// + /// This corresponds to the ADDSET field of FSMC_BTR or FSMC_BWTR. + /// + /// # Panics + /// + /// This function panics if address_setup is greater than Timing::ADDRESS_SETUP_MAX. + pub fn address_setup(self, address_setup: u8) -> Self { + assert!(address_setup <= Timing::ADDRESS_SETUP_MAX); + Timing { + address_setup, + ..self + } + } +} diff --git a/src/lib.rs b/src/lib.rs index f488f63f..964f550f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -180,6 +180,26 @@ pub use stm32 as pac; pub mod dma; #[cfg(feature = "device-selected")] pub mod dwt; +#[cfg(all( + feature = "fsmc_lcd", + any( + feature = "stm32f405", + feature = "stm32f407", + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f415", + feature = "stm32f417", + feature = "stm32f423", + feature = "stm32f427", + feature = "stm32f429", + feature = "stm32f437", + feature = "stm32f439", + feature = "stm32f446", + feature = "stm32f469", + feature = "stm32f479" + ) +))] +pub mod fsmc_lcd; #[cfg(feature = "device-selected")] pub mod prelude; #[cfg(feature = "device-selected")] diff --git a/tools/check.py b/tools/check.py index a5ab1a81..d3db3de8 100755 --- a/tools/check.py +++ b/tools/check.py @@ -28,7 +28,7 @@ def main(): crate_info = cargo_meta["packages"][0] - features = ["{},rt,usb_fs,can,i2s".format(x) + features = ["{},rt,usb_fs,can,i2s,fsmc_lcd".format(x) for x in crate_info["features"].keys() if x.startswith("stm32f4")]