Skip to content

Commit 6d9e79e

Browse files
committed
Implement BIOS API to set new date/time.
1 parent 52e1ddb commit 6d9e79e

File tree

2 files changed

+145
-33
lines changed

2 files changed

+145
-33
lines changed

src/main.rs

Lines changed: 99 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ use cortex_m_rt::entry;
6565
use critical_section::Mutex;
6666
use defmt::info;
6767
use defmt_rtt as _;
68-
use ds1307::{Datelike, Timelike};
68+
use ds1307::{Datelike, NaiveDateTime, Timelike};
6969
use embedded_hal::{
7070
blocking::spi::{Transfer as _SpiTransfer, Write as _SpiWrite},
7171
digital::v2::{InputPin, OutputPin},
@@ -97,6 +97,9 @@ use neotron_common_bios as common;
9797
// Types
9898
// -----------------------------------------------------------------------------
9999

100+
/// What we count our system ticks in
101+
type Duration = fugit::Duration<u64, 1, 1_000_000>;
102+
100103
/// The type of our IRQ input pin from the MCP23S17.
101104
type IrqPin = Pin<bank0::Gpio20, Input<PullUp>>;
102105

@@ -130,8 +133,10 @@ struct Hardware {
130133
irq_count: u32,
131134
/// Our I2C Bus
132135
i2c: shared_bus::BusManagerSimple<hal::i2c::I2C<pac::I2C1, I2cPins, hal::i2c::Controller>>,
133-
/// Our RTC
136+
/// Our External RTC (on the I2C bus)
134137
rtc: rtc::Rtc,
138+
/// The time we started up at, in microseconds since the Neotron epoch
139+
bootup_at: Duration,
135140
/// A Timer
136141
timer: hal::timer::Timer,
137142
}
@@ -269,6 +274,9 @@ static API_CALLS: common::Api = common::Api {
269274
power_idle,
270275
};
271276

277+
/// Seconds between the Neotron Epoch (2000-011-011T00:00:00) and the UNIX Epoch (1970-01-01T00:00:00).
278+
const SECONDS_BETWEEN_UNIX_AND_NEOTRON_EPOCH: i64 = 946684800;
279+
272280
extern "C" {
273281
static mut _flash_os_start: u32;
274282
static mut _flash_os_len: u32;
@@ -381,31 +389,15 @@ fn main() -> ! {
381389
pp.IO_BANK0,
382390
pp.PADS_BANK0,
383391
sio.gpio_bank0,
384-
&mut pp.RESETS,
385392
pp.SPI0,
386393
pp.I2C1,
394+
pp.TIMER,
387395
clocks,
388396
delay,
389-
pp.TIMER,
397+
&mut pp.RESETS,
390398
);
391399
hw.init_io_chip();
392400
hw.set_hdd_led(false);
393-
match hw.rtc.get_time(hw.i2c.acquire_i2c()) {
394-
Ok(time) => {
395-
defmt::info!(
396-
"Time: {:04}-{:02}-{:02} {:02}:{:02}:{:02}",
397-
time.year(),
398-
time.month(),
399-
time.day(),
400-
time.hour(),
401-
time.minute(),
402-
time.second()
403-
);
404-
}
405-
Err(_) => {
406-
defmt::info!("Time: Unknown");
407-
}
408-
}
409401

410402
nirq_io.set_interrupt_enabled(hal::gpio::Interrupt::EdgeLow, true);
411403
nirq_io.set_interrupt_enabled(hal::gpio::Interrupt::EdgeHigh, true);
@@ -511,12 +503,12 @@ impl Hardware {
511503
bank: pac::IO_BANK0,
512504
pads: pac::PADS_BANK0,
513505
sio: hal::sio::SioGpioBank0,
514-
resets: &mut pac::RESETS,
515506
spi: pac::SPI0,
516507
i2c: pac::I2C1,
508+
timer: pac::TIMER,
517509
clocks: ClocksManager,
518510
delay: cortex_m::delay::Delay,
519-
timer: pac::TIMER,
511+
resets: &mut pac::RESETS,
520512
) -> (Hardware, IrqPin) {
521513
let hal_pins = rp_pico::Pins::new(bank, pads, sio, resets);
522514
// We construct the pin here and then throw it away. Then Core 1 does
@@ -544,8 +536,30 @@ impl Hardware {
544536
);
545537
let i2c = shared_bus::BusManagerSimple::new(raw_i2c);
546538
let proxy = i2c.acquire_i2c();
547-
let rtc = rtc::Rtc::new(proxy);
539+
let mut external_rtc = rtc::Rtc::new(proxy);
548540
let timer = hal::timer::Timer::new(timer, resets);
541+
// Do a conversion from external RTC time (chrono::NaiveDateTime) to a format we can track
542+
let ticks_at_boot_us = match external_rtc.get_time(i2c.acquire_i2c()) {
543+
Ok(time) => {
544+
defmt::info!(
545+
"Time: {:04}-{:02}-{:02} {:02}:{:02}:{:02}",
546+
time.year(),
547+
time.month(),
548+
time.day(),
549+
time.hour(),
550+
time.minute(),
551+
time.second()
552+
);
553+
let ticks_at_boot_us =
554+
time.timestamp_micros() - (SECONDS_BETWEEN_UNIX_AND_NEOTRON_EPOCH * 1_000_000);
555+
defmt::info!("Ticks at boot: {}", ticks_at_boot_us);
556+
ticks_at_boot_us
557+
}
558+
Err(_) => {
559+
defmt::info!("Time: Unknown");
560+
0
561+
}
562+
};
549563

550564
(
551565
Hardware {
@@ -695,7 +709,8 @@ impl Hardware {
695709
interrupts_pending: 0,
696710
irq_count: 0,
697711
i2c,
698-
rtc,
712+
rtc: external_rtc,
713+
bootup_at: Duration::from_ticks(ticks_at_boot_us as u64),
699714
timer,
700715
},
701716
hal_pins.gpio20.into_pull_up_input(),
@@ -890,7 +905,7 @@ impl Hardware {
890905
if INTERRUPT_PENDING.load(Ordering::Relaxed) {
891906
// The IO chip reports 0 for pending, 1 for not pending.
892907
self.interrupts_pending = self.io_read_interrupts() ^ 0xFF;
893-
defmt::info!("MCP23S17 IRQ pins: 0b{:08b}", self.interrupts_pending);
908+
defmt::debug!("MCP23S17 IRQ pins: 0b{:08b}", self.interrupts_pending);
894909
// Change the debug LEDs so we can see the interrupts
895910
self.irq_count = self.irq_count.wrapping_add(1);
896911
self.set_debug_leds((self.irq_count & 0x0F) as u8);
@@ -1285,8 +1300,22 @@ pub extern "C" fn serial_read(
12851300
/// If the BIOS does not have a battery-backed clock, or if that battery has
12861301
/// failed to keep time, the system starts up assuming it is the epoch.
12871302
pub extern "C" fn time_clock_get() -> common::Time {
1288-
// TODO: Read from the MCP7940N
1289-
common::Time { secs: 0, nsecs: 0 }
1303+
// We track real-time using the RP2040 1 MHz timer. We noted the wall-time at boot-up.
1304+
1305+
let ticks_since_epoch = critical_section::with(|cs| {
1306+
let mut lock = HARDWARE.borrow_ref_mut(cs);
1307+
let hw = lock.as_mut().unwrap();
1308+
// We can unwrap this because we set the day-of-week correctly on startup
1309+
let ticks_since_boot = hw.timer.get_counter().duration_since_epoch();
1310+
ticks_since_boot + hw.bootup_at
1311+
});
1312+
1313+
let nanos_since_epoch = ticks_since_epoch.to_nanos();
1314+
1315+
let secs = (nanos_since_epoch / 1_000_000_000) as u32;
1316+
let nsecs = (nanos_since_epoch % 1_000_000_000) as u32;
1317+
defmt::info!("Time is {}.{:09}s", secs, nsecs);
1318+
common::Time { secs, nsecs }
12901319
}
12911320

12921321
/// Set the current wall time.
@@ -1298,8 +1327,49 @@ pub extern "C" fn time_clock_get() -> common::Time {
12981327
/// time (e.g. the user has updated the current time, or if you get a GPS
12991328
/// fix). The BIOS should push the time out to the battery-backed Real
13001329
/// Time Clock, if it has one.
1301-
pub extern "C" fn time_clock_set(_time: common::Time) {
1302-
// TODO: Update the MCP7940N RTC
1330+
pub extern "C" fn time_clock_set(time: common::Time) {
1331+
critical_section::with(|cs| {
1332+
let mut lock = HARDWARE.borrow_ref_mut(cs);
1333+
let hw = lock.as_mut().unwrap();
1334+
1335+
// 1. How long have we been running, in 1 MHz ticks?
1336+
let ticks_since_boot = hw.timer.get_counter().duration_since_epoch();
1337+
1338+
// 2. What is the given time as 1 MHz ticks since the epoch?
1339+
let ticks_since_epoch = u64::from(time.secs) * 1_000_000 + u64::from(time.nsecs / 1000);
1340+
let ticks_since_epoch = Duration::from_ticks(ticks_since_epoch);
1341+
1342+
// 3. Work backwards, and find the time it must have been when we booted up.
1343+
let ticks_at_boot = ticks_since_epoch
1344+
.checked_sub(ticks_since_boot)
1345+
.unwrap_or_else(|| Duration::from_ticks(0));
1346+
1347+
// 4. Store that value
1348+
defmt::info!("Ticks at boot: {}", ticks_at_boot.ticks());
1349+
hw.bootup_at = ticks_at_boot;
1350+
1351+
// 5. Convert to calendar time
1352+
if let Some(new_time) = NaiveDateTime::from_timestamp_opt(
1353+
i64::from(time.secs) + SECONDS_BETWEEN_UNIX_AND_NEOTRON_EPOCH,
1354+
time.nsecs,
1355+
) {
1356+
// 6. Update the hardware RTC as well
1357+
match hw.rtc.set_time(hw.i2c.acquire_i2c(), new_time) {
1358+
Ok(_) => {
1359+
defmt::info!("Time set in RTC OK");
1360+
}
1361+
Err(rtc::Error::BusError(_)) => {
1362+
defmt::warn!("Failed to talk to RTC to set time");
1363+
}
1364+
Err(rtc::Error::DriverBug) => {
1365+
defmt::warn!("RTC driver failed");
1366+
}
1367+
Err(rtc::Error::NoRtcFound) => {
1368+
defmt::info!("Not setting time - no RTC found");
1369+
}
1370+
}
1371+
}
1372+
});
13031373
}
13041374

13051375
/// Get the configuration data block.

src/rtc.rs

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ pub enum Rtc {
1010
None,
1111
}
1212

13+
/// The ways this module can fail
14+
pub enum Error<E> {
15+
BusError(E),
16+
DriverBug,
17+
NoRtcFound,
18+
}
19+
1320
impl Rtc {
1421
/// Create a new RTC object.
1522
///
@@ -60,21 +67,56 @@ impl Rtc {
6067
/// We don't care about timezones - this is basic Gregorian naive wall-clock date/time.
6168
///
6269
/// You get `Err()` if the RTC doesn't respond or wasn't found on start-up.
63-
pub fn get_time<T, E>(&self, bus: T) -> Result<NaiveDateTime, ()>
70+
pub fn get_time<T, E>(&mut self, bus: T) -> Result<NaiveDateTime, Error<E>>
71+
where
72+
T: embedded_hal::blocking::i2c::WriteRead<Error = E>
73+
+ embedded_hal::blocking::i2c::Write<Error = E>,
74+
{
75+
match self {
76+
Self::Ds1307 => {
77+
let mut driver = ds1307::Ds1307::new(bus);
78+
driver.datetime().map_err(|e| match e {
79+
ds1307::Error::I2C(bus_error) => Error::BusError(bus_error),
80+
ds1307::Error::InvalidInputData => Error::DriverBug,
81+
})
82+
}
83+
Self::Mcp7940n => {
84+
let mut driver = mcp794xx::Mcp794xx::new_mcp7940n(bus);
85+
driver.datetime().map_err(|e| match e {
86+
mcp794xx::Error::Comm(bus_error) => Error::BusError(bus_error),
87+
mcp794xx::Error::InvalidInputData => Error::DriverBug,
88+
})
89+
}
90+
Self::None => Err(Error::NoRtcFound),
91+
}
92+
}
93+
94+
/// Set the current wall-time
95+
///
96+
/// We don't care about timezones - this is basic Gregorian naive wall-clock date/time.
97+
///
98+
/// You get `Err()` if the RTC doesn't respond or wasn't found on start-up.
99+
pub fn set_time<T, E>(&mut self, bus: T, new_time: NaiveDateTime) -> Result<(), Error<E>>
64100
where
65101
T: embedded_hal::blocking::i2c::WriteRead<Error = E>
66102
+ embedded_hal::blocking::i2c::Write<Error = E>,
67103
{
68104
match self {
69105
Self::Ds1307 => {
70106
let mut driver = ds1307::Ds1307::new(bus);
71-
driver.datetime().map_err(|_e| ())
107+
driver.set_datetime(&new_time).map_err(|e| match e {
108+
ds1307::Error::I2C(bus_error) => Error::BusError(bus_error),
109+
ds1307::Error::InvalidInputData => Error::DriverBug,
110+
})
72111
}
73112
Self::Mcp7940n => {
74113
let mut driver = mcp794xx::Mcp794xx::new_mcp7940n(bus);
75-
driver.datetime().map_err(|_e| ())
114+
driver.set_datetime(&new_time).map_err(|e| match e {
115+
mcp794xx::Error::Comm(bus_error) => Error::BusError(bus_error),
116+
mcp794xx::Error::InvalidInputData => Error::DriverBug,
117+
})
76118
}
77-
Self::None => Err(()),
119+
Self::None => Err(Error::NoRtcFound),
78120
}
79121
}
80122
}

0 commit comments

Comments
 (0)