Skip to content

Instantly share code, notes, and snippets.

@pvdrz
Created March 15, 2023 18:41
Show Gist options
  • Save pvdrz/6ab2752e9dffe7a3acf413d2c35cb603 to your computer and use it in GitHub Desktop.
Save pvdrz/6ab2752e9dffe7a3acf413d2c35cb603 to your computer and use it in GitHub Desktop.
Christian's Solution
//! Board Support Crate (BSC) for the nRF52840 Development Kit
#![deny(missing_docs)]
#![no_std]
use core::{
convert::TryFrom,
fmt, ops,
sync::atomic::{self, Ordering},
time::Duration,
};
use cortex_m::asm;
use embedded_hal::digital::v2::{OutputPin as _, StatefulOutputPin};
use hal::{
gpio::{p0, Input, Level, Output, Pin, Port, PullUp, PushPull},
pac::uarte0::{baudrate::BAUDRATE_A as Baudrate, config::PARITY_A as Parity},
prelude::InputPin,
timer::OneShot,
uarte,
};
use nrf52840_hal as hal;
use defmt;
use defmt_rtt as _; // global logger
/// Components on the boarde
// Add structs for the peripheral you want to implement. First for the buttons, later UARTE
pub struct Board {
/// LEDs
pub leds: Leds,
/// Timer
pub timer: Timer,
/// Buttons
pub buttons: Buttons,
/// Uarte peripheral
pub uarte: Uarte,
}
/// All LEDs on the board
pub struct Leds {
/// LED1: pin P0.13, green LED
pub led_1: Led,
/// LED2: pin P0.14, green LED
pub led_2: Led,
/// LED3: pin P0.15, green LED
pub led_3: Led,
/// LED4: pin P0.16, green LED
pub led_4: Led,
}
/// A single LED
pub struct Led {
inner: Pin<Output<PushPull>>,
}
impl Led {
/// Turns on the LED
pub fn on(&mut self) {
defmt::trace!(
"setting P{}.{} low (LED on)",
port_as_char(&self.inner.port()),
self.inner.pin()
);
// NOTE this operations returns a `Result` but never returns the `Err` variant
let _ = self.inner.set_low();
}
/// Turns off the LED
pub fn off(&mut self) {
defmt::trace!(
"setting P{}.{} high (LED off)",
port_as_char(&self.inner.port()),
self.inner.pin()
);
// NOTE this operations returns a `Result` but never returns the `Err` variant
let _ = self.inner.set_high();
}
/// Returns `true` if the LED is in the OFF state
pub fn is_off(&self) -> bool {
self.inner.is_set_high() == Ok(true)
}
/// Returns `true` if the LED is in the ON state
pub fn is_on(&self) -> bool {
!self.is_off()
}
/// Toggles the state (on/off) of the LED
pub fn toggle(&mut self) {
if self.is_off() {
self.on();
} else {
self.off()
}
}
}
/// All buttons on the board
pub struct Buttons {
/// Button 1, as specified in section 8.7 of the nRF52840-DK User Guide.
pub b_1: Button,
/// Button 2, as specified in section 8.7 of the nRF52840-DK User Guide.
pub b_2: Button,
/// Button 3, as specified in section 8.7 of the nRF52840-DK User Guide.
pub b_3: Button,
/// Button 4, as specified in section 8.7 of the nRF52840-DK User Guide.
pub b_4: Button,
}
/// A single button
pub struct Button {
inner: Pin<Input<PullUp>>,
}
impl Button {
/// Returns `true` if the button is being pushed.
pub fn is_pushed(&self) -> bool {
self.inner.is_low().unwrap_or_else(|never| match never {})
}
}
// Add an impl block for the Button struct
// todo! Add a method that returns true, if the button is pushed.
// ...
/// A timer for creating blocking delays
pub struct Timer {
inner: hal::Timer<hal::pac::TIMER0, OneShot>,
}
impl Timer {
/// Blocks program execution for at least the specified `duration`
pub fn wait(&mut self, duration: Duration) {
defmt::trace!("blocking for {:?} ...", duration);
// 1 cycle = 1 microsecond because the underlying HAL driver
// always sets the timer to 1 MHz.
const NANOS_IN_ONE_MICRO: u32 = 1_000;
let subsec_micros = duration.subsec_nanos() / NANOS_IN_ONE_MICRO;
if subsec_micros != 0 {
self.inner.delay(subsec_micros);
}
const MICROS_IN_ONE_SEC: u32 = 1_000_000;
// maximum number of seconds that fit in a single `delay` call without overflowing the `u32`
// argument
const MAX_SECS: u32 = u32::MAX / MICROS_IN_ONE_SEC;
let mut secs = duration.as_secs();
while secs != 0 {
let cycles = if secs > MAX_SECS as u64 {
secs -= MAX_SECS as u64;
MAX_SECS * MICROS_IN_ONE_SEC
} else {
let cycles = secs as u32 * MICROS_IN_ONE_SEC;
secs = 0;
cycles
};
self.inner.delay(cycles)
}
defmt::trace!("... DONE");
}
}
impl ops::Deref for Timer {
type Target = hal::Timer<hal::pac::TIMER0, OneShot>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl ops::DerefMut for Timer {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
/// Uarte peripheral
// todo! Add a struct that represents the Uarte
// ...
pub struct Uarte {
inner: hal::Uarte<hal::pac::UARTE1>,
}
// todo! Implement the fmt::Write Trait for Uarte
// ...
impl fmt::Write for Uarte {
fn write_str(&mut self, s: &str) -> fmt::Result {
let mut chunks = s.as_bytes().chunks_exact(16);
for chunk in chunks.by_ref() {
// This won't panic as `chunks` has only slices of length 16.
let buf = <[u8; 16]>::try_from(chunk).unwrap();
self.inner.write(&buf).map_err(|_| fmt::Error)?;
}
let remainder = chunks.remainder();
if !remainder.is_empty() {
let mut buf = [0u8; 16];
buf[..remainder.len()].copy_from_slice(remainder);
self.inner.write(&buf).map_err(|_| fmt::Error)?;
}
Ok(())
}
}
/// Initializes the board
///
/// This return an `Err`or if called more than once
pub fn init() -> Result<Board, ()> {
if let Some(periph) = hal::pac::Peripherals::take() {
let pins = p0::Parts::new(periph.P0);
// NOTE LEDs turn on when the pin output level is low
let led_1 = pins.p0_13.degrade().into_push_pull_output(Level::High);
let led_2 = pins.p0_14.degrade().into_push_pull_output(Level::High);
let led_3 = pins.p0_15.degrade().into_push_pull_output(Level::High);
let led_4 = pins.p0_16.degrade().into_push_pull_output(Level::High);
// Buttons
// todo! Assign the pins of the buttons
// ...
let button_1 = pins.p0_11.degrade().into_pullup_input();
let button_2 = pins.p0_12.degrade().into_pullup_input();
let button_3 = pins.p0_24.degrade().into_pullup_input();
let button_4 = pins.p0_25.degrade().into_pullup_input();
defmt::debug!("I/O pins have been configured for digital output");
let timer = hal::Timer::new(periph.TIMER0);
// Uarte
// todo! Assign the pins of the UARTE peripheral
// ...
let uarte_pins = uarte::Pins {
rxd: pins.p0_05.degrade().into_floating_input(),
txd: pins.p0_06.degrade().into_push_pull_output(Level::High),
cts: Some(pins.p0_07.degrade().into_floating_input()),
rts: Some(pins.p0_08.degrade().into_push_pull_output(Level::High)),
};
// todo! Instantiate the UARTE peripheral
// ...
let uarte = Uarte {
inner: uarte::Uarte::new(
periph.UARTE1,
uarte_pins,
Parity::INCLUDED,
Baudrate::BAUD115200,
),
};
Ok(Board {
leds: Leds {
led_1: Led { inner: led_1 },
led_2: Led { inner: led_2 },
led_3: Led { inner: led_3 },
led_4: Led { inner: led_4 },
},
// todo! Create an instance of the struct that contains all the single buttons.
// ...
buttons: Buttons {
b_1: Button { inner: button_1 },
b_2: Button { inner: button_2 },
b_3: Button { inner: button_3 },
b_4: Button { inner: button_4 },
},
timer: Timer { inner: timer },
// todo! Create an instance of the UARTE struct
// ...
uarte,
})
} else {
Err(())
}
}
/// Exits the application when the program is executed through the `probe-run` Cargo runner
pub fn exit() -> ! {
unsafe {
// turn off the USB D+ pull-up before pausing the device with a breakpoint
// this disconnects the nRF device from the USB host so the USB host won't attempt further
// USB communication (and see an unresponsive device). probe-run will also reset the nRF's
// USBD peripheral when it sees the device in a halted state which has the same effect as
// this line but that can take a while and the USB host may issue a power cycle of the USB
// port / hub / root in the meantime, which can bring down the probe and break probe-run
const USBD_USBPULLUP: *mut u32 = 0x4002_7504 as *mut u32;
USBD_USBPULLUP.write_volatile(0)
}
defmt::println!("`dk::exit()` called; exiting ...");
// force any pending memory operation to complete before the BKPT instruction that follows
atomic::compiler_fence(Ordering::SeqCst);
loop {
asm::bkpt()
}
}
// Helper functions
fn port_as_char(port: &Port) -> char {
match port {
Port::Port0 => '0',
Port::Port1 => '1',
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment