Skip to content

Commit

Permalink
Watchdog timer (#38)
Browse files Browse the repository at this point in the history
* Allow generic time units in timer setup functions

 -This implements the ability for any embedded-time::duration::* or embedded-time::rate::* to be used where previously only Hertz or Milliseconds were accepted.

* WDT skeleton implementing all embedded_hal traits

* WDT code written, need example to test function

* More functions to use generic time; misc. cleanup

* Change delay LED to use timer CountDown features

* Added more info about time units/casting in docs

* Tried writing to WFAR/WSAR before setting; fails

- I'm pretty sure the reason things aren't working is that the WFAR/WSAR access_key1 and access_key2 registers need to be sent their passwords in order to configure the WDT. This commit doesn't work any better than the last one, but it seems like it's worth recording the failure state, so it can be referenced in the future.

* Watchdog can now be enabled, WTS bit functioning

- The access keys when sent in the correct order do allow for writing registers. Both must be sent, sequentially, immediately before writing. They are currently implemented for all writes relating to the WDT. I still need to try removing them and seeing which registers actually require the codes, and which can be written without.
- The watchdog is being triggered, and the WTS bit is being set, but the timing values are off. Will need to investigate.

* Watchdog is functioning, at ~30x tick speed

- Added example test code with lots of debug printing.
- Maybe the 1kHz clock is not being divided from the 32kHz clock. if that's the case, we will want to disable it as a valid clock source for the WDT, since it works for the timers but not the WDT.

* WDT working as expected without 1kHz clock source

- Disabling the 1kHz clock has the other clocks working as expected.
- Still need to find and remove unnecessary access key sending.
- Still need to test interrupts.

* Add getters, cleanup functions, comments, layout

* WDT interrupts working; example now shows use

* Fixed `const` error in watchdog.rs get_key fn

* fix bjoernQ's suggestions from PR thread
  • Loading branch information
kiyoshigawa authored Oct 21, 2021
1 parent b673701 commit d96d21e
Show file tree
Hide file tree
Showing 7 changed files with 541 additions and 24 deletions.
28 changes: 19 additions & 9 deletions examples/led_timer_interrupt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// The programmed blinking pattern below results in:
// -the interrupt-controlled blue LED being on for 1500ms, then off for 1500ms.
// -the interrupt-controlled green LED toggling on for 500ms and then off for 1000ms.
// -the delay-loop controlled red LED toggling on for 3000ms and off for 3000ms.
// -the timer::CountDown delay-loop controlled red LED toggling every 3000ms.
// Global access to variables inside the interrupt handler function is controlled by the
// Mutex<RefCell<>> library, as outlined in the rust embedded handbook chapter on concurrency.
// See https://docs.rust-embedded.org/book/concurrency/index.html for more info.
Expand All @@ -16,7 +16,7 @@
use bl602_hal as hal;
use core::cell::RefCell;
use core::ops::DerefMut;
use embedded_hal::delay::blocking::DelayMs;
use embedded_hal::timer::nb::CountDown;
use embedded_hal::digital::blocking::{OutputPin, ToggleableOutputPin};
use embedded_time::{duration::*, rate::*};
use hal::{
Expand Down Expand Up @@ -62,12 +62,17 @@ fn main() -> ! {
let mut g_led_pin = glb.pin14.into_pull_down_output();
let _ = g_led_pin.set_high();

// Initialize TimerCh0 to increment its count at a rate of 1000Hz:
// Initialize TimerCh0 to increment its count at a rate of 160MHz:
let timers = dp.TIMER.split();
let timer_ch0 = timers
.channel0
.set_clock_source(ClockSource::Fclk(&clocks), 160_000_000_u32.Hz());

// Initialize TimerCh1 at a rate of 1kHz to use for a blocking delay on the red LED:
let mut timer_ch1 = timers
.channel1
.set_clock_source(ClockSource::Clock1Khz, 1000u32.Hz());

// Set up Match0 which we will use to control the blue LED:
// Note that you can use any embedded_time::duration as a time value in these set functions.
timer_ch0.enable_match0_interrupt();
Expand All @@ -83,8 +88,9 @@ fn main() -> ! {
timer_ch0.set_preload_value(0.microseconds());
timer_ch0.set_preload(hal::timer::Preload::PreloadMatchComparator0);

// Finally, remember to enable the timer channel so the interrupts will function:
// Finally, remember to enable the timer channels so they will function:
timer_ch0.enable();
timer_ch1.enable();

// Move the references to their UnsafeCells once initialized, and before interrupts are enabled:
riscv::interrupt::free(|cs| G_INTERRUPT_LED_PIN_B.borrow(cs).replace(Some(b_led_pin)));
Expand All @@ -97,12 +103,16 @@ fn main() -> ! {
// and immediately re-triggering the interrupt function when it returns.
enable_interrupt(Interrupt::TimerCh0);

// Create a blocking delay function based on the current cpu frequency for the red LED control:
let mut d = bl602_hal::delay::McycleDelay::new(clocks.sysclk().0);

// The .start() and .wait() functions as specified in the embedded_hal allow us to use
// a timer without needing to configure any match values or handle any interrupts:
loop {
// Toggle the red channel every 3000ms to show it is still running in the background:
let _ = d.delay_ms(3000);
// Start the timer's CountDown functionality. The countdown will last 3000ms.
timer_ch1.start(3_000u32.milliseconds()).ok();
// The .wait() function returns an error until the timer has finished counting down.
while timer_ch1.wait().is_err() {
// Stay in the while loop until the timer is done.
}
// Once the timer is done counting down, toggle the LED:
let _ = r_led_pin.toggle();
}
}
Expand Down
160 changes: 160 additions & 0 deletions examples/watchdog_example.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
This example code is designed to always fail to `feed()` the watchdog so you can see that it is
working as intended. When the watchdog fails, it sets the WTS bit of the WSR register on the
bl602 to be 1. You can read this bit on initialization of the chip to see if the watchdog has
been triggered.
The code below will read this bit as 0 the first time it is powered on, and then it will fail
to `feed()` the watchdog, resulting in a watchdog reset. The next time (and every subsequent
time) it resets via the watchdog the WTS bit value that is read should be 1. To clear the bit,
you either have to manually clear it via the watchdog.clear_wts() function, or reset the board
using either the reset pin or turning off the power to the board and bringing it back online.
After the board has been reset by the watchdog timer once, the code will change the Watchdog's
mode to call the Watchdog interrupt function instead. This interrupt function will no
longer reset the board, but will instead toggle the red led channel of the RGB led every time
the watchdog is triggered.
*/

#![no_std]
#![no_main]

use bl602_hal as hal;
use core::cell::RefCell;
use core::fmt::Write;
use core::ops::DerefMut;
use embedded_hal::delay::blocking::DelayMs;
use embedded_hal::digital::blocking::{OutputPin, ToggleableOutputPin};
use embedded_hal::watchdog::blocking::{Enable, Watchdog};
use embedded_time::{duration::*, rate::*};
use hal::{
clock::{Strict, SysclkFreq, UART_PLL_FREQ},
gpio::{Output, PullDown},
interrupts::*,
pac,
prelude::*,
serial::*,
timer::*,
watchdog::*,
};
use panic_halt as _;
use riscv::interrupt::Mutex;

// Setup custom types to make the code below easier to read:
type RedLedPin = hal::gpio::Pin17<Output<PullDown>>;
type WatchdogTimer = hal::watchdog::ConfiguredWatchdog0;

// Initialize global static containers for peripheral access within the interrupt function:
static G_INTERRUPT_LED_PIN_R: Mutex<RefCell<Option<RedLedPin>>> = Mutex::new(RefCell::new(None));
static G_LED_TIMER: Mutex<RefCell<Option<WatchdogTimer>>> = Mutex::new(RefCell::new(None));

#[riscv_rt::entry]
fn main() -> ! {
//take control of the device peripherals:
let dp = pac::Peripherals::take().unwrap();
let mut gpio_pins = dp.GLB.split();

// Set up all the clocks we need
let clocks = Strict::new()
.use_pll(40_000_000u32.Hz())
.sys_clk(SysclkFreq::Pll160Mhz)
.uart_clk(UART_PLL_FREQ.Hz())
.freeze(&mut gpio_pins.clk_cfg);

// Set up uart output for debug printing. Since this microcontroller has a pin matrix,
// we need to set up both the pins and the muxs
let pin16 = gpio_pins.pin16.into_uart_sig0();
let pin7 = gpio_pins.pin7.into_uart_sig7();
let mux0 = gpio_pins.uart_mux0.into_uart0_tx();
let mux7 = gpio_pins.uart_mux7.into_uart0_rx();

// Configure our UART to 2MBaud, and use the pins we configured above
let mut serial = Serial::uart0(
dp.UART,
Config::default().baudrate(2_000_000.Bd()),
((pin16, mux0), (pin7, mux7)),
clocks,
);

// Initialize the led pin to their default state:
let mut r_led_pin = gpio_pins.pin17.into_pull_down_output();
let _ = r_led_pin.set_low();

// Create a blocking delay function based on the current cpu frequency
let mut d = bl602_hal::delay::McycleDelay::new(clocks.sysclk().0);

// Set up the watchdog timer to the slowest tick rate possible:
let timers = dp.TIMER.split();
let watchdog = timers
.watchdog
.set_clock_source(WdtClockSource::Rc32Khz, 125.Hz());

// Before setting up and enabling the watchdog,
// retrieve and print to serial the state of the WTS bit:
let wts_bit_value = match watchdog.has_watchdog_reset_occurred() {
true => 1_u8,
false => 0_u8,
};

writeln!(serial, "The watchdog WTS bit reads as: {}\r", wts_bit_value).ok();

// On a clean boot, the watchdog will trigger a board reset if not fed in time.
// If the watchdog has previously reset the board, switch to interrupt mode.
match wts_bit_value {
0_u8 => watchdog.set_mode(WatchdogMode::Reset),
1_u8 => {
watchdog.set_mode(WatchdogMode::Interrupt);
enable_interrupt(Interrupt::Watchdog);
}
_ => unreachable!(),
}

// The watchdog timer doesn't begin counting ticks until it is started. We don't need to handle
// the error state, since the watchdog start function will never actually return an Err().
let watchdog = watchdog.start(10_u32.seconds()).unwrap();

// Move the references to their UnsafeCells once initialized, and before interrupts are enabled:
riscv::interrupt::free(|cs| G_INTERRUPT_LED_PIN_R.borrow(cs).replace(Some(r_led_pin)));
riscv::interrupt::free(|cs| G_LED_TIMER.borrow(cs).replace(Some(watchdog)));

loop {
// Since we delay for more than 10 seconds, the watchdog is only fed the first time through
// the loop. If you want to prevent the watchdog from triggering, decrease the number of
// milliseconds in the delay to less than 10 seconds.

// In order to use the watchdog once it has been moved into the RefCell, you must call free():
riscv::interrupt::free(|cs| {
if let Some(watchdog) = G_LED_TIMER.borrow(cs).borrow_mut().deref_mut() {
watchdog.feed().ok();
}
});
d.delay_ms(20_000).ok();
}
}

// This is the interrupt handler for the watchdog. It currently toggles the red led channel of the
// RGB led on the board every time the watchdog is triggered after it has been reset at least once.
#[allow(non_snake_case)]
#[no_mangle]
fn Watchdog(_: &mut TrapFrame) {
disable_interrupt(Interrupt::Watchdog);
clear_interrupt(Interrupt::Watchdog);

//Clear the WDT interrupt flag and feed the watchdog to reset its counter:
riscv::interrupt::free(|cs| {
if let Some(watchdog) = G_LED_TIMER.borrow(cs).borrow_mut().deref_mut() {
watchdog.clear_interrupt();
watchdog.feed().ok();
}
});

// Toggle the red led whenever the interrupt is triggered:
riscv::interrupt::free(|cs| {
if let Some(led_pin) = G_INTERRUPT_LED_PIN_R.borrow(cs).borrow_mut().deref_mut() {
led_pin.toggle().ok();
}
});

enable_interrupt(Interrupt::Watchdog);
}
1 change: 1 addition & 0 deletions hal_defaults.x
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
PROVIDE(Gpio = DefaultHandler);
PROVIDE(TimerCh0 = DefaultHandler);
PROVIDE(TimerCh1 = DefaultHandler);
PROVIDE(Watchdog = DefaultHandler);
9 changes: 9 additions & 0 deletions src/interrupts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
fn Gpio();
fn TimerCh0();
fn TimerCh1();
fn Watchdog();
```
*/

Expand All @@ -29,6 +30,7 @@ extern "C" {
fn Gpio(trap_frame: &mut TrapFrame);
fn TimerCh0(trap_frame: &mut TrapFrame);
fn TimerCh1(trap_frame: &mut TrapFrame);
fn Watchdog(trap_frame: &mut TrapFrame);
}

// see components\bl602\bl602_std\bl602_std\RISCV\Core\Include\clic.h
Expand All @@ -41,6 +43,7 @@ const CLIC_INTIP: u32 = 0x000;
const GPIO_IRQ: u32 = IRQ_NUM_BASE + 44;
const TIMER_CH0_IRQ: u32 = IRQ_NUM_BASE + 36;
const TIMER_CH1_IRQ: u32 = IRQ_NUM_BASE + 37;
const WATCHDOG_IRQ: u32 = IRQ_NUM_BASE + 38;

#[doc(hidden)]
#[no_mangle]
Expand Down Expand Up @@ -137,6 +140,7 @@ pub unsafe extern "C" fn start_trap_rust_hal(trap_frame: *mut TrapFrame) {
Interrupt::Gpio => Gpio(trap_frame.as_mut().unwrap()),
Interrupt::TimerCh0 => TimerCh0(trap_frame.as_mut().unwrap()),
Interrupt::TimerCh1 => TimerCh1(trap_frame.as_mut().unwrap()),
Interrupt::Watchdog => Watchdog(trap_frame.as_mut().unwrap()),
};
}
}
Expand All @@ -152,6 +156,9 @@ pub enum Interrupt {
TimerCh0,
/// Timer Channel 1 Interrupt
TimerCh1,
/// Watchdog Timer Interrupt
/// Used when WDT is configured in Interrupt mode using ConfiguredWatchdog0::set_mode()
Watchdog,
}

impl Interrupt {
Expand All @@ -161,6 +168,7 @@ impl Interrupt {
Interrupt::Gpio => GPIO_IRQ,
Interrupt::TimerCh0 => TIMER_CH0_IRQ,
Interrupt::TimerCh1 => TIMER_CH1_IRQ,
Interrupt::Watchdog => WATCHDOG_IRQ,
}
}

Expand All @@ -169,6 +177,7 @@ impl Interrupt {
GPIO_IRQ => Interrupt::Gpio,
TIMER_CH0_IRQ => Interrupt::TimerCh0,
TIMER_CH1_IRQ => Interrupt::TimerCh1,
WATCHDOG_IRQ => Interrupt::Watchdog,
_ => Interrupt::Unknown,
}
}
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub mod rtc;
pub mod serial;
pub mod spi;
pub mod timer;
pub mod watchdog;

/// HAL crate prelude
pub mod prelude {
Expand Down
Loading

0 comments on commit d96d21e

Please sign in to comment.