From 4fe0c0a63105c639d8e269b44662162f55e93937 Mon Sep 17 00:00:00 2001 From: Gary B Date: Fri, 9 Feb 2024 19:05:10 -0500 Subject: [PATCH] Basic display drawing complete --- .genignore | 1 - .gitignore | 5 +- Cargo.toml | 9 ++- gdb.cfg | 14 ++++ memory.x | 1 - openocd.cfg | 10 +++ run-gdb | 2 + src/bin/minimal.rs | 145 +++++++++++++++++++++++++++++++++------- src/display.rs | 160 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + stm32l4xx-hal | 1 + 11 files changed, 323 insertions(+), 27 deletions(-) delete mode 100644 .genignore create mode 100644 gdb.cfg create mode 100644 openocd.cfg create mode 100644 run-gdb create mode 100644 src/display.rs create mode 160000 stm32l4xx-hal diff --git a/.genignore b/.genignore deleted file mode 100644 index 8d1e189..0000000 --- a/.genignore +++ /dev/null @@ -1 +0,0 @@ -.github/FUNDING.yml diff --git a/.gitignore b/.gitignore index ad2f0ff..1db3cdb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ /target Cargo.lock -.DS_Store \ No newline at end of file +.DS_Store + +.gdb_history +.idea \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 83ccbd5..f0d30c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] # TODO fix `authors` and `name` if you didn't use `cargo-generate` -name = "test-app" +name = "gps-watch" edition = "2021" version = "0.1.0" @@ -14,7 +14,9 @@ rtic = { version = "2.0.0", features = [ "thumbv7-backend" ] } # TODO(5) Add hal as dependency stm32l4xx-hal = { version = "0.7.1", features = ["stm32l442"] } # TODO add a monotonic if you use scheduling -# rtic-monotonics = { version = "1.0.0", features = [ "cortex-m-systick" ]} +rtic-monotonics = { version = "1.0.0", features = [ "cortex-m-systick", "systick-100hz"]} +thiserror = { version = "1.0.50", package = "thiserror-core", default-features = false } +embedded-graphics = "0.8.1" # cargo build/run [profile.dev] @@ -54,6 +56,9 @@ lto = 'fat' opt-level = "s" # <- overflow-checks = false # <- +[patch.crates-io] +stm32l4xx-hal = { path = "./stm32l4xx-hal" } + # uncomment this to switch from the crates.io version of defmt to its git version # check app-template's README for instructions # [patch.crates-io] diff --git a/gdb.cfg b/gdb.cfg new file mode 100644 index 0000000..e850fa5 --- /dev/null +++ b/gdb.cfg @@ -0,0 +1,14 @@ +set architecture armv7e-m + +define reset + monitor reset halt +end + +define flash + monitor program target/thumbv7em-none-eabihf/debug/minimal +end + +target remote localhost:1337 + +symbol-file target/thumbv7em-none-eabihf/debug/minimal +monitor reset halt \ No newline at end of file diff --git a/memory.x b/memory.x index fdf940d..825e7c5 100644 --- a/memory.x +++ b/memory.x @@ -1,7 +1,6 @@ MEMORY { /* NOTE K = KiBi = 1024 bytes */ - /* TODO Adjust these memory regions to match your device memory layout */ FLASH : ORIGIN = 0x8000000, LENGTH = 128K RAM : ORIGIN = 0x20000000, LENGTH = 32K } diff --git a/openocd.cfg b/openocd.cfg new file mode 100644 index 0000000..6114617 --- /dev/null +++ b/openocd.cfg @@ -0,0 +1,10 @@ +telnet_port 4444 +gdb_port 1337 + +source [find interface/stlink.cfg] + +transport select hla_swd + +source [find target/stm32l4x.cfg] + +reset_config srst_only diff --git a/run-gdb b/run-gdb new file mode 100644 index 0000000..cfb8e47 --- /dev/null +++ b/run-gdb @@ -0,0 +1,2 @@ +#!/bin/bash +gdb-multiarch -x gdb.cfg diff --git a/src/bin/minimal.rs b/src/bin/minimal.rs index 372eef6..d6dfbae 100644 --- a/src/bin/minimal.rs +++ b/src/bin/minimal.rs @@ -2,25 +2,46 @@ #![no_std] #![feature(type_alias_impl_trait)] -use test_app as _; // global logger + panicking-behavior + memory layout +use gps_watch as _; +use stm32l4xx_hal as hal; + +use hal::{pac, prelude::*}; +use rtic_monotonics::create_systick_token; +use rtic_monotonics::systick::Systick; +use stm32l4xx_hal::gpio::{Alternate, Output, PA11, PB3, PB4, PB5, PinExt, PushPull}; +use stm32l4xx_hal::hal::spi::{Mode, Phase, Polarity}; +use stm32l4xx_hal::pac::SPI1; +use stm32l4xx_hal::spi::Spi; +use defmt::{trace, info}; +use core::num::Wrapping; +use embedded_graphics::primitives::PrimitiveStyle; +use embedded_graphics::prelude::*; +use rtic::Mutex; + +type SharpMemDisplay = gps_watch::display::SharpMemDisplay< + Spi>, + PB4>, + PB5> + )>, PA11>>; -// TODO(7) Configure the `rtic::app` macro #[rtic::app( - // TODO: Replace `some_hal::pac` with the path to the PAC device = stm32l4xx_hal::pac, + // peripherals = true, // TODO: Replace the `FreeInterrupt1, ...` with free interrupt vectors if software tasks are used // You can usually find the names of the interrupt vectors in the some_hal::pac::interrupt enum. - dispatchers = [EXTI0] + dispatchers = [EXTI0], + )] mod app { - use stm32l4xx_hal as hal; - - use hal::{pac, prelude::*}; + use embedded_graphics::pixelcolor::BinaryColor; + use embedded_graphics::primitives::Rectangle; + use super::*; // Shared resources go here #[shared] struct Shared { - // TODO: Add resources + display: SharpMemDisplay } // Local resources go here @@ -30,21 +51,67 @@ mod app { } #[init] - fn init(_cx: init::Context) -> (Shared, Local) { - let peripherals = pac::Peripherals::take(); + fn init(cx: init::Context) -> (Shared, Local) { + trace!("init enter"); + + // Configure clocks + // unsafe { + // // MSI is already on + // // Set MSI to 200 kHz + // cx.device.RCC.cr.write(|w| w.msirange().bits(0b0001).msirgsel().set_bit()); + // // Set MSI as system clock source + // cx.device.RCC.cfgr.write(|w| w.sw().bits(0b00)); + // } + let mut flash = cx.device.FLASH.constrain(); + let mut rcc = cx.device.RCC.constrain(); + let mut pwr = cx.device.PWR.constrain(&mut rcc.apb1r1); + let clocks = rcc.cfgr.freeze(&mut flash.acr, &mut pwr); + + // Create SysTick monotonic for task scheduling + Systick::start( + cx.core.SYST, + 4_000_000, + // 200_000, + create_systick_token!() + ); + + // Initialize SPI + let mut gpioa = cx.device.GPIOA.split(&mut rcc.ahb2); + let mut gpiob = cx.device.GPIOB.split(&mut rcc.ahb2); - defmt::info!("init"); + let mut cs = gpioa.pa11.into_push_pull_output(&mut gpioa.moder, &mut gpioa.otyper); + cs.set_low(); - // TODO setup monotonic if used - // let sysclk = { /* clock setup + returning sysclk as an u32 */ }; - // let token = rtic_monotonics::create_systick_token!(); - // rtic_monotonics::systick::Systick::new(cx.core.SYST, sysclk, token); + // ; + let mut sck = gpiob.pb3.into_alternate(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl); + let mut mosi = gpiob.pb5.into_alternate(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl); + let mut miso = gpiob.pb4.into_alternate(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl); - blink_led::spawn().ok(); + let SPI1_COPY = unsafe { core::ptr::read(&cx.device.SPI1) }; + let spi1 = hal::spi::Spi::spi1( + cx.device.SPI1, + (sck, miso, mosi), + Mode { + phase: Phase::CaptureOnFirstTransition, + polarity: Polarity::IdleLow + }, + 1.MHz(), + clocks, + &mut rcc.apb2 + ); + // SPI1_COPY.cr1.write(|w| w.lsbfirst().lsbfirst()); + // let spi1_cr1 = 0x4001_3000 as *mut u32; + // unsafe { core::ptr::write_volatile(spi1_cr1, core::ptr::read_volatile(spi1_cr1) | (1u32 << 7)); } + + let mut display: SharpMemDisplay = SharpMemDisplay::new(spi1, cs); + + display_task::spawn().unwrap(); + + trace!("init exit"); ( Shared { - // Initialization of shared resources go here + display }, Local { // Initialization of local resources go here @@ -55,16 +122,50 @@ mod app { // Optional idle, can be removed if not needed. #[idle] fn idle(_: idle::Context) -> ! { - defmt::info!("idle"); + trace!("idle enter"); + + // let device_stolen = unsafe { pac::Peripherals::steal() }; loop { - continue; + core::hint::spin_loop(); + // Sleep + // rtic::export::wfi(); + // defmt::info!("hewo 1"); + // unsafe { + // device_stolen.RCC.cr.write(|w| w.msirange().bits(0b0001).msirgsel().set_bit()); + // } + // defmt::info!("hewo 2"); + // // Return to our previously-selected MSI frequency + // device_stolen.RCC.cr.write(|w| w.msirgsel().set_bit()); + // defmt::info!("hewo 3"); + // unsafe { + // device_stolen.RCC.cfgr.write(|w| w.sw().bits(0b00)); + // } + // defmt::info!("hewo 4"); } } // TODO: Add tasks - #[task(priority = 1)] - async fn blink_led(_cx: blink_led::Context) { - defmt::info!("Hello from task1!"); + #[task( + priority = 1, + shared = [display] + )] + async fn display_task(mut cx: display_task::Context) { + trace!("display_task enter"); + cx.shared.display.lock(|display| display.clear()); + + let mut i = Wrapping(0u8); + loop { + Systick::delay(500.millis()).await; + let stroke = PrimitiveStyle::with_stroke(BinaryColor::On, 3); + let rect_styled = Rectangle { top_left: Point {x: i.0 as i32, y: i.0 as i32}, size: Size { width: 20, height: 20 } } + .into_styled(stroke); + cx.shared.display.lock(|display| { + rect_styled.draw(display).unwrap(); + display.flush(); + }); + info!("hello world"); + i += 1; + } } } diff --git a/src/display.rs b/src/display.rs new file mode 100644 index 0000000..c4380ae --- /dev/null +++ b/src/display.rs @@ -0,0 +1,160 @@ +use embedded_graphics::pixelcolor::BinaryColor; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::Rectangle; +use stm32l4xx_hal::hal::{ + digital::v2::OutputPin, + blocking::spi::Write as SpiWrite, +}; +use stm32l4xx_hal::spi; +use core::convert::Infallible; + +const WIDTH: usize = 400; +const HEIGHT: usize = 240; +const WIDTH_BYTES: usize = WIDTH.div_ceil(8); +const HEIGHT_BYTES: usize = HEIGHT.div_ceil(8); + +const VCOM_BIT: u8 = 0b0000_0010; + +// #[derive(thiserror::Error, Debug)] +// pub enum DisplayError { +// #[error("SPI write failed")] +// Spi, +// #[error("GPIO failed")] +// Gpio +// } + +// type Result = core::result::Result; + +struct SpiTransaction<'a, SPI, CS> { + spi: &'a mut SPI, + cs: &'a mut CS +} + +impl<'a, SPI, CS> SpiTransaction<'a, SPI, CS> + where SPI: SpiWrite, + CS: OutputPin +{ + fn start(disp: &'a mut SharpMemDisplayDriver, command: u8) -> Self { + disp.vcom = !disp.vcom; + disp.cs.set_high().unwrap(); + disp.spi.write(&[ + command | if disp.vcom { VCOM_BIT } else { 0 } + ]).unwrap(); + Self { + spi: &mut disp.spi, + cs: &mut disp.cs + } + } + + fn send(mut self, data: &[u8]) -> Self { + self.spi.write(data).unwrap(); + self + } + + fn finish(mut self) { + self.cs.set_low().unwrap(); + } +} + + +pub struct SharpMemDisplayDriver { + spi: SPI, + cs: CS, + vcom: bool +} + +impl SharpMemDisplayDriver + where SPI: SpiWrite, + CS: OutputPin +{ + pub fn new(spi: SPI, cs: CS) -> Self { + Self { + spi, + cs, + vcom: false + } + } + + fn start(&mut self, command: u8) -> SpiTransaction { + SpiTransaction::start(self, command) + } + + pub fn clear(&mut self) { + self.start(0b0000_0100).send(&[0x00]).finish(); + } + + pub fn write_line(&mut self, line: u8, data: &[u8; WIDTH_BYTES]) { + self.start(0b0000_0001).send(&[line]).send(data).send(&[0x00, 0x00]).finish(); + } +} + +pub struct SharpMemDisplay { + buf: [[u8; WIDTH_BYTES]; HEIGHT], + dirty: [u8; HEIGHT_BYTES], + driver: SharpMemDisplayDriver +} + +impl SharpMemDisplay + where SPI: SpiWrite, + CS: OutputPin +{ + pub fn new(spi: SPI, cs: CS) -> Self { + Self { + buf: [[0; WIDTH_BYTES]; HEIGHT], + dirty: [0; HEIGHT_BYTES], + driver: SharpMemDisplayDriver::new(spi, cs) + } + } + + pub fn draw_pixel(&mut self, x: usize, y: usize, state: bool) { + if x >= WIDTH || y >= HEIGHT { return } + if state { + self.buf[y][x / 8] |= 1u8 << (x % 8); + } else { + self.buf[y][x / 8] &= !(1u8 << (x % 8)); + } + self.dirty[y / 8] |= 1u8 << (y % 8); + } + + pub fn flush(&mut self) { + for y in 0usize..HEIGHT { + if (self.dirty[y / 8] & (1u8 << (y % 8))) != 0u8 { + self.driver.write_line(y as u8, &self.buf[y]); + } + } + self.dirty = [0; HEIGHT_BYTES]; + } + + pub fn clear(&mut self) { + self.buf = [[0xFF; WIDTH_BYTES]; HEIGHT]; + self.dirty = [0; HEIGHT_BYTES]; + self.driver.clear(); + } +} + +impl DrawTarget for SharpMemDisplay + where SPI: SpiWrite, + CS: OutputPin +{ + type Color = BinaryColor; + type Error = Infallible; + + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> where I: IntoIterator> { + for Pixel(pos, color) in pixels { + self.draw_pixel(pos.x as usize, pos.y as usize, match color { + BinaryColor::Off => true, + BinaryColor::On => false + }); + } + Ok(()) + } +} + +impl Dimensions for SharpMemDisplay { + fn bounding_box(&self) -> Rectangle { + Rectangle { + top_left: Point {x: 0, y: 0}, + size: Size {width: WIDTH as u32, height: HEIGHT as u32} + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 43db348..ddd5852 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,8 @@ use panic_probe as _; // TODO(6) Import your HAL use stm32l4xx_hal as _; // memory layout +pub mod display; + // same panicking *behavior* as `panic-probe` but doesn't print a panic message // this prevents the panic message being printed *twice* when `defmt::panic` is invoked #[defmt::panic_handler] diff --git a/stm32l4xx-hal b/stm32l4xx-hal new file mode 160000 index 0000000..26e0917 --- /dev/null +++ b/stm32l4xx-hal @@ -0,0 +1 @@ +Subproject commit 26e0917e782402251e3eeaef53f1adb4fad22a42