pinetime-rs/src/main.rs

243 lines
7.9 KiB
Rust

#![no_std]
#![no_main]
extern crate panic_halt;
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 cortex_m_rt as rt;
use cortex_m_semihosting::hprintln;
use cst816s::CST816S;
use cstr_core::CStr;
use embedded_graphics::prelude::*;
use embedded_hal::blocking::delay::{DelayMs, DelayUs};
use embedded_hal::digital::v2::OutputPin;
use numtoa::NumToA;
use rt::entry;
use st7789::{Orientation, ST7789};
use lvgl::input_device::{InputData, Pointer};
use lvgl::style::Style;
use lvgl::widgets::{Btn, Label};
use lvgl::{self, Align, Color, Part, State, Widget, UI};
use crate::monotonic_nrf52::Instant;
use core::panic::PanicInfo;
use core::time::Duration;
use display_interface_spi::SPIInterfaceNoCS;
///
/// This example was written and tested for the PineTime smart watch
///
#[entry]
fn main() -> ! {
hprintln!("\nStarting...").unwrap();
let cp = pac::CorePeripherals::take().unwrap();
let mut delay_source = Delay::new(cp.SYST);
// PineTime has a 32 MHz HSE (HFXO) and a 32.768 kHz LSE (LFXO)
// Optimize clock config
let dp = pac::Peripherals::take().unwrap();
//let lcd_delay = delay::TimerDelay::new(dp.TIMER0);
// Initialize monotonic timer on TIMER1 (for RTFM)
monotonic_nrf52::Tim1::initialize(dp.TIMER1);
// 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 gpio = p_hal::gpio::p0::Parts::new(dp.P0);
// 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_pins = spim::Pins {
sck: spi_clk,
miso: None,
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: 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);
// setup touchpad external interrupt pin: P0.28/AIN4 (TP_INT)
let touch_int = gpio.p0_28.into_pullup_input().degrade();
// setup touchpad reset pin: P0.10/NFC2 (TP_RESET)
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,
122,
);
// display interface abstraction from SPI and DC
let di = SPIInterfaceNoCS::new(spi, lcd_dc);
// 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::new(
di,
lcd_rst,
lvgl::HOR_RES_MAX as u16,
lvgl::VER_RES_MAX as u16,
);
lcd.init(&mut delay_source).unwrap();
lcd.set_orientation(Orientation::Portrait).unwrap();
hprintln!("\nAll devices set!").unwrap();
// Initialize LVGL
let mut ui = UI::init().unwrap();
ui.disp_drv_register(lcd).unwrap();
// Define the initial state of the input
let mut latest_touch_point = Point::new(0, 0);
let mut latest_touch_status = InputData::Touch(latest_touch_point.clone())
.released()
.once();
// Register a new input device that's capable of reading the current state of the input
let mut touch_device = Pointer::new(|| latest_touch_status);
ui.indev_drv_register(&mut touch_device).unwrap();
// Create screen and widgets
let mut screen = ui.scr_act().unwrap();
// Draw a black background to the screen
let mut screen_style = Style::default();
screen_style.set_bg_color(State::DEFAULT, Color::from_rgb((0, 0, 0)));
screen.add_style(Part::Main, screen_style).unwrap();
// Create the button
let text_click_me = CStr::from_bytes_with_nul("Click me!\0".as_bytes()).unwrap();
let mut button = Btn::new(&mut screen).unwrap();
button
.set_align(&mut screen, Align::InLeftMid, 30, 0)
.unwrap();
button.set_size(180, 80).unwrap();
let mut btn_lbl = Label::new(&mut button).unwrap();
btn_lbl.set_text(text_click_me).unwrap();
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.toggle().unwrap();
}
})
.unwrap();
let mut loop_time = Instant::now();
let mut total_time = Duration::from_secs(0);
let mut time_text_buf = [0u8; 20];
let mut last_update_time_secs = 0;
let mut last_inc_time_ms = 0;
hprintln!("\nLVGL widgets built!").unwrap();
loop {
ui.task_handler();
if let Some(evt) = touchpad.read_one_touch_event(true) {
latest_touch_point = Point::new(evt.x, evt.y);
// Pressed
latest_touch_status = InputData::Touch(latest_touch_point.clone())
.pressed()
.once();
} else {
// Released
latest_touch_status = InputData::Touch(latest_touch_point.clone())
.released()
.once();
delay_source.delay_us(1u32);
}
total_time += Duration::from_micros(loop_time.elapsed().as_cycles() as u64);
if total_time.as_secs() > last_update_time_secs {
last_update_time_secs = total_time.as_secs();
let text = (total_time.as_secs() as u32).numtoa(10, &mut time_text_buf);
let time_text = unsafe { CStr::from_bytes_with_nul_unchecked(&text) };
time_lbl.set_text(time_text).unwrap();
time_lbl
.set_align(&mut button, Align::OutTopMid, 0, -50)
.unwrap();
// Reset buffer
for p in time_text_buf.iter_mut() {
*p = '\0' as u8;
}
}
if total_time.as_millis() > last_inc_time_ms {
//let diff_ms = total_time.as_millis() - last_inc_time_ms;
last_inc_time_ms = total_time.as_millis();
}
ui.tick_inc(Duration::from_millis(50));
loop_time = Instant::now();
}
}