This commit is contained in:
Tanks Transfeld 2023-06-28 06:53:11 -04:00 committed by GitHub
commit 6e96af964f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 48667 additions and 0 deletions

22
down-the-stack/README.md Normal file
View file

@ -0,0 +1,22 @@
# Down the Stack
This folder contains all relevant exercise templates, demo code and solutions for the exercises in the `Down the Stack` session.
## `apps` package
* `button.rs` - demo code for the BSC exercise
* `hello.rs` - for testing as described in the BSC exercise
* `uarte_print.rs` - demo code for the BSC exercise
* `uarte_enable.rs` - exercise template for PAC exercise
## `dk_bsc` package
* `lib.rs` - exercise template for the BSC exercise
* `lib_solution.rs` - solution code to the BSC exercise
## `dk_pac` package
* `Cargo.toml` - a `Cargo.toml` file for the to be generated PAC
* `nrf52.svd` - an SVD file for generating the PAC
You will generate the remaining files during the training
## `solutions` package
* `uarte_enable.rs` - contains the solution for the PAC exercise

View file

@ -0,0 +1,19 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# (..)
rustflags = [
"-C", "linker=flip-link", # adds stack overflow protection
"-C", "link-arg=-Tdefmt.x", # defmt support
# (..)
]
[target.thumbv7em-none-eabihf]
# set custom cargo runner to flash & run on embedded target when we call `cargo run`
# for more information, check out https://github.com/knurling-rs/probe-run
runner = "probe-run --chip nRF52840_xxAA"
rustflags = [
"-C", "link-arg=-Tlink.x",
]
[build]
# cross-compile to this target
target = "thumbv7em-none-eabihf" # = ARM Cortex-M4

View file

@ -0,0 +1,49 @@
[package]
authors = ["Tanks Transfeld <tanks.transfeld@ferrous-systems.com>"]
edition = "2018"
license = "MIT OR Apache-2.0"
name = "apps"
version = "0.0.0"
[dependencies]
cortex-m = {version = "0.7.6", features = ["critical-section-single-core"]}
cortex-m-rt = "0.7.2"
dk_bsc = { path = "../dk_bsc" }
heapless = "0.7.16"
panic-probe = { version = "0.3.0", features = ["print-defmt"] }
defmt = "0.3.2"
defmt-rtt = "0.3.2"
# optimize code in both profiles
[profile.dev]
codegen-units = 1
debug = 2
debug-assertions = true # !
incremental = false
lto = "fat"
opt-level = 'z' # !
overflow-checks = false
[profile.release]
codegen-units = 1
debug = 1
debug-assertions = false
incremental = false
lto = "fat"
opt-level = 3
overflow-checks = false
[features]
default = [
"other-feature"
]
other-feature = []
# do NOT modify these features
defmt-default = []
defmt-trace = []
defmt-debug = []
defmt-info = []
defmt-warn = []
defmt-error = []

View file

@ -0,0 +1,31 @@
#![no_main]
#![no_std]
use cortex_m_rt::entry;
// this imports `down-the-stack/apps/lib.rs` to retrieve our global logger + panicking-behavior
use apps as _;
// ⚠️ ⚠️ ⚠️ Don't change this file! ⚠️ ⚠️ ⚠️
#[entry]
fn main() -> ! {
// to enable more verbose logs, go to your `Cargo.toml` and set defmt logging levels
// to `defmt-trace` by changing the `default = []` entry in `[features]`
let board = dk_bsc::init().unwrap();
let mut led = board.leds;
let button_1 = board.buttons.b_1;
loop {
if button_1.is_pushed() {
led.led_1.on();
} else {
led.led_1.off();
}
}
// this program does not `exit`; use Ctrl+C to terminate it
}

View file

@ -0,0 +1,28 @@
// this program does not use the standard library to avoid heap allocations.
// only the `core` library functions are available.
#![no_std]
// this program uses a custom entry point instead of `fn main()`
#![no_main]
use cortex_m::asm;
use cortex_m_rt::entry;
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
use apps as _;
// the custom entry point
// vvvvv
#[entry]
fn main() -> ! {
// ˆˆˆ
// ! is the 'never' type: this function never returns
// initializes the peripherals
dk_bsc::init().unwrap();
defmt::println!("Hello, world!"); // :wave:
loop {
// breakpoint: halts the program's execution
asm::bkpt();
}
}

View file

@ -0,0 +1,28 @@
#![no_main]
#![no_std]
use cortex_m::asm;
use cortex_m_rt::entry;
// ^^^^ import the PAC here
// this imports `down-the-stack/apps/lib.rs` to retrieve our global logger + panicking-behavior
use apps as _;
use defmt;
use defmt_rtt as _; // global logger
#[entry]
fn main() -> ! {
// to enable more verbose logs, go to your `Cargo.toml` and set defmt logging levels
// to `defmt-trace` by changing the `default = []` entry in `[features]`
// Your code goes here...
// this program does not `exit`; use Ctrl+C to terminate it
loop {
asm::nop();
}
}
// The helper function goes here...

View file

@ -0,0 +1,28 @@
#![no_main]
#![no_std]
use cortex_m::asm;
use cortex_m_rt::entry;
use core::fmt::Write;
// this imports `down-the-stack/apps/lib.rs` to retrieve our global logger + panicking-behavior
use apps as _;
// ⚠️ ⚠️ ⚠️ Don't change this file! ⚠️ ⚠️ ⚠️
#[entry]
fn main() -> ! {
// to enable more verbose logs, go to your `Cargo.toml` and set defmt logging levels
// to `defmt-trace` by changing the `default = []` entry in `[features]`
let board = dk_bsc::init().unwrap();
let mut uarte = board.uarte;
let tx_buffer = "Hello, World!\n";
uarte.write_str(tx_buffer).unwrap();
// this program does not `exit`; use Ctrl+C to terminate it
loop {
asm::nop();
}
}

View file

@ -0,0 +1,10 @@
#![no_std]
// ⚠️ ⚠️ ⚠️ Don't change this file! ⚠️ ⚠️ ⚠️
use panic_probe as _;
// 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()
}

View file

@ -0,0 +1,24 @@
[package]
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>", "Tanks Transfeld <tanks.transfeld@ferrous-systems.com"]
edition = "2018"
license = "MIT OR Apache-2.0"
name = "dk_bsc"
version = "0.0.0"
[dependencies]
cortex-m = {version = "0.7.6", features = ["critical-section-single-core"]}
cortex-m-rt = "0.7.2"
embedded-hal = "0.2.7"
nrf52840-hal = "0.14.0"
panic-probe = { version = "0.3.0", features = ["print-defmt"] }
defmt = "0.3.2"
defmt-rtt = "0.3.2"
[features]
advanced = []
beginner = []
default = [
"other-feature"
]
other-feature = []

View file

@ -0,0 +1,7 @@
# `dk`
Board Support Crate for the nRF52840 Development Kit (DK)
## Getting familiar with the hardware
TODO

View file

@ -0,0 +1,12 @@
use std::{env, error::Error, fs, path::PathBuf};
fn main() -> Result<(), Box<dyn Error>> {
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
// put memory layout (linker script) in the linker search path
fs::copy("memory.x", out_dir.join("memory.x"))?;
println!("cargo:rustc-link-search={}", out_dir.display());
Ok(())
}

View file

@ -0,0 +1,5 @@
MEMORY
{
FLASH : ORIGIN = 0x00000000, LENGTH = 1024K
RAM : ORIGIN = 0x20000000, LENGTH = 256K
}

View file

@ -0,0 +1,259 @@
//! Board Support Crate (BSC) for the nRF52840 Development Kit
#![deny(missing_docs)]
#![no_std]
use core::{
ops,
sync::atomic::{self, Ordering},
time::Duration,
};
use cortex_m::asm;
use embedded_hal::digital::v2::{OutputPin as _, StatefulOutputPin};
use nrf52840_hal as hal;
use hal::{
gpio::{p0, Level, Output, Pin, Port, PushPull},
timer::OneShot,
};
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,
}
/// 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
// todo! Add a struct that represents all buttons of the board.
// ...
/// A single button
// todo! Add a struct that represents a single button.
// ...
// 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
// ...
// todo! Implement the fmt::Write Trait for Uarte
// ...
/// 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
// ...
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
// ...
// todo! Instantiate the UARTE peripheral
// ...
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.
// ...
timer: Timer { inner: timer },
// todo! Create an instance of the UARTE struct
// ...
})
} 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',
}
}

View file

@ -0,0 +1,303 @@
//! Board Support Crate (BSC) for the nRF52840 Development Kit
#![deny(missing_docs)]
#![no_std]
use core::{
ops,
fmt,
sync::atomic::{self, Ordering},
time::Duration,
};
use cortex_m::asm;
use embedded_hal::digital::v2::{OutputPin as _, StatefulOutputPin};
use nrf52840_hal as hal;
pub use hal::pac::uarte0::{
baudrate::BAUDRATE_A as Baudrate, config::PARITY_A as Parity};
use hal::{
gpio::{p0, Level, Output, Input, PullUp, Pin, Port, PushPull},
timer::OneShot, prelude::InputPin,
uarte,
};
use defmt;
use defmt_rtt as _; // global logger
/// Components on the board
pub struct Board {
/// LEDs
pub leds: Leds,
// 🔽 --- Button Exercise --- 🔽
/// Buttons
pub buttons: Buttons,
// 🔼 --- Button Exercise --- 🔼
/// Timer
pub timer: Timer,
// 🔽 --- UARTE Exercise --- 🔽
/// uarte interface
pub uarte: Uarte,
// 🔼 --- UARTE Exercise --- 🔼
}
/// 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()
}
}
}
// 🔽 --- Button Exercise --- 🔽
/// All buttons on the board
pub struct Buttons {
/// BUTTON1: pin P0.11
pub b_1: Button,
/// BUTTON2: pin P0.12
pub b_2: Button,
/// BUTTON3: pin P0.24
pub b_3: Button,
/// BUTTON4: pin P0.25
pub b_4: Button,
}
/// A single button
pub struct Button {
inner: Pin<Input<PullUp>>,
}
impl Button {
/// returns true if button is pushed
pub fn is_pushed(&self) -> bool {
self.inner.is_low() == Ok(true)
}
}
// 🔼 --- Button Exercise --- 🔼
/// 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 Exercise --- 🔽
/// Uarte peripheral
pub struct Uarte {
inner: hal::Uarte<hal::pac::UARTE1>,
}
impl fmt::Write for Uarte {
fn write_str(&mut self, s: &str) -> fmt::Result {
// Copy all data into an on-stack buffer so we never try to EasyDMA from
// flash.
let mut buf: [u8; 16] = [0; 16];
for block in s.as_bytes().chunks(16) {
buf[..block.len()].copy_from_slice(block);
self.inner.write(&buf[..block.len()]).map_err(|_| fmt::Error)?;
}
Ok(())
}
}
// 🔼 --- UARTE Exercise --- 🔼
/// 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);
// 🔽 --- Button Exercise --- 🔽
// Buttons
let b_1 = pins.p0_11.degrade().into_pullup_input();
let b_2 = pins.p0_12.degrade().into_pullup_input();
let b_3 = pins.p0_24.degrade().into_pullup_input();
let b_4 = pins.p0_25.degrade().into_pullup_input();
// 🔼 --- Button Exercise --- 🔼
defmt::debug!("I/O pins have been configured for digital output");
let timer = hal::Timer::new(periph.TIMER0);
// 🔽 --- UARTE Exercise --- 🔽
// Uarte
let pins = hal::uarte::Pins {
rxd: pins.p0_08.degrade().into_floating_input(),
txd: pins.p0_06.degrade().into_push_pull_output(Level::High),
cts: None,
rts: None,
};
let uarte = hal::uarte::Uarte::new(periph.UARTE1, pins, Parity::EXCLUDED, Baudrate::BAUD115200);
// 🔼 --- UARTE Exercise --- 🔼
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 },
},
// 🔽 --- Button Exercise --- 🔽
buttons: Buttons {
b_1: Button { inner: b_1},
b_2: Button { inner: b_2},
b_3: Button { inner: b_3},
b_4: Button { inner: b_4},
},
// 🔼 --- Button Exercise --- 🔼
timer: Timer { inner: timer },
// 🔽 --- UARTE Exercise --- 🔽
uarte: Uarte { inner: uarte },
// 🔼 --- UARTE Exercise --- 🔼
})
} 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',
}
}

View file

@ -0,0 +1,22 @@
[package]
authors = ["Tanks Transfeld <tanks.transfeld@ferrous-systems.com"]
edition = "2018"
license = "MIT OR Apache-2.0"
name = "dk_pac"
version = "0.0.0"
[dependencies]
critical-section = { version = "1.0", optional = true }
cortex-m = {version = "0.7.6", features = ["critical-section-single-core"]}
cortex-m-rt = "0.7.2"
vcell = "0.1.2"
panic-probe = { version = "0.3.0", features = ["print-defmt"] }
defmt = "0.3.2"
defmt-rtt = "0.4"
[features]
rt = ["cortex-m-rt/device"]
default = [
"rt"
]
other-feature = []

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,19 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# (..)
rustflags = [
"-C", "linker=flip-link", # adds stack overflow protection
"-C", "link-arg=-Tdefmt.x", # defmt support
# (..)
]
[target.thumbv7em-none-eabihf]
# set custom cargo runner to flash & run on embedded target when we call `cargo run`
# for more information, check out https://github.com/knurling-rs/probe-run
runner = "probe-run --chip nRF52840_xxAA"
rustflags = [
"-C", "link-arg=-Tlink.x",
]
[build]
# cross-compile to this target
target = "thumbv7em-none-eabihf" # = ARM Cortex-M4

View file

@ -0,0 +1,50 @@
[package]
authors = ["Tanks Transfeld <tanks.transfeld@ferrous-systems.com>"]
edition = "2018"
license = "MIT OR Apache-2.0"
name = "solutions"
version = "0.0.0"
[dependencies]
cortex-m = {version = "0.7.6", features = ["critical-section-single-core"]}
cortex-m-rt = "0.7.2"
dk_bsc = { path = "../dk_bsc" }
dk_pac = { path = "../dk_pac", features = ["critical-section"]}
heapless = "0.7.16"
panic-probe = { version = "0.3.0", features = ["print-defmt"] }
defmt = "0.3.2"
defmt-rtt = "0.3.2"
# optimize code in both profiles
[profile.dev]
codegen-units = 1
debug = 2
debug-assertions = true # !
incremental = false
lto = "fat"
opt-level = 'z' # !
overflow-checks = false
[profile.release]
codegen-units = 1
debug = 1
debug-assertions = false
incremental = false
lto = "fat"
opt-level = 3
overflow-checks = false
[features]
default = [
"other-feature"
]
other-feature = []
# do NOT modify these features
defmt-default = []
defmt-trace = []
defmt-debug = []
defmt-info = []
defmt-warn = []
defmt-error = []

View file

@ -0,0 +1,4 @@
# Solutions
This program does not build if the pac is not generated!
The file is only for reference.

View file

@ -0,0 +1,48 @@
#![no_main]
#![no_std]
use cortex_m::asm;
use cortex_m_rt::entry;
use dk_pac::UARTE0;
// this imports `down-the-stack/apps/lib.rs` to retrieve our global logger + panicking-behavior
use solutions as _;
use defmt;
use defmt_rtt as _; // global logger
#[entry]
fn main() -> ! {
// to enable more verbose logs, go to your `Cargo.toml` and set defmt logging levels
// to `defmt-trace` by changing the `default = []` entry in `[features]`
// takes ownership of the nRF52840-DK peripherals
let periph = dk_pac::Peripherals::take().unwrap();
let uarte = periph.UARTE0;
is_uarte_enabled(&uarte);
// enable the UART0 peripheral the safe way
uarte.enable.write(|w| w.enable().enabled());
is_uarte_enabled(&uarte);
// disable the UART0 peripheral by writing 0 directly into the register -- the unsafe way
unsafe {
uarte.enable.write(|w| w.bits(0x00u32));
}
is_uarte_enabled(&uarte);
// this program does not `exit`; use Ctrl+C to terminate it
loop {
asm::nop();
}
}
fn is_uarte_enabled(uarte: &UARTE0) {
if uarte.enable.read().enable().is_enabled() {
defmt::println!("Uarte0 is enabled");
} else {
defmt::println!("Uarte0 is disabled");
}
}

View file

@ -0,0 +1,11 @@
#![no_std]
// ⚠️ ⚠️ ⚠️ Don't change this file! ⚠️ ⚠️ ⚠️ so it's exactly like apps/src/lib.rs
use panic_probe as _;
// 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()
}

View file

@ -24,6 +24,14 @@
- [Collision avoidance](./collision-avoidance.md)
- [Interrupt handling](./interrupt-handling.md)
- [Starting a Project from Scratch](./from-scratch.md)
- [Down the Stack Workbook](./down-the-stack.md)
- [BSC Exercise](./bsc-exercise.md)
- [Button Implementation](./button-implementation.md)
- [UARTE Implementation](./uarte-implementation.md)
- [Generating and Using a PAC](./pac-exercise.md)
- [Exercise: Generating a PAC ](./generating-pac.md)
- [Reading PAC Documentation ](./pac-docs.md)
- [Exercise: Enabling the UARTE0 Peripheral ](./enabling-uarte.md)
- [Advanced Workbook](./advanced-workbook.md)
- [Code Organization](./code-organisation.md)
- [Listing USB Devices](./listing-usb-devices.md)

View file

@ -0,0 +1,67 @@
# BSC Exercise
In this exercise you will learn how to write a *Board Support Crate* (or BSC, also known as a *Board Support Package*) by implementing support for handling button presses, and support for using the UARTE peripheral.
The template `down-the-stack/dk_bsc/src/lib.rs` already contains the LED and Timer implementations. Add your code to the designated lines. You'll find a `//todo!` there.
You can test after each step by running the following command out of `down-the-stack/apps`
```
cargo run --bin hello
```
This program will not call any of the functions you are implementing, so it does not matter if they are incomplete. It will refuse to build if there are errors present in the `lib.rs`!
`down-the-stack/dk_bsc/src/lib_solution.rs` contains the full solution code.
## You will learn how to
* modify the `init()` function that brings up the board's peripherals
* how to configure pins
* how to write a function that checks the state of a pin
* implement functionality on a type
* implement a Trait
* to document and generate docs for your own library!
## Prerequisites
* installation of `pyserial` or other serial terminal that you are familiar with.
* `impl` keyword
* methods and associated functions
* `pub` keyword
* usage of structs to represent registers
* Trait
## Tasks
### Write a button implementation. This entails the following steps:
* Add `struct Buttons` with 4 fields, that represents each of the four buttons.
* Add `struct Button` that is a wrapper for the pin that a single button is connected to.
* Write a method `is_pushed` that checks if a single button is pushed.
* Initialize the pins in `fn init()`.
* Add the `struct Button` to the definition and instantiation of `struct Board`.
* Run `apps/buttons.rs` to test.
* Run `cargo doc` out of the apps folder to find all your doc comments!
### Write a UARTE implementation. This entails the following steps:
* Check the `uarte` module of the `nrf-hal` for requirements of the instantiating method.
* Add `struct Uarte` that serves as wrapper for the `UARTE1` instance.
* Initialize the UARTE1 peripheral in `fn init()` using the following settings:
* parity: included
* baudrate: 115200 baud
* Add `struct Uarte` to the definition and instantiation of `struct Board`.
* Implement the `fmt::Write` trait for `struct Uarte`.
* Connect your computer to the virtual UART port with `screen`.
* Run `apps/uarte_print.rs` to test.
## Knowledge
### Comments
The `lib.rs` has an attribute `#![deny(missing_docs)]`. This means, that missing doc comments for structs are returned as compiler errors, to remind you to document your work properly.
```rust
/// This is a doc comment
// This is a normal comment
```
### Structs represent Registers
[todo!] insert refresher from rust fundamentals
## Hardware documentation for pin configuration
Go to [Nordic Infocenter](https://infocenter.nordicsemi.com/topic/ug_nrf52840_dk/UG/dk/intro.html) to download the User Guide. You can find all the information that is relevant to this exercise in there.

View file

@ -0,0 +1,146 @@
# Write the Button Implementation
## Step-by-Step Solution
### Step 1: Read the docs!
✅ Read the [User Guide section 8.7](https://infocenter.nordicsemi.com/topic/ug_nrf52840_dk/UG/dk/hw_buttons_leds.html?cp=5_0_4_7_6) for info about pins and pin configuration related to the buttons. Note down the pins that the buttons are connected to.
The pins need to be configured as input pins with an internal pull-up. The pins as well as the configurations are defined as types in the `nrf-hal` in the `gpio` peripheral. Add the following imports: `Input` and `PullUp`.
### Step 2: Add the structs that represent the buttons as a group and a generic single button.
✅ Add the struct that represents the single button. It has only one field, `inner`. The type of this button is the pin configuration: `Pin<Input<PullUp>>`
✅ Add the `struct` that represents the group of buttons has four fields, one for each button. The field name contains the number that corresponds to the button numeration on the board. The type of each field is the struct that represents the generic single button.
✅ Add doc comments for every struct and field!
Building this code should return a warning: field `inner` is never read.
<details>
<summary>Solution</summary>
```rust
/// All buttons on the board
pub struct Buttons {
/// BUTTON1: pin P0.11
pub b_1: Button,
/// BUTTON2: pin P0.12
pub b_2: Button,
/// BUTTON3: pin P0.24
pub b_3: Button,
/// BUTTON4: pin P0.25
pub b_4: Button,
}
/// A single button
pub struct Button {
inner: Pin<Input<PullUp>>,
}
```
</details>
### Step 3: Implement the button function.
✅ Add an `impl` block for the `struct Button`. Add a method `is_pushed` that takes in the struct as `&self` and returns a bool, if the button is pushed.
✅ Now remember, the pins the buttons are connected to are configured as active low. For buttons this means, that the pin is pulled low, when the button is pushed.
✅ In the `nrf-hal` you can find a method to check if a single pin is low. To use it, you have to add the following line to your `nrf52840_hal` imports: `prelude::InputPin`.
<details>
<summary>Solution</summary>
```rust
impl Button {
/// returns true if button is pushed
pub fn is_pushed(&self) -> bool {
self.inner.is_low() == Ok(true)
}
}
```
</details>
### Step 4: Bring up the pins!
✅ Go to `pub fn init()`, the function that initializes the board's peripherals.
✅ Configure each pin as degraded, pull-up input pin and bind it to a variable that makes it clear what button number it is connected to.
Building this code brings up warnings about unused variables.
<details>
<summary>Solution</summary>
```rust
// Buttons
let b_1 = pins.p0_11.degrade().into_pullup_input();
let b_2 = pins.p0_12.degrade().into_pullup_input();
let b_3 = pins.p0_24.degrade().into_pullup_input();
let b_4 = pins.p0_25.degrade().into_pullup_input();
```
</details>
### Step 5: Add everything to the board struct.
✅ In the definition of the `struct Board` add a field for the `struct Buttons`.
✅ In the pub `fn init()` function, where `Board` is instantiated, add the button field, assigning the pins you defined earlier to the respective buttons.
<details>
<summary>Solution</summary>
```rust
/// Components on the board
pub struct Board {
/// LEDs
pub leds: Leds,
/// Buttons
pub buttons: Buttons,
/// Timer
pub timer: Timer,
}
// ...
pub fn init() -> Result<Board, ()> {
// ...
Ok(Board {
leds: Leds {
// ...
},
buttons: Buttons {
b_1: Button { inner: b_1},
b_2: Button { inner: b_2},
b_3: Button { inner: b_3},
b_4: Button { inner: b_4},
},
timer: Timer { inner: timer },
})
} else {
Err(())
}
}
```
</details>
### Step 6: Run the example!
✅ Go to `/down-the-stack/apps`.
✅ Run the following command:
```shell
cargo run --bin button
```
### Step 7: Generate the docs!
✅ Out of the apps folder run the following command to build the docs for this crate and to view your written documentation!
```shell
cargo doc
```

View file

@ -0,0 +1,10 @@
# Down the Stack
In this Session you will learn more about Rust's split crate model that includes
* the Board Support Crate
* an implementation of the Hardware Abstraction Layer
* the Peripheral Access Crate
This session consists of lectures and two blocks of exercises:
* BSC Exercise: Implementing further features in a basic BSC
* PAC Exercise: Generating a PAC from an SVD file and writing into registers using the the generated PAC.

View file

@ -0,0 +1,103 @@
# Enabling the UARTE0 peripheral
Write a simple program which uses the PAC to enable the UARTE0. See how writing arbitrary values to the ENABLE field in the ENABLE register is unsafe, because only values 0 or 8 should be used.
## In this exercise you will learn how to:
* to safely write into a register
* how to write raw bits into a register
* how to read a register
## Prerequisites
* basic use of closures
* usage of the svd2rust's read/write/modify API
## Tasks
* Find out which values can be written into the `enable` register.
* Take ownership of the board's peripherals
* Write a helper function that reads `UARTE0`'s `enable` register and prints the enable status.
* Enable the UARTE0 peripheral using a safe method.
* Disable the UARTE0 peripheral by writing raw bits in it (unsafe).
Final terminal output:
```terminal
Uarte0 is disabled.
Uarte0 is ensabled.
Uarte0 is disabled.
```
Find the starter code in `down-the-stack/apps/src/bin/uarte_enable.rs`
Find the full solution in `down-the-stack/solutions/src/bin/uarte_enable.rs`
## Step-by-Step Solution
### Step 1: Find the values that can be written in the `enable` register:
0: disabled
8: enabled
### Step 2: Import the PAC
In the Cargo configuration file for the `apps` package, `down-the-stack/apps/Cargo.toml`, add:
```
dk_pac = { path = "../dk_pac", features = ["critical-section"]}
```
In the main source file for the `uarte_enable` binary, `apps/bin/uarte_enable.rs`, add:
```rust
use dk_pac::UARTE0;
```
### Step 3: Take ownership of the peripherals with `take()`
Take ownership of the peripherals with `take()`. Be aware, the take returns an `Option<T>` so that needs to be taken care of. Bind the `UARTE0` peripheral to its own variable.
```rust
let periph = dk_pac::Peripherals::take().unwrap();
let uarte = periph.UARTE0;
```
### Step 4: Write a helper function to get the status of the register.
The helper function either reads the raw bits of the enable register or makes use of the specific method available.
The function prints "Uarte0 is enabled" or "Uarte0 is disabled" depending on the case. Add a function call to `fn main()`.
Run the code. The terminal output should read: "Uarte0 is disabled".
```rust
fn is_uarte_enabled(uarte: &UARTE0) {
if uarte.enable.read().enable().is_enabled() {
defmt::println!("Uarte0 is enabled");
} else {
defmt::println!("Uarte0 is disabled");
}
}
```
### Step 5: Enable the peripheral safely.
Enable the peripheral safely by passing `w.enable().enabled()` in the closure of a call to `write()` on the `enable` field of our UARTE object. Call the helper function after this new line and run your code.
It should print:
```terminal
Uarte0 is disabled.
Uarte0 is ensabled.
```
```rust
uarte.enable.write(|w| w.enable().enabled());
```
### Step 6: Disable the peripheral unsafely by writing raw bits into the register.
Write 0 into the register to disable it by passing the closure `|w| w.bits(0u32)` to the `write()` method. This action is unsafe, so it needs to be in an unsafe block. Call the helper function once more and run your code. Compare your output with the expected output on top of this page.
```rust
unsafe {
uarte.enable.write(|w| w.bits(0x00 as u32));
}
```

View file

@ -0,0 +1,79 @@
# Generating the PAC
Generate your own PAC from an SVD file.
## In this exercise you will learn how to
* generate a PAC from an SVD file.
* format the generated code.
* split the single PAC file into one file per module.
## Prerequisites
* usage of cargo install
* generating docs
## Tasks
* Install `svd2rust` and `form` via cargo.
* Run `svd2rust` on `nrf52.svd` using the `cortex-m` target.
* Split the file into its modules using `form`.
* Format the generated file to make it readable.
* Check the documentation.
## Step-by-Step Solution
✅ Install the necessary tools using the following commands:
```terminal
cargo install svd2rust
cargo install form
```
✅ Go `down-the-stack/dk-pac`. The folder contains the SVD file `nrf52.svd`. We also provide a `Cargo.toml` file, as it will not be generated by svd2rust.
✅ In the terminal, go to the SVD file's location. Run `svd2rust` with the SVD file to generate a PAC using the `cortex-m` target.
```
svd2rust --target cortex-m -i nrf52.svd
```
If you check the folder `down-the-stack/dk-pac` now, you see three new files:
* lib.rs - the file that contains the generated code for the pac
* device.x - linker script that weakly aliases all the interrupt handlers to the default exception handler (DefaultHandler).
* build.rs - build script that places device.x somewhere the linker can find.
✅ Make an `/src` and move the generated `lib.rs` into it.
✅ Open the generated `lib.rs` with an editor.
Notice how it's barely correctly formatted.
✅ Look at the PAC docs with the following command:
```terminal
cargo doc --open
```
✅ Format the crate using `cargo fmt`.
This does not change to the docs, but `lib.rs` is a bit more readable.
✅ Use form to process the `lib.rs` to split it into modules, so that each module in in it's own file.
```terminal
form -i src/lib.rs -o src/
```
✅ Re-run `cargo fmt`.
## Troubleshooting
### `form`
In case the `form` command above causes problems, try this instead:
1. Move `lib.rs` out of `/src`.
2. Run the following command:
```terminal
form -i ./lib.rs -o ./src
```

View file

@ -157,3 +157,6 @@ $ cargo install nrfdfu
(..)
Installed package `nrfdfu v0.1.3` (..)
```
## pySerial for Down The Stack
If you don't have a serial terminal you're familiar with, we recommend using `pySerial`. Follow the installation instructions [here](https://pythonhosted.org/pyserial/pyserial.html#installation). This may require an update of your python installation. This is needed for the second day of the 3 day embedded training.

View file

@ -0,0 +1,47 @@
# Reading PAC Documentation
(This should be covered or at least preceded by a lecture that includes basic use of closures and the read/write/modify API)
Generate and open the PAC's docs using the following command:
```
cargo doc --open
```
In the `Structs` section look for the `struct Peripherals`. Taking ownership of it will be the first step later on. Note that only the method `steal()` is documented. It is an unsafe method, and to be avoided. `Peripherals` has a field named `UARTE0`.
In the `modules` section, look for the `uarte0` module. It is divided into submodules. `enable` is the register we are concerned about. Clicking on it shows the associated type definitions.
* `W` - the writer proxy for the ENABLE register, with the following methods:
* `enable()` returns the field ENABLE writer `ENABLE_W`.
* `unsafe bits()` writes raw bits into the register.
* `R` - the reader proxy for the ENABLE register, with the following methods:
* `enable()` returns the field ENABLE reader `ENABLE_R`.
* `bits()` reads raw bits from the register.
The types `ENABLE_R` and `ENABLE_W` have methods that you can use if you don't want to deal with raw bits. Check them out!
Usage: If you want to write or read something from the uarte register and you want to avoid dealing with raw bits, you first have to call a method that gives you access to the respective reader or writer, and then call the method that does what you want.
Example:
```rust
// this reads the enable register, and returns true if the register is disabled.
uarte.enable.read().is_disabled()
```
Note the difference between the struct field `UARTE0` in `Peripherals` and the module `uarte0`.
## Finding corresponding sections in the PAC
* `dk_pac/src/lib.rs` defines the singleton peripherals as part of a larger `struct Peripherals`. There are two methods for this struct: `take()` and `steal()`. `take()` assures, that only one instance of this can exist. Hence, it's safe. Note that `take()` is only available with the `critical-section` feature enabled.
To generate documentation that includes `take()`, pass the `critical-section` feature flag when generating the docs:
```
cargo doc --open --features critical-section
```
* `dk_pac/src/uarte0.rs` defines a struct that contains all the registers of the `UARTE0` peripheral. The `enable` field represents the register of the same name.
* `dk_pac/src/uarte0/enable.rs` defines the types associated with this register that you already saw in the docs.

View file

@ -0,0 +1,5 @@
# Generating and Using a PAC
In this module you will learn how to generate a PAC (Peripheral Access Crate) from an SVD file, read its documentation and write a small program that enables the UARTE0 peripheral.

View file

@ -0,0 +1,204 @@
# Write the Uarte implementation
## Step-by-Step Solution
### Step 1: Check Documentation.
The UART protocol requires up to four pins, two are mandatory, additional two are optional. they are usually labelled:
* RXD
* TXD
* CTS (optional)
* RTS (optional)
We will only use the mandatory ones.
✅ Check the [User Guide in section 7.2](https://infocenter.nordicsemi.com/topic/ug_nrf52840_dk/UG/dk/vir_com_port.html) to find to find out which pins are reserved for these and what their configuration needs to be.
### Step 2: Explore the `nrf-hal` to find out what needs to be done.
The `nrf52840-hal` is a crate that exports all the `52840` flagged features from the `nrf-hal-common`. Let's take a look at the `nrf52840-hal`'s [Uarte module](https://github.com/nrf-rs/nrf-hal/blob/v0.14.1/nrf-hal-common/src/uarte.rs).
In line 16 we see, that the nRF52840 uses the `hal::pac::UARTE1` peripheral.
In line 44 you find the `struct Uarte<T>(T)`, the interface to a UARTE instance `T`. Besides the instance `T`, the instantiating method takes variables of the following types as arguments: `Pins`, `Parity` and `Baudrate`.
A quick search of the document reveals where to find all of them:
* `Pins`: Line 463
* `Parity` and `Baudrate`: Re-export on line 34
✅ Add the following lines as import:
```
use hal::pac::uarte0::{
baudrate::BAUDRATE_A as Baudrate, config::PARITY_A as Parity};
use hal::uarte;
```
### Step 3: Add `struct Uarte`
✅ Add `struct Uarte` that serves as a wrapper for underlying HAL UART driver.
The struct has one field labelled `inner` which is of type `hal::Uarte` but with the type parameter `T` being set to `hal::pac::UARTE1`.
The main difference between using our `Uarte` object and the underlying HAL `Uarte` object is that *ours* will be pre-configured for the correct pins and baud rate, according to the layout of our nRF52840-DK board and the Virtual COM Port interface chip on the other end of the UART link.
<details>
<summary>Solution</summary>
```rust
pub struct Uarte {
inner: hal::Uarte<hal::pac::UARTE1>,
}
```
</details>
### Step 4: Bring up the peripheral in the `fn init()`
✅ Take a closer look at the definition of the `uarte::Pins` struct in the `nrf-hal`. Compare the pin type configurations with the ones you have already imported in `lib.rs`. Add the ones you're missing.
✅ Create an instance of this struct in `fn init()` with the appropriate pins and configurations. Set the output pin's level to `Level::High`.
Note, that the third and fourth pin are each wrapped in an `Option`: Their type is `None` in this case.
✅ Create a Uarte driver with `hal::uarte::Uarte::new(...)` and bind it to a variable called `uarte` - we will stash this in our own `Uarte` struct later.
Creating the Uarte driver requires four arguments:
* The `UARTE1` instance can be found in the `periph` variable.
* Your instance of the `uarte::Pins` struct.
* Set parity to `Parity::EXCLUDED`
* set the baud rate to `Baudrate::BAUD115200`.
<details>
<summary>Solution</summary>
```rust
let pins = hal::uarte::Pins {
rxd: pins.p0_08.degrade().into_floating_input(),
txd: pins.p0_06.degrade().into_push_pull_output(Level::High),
cts: None,
rts: None,
};
let uarte = hal::uarte::Uarte::new(periph.UARTE1, pins, Parity::EXCLUDED, Baudrate::BAUD115200);
```
</details>
### Step 5: Board struct
✅ Add a field for the `Uarte` struct in the `Board` struct.
add the field to the instance of the `Board` struct in `fn init()`.
<details>
<summary>Solution</summary>
```rust
pub struct Board {
/// LEDs
pub leds: Leds,
/// Buttons
pub buttons: Buttons,
/// Timer
pub timer: Timer,
/// uarte interface
pub uarte: Uarte,
}
// ...
pub fn init() -> Result<Board, ()> {
// ...
Ok(Board {
leds: Leds {
// ...
},
buttons: Buttons {
// ...
},
// 🔼 --- Button Exercise --- 🔼
timer: Timer { inner: timer },
uarte: Uarte { inner: uarte },
})
} else {
Err(())
}
```
</details>
### Step 6: Implementing the `fmt::Write` trait
We want to implement the `fmt::Write` trait so that users can call `write!` on our Uarte object
When implementing this, we can't just write to the `Uarte` instance because a simple write of a string literal would try and read the string literal from flash memory. This does not work because the EasyDMA peripheral in the nRF52 series can only access RAM, not flash.
Instead our implementation must ensure all the strings are copied to a stack allocated buffer and that buffer is passed to the Uarte's `write` method.
✅ Add `use::core::fmt;` to your imports.
✅ Create a public method `write_str`. It takes a mutable reference to self and a `&str` as argument. It returns an `fmt::Result`
✅ Create a buffer. The type is an `array` of 16 u8, set to all 0.
✅ To copy all data into an on-stack buffer, *iterate* over *chunks* of the string and copy them into the buffer (noting that the chunk length may be less than the requested size if you are at the end of the input string).
<details>
<summary>Solution</summary>
```rust
impl fmt::Write for Uarte {
fn write_str(&mut self, s: &str) -> fmt::Result {
// Copy all data into an on-stack buffer so we never try to EasyDMA from
// flash.
let mut buf: [u8; 16] = [0; 16];
for block in s.as_bytes().chunks(16) {
buf[..block.len()].copy_from_slice(block);
self.inner.write(&buf[..block.len()]).map_err(|_| fmt::Error)?;
}
Ok(())
}
}
```
</details>
### Step 7: Connect your computer to the virtual UART
✅ Use the following command to find the address of the nRF52840-DK on your computer.
Linux:
```
$ ls /dev/ttyACM*
```
MacOS:
```
ls /dev/tty.usbmodem*
```
Windows:
a USB Serial Device (COM port) in the Device Manager under the Ports section.
✅ Run the following command to run `miniterm` with the nRF52840-DK with 115200 baud. This includes the default settings of no parity, 8 data bits, 1 stop bit
```
python3 -m serial.tools.miniterm <address> 115200
```
### Step 8: Run the example.
✅ In another terminal window go into the folder `down-the-stack/apps` and use the following command.
```
cargo run --bin uarte_print
```
On your terminal window where `miniterm` runs, "Hello, World" should appear.