243 lines
7.9 KiB
Rust
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();
|
|
}
|
|
}
|