commit 7ad549a6c9ae8f37b028f9cd7b6a2a168afbdf85 Author: Sebastian Date: Sun Nov 7 15:38:22 2021 +0100 First working version diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..bf8dd40 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,19 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# TODO(2) replace `$CHIP` with your chip's name (see `probe-run --list-chips` output) +runner = "probe-run --chip STM32F103CB" +rustflags = [ + "-C", "linker=flip-link", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x + # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95 + "-C", "link-arg=--nmagic", +] + +[build] +target = "thumbv7m-none-eabi" # Cortex-M3 + + +[alias] +rb = "run --bin" +rrb = "run --release --bin" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..53d1602 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,80 @@ +[package] +# TODO(1) fix `authors` and `name` if you didn't use `cargo-generate` +authors = ["sebastian "] +name = "nixie-clock" +edition = "2018" +version = "0.1.0" + +[dependencies] +cortex-m = "~0.7.1" +cortex-m-rt = "~0.6.13" +defmt = "~0.2.0" +defmt-rtt = "~0.2.0" +panic-probe = { version = "~0.2.0", features = ["print-defmt"] } +stm32f1xx-hal = { version = "~0.6.1", features = ["stm32f103", "rt"] } +embedded-hal = {version = "~0.2.3"} +nb = "~1.0.0" +nmea0183 = "~0.2.3" +arrayvec = {version = "~0.7.0", default-features = false} + + +[features] +# set logging levels here +default = [ + "defmt-default", + # "dependency-a/defmt-trace", +] + +# do NOT modify these features +defmt-default = [] +defmt-trace = [] +defmt-debug = [] +defmt-info = [] +defmt-warn = [] +defmt-error = [] + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +#lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# 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] +# defmt = { git = "https://github.com/knurling-rs/defmt", rev = "use defmt version reported by `probe-run --version`" } +# defmt-rtt = { git = "https://github.com/knurling-rs/defmt", rev = "use defmt version reported by `probe-run --version`" } +# defmt-test = { git = "https://github.com/knurling-rs/defmt", rev = "use defmt version reported by `probe-run --version`" } +# panic-probe = { git = "https://github.com/knurling-rs/defmt", rev = "use defmt version reported by `probe-run --version`" } diff --git a/memory.x b/memory.x new file mode 100644 index 0000000..71f245d --- /dev/null +++ b/memory.x @@ -0,0 +1,6 @@ +/* Linker script for the STM32F103C8T6 */ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 64K + RAM : ORIGIN = 0x20000000, LENGTH = 20K +} diff --git a/src/application/gps.rs b/src/application/gps.rs new file mode 100644 index 0000000..3a7b355 --- /dev/null +++ b/src/application/gps.rs @@ -0,0 +1,26 @@ +use nmea0183::ParseResult; +use stm32f1xx_hal::prelude::*; + +use crate::application::App; +use crate::time; + +impl App { + pub fn poll_gps(&mut self) -> () { + match self.serial.read() { + Ok(byte) => { + self.board_led.toggle().unwrap(); + if let Some(result) = self.gps_parser.parse_from_byte(byte) { + match result { + Ok(ParseResult::GGA(Some(gga))) => { + time::set(&gga.time); + } + Ok(_) => {} // Some other sentences.. + Err(_) => {} // Got parse error + } + } + } + Err(nb::Error::WouldBlock) => {} + Err(nb::Error::Other(_)) => {} + } + } +} diff --git a/src/application/mod.rs b/src/application/mod.rs new file mode 100644 index 0000000..d166f2d --- /dev/null +++ b/src/application/mod.rs @@ -0,0 +1,103 @@ +use cortex_m::{asm::wfi, prelude::*}; +use embedded_hal::digital::v2::{OutputPin, ToggleableOutputPin}; +use nb::{self, block}; +use nmea0183::Parser; +use stm32f1xx_hal::{ + delay::Delay, + gpio::{gpioa, gpiob, gpioc, Alternate, Floating, Input, OpenDrain, Output, PushPull}, + i2c::BlockingI2c, + pac::I2C1, + pac::SPI1, + prelude::*, + serial::Serial, + spi::{Spi, Spi1NoRemap}, + stm32, +}; + +mod gps; +mod setup; + +//use crate::exit; +pub use setup::setup; + +use crate::time; + +type AppSPI = Spi< + SPI1, + Spi1NoRemap, + ( + gpioa::PA5>, + gpioa::PA6>, + gpioa::PA7>, + ), +>; + +pub struct App { + delay: Delay, + board_led: gpioc::PC13>, + serial: Serial< + stm32::USART3, + ( + gpiob::PB10>, + gpiob::PB11>, + ), + >, + gps_parser: Parser, + + spi: AppSPI, + disp_strobe: gpioa::PA0>, +} + +fn fendangle_digit(num: u8) -> u8 { + if num <= 9 { + (9 - num).reverse_bits() >> 4 + } else { + 15 + } +} + +impl App { + pub fn run(mut self) -> ! { + defmt::info!("Application Startup!"); + + loop { + self.poll_gps(); + + let time = time::get(); + + let hours_high = (time.hours as u8) / 10; + let hours_low = (time.hours as u8) % 10; + let minutes_high = (time.minutes as u8) / 10; + let minutes_low = (time.minutes as u8) % 10; + let seconds_high = (time.seconds as u8) / 10; + let seconds_low = (time.seconds as u8) % 10; + + defmt::info!( + "Time: {} {} : {} {} : {} {}", + hours_high, + hours_low, + minutes_high, + minutes_low, + seconds_high, + seconds_low + ); + + let hours_byte = fendangle_digit(hours_high) << 4 | fendangle_digit(hours_low); + let minutes_byte = fendangle_digit(minutes_high) << 4 | fendangle_digit(minutes_low); + let seconds_byte = fendangle_digit(seconds_high) << 4 | fendangle_digit(seconds_low); + + self.spi + .write(&[hours_byte, minutes_byte, hours_byte]) + .unwrap(); + + defmt::debug!("Bytes: {=[u8]:x}", [hours_byte, minutes_byte, hours_byte]); + + self.disp_strobe.set_high().unwrap(); + self.delay.delay_ms(10u16); + self.disp_strobe.set_low().unwrap(); + + self.board_led.toggle().unwrap(); + self.delay.delay_ms(240u16); + } + } +} diff --git a/src/application/setup.rs b/src/application/setup.rs new file mode 100644 index 0000000..c0a4731 --- /dev/null +++ b/src/application/setup.rs @@ -0,0 +1,94 @@ +use stm32f1xx_hal::{ + delay::Delay, + pac::Interrupt, + prelude::*, + serial::{Config, Serial}, + spi::{Mode, Phase, Polarity, Spi}, + stm32, + timer::{Event, Timer}, +}; + +use nmea0183::Parser; + +use crate::application::App; +use crate::time; + +pub fn setup(cp: cortex_m::peripheral::Peripherals, dp: stm32::Peripherals) -> App { + // Take ownership over the raw flash and rcc devices and convert them into the corresponding + // HAL structs + let mut flash = dp.FLASH.constrain(); + + let mut rcc = dp.RCC.constrain(); + + // Freeze the configuration of all the clocks in the system and store the frozen frequencies in + // `clocks` + let clocks = rcc + .cfgr + .use_hse(8.mhz()) + .sysclk(72.mhz()) + .pclk1(36.mhz()) + .freeze(&mut flash.acr); + + defmt::info!("Clock Setup done"); + + // Acquire the GPIOC peripheral + let mut gpioa = dp.GPIOA.split(&mut rcc.apb2); + let mut gpiob = dp.GPIOB.split(&mut rcc.apb2); + let mut gpioc = dp.GPIOC.split(&mut rcc.apb2); + + let mut afio = dp.AFIO.constrain(&mut rcc.apb2); + + let board_led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh); + + let delay = Delay::new(cp.SYST, clocks); + + // USART3 + // Configure pb10 as a push_pull output, this will be the tx pin + let tx = gpiob.pb10.into_alternate_push_pull(&mut gpiob.crh); + // Take ownership over pb11 + let rx = gpiob.pb11; + + // Set up the usart device. Taks ownership over the USART register and tx/rx pins. The rest of + // the registers are used to enable and configure the device. + let serial = Serial::usart3( + dp.USART3, + (tx, rx), + &mut afio.mapr, + Config::default().baudrate(9600.bps()), + clocks, + &mut rcc.apb1, + ); + + let gps_parser = Parser::new(); + + // SPI1 + let sck = gpioa.pa5.into_alternate_push_pull(&mut gpioa.crl); + let miso = gpioa.pa6; + let mosi = gpioa.pa7.into_alternate_push_pull(&mut gpioa.crl); + + let spi = Spi::spi1( + dp.SPI1, + (sck, miso, mosi), + &mut afio.mapr, + Mode { + polarity: Polarity::IdleLow, + phase: Phase::CaptureOnFirstTransition, + }, + 1.khz(), + clocks, + &mut rcc.apb2, + ); + + let disp_strobe = gpioa.pa0.into_push_pull_output(&mut gpioa.crl); + + time::setup(dp.TIM2, &clocks, &mut rcc.apb1); + + App { + delay, + board_led, + serial, + gps_parser, + spi, + disp_strobe, + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2d2b88f --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,27 @@ +#![no_std] +use defmt_rtt as _; // global logger + +use panic_probe as _; +use stm32f1xx_hal as _; + +pub mod application; +pub mod time; + +// 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] +fn panic() -> ! { + cortex_m::asm::udf() +} + +defmt::timestamp!("{=u32}", { + // NOTE(no-CAS) `timestamps` runs with interrupts disabled + time::get_timestamp() +}); + +/// Terminates the application and makes `probe-run` exit with exit-code = 0 +pub fn exit() -> ! { + loop { + cortex_m::asm::bkpt(); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..1c37002 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,17 @@ +#![no_std] +#![no_main] + +use stm32f1xx_hal::pac; + +use nixie_clock::application; + +#[cortex_m_rt::entry] +fn main() -> ! { + // Get access to the core peripherals from the cortex-m crate + let cp = cortex_m::Peripherals::take().unwrap(); + // Get access to the device specific peripherals from the peripheral access crate + let dp = pac::Peripherals::take().unwrap(); + + let app = application::setup(cp, dp); + app.run() +} diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..c974f6d --- /dev/null +++ b/src/time.rs @@ -0,0 +1,104 @@ +use core::cell::RefCell; +use core::ops::DerefMut; +use cortex_m::interrupt::{free, Mutex}; +use stm32f1xx_hal::{ + delay::Delay, + pac::interrupt, + pac::{Interrupt, TIM2}, + prelude::*, + rcc::{Clocks, APB1}, + serial::{Config, Serial}, + stm32, + timer::{CountDownTimer, Event, Timer}, +}; + +use nmea0183::datetime::Time; + +static TIME: Mutex> = Mutex::new(RefCell::new(Time { + hours: 0, + minutes: 0, + seconds: 0.0, +})); + +static TIMER_TIM2: Mutex>>> = Mutex::new(RefCell::new(None)); + +pub fn setup(tim2: TIM2, clocks: &Clocks, apb1: &mut APB1) { + let mut timer = Timer::tim2(tim2, clocks, apb1).start_count_down(1.khz()); + // Generate an interrupt when the timer expires + timer.listen(Event::Update); + // Move the timer into our global storage + cortex_m::interrupt::free(|cs| *TIMER_TIM2.borrow(cs).borrow_mut() = Some(timer)); + + unsafe { + cortex_m::peripheral::NVIC::unmask(Interrupt::TIM2); + } +} + +#[interrupt] +fn TIM2() { + free(|cs| { + if let Some(ref mut tim2) = TIMER_TIM2.borrow(cs).borrow_mut().deref_mut() { + tim2.clear_update_interrupt_flag(); + } + + let mut time = TIME.borrow(cs).borrow_mut(); + time.seconds += 0.001; + + if time.seconds >= 60.0 { + time.seconds -= 60.0; + time.minutes += 1; + } + if time.minutes == 60 { + time.minutes = 0; + time.hours += 1; + } + if time.hours == 24 { + time.hours = 0; + } + }); +} + +pub fn get() -> Time { + let mut res = Time { + hours: 0, + minutes: 0, + seconds: 0.0, + }; + + free(|cs| { + let time = TIME.borrow(cs).borrow(); + res.seconds = time.seconds; + res.minutes = time.minutes; + res.hours = time.hours; + }); + + return res; +} + +pub fn set(new_time: &Time) -> () { + free(|cs| { + let mut time = TIME.borrow(cs).borrow_mut(); + let sec_delta = time.seconds - new_time.seconds; + if time.hours != new_time.hours + || time.minutes != new_time.minutes + || sec_delta > 0.05 + || sec_delta < -0.05 + { + time.seconds = new_time.seconds; + time.minutes = new_time.minutes; + time.hours = new_time.hours; + } + }); +} + +pub fn get_timestamp() -> u32 { + let mut res = 0; + free(|cs| { + let time = TIME.borrow(cs).borrow(); + res = (time.seconds * 1000.0) as u32; + res += (time.minutes as u32) * 60 * 1000; + res += (time.hours as u32) * 60 * 60 * 1000; + }); + + return res; +}