First working version
This commit is contained in:
commit
7ad549a6c9
|
@ -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"
|
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
|
@ -0,0 +1,80 @@
|
||||||
|
[package]
|
||||||
|
# TODO(1) fix `authors` and `name` if you didn't use `cargo-generate`
|
||||||
|
authors = ["sebastian <sebastian@sebastians-site.de>"]
|
||||||
|
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`" }
|
|
@ -0,0 +1,6 @@
|
||||||
|
/* Linker script for the STM32F103C8T6 */
|
||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
|
||||||
|
RAM : ORIGIN = 0x20000000, LENGTH = 20K
|
||||||
|
}
|
|
@ -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(_)) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Alternate<PushPull>>,
|
||||||
|
gpioa::PA6<Input<Floating>>,
|
||||||
|
gpioa::PA7<Alternate<PushPull>>,
|
||||||
|
),
|
||||||
|
>;
|
||||||
|
|
||||||
|
pub struct App {
|
||||||
|
delay: Delay,
|
||||||
|
board_led: gpioc::PC13<Output<PushPull>>,
|
||||||
|
serial: Serial<
|
||||||
|
stm32::USART3,
|
||||||
|
(
|
||||||
|
gpiob::PB10<Alternate<PushPull>>,
|
||||||
|
gpiob::PB11<Input<Floating>>,
|
||||||
|
),
|
||||||
|
>,
|
||||||
|
gps_parser: Parser,
|
||||||
|
|
||||||
|
spi: AppSPI,
|
||||||
|
disp_strobe: gpioa::PA0<Output<PushPull>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -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<RefCell<Time>> = Mutex::new(RefCell::new(Time {
|
||||||
|
hours: 0,
|
||||||
|
minutes: 0,
|
||||||
|
seconds: 0.0,
|
||||||
|
}));
|
||||||
|
|
||||||
|
static TIMER_TIM2: Mutex<RefCell<Option<CountDownTimer<TIM2>>>> = 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;
|
||||||
|
}
|
Loading…
Reference in New Issue