diff --git a/Cargo.lock b/Cargo.lock index a9ecaff..378cccc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -219,8 +219,6 @@ dependencies = [ [[package]] name = "cst816s" version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "988624bfaeaa34565792f7badc33cd02e84ba51fff4bbc3cd76f59d4af5658e7" dependencies = [ "cortex-m 0.6.2", "embedded-hal", @@ -242,22 +240,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7313c0d620d0cb4dbd9d019e461a4beb501071ff46ec0ab933efb4daa76d73e3" -[[package]] -name = "display-interface" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07dda8d2dfb92b17bff0395151e8fe7f266f0923c75383200c09bc7ef472f28f" - -[[package]] -name = "display-interface-spi" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d2de9922255f2313ce64ffcf587c4380c7a54accc1ba0e02dbf8af16caacd4" -dependencies = [ - "display-interface", - "embedded-hal", -] - [[package]] name = "embedded-graphics" version = "0.6.2" @@ -416,7 +398,7 @@ dependencies = [ [[package]] name = "lvgl-codegen" -version = "0.4.0" +version = "0.5.0" dependencies = [ "Inflector", "lazy_static", @@ -428,7 +410,7 @@ dependencies = [ [[package]] name = "lvgl-sys" -version = "0.4.0" +version = "0.5.0" dependencies = [ "bindgen", "cc", @@ -644,11 +626,10 @@ checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" [[package]] name = "st7789" -version = "0.2.2" -source = "git+https://github.com/tstellanova/st7789#b1fe2c7af1947a044f37665315ddb28d9845b3ec" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ccc7b7e79f9e578e981bfab45b502b72ca02717a4356df0478228a6ff7c47c4" dependencies = [ - "display-interface", - "display-interface-spi", "embedded-graphics", "embedded-hal", "heapless", diff --git a/Cargo.toml b/Cargo.toml index 13d8141..51c1b35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,13 +9,13 @@ license = "BSD-3-Clause" cortex-m = "0.6" cortex-m-rt = "0.6" embedded-graphics = "0.6" -embedded-hal = "0.2" -nrf52832-hal = {version = "0.8.1", default-features = false, features = ["xxAA-package", "rt"]} +embedded-hal = {version ="0.2.3", features = ["unproven"] } #nrf52832-hal = { version = "0.10", default-features = false, features = ["xxAA-package", "rt"] } +nrf52832-hal = {version = "0.8.1", default-features = false, features = ["xxAA-package", "rt"]} shared-bus = {version = "0.1.4", features = ["cortexm"] } -st7789 = { git = "https://github.com/tstellanova/st7789" } -cst816s = "0.1.4" +st7789 = { version = "0.2", features = ["graphics", "batch", "buffer"], default-features = false } cstr_core = "0.2.0" +cst816s = { path = "../cst816s" } lvgl = { path = "../lvgl-rs/lvgl" } [profile.dev] diff --git a/src/backlight.rs b/src/backlight.rs new file mode 100644 index 0000000..789be11 --- /dev/null +++ b/src/backlight.rs @@ -0,0 +1,85 @@ +use embedded_hal::digital::v2::OutputPin; +use nrf52832_hal::gpio::{Output, Pin, PushPull}; + +/// Control the backlight. +/// +/// There are three active-low backlight pins, each connected to a FET that +/// toggles backlight power through a resistor. +/// +/// - Low: 2.2 kΩ +/// - Mid: 100 Ω +/// - High: 30 Ω +/// +/// Through combinations of these pins, 7 brightness levels (+ off) can be +/// configured. +pub struct Backlight { + low: Pin>, + mid: Pin>, + high: Pin>, + + /// The current brightness level (value between 0 and 7). + brightness: u8, +} + +impl Backlight { + /// Initialize the backlight with the specified level (0–7). + pub fn init( + low: Pin>, + mid: Pin>, + high: Pin>, + brightness: u8, + ) -> Self { + let mut backlight = Self { + low, + mid, + high, + brightness, + }; + backlight.set(brightness); + backlight + } + + /// Set the brightness level. Must be a value between 0 (off) and 7 (max + /// brightness). Higher values are clamped to 7. + pub fn set(&mut self, mut brightness: u8) { + if brightness > 7 { + brightness = 7; + } + if brightness & 0x01 > 0 { + self.low.set_low().unwrap(); + } else { + self.low.set_high().unwrap(); + } + if brightness & 0x02 > 0 { + self.mid.set_low().unwrap(); + } else { + self.mid.set_high().unwrap(); + } + if brightness & 0x04 > 0 { + self.high.set_low().unwrap(); + } else { + self.high.set_high().unwrap(); + } + self.brightness = brightness; + } + + /// Turn off the backlight. + pub fn off(&mut self) { + self.set(0); + } + + /// Increase backlight brightness. + pub fn brighter(&mut self) { + self.set(self.brightness + 1); + } + + /// Decrease backlight brightness. + pub fn darker(&mut self) { + self.set(self.brightness - 1); + } + + /// Return the current brightness level (value between 0 and 7). + pub fn get_brightness(&self) -> u8 { + self.brightness + } +} diff --git a/src/delay.rs b/src/delay.rs new file mode 100644 index 0000000..614575f --- /dev/null +++ b/src/delay.rs @@ -0,0 +1,31 @@ +//! Delay implementation using regular timers. +//! +//! This is done because RTFM takes ownership of SYST, and the nrf52-hal by +//! default also wants SYST for its Delay implementation. + +use embedded_hal::blocking::delay::DelayUs; +use hal::nrf52832_pac as pac; +use nrf52832_hal::prelude::TimerExt; +use nrf52832_hal::{self as hal, timer::Timer}; + +pub struct TimerDelay { + timer: hal::Timer, +} + +impl TimerDelay { + pub fn new(timer0: pac::TIMER0) -> Self { + Self { + //timer: Timer::new(timer0), + timer: timer0.constrain(), + } + } +} + +impl DelayUs for TimerDelay { + fn delay_us(&mut self, us: u32) { + // Currently the HAL timer is hardcoded at 1 MHz, + // so 1 cycle = 1 µs. + let cycles = us; + self.timer.delay(cycles); + } +} diff --git a/src/main.rs b/src/main.rs index 3cdd194..722da3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,17 @@ #![no_std] #![no_main] +mod backlight; +mod delay; mod monotonic_nrf52; +// use nrf52832_hal::gpio::Level; +// use nrf52832_hal::{self as p_hal, pac}; +// use p_hal::{delay::Delay, spim, twim}; use nrf52832_hal as p_hal; use p_hal::gpio::{GpioExt, Level}; use p_hal::nrf52832_pac as pac; -use p_hal::{delay::Delay, spim, twim}; +use p_hal::{delay::Delay, rng::RngExt, spim, twim}; use cortex_m_rt as rt; use cst816s::CST816S; @@ -14,9 +19,8 @@ use embedded_graphics::prelude::*; use rt::entry; use st7789::Orientation; -use core::cell::Cell; use cstr_core::CStr; -use lvgl::input_device::{BufferStatus, InputData, Pointer}; +use lvgl::input_device::{InputData, Pointer}; use lvgl::style::Style; use lvgl::widgets::{Btn, Label}; use lvgl::{self, Align, Color, Part, State, Widget, UI}; @@ -24,7 +28,8 @@ use lvgl::{self, Align, Color, Part, State, Widget, UI}; use crate::monotonic_nrf52::Instant; use core::panic::PanicInfo; use core::time::Duration; -use embedded_hal::blocking::delay::DelayMs; +use embedded_hal::blocking::delay::{DelayMs, DelayUs}; +use embedded_hal::digital::v2::OutputPin; use nrf52832_hal::prelude::ClocksExt; pub type HalSpimError = p_hal::spim::Error; @@ -44,64 +49,99 @@ fn main() -> ! { // PineTime has a 32 MHz HSE (HFXO) and a 32.768 kHz LSE (LFXO) // Optimize clock config let dp = pac::Peripherals::take().unwrap(); + + let mut lcd_delay = delay::TimerDelay::new(dp.TIMER0); + + // Set up clocks. On reset, the high frequency clock is already used, + // but we also need to switch to the external HF oscillator. This is + // needed for Bluetooth to work. + + //let _clocks = p_hal::clocks::Clocks::new(dp.CLOCK).enable_ext_hfosc(); let _clockit = dp.CLOCK.constrain().enable_ext_hfosc(); - let port0 = dp.P0.split(); + //let gpio = p_hal::gpio::p0::Parts::new(dp.P0); + let gpio = dp.P0.split(); + // Set up SPI pins + let spi_clk = gpio.p0_02.into_push_pull_output(Level::Low).degrade(); + let spi_mosi = gpio.p0_03.into_push_pull_output(Level::Low).degrade(); + let spi_miso = gpio.p0_04.into_floating_input().degrade(); + let spi_pins = spim::Pins { + sck: spi_clk, + miso: Some(spi_miso), + mosi: Some(spi_mosi), + }; + + // Enable backlight + let mut backlight = backlight::Backlight::init( + gpio.p0_14.into_push_pull_output(Level::High).degrade(), + gpio.p0_22.into_push_pull_output(Level::High).degrade(), + gpio.p0_23.into_push_pull_output(Level::High).degrade(), + 1, + ); + backlight.set(5); + + delay_source.delay_ms(1u8); // internal i2c0 bus devices: BMA421 (accel), HRS3300 (hrs), CST816S (TouchPad) // BMA421-INT: P0.08 // TP-INT: P0.28 let i2c0_pins = twim::Pins { - scl: port0.p0_07.into_floating_input().degrade(), - sda: port0.p0_06.into_floating_input().degrade(), + scl: gpio.p0_07.into_floating_input().degrade(), + sda: gpio.p0_06.into_floating_input().degrade(), }; let i2c_port = twim::Twim::new(dp.TWIM1, i2c0_pins, twim::Frequency::K400); - // let i2c_bus0 = shared_bus::CortexMBusManager::new(i2c_port); - - delay_source.delay_ms(1u8); - - let spim0_pins = spim::Pins { - sck: port0.p0_02.into_push_pull_output(Level::Low).degrade(), - miso: None, - mosi: Some(port0.p0_03.into_push_pull_output(Level::Low).degrade()), - }; - - // create SPIM0 interface, 8 Mbps, use 122 as "over read character" - let spim0 = spim::Spim::new(dp.SPIM0, spim0_pins, spim::Frequency::M8, spim::MODE_3, 122); - let spi_bus0 = shared_bus::CortexMBusManager::new(spim0); - - // backlight control pin for display: always on - let mut _backlight = port0.p0_22.into_push_pull_output(Level::Low); - // SPI chip select (CSN) for the display. - let display_csn = port0.p0_25.into_push_pull_output(Level::High); - // data/clock switch pin for display - let display_dc = port0.p0_18.into_push_pull_output(Level::Low); - // reset pin for display - let display_rst = port0.p0_26.into_push_pull_output(Level::Low); - - // create display driver - let mut display = st7789::new_display_driver( - spi_bus0.acquire(), - display_csn, - display_dc, - display_rst, - lvgl::HOR_RES_MAX as u16, - lvgl::VER_RES_MAX as u16, - ); - display.init(&mut delay_source).unwrap(); - display.set_orientation(&Orientation::Portrait).unwrap(); // setup touchpad external interrupt pin: P0.28/AIN4 (TP_INT) - let touch_int = port0.p0_28.into_pullup_input().degrade(); + let touch_int = gpio.p0_28.into_pullup_input().degrade(); // setup touchpad reset pin: P0.10/NFC2 (TP_RESET) - let touch_rst = port0.p0_10.into_push_pull_output(Level::High).degrade(); + let touch_rst = gpio.p0_10.into_push_pull_output(Level::High).degrade(); let mut touchpad = CST816S::new(i2c_port, touch_int, touch_rst); touchpad.setup(&mut delay_source).unwrap(); + // Set up LCD pins + // LCD_CS (P0.25): Chip select + let mut lcd_cs = gpio.p0_25.into_push_pull_output(Level::Low); + // LCD_RS (P0.18): Data/clock pin + let lcd_dc = gpio.p0_18.into_push_pull_output(Level::Low); + // LCD_RESET (P0.26): Display reset + let lcd_rst = gpio.p0_26.into_push_pull_output(Level::Low); + + // Initialize SPI + let spi = spim::Spim::new( + dp.SPIM0, + spi_pins, + // Use SPI at 8MHz (the fastest clock available on the nRF52832) + // because otherwise refreshing will be super slow. + spim::Frequency::M8, + // SPI must be used in mode 3. Mode 0 (the default) won't work. + spim::MODE_3, + 0, + ); + let spi_bus0 = shared_bus::CortexMBusManager::new(spi); + + // Chip select must be held low while driving the display. It must be high + // when using other SPI devices on the same bus (such as external flash + // storage) so that the display controller won't respond to the wrong + // commands. + lcd_cs.set_low().unwrap(); + + // Initialize LCD + let mut lcd = st7789::ST7789::new( + spi_bus0.acquire(), + lcd_dc, + lcd_rst, + lvgl::HOR_RES_MAX as u16, + lvgl::VER_RES_MAX as u16, + lcd_delay, + ); + + lcd.init().unwrap(); + lcd.set_orientation(&Orientation::Portrait).unwrap(); + // Initialize LVGL let mut ui = UI::init().unwrap(); - ui.disp_drv_register(display).unwrap(); + ui.disp_drv_register(lcd).unwrap(); // Define the initial state of the input let mut latest_touch_point = Point::new(0, 0); @@ -131,11 +171,20 @@ fn main() -> ! { let mut btn_lbl = Label::new(&mut button).unwrap(); btn_lbl.set_text(text_click_me).unwrap(); - let mut btn_state = false; + let mut time_style = Style::default(); + time_style.set_text_color(State::DEFAULT, Color::from_rgb((255, 255, 255))); + + let mut time_lbl = Label::new(&mut screen).unwrap(); + time_lbl + .set_align(&mut button, Align::OutTopMid, 0, -50) + .unwrap(); + let time_text = CStr::from_bytes_with_nul("TIME\0".as_bytes()).unwrap(); + time_lbl.set_text(time_text).unwrap(); + time_lbl.add_style(Part::Main, time_style).unwrap(); + button .on_event(|mut btn, event| { if let lvgl::Event::Clicked = event { - btn_state = !btn_state; btn.toggle().unwrap(); } }) @@ -150,6 +199,13 @@ fn main() -> ! { latest_touch_status = InputData::Touch(latest_touch_point.clone()) .pressed() .once(); + + time_lbl + .set_text(CStr::from_bytes_with_nul("PRESSED\0".as_bytes()).unwrap()) + .unwrap(); + time_lbl + .set_align(&mut button, Align::OutTopMid, 0, -50) + .unwrap(); } else { // Released latest_touch_status = InputData::Touch(latest_touch_point.clone()) @@ -157,7 +213,7 @@ fn main() -> ! { .once(); } - ui.tick_inc(Duration::from_millis(50)); + ui.tick_inc(Duration::from_millis(100)); //delay_source.delay_ms(2u32); loop_start = Instant::now(); }