From cf671c41ca1ba300b6d3fd9db92a5c2593ac16ce Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Sat, 2 Sep 2023 18:35:59 +0200 Subject: [PATCH] feat: add HRS --- firmware/Cargo.lock | 11 +++++++- firmware/Cargo.toml | 3 +++ firmware/app/Cargo.toml | 3 ++- firmware/app/src/main.rs | 54 +++++++++++++++++++++++++++++++++++----- watchful-ui/src/lib.rs | 54 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 117 insertions(+), 8 deletions(-) diff --git a/firmware/Cargo.lock b/firmware/Cargo.lock index 2c7488e..04f1353 100644 --- a/firmware/Cargo.lock +++ b/firmware/Cargo.lock @@ -823,6 +823,14 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "hrs3300" +version = "0.1.0" +source = "git+https://github.com/lulf/hrs3300-rs.git?branch=hal-1.0#5f9cb751538df4db662071af36ab5f70553c2fda" +dependencies = [ + "embedded-hal 1.0.0-rc.1", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1474,7 +1482,7 @@ dependencies = [ [[package]] name = "watchful" -version = "0.2.2" +version = "0.2.4" dependencies = [ "byte-slice-cast 1.2.2", "cortex-m 0.7.7", @@ -1500,6 +1508,7 @@ dependencies = [ "embedded-text", "futures", "heapless", + "hrs3300", "mipidsi", "nrf-dfu-target", "nrf-softdevice", diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml index cf513c1..b4ed6c2 100644 --- a/firmware/Cargo.toml +++ b/firmware/Cargo.toml @@ -16,6 +16,8 @@ embassy-boot = { git = "https://github.com/embassy-rs/embassy.git", branch = "ma embassy-boot-nrf = { git = "https://github.com/embassy-rs/embassy.git", branch = "main" } embassy-futures = { git = "https://github.com/embassy-rs/embassy.git", branch = "main" } +hrs3300 = { git = "https://github.com/lulf/hrs3300-rs.git", branch = "hal-1.0" } + #embassy-executor = { path = "../../embassy/embassy-executor" } #embassy-time = {path = "../../embassy/embassy-time"} #embassy-sync = {path = "../../embassy/embassy-sync"} @@ -33,6 +35,7 @@ nrf-softdevice-s132 = { git = "https://github.com/embassy-rs/nrf-softdevice.git" #nrf-softdevice = { path = "../../nrf-softdevice/nrf-softdevice"} #nrf-softdevice-mbr = { path = "../../nrf-softdevice/nrf-softdevice-mbr"} #nrf-softdevice-s132 = { path = "../../nrf-softdevice/nrf-softdevice-s132"} +# [profile.release] debug = 2 diff --git a/firmware/app/Cargo.toml b/firmware/app/Cargo.toml index 94a6597..0325c2b 100644 --- a/firmware/app/Cargo.toml +++ b/firmware/app/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "watchful" -version = "0.2.3" +version = "0.2.4" license = "MIT OR Apache-2.0" build = "build.rs" @@ -23,6 +23,7 @@ nrf-dfu-target = { version = "0.1.0", features = ["defmt"] } pinetime-flash = { version = "0.1.0", path = "../../pinetime-flash", features = ["defmt"] } watchful-ui = { version = "0.1.0", path = "../../watchful-ui", features = ["defmt"] } cst816s = "0.1.4" +hrs3300 = { version = "0.1.0" } nrf-softdevice = { version = "0.1.0", features = ["nightly", "defmt", "nrf52832", "s132", "ble-gatt-server", "ble-gatt-client", "ble-peripheral", "critical-section-impl", "evt-max-size-256"] } nrf-softdevice-s132 = { version = "0.1.1" } diff --git a/firmware/app/src/main.rs b/firmware/app/src/main.rs index a63bc12..e60a6c2 100644 --- a/firmware/app/src/main.rs +++ b/firmware/app/src/main.rs @@ -9,6 +9,7 @@ use defmt_rtt as _; use embassy_boot::State as FwState; use embassy_boot_nrf::{AlignedBuffer, FirmwareState}; use embassy_embedded_hal::flash::partition::{BlockingPartition, Partition}; +use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice; use embassy_executor::Spawner; use embassy_futures::select::{select, select3, Either, Either3}; @@ -17,6 +18,7 @@ use embassy_nrf::interrupt::Priority; use embassy_nrf::peripherals::{P0_05, P0_10, P0_18, P0_25, P0_26, P0_28, TWISPI0, TWISPI1}; use embassy_nrf::spim::Spim; use embassy_nrf::spis::MODE_3; +use embassy_nrf::twim::Twim; use embassy_nrf::{bind_interrupts, pac, peripherals, saadc, spim, twim}; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::blocking_mutex::Mutex as BMutex; @@ -34,7 +36,7 @@ use nrf_softdevice::{raw, Softdevice}; use panic_probe as _; use pinetime_flash::XtFlash; use static_cell::StaticCell; -use watchful_ui::{FirmwareDetails, MenuAction, MenuView, TimeView}; +use watchful_ui::{FirmwareDetails, MenuAction, MenuView, TimeView, WorkoutView}; mod clock; mod display_interface; @@ -69,7 +71,12 @@ type Display = mipidsi::Display< type InternalFlash = nrf_softdevice::Flash; type StatePartition<'a> = Partition<'a, NoopRawMutex, InternalFlash>; type DfuPartition<'a> = BlockingPartition<'a, NoopRawMutex, ExternalFlash>; -type Touchpad<'a> = cst816s::CST816S, Input<'a, P0_28>, Output<'a, P0_10>>; +type Touchpad<'a> = + cst816s::CST816S>, Input<'a, P0_28>, Output<'a, P0_10>>; +type Hrs<'a> = hrs3300::Hrs3300>>; + +static I2C_BUS: StaticCell>>> = StaticCell::new(); +static SPI_BUS: StaticCell>>> = StaticCell::new(); use core::panic::PanicInfo; @@ -111,11 +118,17 @@ async fn main(s: Spawner) { let mut twim_config = twim::Config::default(); twim_config.frequency = twim::Frequency::K400; let i2c = twim::Twim::new(p.TWISPI1, Irqs, p.P0_06, p.P0_07, twim_config); + let i2c_bus = I2C_BUS.init(BMutex::new(RefCell::new(i2c))); + + let i2c = I2cDevice::new(i2c_bus); + let mut hrs = Hrs::new(i2c); + // setup touchpad external interrupt pin: P0.28/AIN4 (TP_INT) let touch_int = Input::new(p.P0_28, Pull::Up); // setup touchpad reset pin: P0.10/NFC2 (TP_RESET) let touch_rst = Output::new(p.P0_10, Level::High, OutputDrive::Standard); + let i2c = I2cDevice::new(i2c_bus); let mut touchpad = cst816s::CST816S::new(i2c, touch_int, touch_rst); touchpad.setup(&mut embassy_time::Delay).unwrap(); @@ -129,7 +142,6 @@ async fn main(s: Spawner) { default_config.mode = MODE_3; let spim = spim::Spim::new(p.TWISPI0, Irqs, p.P0_02, p.P0_04, p.P0_03, default_config); - static SPI_BUS: StaticCell>>> = StaticCell::new(); let spi_bus = SPI_BUS.init(BMutex::new(RefCell::new(spim))); // Create flash device @@ -170,7 +182,7 @@ async fn main(s: Spawner) { let mut state = WatchState::Idle; loop { let next = state - .process(&mut screen, &mut btn, &mut battery, &mut fw, &mut touchpad) + .process(&mut screen, &mut btn, &mut battery, &mut fw, &mut touchpad, &mut hrs) .await; defmt::info!("{:?} -> {:?}", state, next); state = next; @@ -266,7 +278,7 @@ impl<'a> WatchState<'a> { Either3::Third(selected) => match selected { MenuAction::Workout => { defmt::info!("Not implemented"); - WatchState::TimeView + WatchState::WorkoutView } MenuAction::FindPhone => { defmt::info!("Not implemented"); @@ -295,6 +307,33 @@ impl<'a> WatchState<'a> { } } + pub async fn workout(&mut self, screen: &mut Screen, button: &mut Button, hrs: &mut Hrs<'_>) -> WatchState<'a> { + hrs.init().unwrap(); + hrs.enable_hrs().unwrap(); + hrs.enable_oscillator().unwrap(); + + let mut seconds = 0; + let workout = async { + loop { + let hr = hrs.read_hrs().unwrap(); + WorkoutView::new(hr, time::Duration::new(seconds, 0)) + .draw(screen.display()) + .unwrap(); + screen.on(); + Timer::after(Duration::from_secs(2)).await; + seconds += 2; + } + }; + + let next = match select(button.wait(), workout).await { + Either::First(_) => WatchState::MenuView(MenuView::main()), + Either::Second(state) => state, + }; + hrs.disable_oscillator().unwrap(); + hrs.disable_hrs().unwrap(); + next + } + pub async fn process( &mut self, screen: &mut Screen, @@ -302,10 +341,12 @@ impl<'a> WatchState<'a> { battery: &mut Battery<'_>, fw: &mut FirmwareState<'_, StatePartition<'_>>, touchpad: &mut Touchpad<'_>, + hrs: &mut Hrs<'_>, ) -> WatchState<'a> { match self { WatchState::Idle => self.idle(screen, button).await, WatchState::TimeView => self.time(screen, button, battery).await, + WatchState::WorkoutView => self.workout(screen, button, hrs).await, WatchState::MenuView(mut menu) => self.menu(screen, button, battery, fw, touchpad, &mut menu).await, } } @@ -360,7 +401,7 @@ pub enum WatchState<'a> { TimeView, MenuView(MenuView<'a>), // FindPhone, - // Workout, + WorkoutView, } impl<'a> defmt::Format for WatchState<'a> { @@ -369,6 +410,7 @@ impl<'a> defmt::Format for WatchState<'a> { Self::Idle => defmt::write!(fmt, "Idle"), Self::TimeView => defmt::write!(fmt, "TimeView"), Self::MenuView(_) => defmt::write!(fmt, "MenuView"), + Self::WorkoutView => defmt::write!(fmt, "WorkoutView"), } } } diff --git a/watchful-ui/src/lib.rs b/watchful-ui/src/lib.rs index 9e5ef5a..9087c1a 100644 --- a/watchful-ui/src/lib.rs +++ b/watchful-ui/src/lib.rs @@ -132,6 +132,60 @@ impl TimeView { } } +pub struct WorkoutView { + hr: u32, + duration: time::Duration, +} + +impl WorkoutView { + pub fn new(hr: u32, duration: time::Duration) -> Self { + Self { hr, duration } + } + pub fn draw>(&self, display: &mut D) -> Result<(), D::Error> { + display.clear(Rgb::BLACK)?; + + let mut buf: heapless::String<16> = heapless::String::new(); + write!(buf, "{:03}", self.hr).unwrap(); + let hr = Text::with_text_style( + &buf, + display.bounding_box().center(), + watch_text_style(Rgb::CSS_DARK_CYAN), + TextStyleBuilder::new() + .alignment(embedded_graphics::text::Alignment::Center) + .baseline(embedded_graphics::text::Baseline::Alphabetic) + .build(), + ); + + let mut buf: heapless::String<16> = heapless::String::new(); + write!( + buf, + "{:03}:{:02}", + self.duration.whole_minutes(), + self.duration.whole_seconds() + ) + .unwrap(); + let secs = Text::with_text_style( + &buf, + display.bounding_box().center(), + date_text_style(Rgb::CSS_DARK_CYAN), + TextStyleBuilder::new() + .alignment(embedded_graphics::text::Alignment::Center) + .baseline(embedded_graphics::text::Baseline::Alphabetic) + .build(), + ); + + let display_area = display.bounding_box(); + LinearLayout::vertical(Chain::new(hr).append(secs)) + .with_spacing(spacing::FixedMargin(10)) + .with_alignment(horizontal::Center) + .arrange() + .align_to(&display_area, horizontal::Center, vertical::Center) + .draw(display)?; + + Ok(()) + } +} + #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum MenuAction {