Merge pull request #49 from ferrous-systems/edit-book

more didactic changes
This commit is contained in:
Jorge Aparicio 2020-07-15 09:54:03 +00:00 committed by GitHub
commit cce7ccb705
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 559 additions and 470 deletions

304
README.md
View file

@ -2,6 +2,9 @@
Material for the beginner and advanced workshops of Oxidize Global (15.07.2020).
## Required hardware
- [nRF52840 Development Kit (DK)](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK)
@ -9,307 +12,6 @@ Material for the beginner and advanced workshops of Oxidize Global (15.07.2020).
- 2 micro-USB cables
- 2 available USB-A ports on your laptop / PC (you can use a USB hub if you don't have enough ports)
## Checking the hardware
### nRF52840 Dongle
Connect the Dongle to your PC/laptop. Its red LED should start oscillating in intensity. The device will also show up as:
**Windows**: a USB Serial Device (COM port) in the Device Manager under the Ports section
**Linux**: a USB device under `lsusb`. The device will have a VID of `0x1915` and a PID of `0x521f` -- the `0x` prefix will be omitted in the output of `lsusb`:
``` console
$ lsusb
(..)
Bus 001 Device 023: ID 1915:521f Nordic Semiconductor ASA 4-Port USB 2.0 Hub
```
The device will also show up in the `/dev` directory as a `ttyACM` device:
``` console
$ ls /dev/ttyACM*
/dev/ttyACM0
```
**macOS**: a usb device when executing `ioreg -p IOUSB -b -n "Open DFU Bootloader"`. The device will have a vendor ID (`"idVendor"`) of `6421` and a product ID (`"idProduct"`) of `21023`:
``` console
$ ioreg -p IOUSB -b -n "Open DFU Bootloader"
(...)
| +-o Open DFU Bootloader@14300000 <class AppleUSBDevice, id 0x100005d5b, registered, matched, ac$
| {
| (...)
| "idProduct" = 21023
| (...)
| "USB Product Name" = "Open DFU Bootloader"
| (...)
| "USB Vendor Name" = "Nordic Semiconductor"
| "idVendor" = 6421
| (...)
| USB Serial Number" = "CA1781C8A1EE"
| (...)
| }
|
```
The device will show up in the `/dev` directory as `tty.usbmodem<USB Serial Number>`:
``` console
$ ls /dev/tty.usbmodem*
/dev/tty.usbmodemCA1781C8A1EE1
```
### nRF52840 Development Kit (DK)
Connect one end of a micro USB cable to the USB connector *J2* of the board and the other end to your PC. After connecting the DK to your PC/laptop it will show up as:
**Windows**: a removable USB flash drive (named JLINK) and also as a USB Serial Device (COM port) in the Device Manager under the Ports section
**Linux**: a USB device under `lsusb`. The device will have a VID of `0x1366` and a PID of `0x1015` -- the `0x` prefix will be omitted in the output of `lsusb`:
``` console
$ lsusb
(..)
Bus 001 Device 014: ID 1366:1015 SEGGER 4-Port USB 2.0 Hub
```
The device will also show up in the `/dev` directory as a `ttyACM` device:
``` console
$ ls /dev/ttyACM*
/dev/ttyACM0
```
**macOS**: a removable USB flash drive (named JLINK) in Finder and also a USB device named "J-Link" when executing `ioreg -p IOUSB -b -n "J-Link"`.
``` console
$ ioreg -p IOUSB -b -n "J-Link"
(...)
| +-o J-Link@14300000 <class AppleUSBDevice, id 0x10000606a, registered, matched, active, busy 0 $
| {
| (...)
| "idProduct" = 4117
| (...)
| "USB Product Name" = "J-Link"
| (...)
| "USB Vendor Name" = "SEGGER"
| "idVendor" = 4966
| (...)
| "USB Serial Number" = "000683420803"
| (...)
| }
|
```
The device will also show up in the `/dev` directory as `tty.usbmodem<USB Serial Number>`:
``` console
$ ls /dev/tty.usbmodem*
/dev/tty.usbmodem0006834208031
```
The board has several switches to configure its behavior. The out of the box configuration is the one we want. If the above instructions didn't work for you, check the position of the on-board switches:
These directions assume you are holding the board "horizontally" with components (switches and button) facing up. In this horizontal position you'll find one USB connector (J2) on the left edge, another USB connector (J3) on the bottom edge and 4 buttons on the bottom right corner.
- Switch SW8, on the bottom edge left corner, is set to the ON position; this is the left position of the two possible positions
- Switch SW9, to the right the left edge USB connector (J2), is set to the VDD position; this is the center position of the three possible positions
## Installation instructions
### Base Rust installation
Go to https://rustup.rs and follow the instructions.
**Windows**: *Do* install the optional components of the C++ build tools package. The installation size may take up to 2 GB of disk space.
### VS Code
**Windows**: Go to https://code.visualstudio.com and run the installer
**Linux**: Check your Linux distribution package manager (example below). If it's not there follow the instructions on https://code.visualstudio.com/docs/setup/linux
``` console
$ # Arch Linux
$ sudo pacman -S code
```
**macOS**: Go to https://code.visualstudio.com and click on "Download for Mac"
### Rust Analyzer
**All**: Open VS Code and look for Rust Analyzer in the marketplace (bottom icon in the left panel). Then install it.
**Windows**: it's OK to ignore the message about `git` not being installed, if you get one
### Rust Cross compilation support
**All**: Run this command in a terminal:
``` console
$ rustup +stable target add thumbv7em-none-eabi
```
### ELF analysis tools
**All**: Run these commands in a terminal:
``` console
$ rustup +stable component add llvm-tools-preview
$ cargo install cargo-binutils
```
### Cargo subcommands
Install version v0.8.0 of the `cargo-flash` and `cargo-embed` subcommands, as well as the `cargo-binutils` set of subcommands and the `cargo-bloat` subcommand using the following Cargo commands:
``` console
$ cargo install cargo-flash --version 0.8.0 -f
(..)
Installed package `cargo-flash v0.8.0` (..)
$ cargo install cargo-embed --version 0.8.0 -f
(..)
Installed package `cargo-embed v0.8.0` (..)
$ cargo install cargo-binutils
(..)
Installed package `cargo-binutils v0.3.0` (..)
$ cargo install cargo-bloat
(..)
Installed package `cargo-bloat v0.9.3` (..)
```
### Python
**Windows**: Go to https://www.python.org/downloads/ and run the Python *3* installer
- in the installer check the "add Python 3.x to PATH" box
- also run the "Disable path length limit" action at the end, if you are on Windows 10 and the option is displayed to you
**Linux**: Install `pip` using the package manager; this will also install Python.
``` console
$ # Arch Linux
$ sudo pacman -S python-pip
```
**macOS**:
Ensure that you have python 3 and pip installed. Refer to the following link for [Instructions on how to install python 3 and pip](https://docs.python-guide.org/starting/install3/osx/)
```console
$ python --version
Python 3.7.7
$ pip --version
pip 20.0.2 from /usr/local/lib/python3.7/site-packages/pip (python 3.7)
```
### nrfutil
**All**: Open a terminal and install nrfutil as follows. *If you are familiar with python, it is advised to do this in a [virtual environment](https://docs.python.org/3/library/venv.html).*
``` console
$ pip install nrfutil
(..)
$ nrfutil version
nrfutil version 6.1.0
```
### USB (Linux only)
Some of our tools depend on `pkg-config` and `libudev.pc`. Ensure you have the proper packages installed; on Debian based distributions you can use:
``` console
$ sudo apt-get install libudev-dev libusb-1.0-0-dev
```
To access the USB devices as a non-root user, follow these steps:
1. (Optional) Connect the dongle and check its permissions with these commands:
``` console
$ lsusb -d 1915:521f
Bus 001 Device 016: ID 1915:521f Nordic Semiconductor ASA USB Billboard
$ # ^ ^^
$ # take note of the bus and device numbers that appear for you when run the next command
$ ls -l /dev/bus/usb/001/016
crw-rw-r-- 1 root root 189, 15 May 20 12:00 /dev/bus/usb/001/016
```
The `root root` part in `crw-rw-r-- 1 root root` indicates the device can only be accessed by the `root` user.
2. Create the following file with the displayed contents. You'll need root permissions to create the file.
``` console
$ cat /etc/udev/rules.d/50-oxidize-global.rules
# udev rules to allow access to USB devices as a non-root user
# nRF52840 Dongle in bootloader mode
ATTRS{idVendor}=="1915", ATTRS{idProduct}=="521f", TAG+="uaccess"
# nRF52840 Dongle applications
ATTRS{idVendor}=="2020", TAG+="uaccess"
# nRF52840 Development Kit
ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1015", TAG+="uaccess"
```
3. Run the following command to make the new udev rules effective
``` console
$ sudo udevadm control --reload-rules
```
4. (Optional) Disconnect and reconnect the dongle. Then check its permissions again.
``` console
$ lsusb
Bus 001 Device 017: ID 1915:521f Nordic Semiconductor ASA 4-Port USB 2.0 Hub
$ ls -l /dev/bus/usb/001/017
crw-rw-r--+ 1 root root 189, 16 May 20 12:11 /dev/bus/usb/001/017
```
The `+` part in `crw-rw-r--+` indicates the device can be accessed without `root` permissions.
### JLink driver (Windows only)
On Windows you'll need to associate the nRF52840 Development Kit's USB device to the WinUSB driver.
To do that connect the nRF52840 DK to your PC using micro-USB port J2 (as done before) then download and run the [Zadig] tool.
[Zadig]: https://zadig.akeo.ie/
In Zadig's graphical user interface,
1. Select the 'List all devices' option from the Options menu at the top.
2. From the device (top) drop down menu select "BULK interface (Interface 2)"
3. Once that device is selected, `1366 1015` should be displayed in the USB ID field. That's the Vendor ID - Product ID pair.
4. Select 'WinUSB' as the target driver (right side)
5. Click "Install WinUSB driver". The process may take a few minutes to complete.
### `nrf-recover`
Some nRF52840 devices, specially older revisions, may have parts of their Flash memory locked. To unlock the memory use the `nrf-recover` tool.
This is only relevant to the nRF52840 Development Kit. First connect the nRF52840 DK to your PC using micro-USB J2 (as done before) then run the following commands:
``` console
$ cargo install nrf-recover
$ nrf-recover -y
Starting mass erase...
Mass erase completed, chip unlocked
```
## License

View file

@ -1,7 +1,8 @@
# Summary
- [Intro](./intro.md)
- [Hardware](./hardware.md)
- [Preparations](./preparations.md)
- [Checking the Hardware](./hardware.md)
- [Installation Instructions](./installation.md)
- [Beginner Workbook](./beginner-workbook.md)
- [Parts of an Embedded Program](./parts-embedded-program.md)
- [Building an Embedded Program](./building-program.md)
@ -14,6 +15,9 @@
- [Timers and Time](./time.md)
- [nRF52840 Dongle](./dongle.md)
- [Radio Out](./radio-out.md)
- [Radio Setup](./radio-setup.md)
- [Messages](./messages.md)
- [Link Quality Indicator (LQI)](./link-quality.md)
- [Radio In ](./radio-in.md)
- [Radio Puzzle](./radio-puzzle.md)
- [Starting a Project from Scratch](./from-scratch.md)

View file

@ -1 +1,23 @@
# Advanced Workbook
# `advanced`
> Advanced workshop
In this workshop we'll build a toy USB device application that gets enumerated and configured by the host.
The embedded application will run in a fully event driven fashion: only doing work when the host asks for it.
## The nRF52840
Some details about the nRF52840 microcontroller that are relevant to this workshop.
- single core ARM Cortex-M4 processor clocked at 64 MHz
- 1 MB of Flash (at address `0x0000_0000`)
- 256 KB of RAM (at address `0x2000_0000`)
- IEEE 802.15.4 and BLE (Bluetooth Low Energy) compatible radio
- USB controller (device function)
## The nRF52840 Development Kit
The development board we'll use has two USB ports: J2 and J3 -- you can find a description of the board in the top-level README of this repository -- and an on-board J-Link programmer / debugger. USB port J2 is the J-Link's USB port. USB port J3 is the nRF52840's USB port. Connect the Development Kit to your computer using both ports.

View file

@ -1,8 +1,14 @@
# nRF52840 Dongle
Next, we'll look into the radio API exposed by the `dk` HAL. But before that we'll need to set up the nRF52840 Dongle.
From this section on, we'll use the nRF52840 Dongle in addition to the nRF52840 DK. We'll run some pre-compiled programs on the Dongle and write programs for the DK that will interact with the Dongle over a radio link.
Install the `dongle-flash` tool by running the following command from the `tools/dongle-flash` directory.
**💬 How to find the buttons on the Dongle:**
Put the Dongle in front of you, so that the side with the parts mounted on faces up. Rotate it, so that the narrower part of the board, the surface USB connector, faces away from you.
The Dongle has two buttons. They are next to each other in the lower left corner of the Dongle. The reset button (RESET) is mounted sideways, it's square shaped button faces you. Further away from you is the round-ish user button (SW1), which faces up.
✅ Install the `dongle-flash` tool by running the following command from the `tools/dongle-flash` directory.
``` console
$ cargo install --path . -f
@ -12,13 +18,15 @@ The Dongle does not contain an on-board debugger, like the DK, so we cannot use
When put in bootloader mode the Dongle will run a bootloader program instead of the last application that was flashed into it. This bootloader program will make the Dongle show up as a USB CDC ACM device (AKA Serial over USB device) that accepts new application images over this interface. We'll use the `nrfutil` tool to communicate with the bootloader-mode Dongle and flash new images into it.
To put the Dongle in bootloader mode connect it to your laptop / PC / mac and then press its *reset* button. The Dongle has two buttons: a round-ish user button (SW1) and a square-ish reset button (RESET); the latter is mounted "sideways". The buttons are next to each other. The RESET button is mounted closer to the edge of the board that has the Nordic logo on silkscreen and the actual button is facing towards that edge. The opposite edge of the board is narrower and has the surface USB connector; this is the end that goes into your PC USB port.
✅ Connect the Dongle to your computer. Put the Dongle in bootloader mode by pressing its *reset* button.
When the Dongle is in bootloader mode its red LED will oscillate in intensity. The Dongle will also appear as a USB CDC ACM device with vendor ID `0x1915` and product ID `0x521f`.
When the Dongle is in bootloader mode its red LED will oscillate in intensity. Alternatively, the status can be checked using the `usb-list` tool introduced below. The Dongle will also appear as a USB CDC ACM device with vendor ID `0x1915` and product ID `0x521f`.
In the `tools` folder you'll find `usb-list`: a minimal cross-platform version of the `lsusb` tool. Run it (`cargo run` from `tools/usb-list`) to list all USB devices; the Dongle will be highlighted in the output, along with a note if in bootloader mode.
You can also use the tool `usb-list`, a minimal cross-platform version of the `lsusb` tool, to check out the status of the Dongle.
✅ Run `cargo run` from `tools/usb-list` to list all USB devices; the Dongle will be highlighted in the output, along with a note if in bootloader mode.
Output should look like this:
``` console
$ cargo run
(..)
@ -29,8 +37,14 @@ Now that the device is in bootloader mode browse to the `boards/dongle` director
For the next section you'll need to flash the `loopback.hex` file into the Dongle. There are two ways to do this. You can make 2 long `nrfutil` invocations or you can use our `dongle-flash` tool, which will invoke `nrfutil` for you. The `dongle-flash` way is shown below:
✅ Run the following command:
``` console
$ dongle-flash loopback.hex
```
Expected output:
``` console
packaging iHex using nrfutil ...
DONE
[####################################] 100%
@ -39,20 +53,24 @@ Device programmed.
After the device has been programmed it will automatically reset and start running the new application.
The `loopback` application will *blink* the red LED in a heartbeat fashion: two fast blinks (LED on then off) followed by two periods of silence (LED off). The application will also make the Dongle enumerate itself as a CDC ACM device. If you run `usb-list` tool (from the `tools/usb-list` directory) you should see the newly enumerated Dongle in the output:
The `loopback` application will *blink* the red LED in a heartbeat fashion: two fast blinks (LED on then off) followed by two periods of silence (LED off). The application will also make the Dongle enumerate itself as a CDC ACM device.
✅ Run `usb-list` tool from the `tools/usb-list` directory to see the newly enumerated Dongle in the output:
``` console
$ cargo run
Bus 001 Device 020: ID 2020:0309 <- nRF52840 Dongle (loopback.hex)
```
The `loopback` app will log messages over the USB interface. To display these messages on the host we have provided a cross-platform tool: `serial-term`. Install it by running the following command from the `tools/serial-term` directory.
The `loopback` app will log messages over the USB interface. To display these messages on the host we have provided a cross-platform tool: `serial-term`.
✅ Install it by running the following command from the `tools/serial-term` directory.
``` console
$ cargo install --path . -f
```
If you run the `serial-term` application you should see the following output:
✅ Run the `serial-term` application. You should see the following output:
``` console
$ serial-term
@ -61,7 +79,9 @@ deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm app=loopback.hex
This line is printed by the `loopback` app on boot. It contains the device ID of the dongle, a 64-bit unique identifier (so everyone will see a different number); the radio channel that the device will use to communicate; and the transmission power of the radio in dBm.
At this point you should *not* get more output from `serial-term`. If you get "received N bytes" lines in output like this:
At this point you should *not* get more output from `serial-term`.
❗If you get "received N bytes" lines in output like this:
``` console
$ serial-term

View file

@ -1,19 +1,112 @@
# Hardware
# Checking the Hardware
In this workshop we'll use both the nRF52840 Development Kit (DK) and the nRF52840 Dongle. We'll mainly develop programs for the DK and use the Dongle to assist with some exercises.
## nRF52840 Dongle
For the span of this workshop keep the nRF52840 DK connected to your PC using a micro-USB cable. Connect the USB cable to the J2 port on the nRF52840 DK. Instructions to identify the USB ports on the nRF52840 board can be found in the top level README file.
Connect the Dongle to your PC/laptop. Its red LED should start oscillating in intensity. The device will also show up as:
[❗link or crosspost J2 instructions, for less clicking]
**Windows**: a USB Serial Device (COM port) in the Device Manager under the Ports section
## The nRF52840
**Linux**: a USB device under `lsusb`. The device will have a VID of `0x1915` and a PID of `0x521f` -- the `0x` prefix will be omitted in the output of `lsusb`:
Some details about the nRF52840 microcontroller that are relevant to this workshop.
``` console
$ lsusb
(..)
Bus 001 Device 023: ID 1915:521f Nordic Semiconductor ASA 4-Port USB 2.0 Hub
```
- single core ARM Cortex-M4 processor clocked at 64 MHz
- 1 MB of Flash (at address `0x0000_0000`)
- 256 KB of RAM (at address `0x2000_0000`)
- IEEE 802.15.4 and BLE (Bluetooth Low Energy) compatible radio
- USB controller (device function)
The device will also show up in the `/dev` directory as a `ttyACM` device:
[❗Info about Dongle]
``` console
$ ls /dev/ttyACM*
/dev/ttyACM0
```
**macOS**: a usb device when executing `ioreg -p IOUSB -b -n "Open DFU Bootloader"`. The device will have a vendor ID (`"idVendor"`) of `6421` and a product ID (`"idProduct"`) of `21023`:
``` console
$ ioreg -p IOUSB -b -n "Open DFU Bootloader"
(...)
| +-o Open DFU Bootloader@14300000 <class AppleUSBDevice, id 0x100005d5b, registered, matched, ac$
| {
| (...)
| "idProduct" = 21023
| (...)
| "USB Product Name" = "Open DFU Bootloader"
| (...)
| "USB Vendor Name" = "Nordic Semiconductor"
| "idVendor" = 6421
| (...)
| USB Serial Number" = "CA1781C8A1EE"
| (...)
| }
|
```
The device will show up in the `/dev` directory as `tty.usbmodem<USB Serial Number>`:
``` console
$ ls /dev/tty.usbmodem*
/dev/tty.usbmodemCA1781C8A1EE1
```
## nRF52840 Development Kit (DK)
Connect one end of a micro USB cable to the USB connector *J2* of the board and the other end to your PC.
💬 These directions assume you are holding the board "horizontally" with components (switches, buttons and pins) facing up. In this position, rotate the board, so that its concave shaped short side faces right. You'll find one USB connector (J2) on the left edge, another USB connector (J3) on the bottom edge and 4 buttons on the bottom right corner.
After connecting the DK to your PC/laptop it will show up as:
**Windows**: a removable USB flash drive (named JLINK) and also as a USB Serial Device (COM port) in the Device Manager under the Ports section
**Linux**: a USB device under `lsusb`. The device will have a VID of `0x1366` and a PID of `0x1015` -- the `0x` prefix will be omitted in the output of `lsusb`:
``` console
$ lsusb
(..)
Bus 001 Device 014: ID 1366:1015 SEGGER 4-Port USB 2.0 Hub
```
The device will also show up in the `/dev` directory as a `ttyACM` device:
``` console
$ ls /dev/ttyACM*
/dev/ttyACM0
```
**macOS**: a removable USB flash drive (named JLINK) in Finder and also a USB device named "J-Link" when executing `ioreg -p IOUSB -b -n "J-Link"`.
``` console
$ ioreg -p IOUSB -b -n "J-Link"
(...)
| +-o J-Link@14300000 <class AppleUSBDevice, id 0x10000606a, registered, matched, active, busy 0 $
| {
| (...)
| "idProduct" = 4117
| (...)
| "USB Product Name" = "J-Link"
| (...)
| "USB Vendor Name" = "SEGGER"
| "idVendor" = 4966
| (...)
| "USB Serial Number" = "000683420803"
| (...)
| }
|
```
The device will also show up in the `/dev` directory as `tty.usbmodem<USB Serial Number>`:
``` console
$ ls /dev/tty.usbmodem*
/dev/tty.usbmodem0006834208031
```
The board has several switches to configure its behavior. The out of the box configuration is the one we want. If the above instructions didn't work for you, check the position of the on-board switches:
- Switch SW8, located on the bottom edge left corner, is set to the ON position; this is the left position of the two possible positions.
- Switch SW9, located on the surface of the board, to the right of USB connector (J2), is set to the VDD position; this is the center position of the three possible positions.

View file

@ -0,0 +1,194 @@
# Installation Instructions
## Base Rust installation
Go to https://rustup.rs and follow the instructions.
**Windows**: *Do* install the optional components of the C++ build tools package. The installation size may take up to 2 GB of disk space.
## VS Code
**Windows**: Go to https://code.visualstudio.com and run the installer
**Linux**: Check your Linux distribution package manager (example below). If it's not there follow the instructions on https://code.visualstudio.com/docs/setup/linux
``` console
$ # Arch Linux
$ sudo pacman -S code
```
**macOS**: Go to https://code.visualstudio.com and click on "Download for Mac"
## Rust Analyzer
**All**: Open VS Code and look for Rust Analyzer in the marketplace (bottom icon in the left panel). Then install it.
**Windows**: it's OK to ignore the message about `git` not being installed, if you get one
## Rust Cross compilation support
**All**: Run this command in a terminal:
``` console
$ rustup +stable target add thumbv7em-none-eabi
```
## ELF analysis tools
**All**: Run these commands in a terminal:
``` console
$ rustup +stable component add llvm-tools-preview
$ cargo install cargo-binutils
```
## Python
**Windows**: Go to https://www.python.org/downloads/ and run the Python *3* installer
- in the installer check the "add Python 3.x to PATH" box
- also run the "Disable path length limit" action at the end, if you are on Windows 10 and the option is displayed to you
**Linux**: Install `pip` using the package manager; this will also install Python.
``` console
$ # Arch Linux
$ sudo pacman -S python-pip
```
**macOS**:
Ensure that you have python 3 and pip installed. Refer to the following link for [Instructions on how to install python 3 and pip](https://docs.python-guide.org/starting/install3/osx/)
```console
$ python --version
Python 3.7.7
$ pip --version
pip 20.0.2 from /usr/local/lib/python3.7/site-packages/pip (python 3.7)
```
## nrfutil
**All**: Open a terminal and install nrfutil as follows. *If you are familiar with python, it is advised to do this in a [virtual environment](https://docs.python.org/3/library/venv.html).*
``` console
$ pip install nrfutil
(..)
$ nrfutil version
nrfutil version 6.1.0
```
## USB (Linux only)
Some of our tools depend on `pkg-config` and `libudev.pc`. Ensure you have the proper packages installed; on Debian based distributions you can use:
``` console
$ sudo apt-get install libudev-dev libusb-1.0-0-dev
```
To access the USB devices as a non-root user, follow these steps:
1. (Optional) Connect the dongle and check its permissions with these commands:
``` console
$ lsusb -d 1915:521f
Bus 001 Device 016: ID 1915:521f Nordic Semiconductor ASA USB Billboard
$ # ^ ^^
$ # take note of the bus and device numbers that appear for you when run the next command
$ ls -l /dev/bus/usb/001/016
crw-rw-r-- 1 root root 189, 15 May 20 12:00 /dev/bus/usb/001/016
```
The `root root` part in `crw-rw-r-- 1 root root` indicates the device can only be accessed by the `root` user.
2. Create the following file with the displayed contents. You'll need root permissions to create the file.
``` console
$ cat /etc/udev/rules.d/50-oxidize-global.rules
# udev rules to allow access to USB devices as a non-root user
# nRF52840 Dongle in bootloader mode
ATTRS{idVendor}=="1915", ATTRS{idProduct}=="521f", TAG+="uaccess"
# nRF52840 Dongle applications
ATTRS{idVendor}=="2020", TAG+="uaccess"
# nRF52840 Development Kit
ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1015", TAG+="uaccess"
```
3. Run the following command to make the new udev rules effective
``` console
$ sudo udevadm control --reload-rules
```
4. (Optional) Disconnect and reconnect the dongle. Then check its permissions again.
``` console
$ lsusb
Bus 001 Device 017: ID 1915:521f Nordic Semiconductor ASA 4-Port USB 2.0 Hub
$ ls -l /dev/bus/usb/001/017
crw-rw-r--+ 1 root root 189, 16 May 20 12:11 /dev/bus/usb/001/017
```
The `+` part in `crw-rw-r--+` indicates the device can be accessed without `root` permissions.
## JLink driver (Windows only)
On Windows you'll need to associate the nRF52840 Development Kit's USB device to the WinUSB driver.
To do that connect the nRF52840 DK to your PC using micro-USB port J2 (as done before) then download and run the [Zadig] tool.
[Zadig]: https://zadig.akeo.ie/
In Zadig's graphical user interface,
1. Select the 'List all devices' option from the Options menu at the top.
2. From the device (top) drop down menu select "BULK interface (Interface 2)"
3. Once that device is selected, `1366 1015` should be displayed in the USB ID field. That's the Vendor ID - Product ID pair.
4. Select 'WinUSB' as the target driver (right side)
5. Click "Install WinUSB driver". The process may take a few minutes to complete.
## `nrf-recover`
Some nRF52840 devices, specially older revisions, may have parts of their Flash memory locked. To unlock the memory use the `nrf-recover` tool.
This is only relevant to the nRF52840 Development Kit. First connect the nRF52840 DK to your PC using micro-USB J2 (as done before) then run the following commands:
``` console
$ cargo install nrf-recover
$ nrf-recover -y
Starting mass erase...
Mass erase completed, chip unlocked
```
## Cargo subcommands
Install version v0.8.0 of the `cargo-flash` and `cargo-embed` subcommands, as well as the `cargo-binutils` set of subcommands and the `cargo-bloat` subcommand using the following Cargo commands:
``` console
$ cargo install cargo-flash --version 0.8.0 -f
(..)
Installed package `cargo-flash v0.8.0` (..)
$ cargo install cargo-embed --version 0.8.0 -f
(..)
Installed package `cargo-embed v0.8.0` (..)
$ cargo install cargo-binutils
(..)
Installed package `cargo-binutils v0.3.0` (..)
$ cargo install cargo-bloat
(..)
Installed package `cargo-bloat v0.9.3` (..)
```

View file

@ -1,12 +0,0 @@
# Intro
## Icons used
### in development
❗️ To do
### in Production
## About the Course

View file

@ -0,0 +1,25 @@
# Link Quality Indicator (LQI)
```console
received 7 bytes (CRC=Ok(0x2459), LQI=60)
```
✅ Now run the `radio-send` program several times with different variations to explore how LQI can be influenced
- change the distance between the Dongle and the DK -- move the DK closer to or further away from the Dongle.
- change the transmit power
- change the channel
- change the length of the packet
- different combinations of all of the above
Take note of how LQI changes with these changes. Does packet loss occur in any of these configurations?
> NOTE if you decide to send many packets in a single program then you should use the `Timer` API to insert a delay of at least five milliseconds between the transmissions. This is required because the Dongle will use the radio medium right after it receives a packet. Not including the delay will result in the Dongle missing packets
802.15.4 radios are often used in mesh networks like Wireless Sensors Networks (WSN). The devices, or *nodes*, in these networks can be mobile so the distance between nodes can change in time. To prevent a link between two nodes getting broken due to mobility the LQI metric is used to decide the transmission power -- if the metric degrades power should be increased, etc. At the same time, the nodes in these networks often need to be power efficient (e.g. are battery powered) so the transmission power is often set as low as possible -- again the LQI metric is used to pick an adequate transmission power.
## 🔎 802.15.4 compatibility
The radio API we are using follows the PHY layer of the IEEE 802.15.4 specification, but it's missing MAC level features like addressing (each device gets its own address), opt-in acknowledgment (a transmitted packet must be acknowledged with a response acknowledgment packet; the packet is re-transmitted if the packet is not acknowledged in time). These MAC level features are not implemented *in hardware* (in the nRF52840 Radio peripheral) so they would need to be implemented in software to be fully IEEE 802.15.4 compliant.
This is not an issue for the workshop exercises but it's something to consider if you would like to continue from here and build a 802.15.4 compliant network API.

View file

@ -0,0 +1,89 @@
# Messages
In `radio-send.rs` we introduce three different types for messages:
``` rust
let msg: &[u8; 5] = &[72, 101, 108, 108, 111];
let msg: &[u8; 5] = &[b'H', b'e', b'l', b'l', b'o'];
let msg: &[u8; 5] = b"Hello";
```
Here, we explain the different types.
## Slices
The `send` method takes a *reference* -- in Rust, a reference (`&`) is a non-null pointer that's compile-time known to point into valid (e.g. non-freed) memory -- to a `Packet` as argument. A `Packet` is a stack-allocated, fixed-size buffer. You can fill the `Packet` (buffer) with data using the `copy_from_slice` method -- this will overwrite previously stored data.
This `copy_from_slice` method takes a *slice of bytes* (`&[u8]`). A slice is a reference into a list of elements stored in contiguous memory. One way to create a slice is to take a reference to an *array*, a fixed-size list of elements stored in contiguous memory.
``` rust
// stack allocated array
let array: [u8; 3] = [0, 1, 2];
let ref_to_array: &[u8; 3] = &array;
let slice: &[u8] = &array;
```
`slice` and `ref_to_array` are constructed in the same way but have different types. `ref_to_array` is represented in memory as a single pointer (1 word / 4 bytes); `slice` is represented as a pointer + length (2 words / 8 bytes).
Because slices track length at runtime rather than in their type they can point to chunks of memory of any length.
``` rust
let array1: [u8; 3] = [0, 1, 2];
let array2: [u8; 4] = [0, 1, 2, 3];
let mut slice: &[u8] = &array1;
log::info!("{:?}", slice); // length = 3
// now point to the other array
slice = &array2;
log::info!("{:?}", slice); // length = 4
```
## Byte literals
In the example we sent the list of bytes: `[72, 101, 108, 108, 111]`, which can be interpreted as the string `"Hello"`. To see why this is the case check this [list of printable ASCII characters][ascii]. You'll see that letter `H` is represented by the (single-byte) value `72`, `e` by `101`, etc.
[ascii]: https://en.wikipedia.org/wiki/ASCII#Printable_characters
Rust provides a more convenient way to write ASCII characters: byte literals. `b'H'` is syntactic sugar for the literal `72u8`, `b'e'` is equivalent to `101u8`, etc.. So we can rewrite `[72, 101, 108, 108, 111]` as `[b'H', b'e', b'l', b'l', b'o']`. Note that byte literals can also represent `u8` values that are not printable ASCII characters: those values are written using escaped sequences like `b'\x7F'`, which is equivalent to `0x7F`.
## Byte string literals
`[b'H', b'e', b'l', b'l', b'o']` can be further rewritten as `b"Hello"`. This is called a *byte* string literal (note that unlike a string literal like `"Hello"` this one has a `b` before the opening double quote). A byte string literal is a series of byte literals (`u8` values); these literals have type `&[u8; N]` where `N` is the number of byte literals in the string.
Because byte string literals are references you need to *dereference* them to get an array type.
``` rust
let reftoarray: &[u8; 2] = b"Hi";
// these two are equivalent
let array1: [u8; 2] = [b'H', 'i'];
let array2: [u8; 2] = *b"Hi";
// ^ ^ dereference
```
Or if you want to go the other way around: you need to take a reference to an array to get the same type as a byte string literal.
``` rust
// these two are equivalent
let reftoarray1: &[u8; 2] = b"Hi";
let reftoarray2: &[u8; 2] = &[b'H', 'i'];
// ^ ^
```
## Character constraints in byte string vs. string literals
You can encode text as `b"Hello"` or as `"Hello"`.
`b"Hello"` is by definition a string (series) of byte literals so each character has to be a byte literal like `b'A'` or `b'\x7f'`. You cannot use "Unicode characters" (`char` type) like emoji or CJK (Chinese Japanese Korean) in byte string literals.
On the other hand, `"Hello"` is a string literal with type `&str`. `str` strings in Rust contain UTF-8 data so these string literals can contain CJK characters, emoji, Greek letters, Cyrillic script, etc.
## Printing strings and characters
In this workshop we'll work with ASCII strings so byte string literals that contain no escaped characters are OK to use as packet payloads.
You'll note that `log::info!("{:?}", b"Hello")` will print `[72, 101, 108, 108, 111]` rather than `"Hello"` and that the `{}` format specifier (`Display`) does not work. This is because the type of the literal is `&[u8; N]` and in Rust this type means "bytes"; those bytes could be ASCII data, UTF-8 data or something else.
To print this you'll need to convert the slice `&[u8]` into a string (`&str`) using the `str::from_utf8` function. This function will verify that the slice contains well formed UTF-8 data and interpret it as a UTF-8 string (`&str`). As long as we use ASCII data (printable ASCII characters) this conversion will not fail.
Something similar will happen with byte literals: `log::info!("{}", b'A')` will print `65` rather than `A`. To get the `A` output you can cast the byte literal (`u8` value) to the `char` type: `log::info!("{}", b'A' as char)`.

View file

@ -1,6 +1,6 @@
# Panicking
Open the `src/bin/panic.rs` file and click the "Run" button.
Open the `src/bin/panic.rs` file and click the "Run" button.
This program attempts to index an array beyond its length and this results in a panic.
@ -18,7 +18,9 @@ stack backtrace:
8: 0x0000199e - Reset
```
In `no_std` programs the behavior of panic is defined using the `#[panic_handler]` attribute. In the example, the *panic handler* is defined in the `panic_log` crate but we can also implement it manually: comment out the `panic_log` import and add the following function to the example:
In `no_std` programs the behavior of panic is defined using the `#[panic_handler]` attribute. In the example, the *panic handler* is defined in the `panic_log` crate but we can also implement it manually:
✅ Comment out the `panic_log` import and add the following function to the example:
``` rust
#[panic_handler]

View file

@ -0,0 +1,31 @@
# Preparations
This chapter contains informations about the course material, the required hardware and an installation guide.
## Required Hardware
- [nRF52840 Development Kit (DK)](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK)
- [nRF52840 Dongle](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle)
- 2 micro-USB cables
- 2 available USB-A ports on your laptop / PC (you can use a USB hub if you don't have enough ports)
In this workshop we'll use both the nRF52840 Development Kit (DK) and the nRF52840 Dongle. We'll mainly develop programs for the DK and use the Dongle to assist with some exercises.
For the span of this workshop keep the nRF52840 DK connected to your PC using a micro-USB cable. Connect the USB cable to the J2 port on the nRF52840 DK. Instructions to identify the USB ports on the nRF52840 board can be found in the top level README file.
## Required Software
Please install the required software before the course starts.
## Course Material
This book contains the material for the beginner and the advanced workshop in embedded Rust. It aims to be as inclusive as possible. This means, that some information is available in several forms, for example pictures and text description. We also use icons so that different kinds of information are visually distiguishable on the first glance. If you have accessibility needs that are not covered, please let us know.
# Icons and Formatting we use
We use Icons to mark different kinds of information in the book:
* ✅ Call for action
* ❗️ Warnings, Details that require special attention
* 🔎 Knowledge, that gets you deeper into the subject, but you do not have to understand it completely to proceed.
* 💬 Descriptions for Accessibility
> Note: Notes like this one contain helpful information

View file

@ -4,11 +4,13 @@ In this section we'll explore the `recv_timeout` method of the Radio API. As the
The `loopback` application running on the Dongle will broadcast a radio packet after receiving one over channel 20. The contents of this outgoing packet will be the contents of the received one but reversed.
Open the `src/bin/radio-recv.rs` file and click the "Run" button.
Open the `src/bin/radio-recv.rs` file and click the "Run" button.
The Dongle expects the packet to contain only ASCII characters and will not respond to packets that contain non-ASCII data. If you only send packets that contain byte string literals *with no escaped characters* (e.g. `b"hello"`) then this requirement will be satisfied. At the same time the Dongle will always respond with ASCII data so calling `str::from_utf8` on the response should never fail, unless the packet contents got corrupted in the transmission but the CRC should detect this scenario.
The Dongle will respond as soon as it receives a packet. If you insert a delay between the `send` operation and the `recv` operation in the `radio-recv` program this will result in the DK not seeing the Dongle's response. So try this: add a `timer.delay(x)` call before the `recv_timeout` call; try different values of `x` and observe what happens.
The Dongle will respond as soon as it receives a packet. If you insert a delay between the `send` operation and the `recv` operation in the `radio-recv` program this will result in the DK not seeing the Dongle's response. So try this:
✅ Add a `timer.delay(x)` call before the `recv_timeout` call; try different values of `x` and observe what happens.
Having log statements between `send` and `recv_timeout` can also cause packets to be missed so try to keep those two calls as close to each other as possible and with as little code in between as possible.

View file

@ -1,116 +1,9 @@
# Radio Out
Open the `src/bin/radio-send.rs` file.
In this section you'll send radio packets from the DK to the Dongle and get familiar with the different settings of the radio API.
First run the program as it is. You should new output in the output of the `serial-term` program.
``` console
$ serial-term
deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm app=loopback.hex
received 5 bytes (LQI=49)
```
The program broadcasts a radio packet that contains the 5-byte string `Hello` over channel 20 (which has a center frequency of 2450 MHz). The `loopback` program running on the Dongle is listening to all packets sent over channel 20; every time it receives a new packet it reports its length and the Link Quality Indicator (LQI) metric of the transmission over the USB/serial interface. As the name implies the LQI metric indicates how good the connection between the sender and the receiver is.
## Slices
The `send` method takes a *reference* -- in Rust, a reference (`&`) is a non-null pointer that's compile-time known to point into valid (e.g. non-freed) memory -- to a `Packet` as argument. A `Packet` is a stack-allocated, fixed-size buffer. You can fill the `Packet` (buffer) with data using the `copy_from_slice` method -- this will overwrite previously stored data.
This `copy_from_slice` method takes a *slice of bytes* (`&[u8]`). A slice is a reference into a list of elements stored in contiguous memory. One way to create a slice is to take a reference to an *array*, a fixed-size list of elements stored in contiguous memory.
``` rust
// stack allocated array
let array: [u8; 3] = [0, 1, 2];
let ref_to_array: &[u8; 3] = &array;
let slice: &[u8] = &array;
```
`slice` and `ref_to_array` are constructed in the same way but have different types. `ref_to_array` is represented in memory as a single pointer (1 word / 4 bytes); `slice` is represented as a pointer + length (2 words / 8 bytes).
Because slices track length at runtime rather than in their type they can point to chunks of memory of any length.
``` rust
let array1: [u8; 3] = [0, 1, 2];
let array2: [u8; 4] = [0, 1, 2, 3];
let mut slice: &[u8] = &array1;
log::info!("{:?}", slice); // length = 3
// now point to the other array
slice = &array2;
log::info!("{:?}", slice); // length = 4
```
## Byte literals
In the example we sent the list of bytes: `[72, 101, 108, 108, 111]`, which can be interpreted as the string `"Hello"`. To see why this is the case check this [list of printable ASCII characters][ascii]. You'll see that letter `H` is represented by the (single-byte) value `72`, `e` by `101`, etc.
[ascii]: https://en.wikipedia.org/wiki/ASCII#Printable_characters
Rust provides a more convenient way to write ASCII characters: byte literals. `b'H'` is syntactic sugar for the literal `72u8`, `b'e'` is equivalent to `101u8`, etc.. So we can rewrite `[72, 101, 108, 108, 111]` as `[b'H', b'e', b'l', b'l', b'o']`. Note that byte literals can also represent `u8` values that are not printable ASCII characters: those values are written using escaped sequences like `b'\x7F'`, which is equivalent to `0x7F`.
## Byte string literals
`[b'H', b'e', b'l', b'l', b'o']` can be further rewritten as `b"Hello"`. This is called a *byte* string literal (note that unlike a string literal like `"Hello"` this one has a `b` before the opening double quote). A byte string literal is a series of byte literals (`u8` values); these literals have type `&[u8; N]` where `N` is the number of byte literals in the string.
Because byte string literals are references you need to *dereference* them to get an array type.
``` rust
let reftoarray: &[u8; 2] = b"Hi";
// these two are equivalent
let array1: [u8; 2] = [b'H', 'i'];
let array2: [u8; 2] = *b"Hi";
// ^ ^ dereference
```
Or if you want to go the other way around: you need to take a reference to an array to get the same type as a byte string literal.
``` rust
// these two are equivalent
let reftoarray1: &[u8; 2] = b"Hi";
let reftoarray2: &[u8; 2] = &[b'H', 'i'];
// ^ ^
```
## Character constraints in byte string vs. string literals
You can encode text as `b"Hello"` or as `"Hello"`.
`b"Hello"` is by definition a string (series) of byte literals so each character has to be a byte literal like `b'A'` or `b'\x7f'`. You cannot use "Unicode characters" (`char` type) like emoji or CJK (Chinese Japanese Korean) in byte string literals.
On the other hand, `"Hello"` is a string literal with type `&str`. `str` strings in Rust contain UTF-8 data so these string literals can contain CJK characters, emoji, Greek letters, Cyrillic script, etc.
## Printing strings and characters
In this workshop we'll work with ASCII strings so byte string literals that contain no escaped characters are OK to use as packet payloads.
You'll note that `log::info!("{:?}", b"Hello")` will print `[72, 101, 108, 108, 111]` rather than `"Hello"` and that the `{}` format specifier (`Display`) does not work. This is because the type of the literal is `&[u8; N]` and in Rust this type means "bytes"; those bytes could be ASCII data, UTF-8 data or something else.
To print this you'll need to convert the slice `&[u8]` into a string (`&str`) using the `str::from_utf8` function. This function will verify that the slice contains well formed UTF-8 data and interpret it as a UTF-8 string (`&str`). As long as we use ASCII data (printable ASCII characters) this conversion will not fail.
Something similar will happen with byte literals: `log::info!("{}", b'A')` will print `65` rather than `A`. To get the `A` output you can cast the byte literal (`u8` value) to the `char` type: `log::info!("{}", b'A' as char)`.
## Link Quality Indicator (LQI)
Now run the `radio-send` program several times with different variations:
- change the distance between the Dongle and the DK -- move the DK closer to or further away from the Dongle.
- change the transmit power
- change the channel
- change the length of the packet
- different combinations of all of the above
Take note of how LQI changes with these changes. Does packet loss occur in any of these configurations?
> NOTE if you decide to send many packets in a single program then you should use the `Timer` API to insert a delay of at least five milliseconds between the transmissions. This is required because the Dongle will use the radio medium right after it receives a packet. Not including the delay will result in the Dongle missing packets
802.15.4 radios are often used in mesh networks like Wireless Sensors Networks (WSN). The devices, or *nodes*, in these networks can be mobile so the distance between nodes can change in time. To prevent a link between two nodes getting broken due to mobility the LQI metric is used to decide the transmission power -- if the metric degrades power should be increased, etc. At the same time, the nodes in these networks often need to be power efficient (e.g. are battery powered) so the transmission power is often set as low as possible -- again the LQI metric is used to pick an adequate transmission power.
## 802.15.4 compatibility
The radio API we are using follows the PHY layer of the IEEE 802.15.4 specification, but it's missing MAC level features like addressing (each device gets its own address), opt-in acknowledgment (a transmitted packet must be acknowledged with a response acknowledgment packet; the packet is re-transmitted if the packet is not acknowledged in time). These MAC level features are not implemented *in hardware* (in the nRF52840 Radio peripheral) so they would need to be implemented in software to be fully IEEE 802.15.4 compliant.
This is not an issue for the workshop exercises but it's something to consider if you would like to continue from here and build a 802.15.4 compliant network API.

View file

@ -0,0 +1,14 @@
# Radio Setup
✅ Open the `src/bin/radio-send.rs` file.
✅ First run the program `radio-send.rs` as it is. You should see new output in the output of the `serial-term` program.
``` console
$ serial-term
deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm app=loopback.hex
received 5 bytes (LQI=49)
```
The program broadcasts a radio packet that contains the 5-byte string `Hello` over channel 20 (which has a center frequency of 2450 MHz). The `loopback` program running on the Dongle is listening to all packets sent over channel 20; every time it receives a new packet it reports its length and the Link Quality Indicator (LQI) metric of the transmission over the USB/serial interface. As the name implies the LQI metric indicates how good the connection between the sender and the receiver is.

View file

@ -2,17 +2,23 @@
Both `cargo-embed` and `cargo-flash` are tools based on the `probe-rs` library. This library exposes an API to communicate with the J-Link and perform all the operations exposed by the JTAG protocol. For this workshop we have developed a small Cargo runner that uses the `probe-rs` library to streamline the process of running a program and printing logs, like `cargo-embed`, while also having better integration into VS code.
1. Run this command from the `tools/dk-run` folder:
✅ Install the Cargo runner.
1. Run the following command from the `tools/dk-run` folder:
``` console
$ cargo install --path . -f
```
2. Open the `src/bin/hello.rs` file and click the "Run" button that's hovering over the `main` function.
2. Open the `src/bin/hello.rs` file in VS Code and click the "Run" button that's hovering over the `main` function.
Note: you will get the "Run" button if the Rust analyzer's workspace is set to the `beginner/apps` folder. This will be the case if the current folder in VS code (left side panel) is set to `beginner/apps`.
> Note: you will get the "Run" button if the Rust analyzer's workspace is set to the `beginner/apps` folder. This will be the case if the current folder in VS code (left side panel) is set to `beginner/apps`.
If you are not using VS code, run the command `cargo run --bin hello` from within the `beginer/apps` folder. Rust Analyzer's "Run" button is a short-cut for that command.
If you are not using VS code, you can run the program out of your console.
Enter the command `cargo run --bin hello` from within the `beginer/apps` folder. Rust Analyzer's "Run" button is a short-cut for that command.
Expected output:
``` console
$ cargo run --bin hello

View file

@ -2,7 +2,7 @@
Next we'll look into the time related APIs exposed by the `dk` HAL.
Open the `src/bin/blinky.rs` file.
Open the `src/bin/blinky.rs` file.
This program will blink (turn on and off) one of the LEDs on the board. The time interval between each toggle operation is one second. This wait time between consecutive operations is generated by the blocking `timer.wait` operation. This function call will block the program execution for the specified [`Duration`] argument.
@ -10,6 +10,4 @@ This program will blink (turn on and off) one of the LEDs on the board. The time
The other time related API exposed by the `dk` HAL is the `dk::uptime` function. This function returns the time that has elapsed since the call to the `dk::init` function. This function is used in the program to log the time of each LED toggle operation.
Next, we'll look into the radio API exposed by the `dk` HAL. But before that we'll need to set up the nRF52840 Dongle.
[❗assignment to chnge something]

View file

@ -1,23 +1,24 @@
# Using a Hardware Abstraction Layer
[❗Idea: explain HAL, run program, open doc with an assignment to change led pattern]
In this section we'll start using the hardware features of the nRF52840 and the board.
Open the `src/bin/led.rs` file.
The `dk` crate / library is a Hardware Abstraction Layer (HAL) over the nRF52840 Development Kit. The purpose of a HAL is to abstract away the device-specific details of the hardware, for example registers, and instead expose a higher level API more suitable for application development.
The `dk::init` function we have been calling in all programs initializes a few of the nRF52840 peripherals and returns a `Board` structure that provides access to those peripherals. We'll first look at the `Leds` API. Open the documentation for the `dk` crate running the following command from the `beginner/apps` folder:
The `dk::init` function we have been calling in all programs initializes a few of the nRF52840 peripherals and returns a `Board` structure that provides access to those peripherals. We'll first look at the `Leds` API.
✅ Run the `led` program. Two of the green LEDs on the board should turn on; the other two should stay off.
> NOTE this program will not terminate itself. Within VS code you need to click "Kill terminal" (garbage bin icon) in the bottom panel to terminate it.
✅ Open the documentation for the `dk` crate by running the following command from the `beginner/apps` folder:
``` console
$ cargo doc -p dk --open
```
Check the API docs of the `Led` abstraction then run the `led` program. Two of the green LEDs on the board should turn on; the other two should stay off.
Check the API docs of the `Led` abstraction then run the `led` program. Change the `led` program, so that the bottom two leds are turned on, and the top two are turned off.
> NOTE this program will not terminate itself. Within VS code you need to click "Kill terminal" (garbage bin icon) in the bottom panel to terminate it.
Now, uncomment the `log::set_max_level` line. This will make the logs more verbose; they will now include logs from the board initialization function (`dk::init`) and from the `Led` API.
✅ Uncomment the `log::set_max_level` line. This will make the logs more verbose; they will now include logs from the board initialization function (`dk::init`) and from the `Led` API.
Among the logs you'll find the line "I/O pins have been configured for digital output". At this point the electrical pins of the nRF52840 microcontroller have been configured to drive the 4 LEDs on the board.

View file

@ -1,14 +1,8 @@
# Viewing Logs
# Viewing Logs with `cargo-embed`
To observe the program logs you can use the `cargo-embed` tool.
``` console
$ cargo embed --bin hello
```
This command will bring up a Text User Interface (TUI). You should see "Hello, world!" in the output. You can close the interface using Ctrl-C.
`cargo-embed` has no `--chip` flag; instead the target chip needs to be specified in a file named `Embed.toml`. This file must be placed in the root of the Cargo project / workspace, next to the `Cargo.toml` file.
Unlike `cargo flash`, `cargo-embed` has no `--chip` flag; instead the target chip needs to be specified in a file named `Embed.toml`. This file must be placed in the root of the Cargo project / workspace, next to the `Cargo.toml` file.
``` toml
# Embed.toml
@ -16,4 +10,15 @@ This command will bring up a Text User Interface (TUI). You should see "Hello, w
chip = "nRF52840_xxAA"
```
✅ Use the following command to view your logs.
``` console
$ cargo embed --bin hello
```
This command will bring up a Text User Interface (TUI). You should see "Hello, world!" in the output. You can close the interface using Ctrl-C.
**🔎 How does logging work?**
Logging is implemented using the Real Time Transfer (RTT) protocol. Under this protocol the target device writes log messages to a ring buffer stored in RAM; the PC communicates with the J-Link to read out log messages from this ring buffer. This logging approach is non-blocking in the sense that the target device does not have to wait for physical IO (USB comm, serial interface, etc.) to complete while logging messages since they are written to memory. It is possible, however, for the target device to run out of space in its logging ring buffer; this causes old log messages to be overwritten.