This commit is contained in:
Jorge Aparicio 2020-06-09 11:52:27 +02:00
commit 53d10f9a4a
68 changed files with 6876 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target/

258
README.md Normal file
View File

@ -0,0 +1,258 @@
# `embedded-trainings-2020`
Material for the beginner and advanced workshops of Oxidize Global (15.07.2020).
> TODO add license text (from commit 1?)
## 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)
## Checking the hardware
> NOTE To be done *before* the workshop
### 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**: TODO instead of `lsusb` try `system_profiler SPUSBDataType` and/or `ioreg -p IOUSB`. The device should also show up in `/dev/`
### 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**: TODO instead of `lsusb` try `system_profiler SPUSBDataType` and/or `ioreg -p IOUSB`. The device should also show up in `/dev/`
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:
NOTE: 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
> NOTE To be done *before* the workshop
### 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**: TODO(confirm) Go to https://code.visualstudio.com and download the .app
### 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**: TODO(confirm) should come with Python 2 pre-installed. Is `pip` installed? Does `nrfutil` work with Python 2?
### nrfutil
**All**: Open a terminal and run these commands:
``` console
$ pip install nrfutil
(..)
$ nrfutil version
nrfutil version 6.1.0
```
### USB permissions (Linux only)
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 /dev/udev/rules.d/50-oxidize-global.conf
# 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`
> NOTE(japaric) this may not be necessary in the future -- the probe-rs library may do this on its own
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 the `cargo-flash` and `cargo-embed` subcommands using the following command:
``` console
$ cargo install cargo-flash cargo-embed cargo-binutils
(..)
Summary Successfully installed cargo-flash, cargo-embed, cargo-binutils!
```
### "power debug error"
> NOTE(japaric) we need to investigate what this error is about -- it seems to occur only once?
Install `probe-rs-cli`:
``` console
$ cargo install probe-rs-cli
```
Connect the nRF52840 DK to your PC / laptop; use USB connector J2 on the DK. Then run this command:
``` console
$ probe-rs-cli info
```
If you get an error, run the above program above a second time. Let us know what the output of the command(s) is.

284
advanced/README.md Normal file
View File

@ -0,0 +1,284 @@
# `advanced`
> Advanced workshop
In this workshop we'll build a toy USB device application that gets enumerated by the host.
The embedded application will run in a fully event driven fashion: only doing work when the host asks it to.
## Code organization
The `advanced` folder contains both "host" code, code that will run on the host, and "firmware" code, code that will run on the nRF52840 SoC. "host" and "firmware" source code has been placed in different Cargo workspaces so that each can be compiled with different compilation profiles. The `host` workspace will be natively compiled, whereas the `firmware` workspace will be cross-compiled for the ARM Cortex-M architecture.
``` console
$ cd advanced
$ tree -L 1 .
.
├── common
├── firmware
├── host
└── README.md
```
In addition to these two workspaces there's a third folder called "common". This folder contains `no_std` code that be depended on by either "host" code or "firmware" code.
## Listing USB devices
Open the `advanced/host/lsusb` folder in VS Code and run the program.
``` console
$ cargo run
Bus 002 Device 001: ID 1d6b:0003
Bus 001 Device 002: ID 0cf3:e300
Bus 001 Device 003: ID 0c45:6713
Bus 001 Device 001: ID 1d6b:0002
```
The goal is to get the nRF52840 SoC to show in this list. The embedded application will use the vendor ID (VID) and product ID (PID) defined in `advanced/common/consts`; the `lsusb` tool will also look for a USB device with that VID/PID and highlight it in the output.
``` console
$ # expected output
$ cargo run
Bus 002 Device 001: ID 1d6b:0003
Bus 001 Device 002: ID 0cf3:e300
Bus 001 Device 003: ID 0c45:6713
Bus 001 Device 001: ID 1d6b:0002
Bus 001 Device 059: ID 0000:0000 <- !
```
## Hello, world!
0. Open the `tools/dk-run` folder and run `cargo install --path . -f` to install the `dk-run` tool.
1. Open the `advanced/firmware` folder in VS Code.
``` console
$ # or use "File > Open Folder" in VS Code
$ code advanced/firmware
```
2. Open the `src/bin/hello.rs` file.
3. Click the "Run" button
The `firmware` workspace has been configured to cross-compile applications to the ARM Cortex-M architecture and then run them through the `dk-run` custom Cargo runner. The `dk-run` tool will load and run the embedded application on the microcontroller and collect logs from the microcontroller.
The `dk-run` process will terminate when the microcontrollers enters the "halted" state. From the embedded application, one can enter the "halted" state using the `asm::bkpt` function.
## RTIC hello
RTIC, Real Time on Integrated Circuits, is a framework for building evented, time sensitive applications.
1. Open the `advanced/apps` folder in VS Code.
``` console
$ # or use "File > Open Folder" in VS Code
$ code beginner/apps
```
2. Open the `src/bin/rtic-hello.rs` file.
3. Click the "Run" button
> TODO explain differences between `hello` and `rtic-hello`
## Dealing with registers
Open the `advanced/firmware` folder in VS Code; then open the `src/bin/rtic-events.rs` file.
> TODO explain the basics of the svd2rust API
> TODO explain what the code in `init` is doing
## Event handling
"Run" the `rtic-events` application.
Connect a micro-USB cable to your PC/laptop then connect the other end to the DK (TODO specify port).
> TODO explain the event handler
## Adding state
Open the `advanced/firmware` folder in VS Code; then open the `src/bin/rtic-resources.rs` file.
> TODO
You should always disconnect the device from the host before halting the device. Otherwise, the host will observe an unresponsive USB device and try power cycling the whole USB hub / bus.
## USB primer
Some basics about the USB protocol. The protocol is complex so we'll leave out many details and focus on the concepts required to get enumeration working.
A USB device, the nRF52840 in our case, can be one of these three states: the Default state, the Address state or the Configured state.
After being powered the device will start in the Default state. The enumeration process will take the device from the Default state to the Address state. As a result of the enumeration process the device will be assigned an address, in the range `1..=127`, by the host.
Each OS may perform the enumeration process slightly differently but the process will always involve these host actions:
- USB reset. This will put the device in the Default state, regardless of what state it was in.
- GET_DESCRIPTOR request to get the device descriptor.
- SET_ADDRESS request to assign an address to the device.
The device descriptor is a binary encoded data structure sent by the device to the host that contains information about the device, like its product and vendor identifiers and how many *configurations* it has.
A *configuration* is basically an operation mode or profile. The USB device may act as a HID device in one configuration but as a CDC ACM device (serial terminal emulation over USB) in another configuration. The number of configurations the device supports is part of the device descriptor.
> TODO add/info: drivers are bound to interfaces, not devices
> FIXME configuration descriptor requires at least one interface
Like the device descriptor, the configuration descriptor is also a binary encoded data structure sent by the device to the host. This descriptor contains information about the *interfaces* and *endpoints* the configuration uses. An *endpoint* is similar to a UDP or TCP port on a single PC: it allows logical multiplexing on a single physical USB bus. An *interface* is a logical grouping of endpoints.
In this workshop we'll only deal with the control endpoint 0, which is mandatory on all USB devices. Endpoints have directions: in an OUT endpoint data flows from the host to the device; in an IN endpoint data flows from the device to the host. The control endpoint 0 actually refers to two endpoints: endpoint 0 IN (EP0IN) and endpoint 0 OUT (EP0OUT). Although both should be implemented, it's usually sufficient to implement EP0IN to get enumeration working.
A USB device must report at least one configuration. The control endpoint, EP0IN and EP0OUT, however does not need to described in the configuration descriptor so we can report 0 endpoints and 0 interfaces in the configuration descriptor.
## Dealing with USB events
Open the `advanced/firmware` folder in VS Code; then open the `src/bin/rtic-usb-1.rs` file.
The USB peripheral contains a series of registers, called EVENTS registers, that indicate the reason for entering the USBD event handler. These events must be handled by the application to complete the enumeration process.
In this stage you will need to deal with the following USB events until you reach the EP0STATUS event.
- `USBRESET`. This event indicates that the host issued a USB reset signal. According to the USB specification this will move the device from any state to the `Default` state. Where are not dealing with any other state so doing nothing in response to this event is OK for now.
- `EP0SETUP`. The USBD peripheral has detected the SETUP stage of a control transfer. If you get to this point move to the next section.
- `EP0SETUP`. The USBD peripheral is signaling the end of the DATA stage of a control transfer. You won't encounter this event just yet.
## SETUP stage
Control transfers over endpoint 0 consists of three stages: a SETUP stage, an optional DATA stage and a STATUS stage.
The SETUP stage consists of the host sending a 8 byte header to the device. This header describes the host request: is the host expecting a response (data) from the device, is the host about to send some data to the device or is this a request with no data payload? The SETUP stage conveys this information.
The nRF52840 USBD peripheral will parse this header and store its contents in the following registers: BMREQUESTTYPE, BREQUEST, WVALUE{L,H}, WINDEX{L,H} and WLENGTH{L,H}. These registers match the logical division of the setup packet, in *fields*, as specified in the USB 2.0 specification. The fields that start with the letter 'b' are 1-byte large; the ones that start with the letter 'w' are 2-bytes large.
Open the `advanced/firmware` folder in VS Code; then open the `src/bin/rtic-usb-2.rs` file.
The task here is to write a SETUP packet parser in the `common/usb` crate and reach the GOAL comment when executing the program. The starter code has already read the relevant registers using helper functions.
To implement the parser, refer to table 9-3 under section 9.4 of the USB 2.0 specification (page 250). Note that at this stage you only need to be able to parse the `GetDescriptor` variant of the `Request` enum and within that variant you only need to handle the `Device` variant of the `Descriptor` enum.
You should develop this part completely on the host. Switch the workspace to the `common/usb` directory and run the unit tests on the host using Rust Analyzer's "Test" button.
## Device descriptor
After receiving a GET_DESCRIPTOR request during the SETUP stage the device needs to respond with a descriptor during the DATA stage.
Open the `advanced/firmware` folder in VS Code; then open the `src/bin/rtic-usb-3.rs` file.
This starter code parses the GET_DESCRIPTOR request and sends back a zero-byte response to the host using the `Ep0In` abstraction, which we'll cover in the next section. The starter code is wrong because a zero-byte response is not a valid descriptor.
The task in this section is to build a device descriptor using the `usb2::device::Descriptor` API, turn that `Descriptor` instance into a sequence of bytes and respond with that. Note that the device must send back at most `length` bytes to the host; if the byte representation of `Descriptor` is longer than `length` then the slice must be truncated to `length` bytes.
As for the contents of the descriptor you should use these values:
- Vendor ID: `consts::VID`
- Product ID: `consts::PID`
- Max packet size for endpoint 0: 64 bytes
- Number of configurations: 1
- bcdDevice: `0x01_00`, which means version "1.00"
- everything else can be set to `0` or `None`
We suggest you check the API docs of the `usb2` crate by running the command `cargo doc -p usb2 --open`. This should open the API docs in your browser.
More information about the fields of the device descriptor can be found in section 9.6.1 of the USB 2.0 spec.
After that has been fixed you'll reach the EP0DATADONE event. It indicates that the data transfer has completed. You must call the `Ep0In.end` method at that point. After that you'll likely get a new request from the host.
## DMA
Let's zoom into the `Ep0In` abstraction next. You can use the "go to definition" to see the implementation of the `Ep0In.start` method. What this method does is start a DMA transfer to send `bytes` to the host. The data (`bytes`) is first copied into an internal buffer and then the DMA is configured to move the data from that internal buffer to the USBD peripheral.
The signature of the `start` method does *not* ensure that (a) `bytes` won't be deallocated before the DMA transfer is over (e.g. `bytes` could be pointing into the stack) or that (b) `bytes` won't be modified right after the DMA transfer starts (this would be a data race in the general case). For these two safety reasons the API is implemented using an internal buffer. The internal buffer has a `'static` lifetime so it's guaranteed to never be deallocated -- this prevent issue (a). The `busy` flag prevents any further modification to the buffer -- from the public API -- while the DMA transfer is in progress.
Apart from thinking about lifetimes and explicit data races in the surface API one must internally use memory fences to prevent reordering of memory operations (e.g. by the compiler), which can also cause data races. DMA transfers run in parallel to the instructions performed by the processor and are "invisible" to the compiler.
In the implementation of the `start` method, data is copied from `bytes` to the internal buffer, `memcpy` operation, and then the DMA transfer is started. The compiler sees the start of the DMA transfer as an unrelated memory operation so it may move the `memcpy` to *after* the DMA transfer has started. This reordering results in a data race: the processor modifies the internal buffer while the DMA is reading data out from it. To avoid this reordering a memory fence, `dma_start`, is used. The fence pairs with the store operation that starts the DMA transfer and prevents the previous `memcpy`, and any other memory operation, from being move to *after* the store operation.
Another memory fence, `dma_end`, is need at the end of the DMA transfer. In the general case, this prevents instruction reordering that would result in the processor accessing the internal buffer *before* the DMA transfer has finished. This is particularly problematic with DMA transfers that modify a region of memory which the processor intends to read after the transfer.
Not relevant to the DMA operation but relevant to the USB specification, the `start` method sets a shortcut in the USBD peripheral to issue a STATUS stage right after the DATA stage is finished. Thanks to this it is not necessary to manually start a STATUS stage after calling the `end` method.
## Error handling: stalling the endpoint
The USB specification defines a device-side procedure for "stalling a endpoint", which amounts to the device telling the host that it doesn't support some request. This procedure should be used to deal with invalid requests, requests whose SETUP stage doesn't match any USB 2.0 standard request, and requests not supported by the device, for instance the SET_DESCRIPTOR request is not mandatory.
Open the `advanced/firmware` folder in VS Code; then open the `src/bin/rtic-usb-4.rs` file.
In this starter code the code that handles the SETUP stage of endpoint 0 has been refactored into a separate function, `ep0setup`, that uses Rust's built-in error handling features. This function will return the `Err` variant when it encounters an invalid request or a request that is not supported.
You can use the `usbd:ep0stall` helper function or write 1 to the TASKS_EP0STALL register to stall endpoint 0.
## State
The starter code in `src/bin/rtic-usb-4.rs` also passes a `State` variable to the `ep0setup` function. This variable tracks the state of the USB device, which can be one of `Default`, `Address` or `Configured`.
From this point you'll need to track the state of the device in software to be able to correctly respond to the host requests.
The handling of the USB reset condition (`Event::UsbReset`) will need to be updated. According to the USB specification this needs to change the device state, from any state, to the `Default` state.
## SET_ADDRESS
This request changes the device's state as follows:
- If the device is in the `Default` state, then
- if the requested address was `0` (`None` in the `usb2` API) then the device should stay in the `Default` state
- otherwise the device should move to the `Address` state
- If the device is in the `Address` state, then
- if the requested address was `0` (`None` in the `usb2` API) then the device should return to the `Default` state
- otherwise the device should remain in the `Address` state but start using the new address
- If the device is in the `Configured` state this request results in "unspecified" behavior according to the USB specication. You should stall the endpoint in this case.
According to the USB specification the device needs to respond to this request with a STATUS stage -- the DATA stage is omitted. The nRF52840 USBD peripheral will automatically issue the STATUS stage and switch to listening only to the requested address (see the USBADDR register) so no further interaction with the USBD peripheral is required for this request.
For more details, read the introduction of section 6.35.9 of the nRF52840 Product Specification 1.0 (pages 486 and 487).
## Configuration descriptors
When the host issues a GET_DESCRIPTOR request to request a configuration descriptor the device needs to respond with the requested configuration descriptor *plus* all the interface and endpoint descriptors associated to that configuration descriptor during the DATA stage.
For simplicity we'll report that the device has zero interfaces and zero endpoints, other than the control endpoint 0 which doesn't need to be reported. So in response to a `GET_DESCRIPTOR Configuration 0` request the device should respond with the binary representation of a `usb2::configuration::Descriptor` instance. The instance should contain these values:
- wTotalLength: `usb2::configuration::Descriptor::SIZE`, as just the configuration descriptor is being reported back
- bNumInterfaces: `0`
- bConfigurationValue: `1`
- bmAttributes: `{ self_powered: true, remote_wakeup: false }`
- bMaxPower: `250`, equivalent to 500 mA
Note that the index of the `GET_DESCRIPTOR Configuration` request must be `0` because the device reported a single configuration in its device descriptor. Any other index is an "out of bounds" attempt and should be handled by stalling the endpoint.
## (Linux) SET_CONFIGURATION
On Linux, the host will sent a SET_CONFIGURATION request, after enumeration, to put the device in the `Configured` state. You'll need to handle the request according to the following logic:
- If the device is in the `Default` state, you should stall the endpoint.
- If the device is in the `Address` state, then
- if the requested configuration value is 0 (`None` in the `usb2` API) then stay in the `Address` state
- if the requested configuration value is non-zero and valid (was previously reported in a configuration descriptor) then move to the `Configured` state
- if the requested configuration value is not valid then stall the endpoint
- If the device is in the `Configured` state, then
- if the requested configuration value is 0 (`None` in the `usb2` API) then return to the `Address` state
- if the requested configuration value is non-zero and valid (was previously reported in a configuration descriptor) then move to the `Configured` state with the new configuration value
- if the requested configuration value is not valid then stall the endpoint
In all the cases where you did not stall the endpoint (returned `Err`) you'll need to acknowledge the request by starting a STATUS stage. This can be done by writing 1 to the TASKS_EP0STATUS register.
For more details, read the section 9.4.7 'SET_CONFIGURATION' of the USB 2.0 specification.
## (homework) String descriptors
> NOTE(japaric) I hardly think the workshop can fit any more material within the allocated time frame but this is one, of many things, people could continue working on
## References
- [nRF52840 Product Specification 1.0](https://infocenter.nordicsemi.com/pdf/nRF52840_PS_v1.0.pdf)
- [Universal Serial Bus Specification Revision 2.0](https://www.usb.org/document-library/usb-20-specification)

1
advanced/common/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
Cargo.lock

View File

@ -0,0 +1,9 @@
[package]
name = "consts"
version = "0.1.0"
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@ -0,0 +1,4 @@
#![no_std]
pub const VID: u16 = 0x0000;
pub const PID: u16 = 0x0000;

View File

@ -0,0 +1,9 @@
[package]
name = "usb"
version = "0.1.0"
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@ -0,0 +1,135 @@
//! Some USB 2.0 data types
#![deny(missing_docs)]
#![deny(warnings)]
#![no_std]
use core::num::NonZeroU8;
/// Standard USB request
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Request {
/// GET_DESCRIPTOR
GetDescriptor {
/// Requested descriptor
descriptor: Descriptor,
/// How many bytes of data to return
length: u16,
},
/// SET_CONFIGURATION
SetConfiguration {
/// bConfigurationValue to change the device to
value: Option<NonZeroU8>,
},
/// SET_ADDRESS
SetAddress {
/// New device address, in the range `1..=127`
address: Option<NonZeroU8>,
},
}
impl Request {
/// Parses SETUP packet data into a USB request
pub fn parse(
_bmrequesttype: u8,
_brequest: u8,
_wvalue: u16,
_windex: u16,
_wlength: u16,
) -> Result<Self, ()> {
// TODO
Err(())
}
}
/// Descriptor types that appear in GET_DESCRIPTOR requests
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Descriptor {
/// Device descriptor
Device,
/// Configuration descriptor
Configuration {
/// Index of the descriptor
index: u8,
},
}
#[cfg(test)]
mod tests {
use core::num::NonZeroU8;
use crate::{Descriptor, Request};
#[test]
fn get_descriptor_device() {
// OK: GET_DESCRIPTOR Device [length=18]
assert_eq!(
Request::parse(0b1000_0000, 0x06, 0x0100, 0, 18),
Ok(Request::GetDescriptor {
descriptor: Descriptor::Device,
length: 18
})
);
// wrong descriptor index
assert!(Request::parse(0b1000_0000, 0x06, 0x01_01, 0, 18).is_err());
// has language ID but shouldn't
assert!(Request::parse(0b1000_0000, 0x06, 0x01_00, 1033, 18).is_err());
}
#[ignore]
#[test]
fn get_descriptor_configuration() {
// OK: GET_DESCRIPTOR Configuration 0 [length=9]
assert_eq!(
Request::parse(0b1000_0000, 0x06, 0x02_00, 0, 9),
Ok(Request::GetDescriptor {
descriptor: Descriptor::Configuration { index: 0 },
length: 9
})
);
// has language ID but shouldn't
assert!(Request::parse(0b1000_0000, 0x06, 0x02_00, 1033, 9).is_err());
}
#[ignore]
#[test]
fn set_address() {
// OK: SET_ADDRESS 16
assert_eq!(
Request::parse(0b0000_0000, 0x05, 0x00_10, 0, 0),
Ok(Request::SetAddress {
address: NonZeroU8::new(0x10)
})
);
// has language id but shouldn't
assert!(Request::parse(0b0000_0000, 0x05, 0x00_10, 1033, 0).is_err());
// length should be zero
assert!(Request::parse(0b0000_0000, 0x05, 0x00_10, 0, 1).is_err());
}
#[ignore]
#[test]
fn set_configuration() {
// OK: SET_CONFIGURATION 1
assert_eq!(
Request::parse(0b0000_0000, 0x09, 0x00_01, 0, 0),
Ok(Request::SetConfiguration {
value: NonZeroU8::new(1)
})
);
// has language id but shouldn't
assert!(Request::parse(0b0000_0000, 0x09, 0x00_01, 1033, 0).is_err());
// length should be zero
assert!(Request::parse(0b0000_0000, 0x09, 0x00_01, 0, 1).is_err());
}
}

View File

@ -0,0 +1,8 @@
[target.thumbv7em-none-eabi]
runner = "dk-run"
rustflags = [
"-C", "link-arg=-Tlink.x",
]
[build]
target = "thumbv7em-none-eabi"

View File

@ -0,0 +1,9 @@
{
// override the default setting (`cargo check --all-targets`) which produces the following error
// "can't find crate for `test`" when the default compilation target is a no_std target
// with these changes RA will call `cargo check --bins` on save
"rust-analyzer.checkOnSave.allTargets": false,
"rust-analyzer.checkOnSave.extraArgs": [
"--bins"
]
}

420
advanced/firmware/Cargo.lock generated Normal file
View File

@ -0,0 +1,420 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "as-slice"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37dfb65bc03b2bc85ee827004f14a6817e04160e3b1a28931986a666a9290e70"
dependencies = [
"generic-array 0.12.3",
"generic-array 0.13.2",
"stable_deref_trait",
]
[[package]]
name = "autocfg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
[[package]]
name = "bare-metal"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3"
dependencies = [
"rustc_version",
]
[[package]]
name = "bitfield"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
[[package]]
name = "byteorder"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
[[package]]
name = "cast"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0"
dependencies = [
"rustc_version",
]
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "consts"
version = "0.1.0"
[[package]]
name = "cortex-m"
version = "0.6.2"
source = "git+https://github.com/rust-embedded/cortex-m#3136e01e708413774d7be2868705e1782c910027"
dependencies = [
"bare-metal",
"bitfield",
"volatile-register",
]
[[package]]
name = "cortex-m-rt"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00d518da72bba39496024b62607c1d8e37bcece44b2536664f1132a73a499a28"
dependencies = [
"cortex-m-rt-macros",
"r0",
]
[[package]]
name = "cortex-m-rt-macros"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4717562afbba06e760d34451919f5c3bf3ac15c7bb897e8b04862a7428378647"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "cortex-m-rtfm"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaf0b9fd3f042cb3793d15daf3cea201b2f25c99b0b5b936a551bb6909c3ae5b"
dependencies = [
"cortex-m",
"cortex-m-rt",
"cortex-m-rtfm-macros",
"heapless",
"rtfm-core",
]
[[package]]
name = "cortex-m-rtfm-macros"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c62092f6ff344e9b0adb748f0302ed69889ba2fae1fce446e3788d4726ea73bb"
dependencies = [
"proc-macro2",
"quote",
"rtfm-syntax",
"syn",
]
[[package]]
name = "dk"
version = "0.1.0"
dependencies = [
"cortex-m",
"embedded-hal",
"log",
"nrf52840-hal",
"rtt-target",
]
[[package]]
name = "embedded-hal"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee4908a155094da7723c2d60d617b820061e3b4efcc3d9e293d206a5a76c170b"
dependencies = [
"nb",
"void",
]
[[package]]
name = "firmware"
version = "0.1.0"
dependencies = [
"consts",
"cortex-m",
"cortex-m-rt",
"cortex-m-rtfm",
"dk",
"log",
"panic-log",
"quote",
"usb",
"usb2",
]
[[package]]
name = "fpa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f074479d683e5a8fd0bf1251d0a5d91b0d9178b867b44962191ed0eaaf8d4009"
dependencies = [
"cast",
"typenum",
]
[[package]]
name = "generic-array"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
dependencies = [
"typenum",
]
[[package]]
name = "generic-array"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ed1e761351b56f54eb9dcd0cfaca9fd0daecf93918e1cfc01c8a3d26ee7adcd"
dependencies = [
"typenum",
]
[[package]]
name = "hash32"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc"
dependencies = [
"byteorder",
]
[[package]]
name = "heapless"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73a8a2391a3bc70b31f60e7a90daa5755a360559c0b6b9c5cfc0fee482362dc0"
dependencies = [
"as-slice",
"generic-array 0.13.2",
"hash32",
"stable_deref_trait",
]
[[package]]
name = "indexmap"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292"
dependencies = [
"autocfg",
]
[[package]]
name = "log"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
dependencies = [
"cfg-if",
]
[[package]]
name = "nb"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1411551beb3c11dedfb0a90a0fa256b47d28b9ec2cdff34c25a2fa59e45dbdc"
[[package]]
name = "nrf-hal-common"
version = "0.10.0"
source = "git+https://github.com/japaric/nrf-hal?branch=radio#443662d20591c3bef26a4fff2de03de90fec51aa"
dependencies = [
"cast",
"cortex-m",
"embedded-hal",
"fpa",
"nb",
"nrf52840-pac",
"rand_core",
"void",
]
[[package]]
name = "nrf52840-hal"
version = "0.10.0"
source = "git+https://github.com/japaric/nrf-hal?branch=radio#443662d20591c3bef26a4fff2de03de90fec51aa"
dependencies = [
"cast",
"cortex-m",
"embedded-hal",
"nb",
"nrf-hal-common",
"nrf52840-pac",
"void",
]
[[package]]
name = "nrf52840-pac"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1b780a5afd2621774652f28c82837f6aa6d19cf0ad71c734fc1fe53298a2d73"
dependencies = [
"bare-metal",
"cortex-m",
"cortex-m-rt",
"vcell",
]
[[package]]
name = "panic-log"
version = "0.1.0"
dependencies = [
"cortex-m",
"log",
]
[[package]]
name = "proc-macro2"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r0"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f"
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
[[package]]
name = "rtfm-core"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ec893edb2aa5b70320b94896ffea22a7ebb1cf3f942bb67cd5b60a865a63493"
[[package]]
name = "rtfm-syntax"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4455e23c34df3d66454e7e218a4d76a7f83321d04a806be614463341cec4116e"
dependencies = [
"indexmap",
"proc-macro2",
"syn",
]
[[package]]
name = "rtt-target"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58b1f36984bbcf227044b3b7af1de14a6ebe51b9d21cd856a3d5ba41c70ec191"
dependencies = [
"cortex-m",
"ufmt-write",
"vcell",
]
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver",
]
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "stable_deref_trait"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
[[package]]
name = "syn"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f14a640819f79b72a710c0be059dce779f9339ae046c8bef12c361d56702146f"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "typenum"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
[[package]]
name = "ufmt-write"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69"
[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
[[package]]
name = "usb"
version = "0.1.0"
[[package]]
name = "usb2"
version = "0.0.0"
source = "git+https://github.com/japaric/usb2#c2b8118e667026f690c847782ebc5809aba749fe"
[[package]]
name = "vcell"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "volatile-register"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286"
dependencies = [
"vcell",
]

View File

@ -0,0 +1,60 @@
[package]
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
edition = "2018"
name = "firmware"
version = "0.1.0"
[build-dependencies]
consts = { path = "../common/consts" }
quote = "1"
usb2 = { git = "https://github.com/japaric/usb2" }
[dependencies]
consts = { path = "../common/consts" }
cortex-m = "0.6.2"
cortex-m-rt = "0.6.12"
# TODO switch to RTIC before public release
cortex-m-rtfm = "0.5.1"
dk = { path = "../../boards/dk" }
log = "0.4.8"
panic-log = { path = "../../common/panic-log" }
usb = { path = "../common/usb" }
usb2 = { git = "https://github.com/japaric/usb2" }
# optimize code in both profiles
[profile.dev]
codegen-units = 1
debug = 1
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
# faster builds from scratch
[profile.dev.build-override]
codegen-units = 8
debug = false
debug-assertions = false
opt-level = 0
overflow-checks = false
[profile.release.build-override]
codegen-units = 8
debug = false
debug-assertions = false
opt-level = 0
overflow-checks = false
# adds unwind info to external assembly
[patch.crates-io]
cortex-m = { git = "https://github.com/rust-embedded/cortex-m" }

View File

@ -0,0 +1,17 @@
#![no_main]
#![no_std]
use cortex_m::asm;
use cortex_m_rt::entry;
use panic_log as _; // panic handler
#[entry]
fn main() -> ! {
dk::init().unwrap();
log::info!("Hello, world!");
loop {
asm::bkpt();
}
}

View File

@ -0,0 +1,43 @@
#![no_main]
#![no_std]
use cortex_m::asm;
use panic_log as _; // panic handler
#[rtfm::app(device = dk)]
const APP: () = {
#[init]
fn init(_cx: init::Context) {
let board = dk::init().unwrap();
// `POWER` is a peripheral, or register block
let power = board.power;
// INTENSET is one of POWER's registers
// the `write` method writes a (32-bit) value into the register
power.intenset.write(|w| {
// `w` is a "constructor" with methods to clear/set the bitfields of INTENSET
// `w` starts with all bitfields set to their reset value
// USBDETECTED is one of INTENSET's 1-bit fields
w.usbdetected().set_bit()
});
log::info!("USBDETECTED interrupt enabled");
}
#[idle]
fn idle(_cx: idle::Context) -> ! {
log::info!("idle: going to sleep");
// sleep in the background
loop {
asm::wfi();
}
}
#[task(binds = POWER_CLOCK)]
fn on_power_event(_cx: on_power_event::Context) {
log::info!("POWER event occurred");
asm::bkpt();
}
};

View File

@ -0,0 +1,24 @@
#![no_main]
#![no_std]
use cortex_m::asm;
use panic_log as _; // panic handler
#[rtfm::app(device = dk)]
const APP: () = {
#[init]
fn init(_cx: init::Context) {
dk::init().unwrap();
log::info!("Hello");
}
#[idle]
fn idle(_cx: idle::Context) -> ! {
log::info!("world!");
loop {
asm::bkpt();
}
}
};

View File

@ -0,0 +1,38 @@
#![no_main]
#![no_std]
use cortex_m::asm;
use dk::{peripheral::USBD, usbd};
use panic_log as _; // panic handler
#[rtfm::app(device = dk)]
const APP: () = {
struct Resources {
usbd: USBD,
}
#[init]
fn init(_cx: init::Context) -> init::LateResources {
let board = dk::init().unwrap();
// initialize the USB peripheral; will block until the USB cable is physically connected
usbd::init(board.power, &board.usbd);
// electrically connects the device to the host
usbd::connect(&board.usbd);
init::LateResources { usbd: board.usbd }
}
#[task(binds = USBD, resources = [usbd])]
fn usb(cx: usb::Context) {
let usbd = cx.resources.usbd;
log::info!("USB event occurred");
// electrically disconnects the device to the host
usbd::disconnect(usbd);
asm::bkpt();
}
};

View File

@ -0,0 +1,47 @@
#![no_main]
#![no_std]
use dk::{
peripheral::USBD,
usbd::{self, Event},
};
use panic_log as _; // panic handler
#[rtfm::app(device = dk)]
const APP: () = {
struct Resources {
usbd: USBD,
}
#[init]
fn init(_cx: init::Context) -> init::LateResources {
let board = dk::init().unwrap();
usbd::init(board.power, &board.usbd);
usbd::connect(&board.usbd);
init::LateResources { usbd: board.usbd }
}
#[task(binds = USBD, resources = [usbd])]
fn usb(cx: usb::Context) {
let usbd = cx.resources.usbd;
while let Some(event) = usbd::next_event(usbd) {
on_event(usbd, event)
}
}
};
fn on_event(usbd: &USBD, event: Event) {
log::info!("USB: {:?}", event);
match event {
Event::UsbReset => usbd::todo(usbd),
Event::UsbEp0DataDone => usbd::todo(usbd),
Event::UsbEp0Setup => usbd::todo(usbd),
}
}

View File

@ -0,0 +1,82 @@
#![no_main]
#![no_std]
use dk::{
peripheral::USBD,
usbd::{self, Event},
};
use panic_log as _; // panic handler
use usb::{Descriptor, Request};
#[rtfm::app(device = dk)]
const APP: () = {
struct Resources {
usbd: USBD,
}
#[init]
fn init(_cx: init::Context) -> init::LateResources {
let board = dk::init().unwrap();
usbd::init(board.power, &board.usbd);
usbd::connect(&board.usbd);
init::LateResources { usbd: board.usbd }
}
#[task(binds = USBD, resources = [usbd])]
fn on_usbd(cx: on_usbd::Context) {
let usbd = cx.resources.usbd;
while let Some(event) = usbd::next_event(usbd) {
on_event(usbd, event)
}
}
};
fn on_event(usbd: &USBD, event: Event) {
log::info!("USB: {:?}", event);
match event {
Event::UsbReset => {
// nothing to do here at the moment
}
Event::UsbEp0DataDone => usbd::todo(usbd),
Event::UsbEp0Setup => {
let bmrequesttype = usbd.bmrequesttype.read().bits() as u8;
let brequest = usbd.brequest.read().brequest().bits();
let wlength = usbd::wlength(usbd);
let windex = usbd::windex(usbd);
let wvalue = usbd::wvalue(usbd);
log::info!(
"bmrequesttype: {}, brequest: {}, wlength: {}, windex: {}, wvalue: {}",
bmrequesttype,
brequest,
wlength,
windex,
wvalue
);
if let Ok(Request::GetDescriptor { descriptor, length }) =
Request::parse(bmrequesttype, brequest, wvalue, windex, wlength)
{
match descriptor {
Descriptor::Device => {
// GOAL
log::info!("GET_DESCRIPTOR Device [length={}]", length);
usbd::todo(usbd)
}
_ => usbd::todo(usbd),
}
} else {
usbd::todo(usbd)
}
}
}
}

View File

@ -0,0 +1,97 @@
#![no_main]
#![no_std]
use dk::{
peripheral::USBD,
usbd::{self, Ep0In, Event},
};
use panic_log as _; // panic handler
// use one of these
// use usb::{Descriptor, Request}; // your implementation
use usb2::{GetDescriptor as Descriptor, StandardRequest as Request}; // crates.io impl
#[rtfm::app(device = dk)]
const APP: () = {
struct Resources {
usbd: USBD,
ep0in: Ep0In,
#[init([0; 64])]
buffer: [u8; 64],
}
#[init(resources = [buffer])]
fn init(cx: init::Context) -> init::LateResources {
let buffer: &'static mut [u8; 64] = cx.resources.buffer;
let ep0in = Ep0In::new(buffer);
let board = dk::init().unwrap();
usbd::init(board.power, &board.usbd);
usbd::connect(&board.usbd);
init::LateResources {
usbd: board.usbd,
ep0in,
}
}
#[task(binds = USBD, resources = [usbd, ep0in])]
fn on_usbd(cx: on_usbd::Context) {
let usbd = cx.resources.usbd;
let ep0in = cx.resources.ep0in;
while let Some(event) = usbd::next_event(usbd) {
on_event(usbd, ep0in, event)
}
}
};
fn on_event(usbd: &USBD, ep0in: &mut Ep0In, event: Event) {
log::info!("USB: {:?}", event);
match event {
Event::UsbReset => {
// nothing to do here at the moment
}
Event::UsbEp0DataDone => usbd::todo(usbd),
Event::UsbEp0Setup => {
let bmrequesttype = usbd.bmrequesttype.read().bits() as u8;
let brequest = usbd.brequest.read().brequest().bits();
let wlength = usbd::wlength(usbd);
let windex = usbd::windex(usbd);
let wvalue = usbd::wvalue(usbd);
log::info!(
"bmrequesttype: {}, brequest: {}, wlength: {}, windex: {}, wvalue: {}",
bmrequesttype,
brequest,
wlength,
windex,
wvalue
);
if let Ok(Request::GetDescriptor { descriptor, length }) =
Request::parse(bmrequesttype, brequest, wvalue, windex, wlength)
{
match descriptor {
Descriptor::Device => {
log::info!("GET_DESCRIPTOR Device [length={}]", length);
// FIXME send back a valid device descriptor, truncated to `length` bytes
// let desc = usb2::device::Descriptor { .. };
let resp = [];
let _ = ep0in.start(&resp, usbd);
}
_ => usbd::todo(usbd),
}
} else {
usbd::todo(usbd)
}
}
}
}

View File

@ -0,0 +1,126 @@
#![no_main]
#![no_std]
use dk::{
peripheral::USBD,
usbd::{self, Ep0In, Event},
};
use panic_log as _; // panic handler
// use one of these
// use usb::{Descriptor, Request}; // your implementation
use usb2::State;
use usb2::{GetDescriptor as Descriptor, StandardRequest as Request}; // crates.io impl
#[rtfm::app(device = dk)]
const APP: () = {
struct Resources {
#[init([0; 64])]
buffer: [u8; 64],
usbd: USBD,
ep0in: Ep0In,
state: State,
}
#[init(resources = [buffer])]
fn init(cx: init::Context) -> init::LateResources {
let buffer: &'static mut [u8; 64] = cx.resources.buffer;
let ep0in = Ep0In::new(buffer);
let board = dk::init().unwrap();
usbd::init(board.power, &board.usbd);
usbd::connect(&board.usbd);
init::LateResources {
usbd: board.usbd,
state: State::Default,
ep0in,
}
}
#[task(binds = USBD, resources = [usbd, ep0in, state])]
fn on_usbd(cx: on_usbd::Context) {
let usbd = cx.resources.usbd;
let ep0in = cx.resources.ep0in;
let state = cx.resources.state;
while let Some(event) = usbd::next_event(usbd) {
on_event(usbd, ep0in, state, event)
}
}
};
fn on_event(usbd: &USBD, ep0in: &mut Ep0In, state: &mut State, event: Event) {
log::info!("USB: {:?}", event);
match event {
Event::UsbReset => {
// TODO change `state`
}
Event::UsbEp0DataDone => ep0in.end(usbd),
Event::UsbEp0Setup => {
if ep0setup(usbd, ep0in, state).is_err() {
// unsupported or invalid request: stall the endpoint
log::warn!("EP0IN: stalled");
usbd::todo(usbd)
}
}
}
}
fn ep0setup(usbd: &USBD, ep0in: &mut Ep0In, state: &mut State) -> Result<(), ()> {
let bmrequesttype = usbd.bmrequesttype.read().bits() as u8;
let brequest = usbd.brequest.read().brequest().bits();
let wlength = usbd::wlength(usbd);
let windex = usbd::windex(usbd);
let wvalue = usbd::wvalue(usbd);
log::info!(
"bmrequesttype: {}, brequest: {}, wlength: {}, windex: {}, wvalue: {}",
bmrequesttype,
brequest,
wlength,
windex,
wvalue
);
let req = Request::parse(bmrequesttype, brequest, wvalue, windex, wlength)?;
log::info!("{:?}", req);
match req {
Request::GetDescriptor { descriptor, length } => match descriptor {
Descriptor::Device => {
let desc = usb2::device::Descriptor {
bDeviceClass: 0,
bDeviceProtocol: 0,
bDeviceSubClass: 0,
bMaxPacketSize0: usb2::device::bMaxPacketSize0::B64,
bNumConfigurations: core::num::NonZeroU8::new(1).unwrap(),
bcdDevice: 0x01_00, // 1.00
iManufacturer: None,
iProduct: None,
iSerialNumber: None,
idProduct: consts::PID,
idVendor: consts::VID,
};
let bytes = desc.bytes();
let _ = ep0in.start(&bytes[..core::cmp::min(bytes.len(), length.into())], usbd);
}
_ => usbd::todo(usbd),
},
Request::SetAddress { .. } => usbd::todo(usbd),
Request::SetConfiguration { .. } => usbd::todo(usbd),
// this request is not supported
_ => return Err(()),
}
Ok(())
}

View File

@ -0,0 +1,212 @@
#![no_main]
#![no_std]
use dk::{
peripheral::USBD,
usbd::{self, Ep0In, Event},
};
use panic_log as _; // panic handler
use usb2::{GetDescriptor, StandardRequest, State};
#[rtfm::app(device = dk)]
const APP: () = {
struct Resources {
ep0in: Ep0In,
state: State,
usbd: USBD,
}
#[init]
fn init(_cx: init::Context) -> init::LateResources {
static mut BUFFER: [u8; 64] = [0; 64];
let board = dk::init().unwrap();
let ep0in = Ep0In::new(BUFFER);
usbd::init(board.power, &board.usbd);
usbd::connect(&board.usbd);
init::LateResources {
usbd: board.usbd,
state: State::Default,
ep0in,
}
}
#[task(binds = USBD, resources = [state, usbd, ep0in])]
fn usb(cx: usb::Context) {
let usbd = cx.resources.usbd;
let state = cx.resources.state;
let ep0in = cx.resources.ep0in;
while let Some(event) = usbd::next_event(usbd) {
on_event(usbd, state, ep0in, event)
}
}
};
fn on_event(usbd: &USBD, state: &mut State, ep0in: &mut Ep0In, event: Event) {
log::info!("USB: {:?}", event);
match event {
Event::UsbReset => {
log::info!("USB reset condition detected");
*state = State::Default;
}
Event::UsbEp0DataDone => {
log::info!("EP0IN: transfer complete");
ep0in.end(usbd)
}
Event::UsbEp0Setup => {
if ep0setup(usbd, state, ep0in).is_err() {
log::warn!("EP0IN: stalled");
usbd::ep0stall(usbd);
}
}
}
}
/// The `bConfigurationValue` of the only supported configuration
const CONFIG_VAL: u8 = 1;
fn ep0setup(usbd: &USBD, state: &mut State, ep0in: &mut Ep0In) -> Result<(), ()> {
let bmrequesttype = usbd.bmrequesttype.read().bits() as u8;
let brequest = usbd.brequest.read().brequest().bits();
let wlength = usbd::wlength(usbd);
let windex = usbd::windex(usbd);
let wvalue = usbd::wvalue(usbd);
let request = StandardRequest::parse(bmrequesttype, brequest, wvalue, windex, wlength)?;
log::info!("{:?}", request);
match request {
// section 9.4.3
// this request is valid in any state
StandardRequest::GetDescriptor { descriptor, length } => match descriptor {
GetDescriptor::Device => {
let desc = usb2::device::Descriptor {
bDeviceClass: 0,
bDeviceProtocol: 0,
bDeviceSubClass: 0,
bMaxPacketSize0: usb2::device::bMaxPacketSize0::B64,
bNumConfigurations: core::num::NonZeroU8::new(1).unwrap(),
bcdDevice: 0x0100, // 1.00
iManufacturer: None,
iProduct: None,
iSerialNumber: None,
idProduct: consts::PID,
idVendor: consts::VID,
};
let bytes = desc.bytes();
ep0in.start(&bytes[..core::cmp::min(bytes.len(), length.into())], usbd)?
}
GetDescriptor::Configuration { index } => {
if index == 0 {
let desc = usb2::configuration::Descriptor {
wTotalLength: usb2::configuration::Descriptor::SIZE.into(),
bNumInterfaces: 0,
bConfigurationValue: core::num::NonZeroU8::new(CONFIG_VAL).unwrap(),
iConfiguration: None,
bmAttributes: usb2::configuration::bmAttributes {
self_powered: true,
remote_wakeup: false,
},
bMaxPower: 250, // 500 mA
};
let bytes = desc.bytes();
ep0in.start(&bytes[..core::cmp::min(bytes.len(), length.into())], usbd)?;
} else {
// out of bounds access: stall the endpoint
return Err(());
}
}
_ => return Err(()),
},
StandardRequest::SetAddress { address } => {
match state {
State::Default => {
if let Some(address) = address {
*state = State::Address(address);
} else {
// stay in the default state
}
}
State::Address(..) => {
if let Some(address) = address {
// use the new address
*state = State::Address(address);
} else {
*state = State::Default;
}
}
// unspecified behavior
State::Configured { .. } => return Err(()),
}
// the response to this request is handled in hardware
}
StandardRequest::SetConfiguration { value } => {
log::info!("SET_CONFIGURATION {:?} ({:?})", value, state);
match *state {
// unspecified behavior
State::Default => return Err(()),
State::Address(address) => {
if let Some(value) = value {
if value.get() == CONFIG_VAL {
log::info!("entering the configured state");
*state = State::Configured { address, value };
} else {
log::error!("unsupported configuration value");
return Err(());
}
} else {
// stay in the address mode
}
}
State::Configured {
address,
value: curr_value,
} => {
if let Some(new_value) = value {
if new_value.get() == CONFIG_VAL {
if new_value != curr_value {
log::info!("changing configuration");
*state = State::Configured {
address,
value: new_value,
};
}
} else {
log::error!("unsupported configuration value");
return Err(());
}
} else {
log::info!("returned to the address state");
*state = State::Address(address);
}
}
}
usbd.tasks_ep0status
.write(|w| w.tasks_ep0status().set_bit());
}
// stall any other request
_ => return Err(()),
}
Ok(())
}

183
advanced/host/lsusb/Cargo.lock generated Normal file
View File

@ -0,0 +1,183 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "adler32"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2"
[[package]]
name = "bit-set"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3"
[[package]]
name = "cc"
version = "1.0.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "consts"
version = "0.1.0"
[[package]]
name = "crc32fast"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
dependencies = [
"cfg-if",
]
[[package]]
name = "filetime"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "affc17579b132fc2461adf7c575cc6e8b134ebca52c51f5411388965227dc695"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"winapi",
]
[[package]]
name = "libc"
version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f"
[[package]]
name = "libflate"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9135df43b1f5d0e333385cb6e7897ecd1a43d7d11b91ac003f4d2c2d2401fdd"
dependencies = [
"adler32",
"crc32fast",
"rle-decode-fast",
"take_mut",
]
[[package]]
name = "libusb1-sys"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d9ddd446b6f233a79ef7e6f73de63a58f3a9047d60c46f15cda31452a8f86e"
dependencies = [
"cc",
"libc",
"libflate",
"pkg-config",
"tar",
"vcpkg",
]
[[package]]
name = "lsusb"
version = "0.1.0"
dependencies = [
"consts",
"rusb",
]
[[package]]
name = "pkg-config"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
[[package]]
name = "redox_syscall"
version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
[[package]]
name = "rle-decode-fast"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac"
[[package]]
name = "rusb"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d10caa3e5fc7ad1879a679bf16d3304ea10614b8f2f1a1386be4ec942d44062a"
dependencies = [
"bit-set",
"libc",
"libusb1-sys",
]
[[package]]
name = "take_mut"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
[[package]]
name = "tar"
version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3196bfbffbba3e57481b6ea32249fbaf590396a52505a2615adbb79d9d826d3"
dependencies = [
"filetime",
"libc",
"redox_syscall",
"xattr",
]
[[package]]
name = "vcpkg"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
[[package]]
name = "winapi"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "xattr"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c"
dependencies = [
"libc",
]

View File

@ -0,0 +1,9 @@
[package]
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
edition = "2018"
name = "lsusb"
version = "0.1.0"
[dependencies]
consts = { path = "../../common/consts" }
rusb = "0.5.5"

View File

@ -0,0 +1,26 @@
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
for dev in rusb::devices()?.iter() {
let desc = dev.device_descriptor()?;
let mut show_config = false;
let suffix = match (desc.vendor_id(), desc.product_id()) {
(0x1366, 0x1015) => " <- J-Link on the nRF52840 Development Kit",
(0x1915, 0x521f) => " <- nRF52840 Dongle (in bootloader mode)",
(0x2020, 0x0309) => " <- nRF52840 Dongle (loopback.hex)",
(consts::VID, consts::PID) => {
show_config = true;
" <- nRF52840 on the nRF52840 Development Kit"
}
_ => "",
};
println!("{:?}{}", dev, suffix);
if show_config {
let desc = dev.config_descriptor(0)?;
println!("> {:?}", desc);
}
}
Ok(())
}

397
beginner/README.md Normal file
View File

@ -0,0 +1,397 @@
# `beginner`
> Beginner workshop
## 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.
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.
## 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)
## Parts of an embedded program
1. Open the `beginner/apps` folder in VS Code.
``` console
$ # or use "File > Open Folder" in VS Code
$ code beginner/apps
```
2. Open the `src/bin/hello.rs` file.
Note the differences between this embedded program and a desktop program:
The `#![no_std]` attribute indicates that the program will not make use of the standard library, `std` crate. Instead it will use the `core` library, a subset of the standard library that does not on a underlying operating system (OS).
The `#![no_main]` attribute indicates that the program will use a custom entry point instead of the default one: `fn main() { .. }`.
The `#[entry]` attribute declares the custom entry point of the program. The entry point must be a divergent function; note that the return type is the never type `!`. The function is not allowed to return; therefore the program is not allowed to terminate.
## Building the program
The following command cross compiles the program to the ARM Cortex-M4 architecture. The `--target` arguments instructs Cargo to cross compile the program.
``` console
$ cargo build --target thumbv7em-none-eabi --bin hello
```
The default in a new Cargo project is to compile for the host (native compilation). Within the `beginner/apps` folder you can however omit the `--target ` flag and Cargo will still cross compile for the ARM Cortex-M4 architecture.
``` console
$ cargo build --bin hello
```
The reason for this is that the default compilation target has been set to ARM Cortex-M4 in the Cargo configuration file (`.cargo/config`):
``` text
# .cargo/config
[build]
target = "thumbv7em-none-eabi"
```
The output of the compilation process will be an ELF (Executable and Linkable Format) file. The file will be placed in the `beginner/apps/target` directory. To display the amount of Flash the program will occupy on the target device use the `rust-size` tool (part of the `cargo-binutils` package):
``` console
$ rust-size target/thumbv7em-none-eabi/debug/hello
text data bss dec hex filename
14564 8 2124 16696 4138 target/thumbv7em-none-eabi/debug/hello
```
`14460` bytes is the amount of Flash memory the program will occupy.
Alternatively, you can run the `cargo-size` subcommand, which will build the program before displaying the size of the binary.
``` console
$ cargo size --bin hello
text data bss dec hex filename
14564 8 2124 16696 4138 hello
```
Passing the `-A` flag to `rust-size` or `cargo-size` will give a more detailed breakdown of the static memory usage:
``` console
$ # omit the `--` flag if using `rust-size`
$ cargo size --bin hello -- -A
hello :
section size addr
.vector_table 256 0x0
.text 9740 0x100
.rodata 4568 0x270c
.data 8 0x20000000
.bss 2124 0x20000008
.uninit 0 0x20000854
```
The `.vector_table` section contains the *vector table*, a data structure required by the Cortex-M ISA. The `.text` section contains the instructions the program will execute. The `.rodata` section contains constants like strings literals. These three sections are contiguously located in Flash memory -- Flash memory spans from address `0x0000_0000` to `0x0010_0000` (1 MB).
The next three sections, `.data`, `.bss` and `.uninit`, are located in RAM -- RAM memory spans the address range `0x2000_0000` - `0x2004_0000` (256 KB). These sections contain statically allocated variables (`static` variables).
## Flashing the program
The following command will flash the ELF file to the device.
``` console
$ cargo flash --chip nRF52840_xxAA --elf target/thumbv7em-none-eabi/debug/hello
```
Alternatively you can run this command, which builds the application before flashing it.
``` console
$ cargo flash --chip nRF52840_xxAA --bin hello
```
The `cargo-flash` subcommand flashes and runs the program but won't display logs. It is a deployment tool.
The flashing process consists of the PC communicating with a second microcontroller on the nRF52840 DK over USB (J2 port). This second microcontroller, named J-Link, is connected to the nRF52840 through a electrical interface known as JTAG. The JTAG protocol specifies procedures for reading memory, writing to memory, halting the target processor, reading the target processor registers, etc.
## Viewing logs
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.
``` toml
# Embed.toml
[general]
chip = "nRF52840_xxAA"
```
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 overrun the ring buffer; this causes old log messages to be overwritten.
## Running the program from VS code
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. Browse to the `tools/dk-run` folder and run this command from there:
``` console
$ cargo install --path . -f
```
2. Open the `beginner/apps` folder in VS Code; then open the `src/bin/hello.rs` file and click the "Run" button that's hovering over the `main` function.
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.
``` console
$ cargo run --bin hello
INFO:hello -- Hello, world!
stack backtrace:
0: 0x0000229c - __bkpt
1: 0x0000030e - hello::__cortex_m_rt_main
2: 0x0000011a - main
3: 0x00001ba2 - Reset
```
`cargo run` will compile the application and then invoke the `dk-run` tool with its argument set to the path of the output ELF file.
Unlike `cargo-embed`, `dk-run` will terminate when the program reaches a breakpoint (`asm::bkpt`) that halts the device. Before exiting `dk-run` will print a stack backtrace of the program starting from the breakpoint. This can be used to write small test programs that are meant to perform some work and then terminate.
## Panicking
Open the `beginner/apps` folder in VS Code then 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.
``` console
ERROR:panic_log -- panicked at 'index out of bounds: the len is 3 but the index is 3', src/bin/panic.rs:29:13
stack backtrace:
0: 0x000022f0 - __bkpt
1: 0x00002010 - rust_begin_unwind
2: 0x00000338 - core::panicking::panic_fmt
3: 0x00000216 - core::panicking::panic_bounds_check
4: 0x0000016a - panic::bar
5: 0x00000158 - panic::foo
6: 0x00000192 - panic::__cortex_m_rt_main
7: 0x00000178 - main
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:
``` rust
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
log::error!("{}", info);
loop {
asm::bkpt()
}
}
```
Now run the program again. Try changing the format string of the `error!` macro.
## Using a Hardware Abstraction Layer (HAL)
In this section we'll start using the hardware features of the nRF52840 and the board.
Open the `beginner/apps` folder in VS Code then 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:
``` 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.
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.
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 has been configured to drive the 4 LEDs on the board.
After the `dk::init` logs you'll find logs about the `Led` API. As the logs indicate an LED becomes active when the output of the pin is a *logical zero*, which is also referred as the "low" state. This "active low" configuration does not apply to all boards: it depends on how the pins have been wired to the LEDs. You should refer to the [board documentation] to find out which pins are connected to LEDs and whether "active low" or "active high" applies to it.
[board documentation]: https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrf52840_dk%2FUG%2Fnrf52840_DK%2Fintro.html
## Timers and time
Next we'll look into the time related APIs exposed by the `dk` HAL.
Open the `beginner/apps` folder in VS Code then 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.
[`Duration`]: https://doc.rust-lang.org/core/time/struct.Duration.html
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.
## 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.
``` console
$ cargo install --path . -f
```
The Dongle does not contain an on-board debugger, like the DK, so we cannot use `probe-rs` tools to write programs into it. Instead, the Dongle's stock firmware comes with a *bootloader*.
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.
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`. If you have the `lsusb` tool installed (Linux distributions have it) then you can run it to check the presence of the USB device:
``` console
$ lsusb
(..)
Bus 001 Device 016: ID 1915:521f Nordic Semiconductor ASA 4-Port USB 2.0 Hub
```
If you don't have the `lsusb` tool installed, we have provided a cross-platform version of it in the `advanced/host/lsusb` folder. Call `cargo run` from that directory to run the tool.
``` console
$ cargo run
(..)
Bus 001 Device 016: ID 1915:521f <- nRF52840 Dongle (in bootloader mode)
```
Now that the device is in bootloader mode browse to the `boards/dongle` directory. You'll find some `*.hex` files there. These are pre-compiled Rust programs that have been converted into the Intel Hex format that the `nrfutil` tool expects.
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:
``` console
$ dongle-flash loopback.hex
packaging iHex using nrfutil ...
DONE
[####################################] 100%
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 our `lsusb` tool (from the `advanced/host/lsusb` directory) you should 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.
``` console
$ cargo install --path . -f
```
If you run the `serial-term` application you should see the following output:
``` console
$ serial-term
deviceid=588c06af0877c8f2 channel=20 TxPower=+8dBm
(..)
```
> NOTE The application may take a while to print to the console
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.
Leave the Dongle connected and the `serial-term` application running. Now we'll switch back to the Development Kit.
## Radio out
Open the `beginner/apps` folder in VS Code; then 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
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 device is.
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
> 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
The radio interface we are using follows 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.
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.
## Radio in
In this section we'll explore the `recv` method of the Radio API. As the name implies, this is used to listen for packets. The method will block the program execution until a packet is received. We'll continue to use the Dongle in this section; it should be running the `loopback` application; and the `serial-term` application should also be running in the background.
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 `beginner/apps` folder in VS Code; then open the `src/bin/radio-recv.rs` file and click the "Run" button.
The Dongle will response 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.
In a fully IEEE 802.15.4 compliant implementation one can mark a packet as "requires acknowledgment". The recipient must respond to these packets with an acknowledgment packet; if the sender doesn't receive the acknowledgment packet it will re-transmit the packet. This feature is part of the MAC layer and not implemented in the `Radio` API we are using so packet loss is possible even when the radios are close enough to communicate.
## Radio puzzle
> TODO(japaric) before this section maybe cover collision avoidance
For this section you'll need to flash the `puzzle.hex` program on the Dongle. Follow the instructions from the "nRF52840 Dongle" section but flash the `puzzle.hex` program instead of the `loopback.hex` one -- don't forget to put the Dongle in bootloader mode before invoking `dongle-flash`.
Like in the previous sections the Dongle will listen for radio packets over channel 20 while also logging messages over a USB/serial interface.
Open the `beginner/apps` folder in VS Code; then open the `src/bin/radio-puzzle.rs` file.
Your task in this section is to decrypt the [substitution cipher] encrypted *ascii* string stored in the Dongle. The string has been encrypted using *simple substitution*.
[substitution cipher]: https://en.wikipedia.org/wiki/Substitution_cipher
The Dongle will respond differently depending on the length of the incoming packet:
- On zero-sized packets it will respond with the encrypted string.
- On one-byte sized packets it will respond with the *direct* mapping from a *plaintext* letter -- the letter contained in the packet -- to the *ciphertext* letter.
- On packets of any other length the Dongle will respond with the string `correct` if it received the decrypted string, otherwise it will respond with the `incorrect` string.
Our suggestion is to use a dictionary / map. `std::collections::HashMap` is not available in `no_std` code (without linking to a global allocator) but you can use one of the maps in the [`heapless`] crate. To make this crate available in your application get the latest version from [crates.io] and add it to the `beginner/apps/Cargo.toml` file, for example:
[`heapless`]: https://docs.rs/heapless
[crates.io]: https://crates.io/crates/heapless
``` toml
# Cargo.toml
[dependencies]
heapless = "0.5.0"
```
If you haven't use a stack-allocated collection before note that you'll need to specify the capacity of the collection as a type parameter using one of the "type-level values" in the `heapless::consts` module. The crate level documentation of the `heapless` crate has some examples.
P.S. The plaintext string is *not* stored in `puzzle.hex` so running `strings` on it will not give you the answer.
---
> NOTE additional content, if needed / desired
## Starting a project from scratch
> cortex-m-quickstart
## (extra) adding addresses to packets
> have people use the `ieee802154` crate to add a MAC header to the radio packet. New dongle firmware would be required to respond differently to broadcast packets and addressed packets

View File

@ -0,0 +1,8 @@
[target.thumbv7em-none-eabi]
runner = "dk-run"
rustflags = [
"-C", "link-arg=-Tlink.x",
]
[build]
target = "thumbv7em-none-eabi"

9
beginner/apps/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,9 @@
{
// override the default setting (`cargo check --all-targets`) which produces the following error
// "can't find crate for `test`" when the default compilation target is a no_std target
// with these changes RA will call `cargo check --bins` on save
"rust-analyzer.checkOnSave.allTargets": false,
"rust-analyzer.checkOnSave.extraArgs": [
"--bins"
]
}

284
beginner/apps/Cargo.lock generated Normal file
View File

@ -0,0 +1,284 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "apps"
version = "0.1.0"
dependencies = [
"cortex-m",
"cortex-m-rt",
"dk",
"log",
"panic-log",
]
[[package]]
name = "bare-metal"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3"
dependencies = [
"rustc_version",
]
[[package]]
name = "bitfield"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
[[package]]
name = "cast"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0"
dependencies = [
"rustc_version",
]
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cortex-m"
version = "0.6.2"
source = "git+https://github.com/rust-embedded/cortex-m#c9c7539233954822a6132f4bc13e5763371b5cb2"
dependencies = [
"bare-metal",
"bitfield",
"volatile-register",
]
[[package]]
name = "cortex-m-rt"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00d518da72bba39496024b62607c1d8e37bcece44b2536664f1132a73a499a28"
dependencies = [
"cortex-m-rt-macros",
"r0",
]
[[package]]
name = "cortex-m-rt-macros"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4717562afbba06e760d34451919f5c3bf3ac15c7bb897e8b04862a7428378647"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "dk"
version = "0.1.0"
dependencies = [
"cortex-m",
"cortex-m-rt",
"embedded-hal",
"log",
"nrf52840-hal",
"rtt-target",
]
[[package]]
name = "embedded-hal"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee4908a155094da7723c2d60d617b820061e3b4efcc3d9e293d206a5a76c170b"
dependencies = [
"nb",
"void",
]
[[package]]
name = "fpa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f074479d683e5a8fd0bf1251d0a5d91b0d9178b867b44962191ed0eaaf8d4009"
dependencies = [
"cast",
"typenum",
]
[[package]]
name = "log"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
dependencies = [
"cfg-if",
]
[[package]]
name = "nb"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1411551beb3c11dedfb0a90a0fa256b47d28b9ec2cdff34c25a2fa59e45dbdc"
[[package]]
name = "nrf-hal-common"
version = "0.10.0"
source = "git+https://github.com/japaric/nrf-hal?branch=radio#d624e80e5724e4709081ed65abaf63271fe1eca7"
dependencies = [
"cast",
"cortex-m",
"embedded-hal",
"fpa",
"nb",
"nrf52840-pac",
"rand_core",
"void",
]
[[package]]
name = "nrf52840-hal"
version = "0.10.0"
source = "git+https://github.com/japaric/nrf-hal?branch=radio#d624e80e5724e4709081ed65abaf63271fe1eca7"
dependencies = [
"cast",
"cortex-m",
"embedded-hal",
"nb",
"nrf-hal-common",
"nrf52840-pac",
"void",
]
[[package]]
name = "nrf52840-pac"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1b780a5afd2621774652f28c82837f6aa6d19cf0ad71c734fc1fe53298a2d73"
dependencies = [
"bare-metal",
"cortex-m",
"cortex-m-rt",
"vcell",
]
[[package]]
name = "panic-log"
version = "0.1.0"
dependencies = [
"cortex-m",
"log",
]
[[package]]
name = "proc-macro2"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r0"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f"
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
[[package]]
name = "rtt-target"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58b1f36984bbcf227044b3b7af1de14a6ebe51b9d21cd856a3d5ba41c70ec191"
dependencies = [
"cortex-m",
"ufmt-write",
"vcell",
]
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver",
]
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "syn"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "typenum"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
[[package]]
name = "ufmt-write"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69"
[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
[[package]]
name = "vcell"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "876e32dcadfe563a4289e994f7cb391197f362b6315dc45e8ba4aa6f564a4b3c"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "volatile-register"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d67cb4616d99b940db1d6bd28844ff97108b498a6ca850e5b6191a532063286"
dependencies = [
"vcell",
]

50
beginner/apps/Cargo.toml Normal file
View File

@ -0,0 +1,50 @@
[package]
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
edition = "2018"
name = "apps"
version = "0.1.0"
[dependencies]
cortex-m = "0.6.2"
cortex-m-rt = "0.6.12"
dk = { path = "../../boards/dk" }
log = "0.4.8"
panic-log = { path = "../../common/panic-log" }
# optimize code in both profiles
[profile.dev]
codegen-units = 1
debug = 1
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
# faster builds from scratch
[profile.dev.build-override]
codegen-units = 8
debug = false
debug-assertions = false
opt-level = 0
overflow-checks = false
[profile.release.build-override]
codegen-units = 8
debug = false
debug-assertions = false
opt-level = 0
overflow-checks = false
# adds unwind info to external assembly
[patch.crates-io]
cortex-m = { git = "https://github.com/rust-embedded/cortex-m" }

6
beginner/apps/Embed.toml Normal file
View File

@ -0,0 +1,6 @@
[general]
chip = "nRF52840_xxAA"
[rtt]
enabled = true
channels = [{ up = 0 }]

View File

@ -0,0 +1,26 @@
#![no_main]
#![no_std]
use core::time::Duration;
use cortex_m_rt::entry;
use panic_log as _; // panic handler
#[entry]
fn main() -> ! {
// uncomment to enable more verbose logs
// log::set_max_level(log::LevelFilter::Trace);
let board = dk::init().unwrap();
let mut led = board.leds._1;
let mut timer = board.timer;
for _ in 0..10 {
led.toggle();
timer.wait(Duration::from_secs(1));
log::info!("LED toggled at {:?}", dk::uptime());
}
dk::exit()
}

View File

@ -0,0 +1,19 @@
#![no_main]
#![no_std]
use cortex_m::asm;
use cortex_m_rt::entry;
use panic_log as _; // the panicking behavior
#[entry]
fn main() -> ! {
// initializes the peripherals
dk::init().unwrap();
log::info!("Hello, world!"); // :wave:
loop {
// breakpoint: halts the program's execution
asm::bkpt();
}
}

View File

@ -0,0 +1,25 @@
#![no_main]
#![no_std]
use cortex_m::asm;
use cortex_m_rt::entry;
use panic_log as _; // the panicking behavior
#[entry]
fn main() -> ! {
// uncomment to make logs more verbose
// log::set_max_level(log::LevelFilter::Trace);
let board = dk::init().unwrap();
let mut leds = board.leds;
leds._1.on();
leds._2.off();
leds._3.off();
leds._4.on();
// this program does not `exit`; use Ctrl+C to terminate it
loop {
asm::nop();
}
}

View File

@ -0,0 +1,32 @@
#![no_main]
#![no_std]
use cortex_m::asm;
use cortex_m_rt::entry;
use panic_log as _; // the panicking behavior
#[entry]
fn main() -> ! {
dk::init().unwrap();
foo();
loop {
asm::bkpt();
}
}
#[inline(never)]
fn foo() {
asm::nop();
bar();
}
#[inline(never)]
fn bar() {
let i = 3;
let array = [0, 1, 2];
let x = array[i]; // out of bounds access
log::info!("{}", x);
}

View File

@ -0,0 +1,34 @@
#![deny(unused_must_use)]
#![no_main]
#![no_std]
use core::str;
use cortex_m_rt::entry;
use dk::ieee802154::Packet;
use panic_log as _; // the panicking behavior
#[entry]
fn main() -> ! {
let board = dk::init().unwrap();
let mut radio = board.radio;
let mut packet = Packet::new();
// try these
let msg = "";
// let msg = "A";
// let msg = "Hello?";
packet.copy_from_slice(msg.as_bytes());
radio.send(&packet);
log::info!("sent: {:?}", msg);
radio.recv(&mut packet).ok();
if let Ok(s) = str::from_utf8(&*packet) {
log::info!("received: {}", s);
} else {
log::info!("received: {:?}", &*packet);
}
dk::exit()
}

View File

@ -0,0 +1,30 @@
#![deny(unused_must_use)]
#![no_main]
#![no_std]
use core::str;
use cortex_m::asm;
use cortex_m_rt::entry;
use dk::ieee802154::Packet;
use panic_log as _; // the panicking behavior
#[entry]
fn main() -> ! {
// initializes the peripherals
let board = dk::init().unwrap();
let mut radio = board.radio;
let mut packet = Packet::new();
let msg = b"olleh";
packet.copy_from_slice(msg);
radio.send(&packet);
log::info!("sent: {:?}", msg);
let crc = radio.recv(&mut packet);
let s = str::from_utf8(&*packet).expect("response is not valid UTF-8");
log::info!("received: {} (CRC={:?})", s, crc);
loop {
asm::bkpt();
}
}

View File

@ -0,0 +1,28 @@
#![deny(unused_must_use)]
#![no_main]
#![no_std]
use cortex_m::asm;
use cortex_m_rt::entry;
use dk::ieee802154::{Packet, Channel, TxPower};
use panic_log as _; // the panicking behavior
#[entry]
fn main() -> ! {
let board = dk::init().unwrap();
let mut radio = board.radio;
// these are the initial settings
radio.set_channel(Channel::_20);
radio.set_txpower(TxPower::Pos8dBm);
let mut packet = Packet::new();
packet.copy_from_slice(b"Hello");
//radio.send(&packet);
let res = radio.try_send(&packet);
log::info!("{:?}", res);
loop {
asm::bkpt();
}
}

1
boards/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
Cargo.lock

13
boards/dk/Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
edition = "2018"
name = "dk"
version = "0.1.0"
[dependencies]
cortex-m = "0.6.2"
cortex-m-rt = "0.6.12"
embedded-hal = "0.2.3"
hal = { package = "nrf52840-hal", git = "https://github.com/japaric/nrf-hal", branch = "radio" }
log = "0.4.8"
rtt-target = { version = "0.2.0", features = ["cortex-m"] }

7
boards/dk/README.md Normal file
View File

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

12
boards/dk/build.rs Normal file
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(())
}

5
boards/dk/memory.x Normal file
View File

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

13
boards/dk/src/errata.rs Normal file
View File

@ -0,0 +1,13 @@
/// USBD cannot be enabled
pub unsafe fn e187a() {
(0x4006_EC00 as *mut u32).write_volatile(0x9375);
(0x4006_ED14 as *mut u32).write_volatile(3);
(0x4006_EC00 as *mut u32).write_volatile(0x9375);
}
/// USBD cannot be enabled
pub unsafe fn e187b() {
(0x4006_EC00 as *mut u32).write_volatile(0x9375);
(0x4006_ED14 as *mut u32).write_volatile(0);
(0x4006_EC00 as *mut u32).write_volatile(0x9375);
}

308
boards/dk/src/lib.rs Normal file
View File

@ -0,0 +1,308 @@
//! Hardware Abstraction Layer (HAL) for the nRF52840 Development Kit
#![no_std]
use core::{
sync::atomic::{AtomicU32, Ordering},
time::Duration,
};
use cortex_m::asm;
use embedded_hal::digital::v2::{OutputPin as _, StatefulOutputPin};
pub use hal::ieee802154;
pub use hal::target::{interrupt, Interrupt, NVIC_PRIO_BITS, RTC0};
use hal::{
clocks::{Clocks, ExternalOscillator, LfOscConfiguration, LfOscStarted},
gpio::{p0, Level, Output, Pin, PushPull},
ieee802154::Radio,
rtc::{Rtc, RtcInterrupt},
timer::OneShot,
};
use log::{LevelFilter, Log};
use rtt_target::{rprintln, rtt_init_print};
use crate::peripheral::{POWER, USBD};
mod errata;
pub mod peripheral;
pub mod usbd;
/// Components on the board
pub struct Board {
pub leds: Leds,
// TODO put behind feature flag (off in advanced workshop)
pub radio: Radio<'static>,
pub timer: Timer,
// TODO put behind feature flag (off in beginner workshop)
pub usbd: USBD,
pub power: POWER,
}
/// All LEDs on the board
pub struct Leds {
/// LED1: pin P0.13, green LED
pub _1: Led,
/// LED2: pin P0.14, green LED
pub _2: Led,
/// LED3: pin P0.15, green LED
pub _3: Led,
/// LED4: pin P0.16, green LED
pub _4: Led,
}
/// A single LED
pub struct Led {
inner: Pin<Output<PushPull>>,
}
impl Led {
/// Turns on the LED
pub fn on(&mut self) {
log::trace!(
"setting P{}.{} low (LED on)",
if self.inner.port { '1' } else { '0' },
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) {
log::trace!(
"setting P{}.{} high (LED off)",
if self.inner.port { '1' } else { '0' },
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()
}
}
}
/// A timer for blocking delay
pub struct Timer {
inner: hal::Timer<hal::target::TIMER0, OneShot>,
}
impl Timer {
/// Blocks program execution for at least the specified `duration`
pub fn wait(&mut self, duration: Duration) {
log::trace!("blocking for {:?} ...", duration);
// 1 cycle = 1 microsecond
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)
}
log::trace!("... DONE");
}
}
// add Instant API
/// Initializes the board
///
/// This return an `Err`or if called more than once
pub fn init() -> Result<Board, ()> {
if let (Some(mut core), Some(periph)) = (
cortex_m::Peripherals::take(),
hal::target::Peripherals::take(),
) {
// NOTE(unsafe) this branch runs at most once
static mut CLOCKS: Option<Clocks<ExternalOscillator, ExternalOscillator, LfOscStarted>> =
None;
// NOTE this must be executed as early as possible or the tool will timeout
// NOTE the unsafety of this macro is incorrect; it must be run at most once
rtt_init_print!(NoBlockSkip, 2048);
log::set_logger(&Logger).unwrap();
// if not configured in the application we default to the `Info` level
if log::max_level() == LevelFilter::Off {
log::set_max_level(LevelFilter::Info)
}
log::debug!("Initializing the board");
let clocks = Clocks::new(periph.CLOCK);
let clocks = clocks.enable_ext_hfosc();
let clocks = clocks.set_lfclk_src_external(LfOscConfiguration::NoExternalNoBypass);
let clocks = clocks.start_lfclk();
// extend lifetime to `'static`
let clocks = unsafe { CLOCKS.get_or_insert(clocks.enable_ext_hfosc()) };
log::debug!("Clocks configured");
let mut rtc = Rtc::new(periph.RTC0);
rtc.enable_interrupt(RtcInterrupt::Overflow, Some(&mut core.NVIC));
rtc.enable_counter();
log::debug!("RTC started");
let pins = p0::Parts::new(periph.P0);
// NOTE LEDs turn on when the pin output level is low
let _1 = pins.p0_13.degrade().into_push_pull_output(Level::High);
let _2 = pins.p0_14.degrade().into_push_pull_output(Level::High);
let _3 = pins.p0_15.degrade().into_push_pull_output(Level::High);
let _4 = pins.p0_16.degrade().into_push_pull_output(Level::High);
log::debug!("I/O pins have been configured for digital output");
let timer = hal::Timer::new(periph.TIMER0);
let mut radio = Radio::init(periph.RADIO, clocks);
// set TX power to its maximum value
radio.set_txpower(ieee802154::TxPower::Pos8dBm);
log::debug!("Radio initialized and configured with TX power set to the maximum value");
Ok(Board {
leds: Leds {
_1: Led { inner: _1 },
_2: Led { inner: _2 },
_3: Led { inner: _3 },
_4: Led { inner: _4 },
},
radio,
timer: Timer { inner: timer },
usbd: periph.USBD,
power: periph.POWER,
})
} else {
Err(())
}
}
struct Logger;
impl Log for Logger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= log::STATIC_MAX_LEVEL
}
fn log(&self, record: &log::Record) {
if !self.enabled(record.metadata()) {
return;
}
rprintln!(
"{}:{} -- {}",
record.level(),
record.target(),
record.args()
);
}
fn flush(&self) {}
}
// Counter of OVERFLOW events -- an OVERFLOW occurs every (1<<24) ticks
static OVERFLOWS: AtomicU32 = AtomicU32::new(0);
// NOTE this will at the highest priority, higher priority than RTIC tasks
#[interrupt]
fn RTC0() {
let curr = OVERFLOWS.load(Ordering::Relaxed);
OVERFLOWS.store(curr + 1, Ordering::Relaxed);
// clear the EVENT register
unsafe { core::mem::transmute::<_, RTC0>(()).events_ovrflw.reset() }
}
/// Exits the application and prints a backtrace when the program is executed through the `dk-run`
/// Cargo runner
pub fn exit() -> ! {
loop {
asm::bkpt()
}
}
/// Returns the time elapsed since the call to the `dk::init` function
///
/// The clock that is read to compute this value has a resolution of 30 microseconds.
///
/// Calling this function before calling `dk::init` will return a value of `0` nanoseconds.
pub fn uptime() -> Duration {
// here we are going to perform a 64-bit read of the number of ticks elapsed
//
// a 64-bit load operation cannot performed in a single instruction so the operation can be
// preempted by the RTC0 interrupt handler (which increases the OVERFLOWS counter)
//
// the loop below will load both the lower and upper parts of the 64-bit value while preventing
// the issue of mixing a low value with an "old" high value -- note that, due to interrupts, an
// arbitrary amount of time may elapse between the `hi1` load and the `low` load
let overflows = &OVERFLOWS as *const AtomicU32 as *const u32;
let ticks = loop {
unsafe {
// NOTE volatile is used to order these load operations among themselves
let hi1 = overflows.read_volatile();
let low = core::mem::transmute::<_, RTC0>(())
.counter
.read()
.counter()
.bits();
let hi2 = overflows.read_volatile();
if hi1 == hi2 {
break u64::from(low) | (u64::from(hi1) << 24);
}
}
};
// 2**15 ticks = 1 second
let freq = 1 << 15;
let secs = ticks / freq;
// subsec ticks
let ticks = (ticks % freq) as u32;
// one tick is equal to `1e9 / 32768` nanos
// the fraction can be reduced to `1953125 / 64`
// which can be further decomposed as `78125 * (5 / 4) * (5 / 4) * (1 / 4)`.
// Doing the operation this way we can stick to 32-bit arithmetic without overflowing the value
// at any stage
let nanos =
(((ticks % 32768).wrapping_mul(78125) >> 2).wrapping_mul(5) >> 2).wrapping_mul(5) >> 2;
Duration::new(secs, nanos as u32)
}

View File

@ -0,0 +1 @@
pub use hal::target::{POWER, USBD};

216
boards/dk/src/usbd.rs Normal file
View File

@ -0,0 +1,216 @@
use core::sync::atomic::{self, Ordering};
use cortex_m::asm;
use crate::{
errata,
peripheral::{POWER, USBD},
};
/// Endpoint IN 0
pub struct Ep0In {
buffer: &'static mut [u8; 64],
busy: bool,
}
impl Ep0In {
pub fn new(buffer: &'static mut [u8; 64]) -> Self {
Self {
buffer,
busy: false,
}
}
pub fn start(&mut self, bytes: &[u8], usbd: &USBD) -> Result<(), ()> {
if self.busy {
log::error!("EP0IN: last transfer not completed");
return Err(());
}
if bytes.len() > self.buffer.len() {
log::error!("EP0IN: multi-packet data transfers are not supported");
return Err(());
}
let n = bytes.len();
self.buffer[..n].copy_from_slice(bytes);
// use a "shortcut" to issue a status stage after the data transfer is complete
usbd.shorts
.modify(|_, w| w.ep0datadone_ep0status().set_bit());
usbd.epin0
.maxcnt
.write(|w| unsafe { w.maxcnt().bits(n as u8) });
usbd.epin0
.ptr
.write(|w| unsafe { w.ptr().bits(self.buffer.as_ptr() as u32) });
self.busy = true;
log::info!("EP0IN: start {}B transfer", n);
// start DMA transfer
dma_start();
usbd.tasks_startepin[0].write(|w| w.tasks_startepin().set_bit());
Ok(())
}
pub fn end(&mut self, usbd: &USBD) {
if usbd.events_ep0datadone.read().bits() == 0 {
log::error!("Ep0In.end called before the EP0DATADONE event was raised");
loop {
asm::bkpt();
}
} else {
// DMA transfer complete
dma_end();
usbd.events_ep0datadone.reset();
self.busy = false;
}
}
}
// memory barrier to synchronize the start of a DMA transfer (which will run in parallel) with the
// caller's memory operations
//
// This function call *must* be *followed* by a memory *store* operation. Memory operations that
// follow this function call will *not* be moved, by the compiler or the instruction pipeline, to
// *after* the function call.
fn dma_start() {
atomic::fence(Ordering::Release);
}
// memory barrier to synchronize the end of a DMA transfer (which ran in parallel) to the caller's
// memory operations
//
// This function call *must* be *preceded* by a memory *load* operation. Memory operations that
// follow this function call will *not* be moved, by the compiler or the instruction pipeline, to
// *before* the function call.
fn dma_end() {
atomic::fence(Ordering::Acquire);
}
// NOTE will be called from user code; at that point the high frequency clock source has already
// been configured to use to the external crystal
// Reference: section 6.35.4 of the nRF52840 Product Specification
pub fn init(power: POWER, usbd: &USBD) {
// wait until the USB has been connected
while power.events_usbdetected.read().bits() == 0 {
power.events_usbdetected.reset();
continue;
}
// workaround silicon bug
unsafe { errata::e187a() }
// enable the USB peripheral
usbd.enable.write(|w| w.enable().set_bit());
// wait for the peripheral to signal it has reached the READY state
while usbd.eventcause.read().ready().bit_is_clear() {
continue;
}
// write 1 to clear the flag
usbd.eventcause.write(|w| w.ready().set_bit());
// if EVENTCAUSE is all zeroes then also clear the USBEVENT register
if usbd.eventcause.read().bits() == 0 {
usbd.events_usbevent.reset();
}
// complete the silicon bug workaround
unsafe { errata::e187b() }
// also need to wait for the USB power supply regulator to stabilize
while power.events_usbpwrrdy.read().bits() == 0 {
continue;
}
power.events_usbpwrrdy.reset();
// before returning unmask the relevant interrupts
usbd.intenset.write(|w| {
w.ep0datadone().set_bit();
w.ep0setup().set_bit();
w.usbreset().set_bit()
});
}
/// Connects the device to the host (enables the D+ line pull-up)
pub fn connect(usbd: &USBD) {
usbd.usbpullup.write(|w| w.connect().set_bit());
}
/// Disconnects the device from the host (disables the D+ line pull-up)
pub fn disconnect(usbd: &USBD) {
usbd.usbpullup.reset();
}
pub fn ep0stall(usbd: &USBD) {
usbd.tasks_ep0stall.write(|w| w.tasks_ep0stall().set_bit());
}
/// USBD.EVENTS registers mapped to an enum
#[derive(Debug)]
pub enum Event {
/// `EVENTS_USBRESET` register was active
UsbReset,
/// `EVENTS_EP0DATADONE` register was active
UsbEp0DataDone,
/// `EVENTS_EP0SETUP` register was active
UsbEp0Setup,
}
/// Returns the next unhandled USB event; returns none if there's no event to handle
///
/// NOTE this function will clear the corresponding the EVENT register (*) so the caller should
/// handle the returned event properly. Expect for USBEVENT and EP0DATADONE
pub fn next_event(usbd: &USBD) -> Option<Event> {
if usbd.events_usbreset.read().bits() != 0 {
usbd.events_usbreset.reset();
return Some(Event::UsbReset);
}
if usbd.events_ep0datadone.read().bits() != 0 {
// this will be cleared elsewhere
// usbd.events_ep0datadone.reset();
return Some(Event::UsbEp0DataDone);
}
if usbd.events_ep0setup.read().bits() != 0 {
usbd.events_ep0setup.reset();
return Some(Event::UsbEp0Setup);
}
None
}
/// Use this instead of the `todo!` / `unimplemented!` macro
pub fn todo(usbd: &USBD) {
disconnect(usbd);
log::error!("unimplemented");
loop {
asm::bkpt()
}
}
pub fn wlength(usbd: &USBD) -> u16 {
u16::from(usbd.wlengthl.read().wlengthl().bits())
| u16::from(usbd.wlengthh.read().wlengthh().bits()) << 8
}
pub fn windex(usbd: &USBD) -> u16 {
u16::from(usbd.windexl.read().windexl().bits())
| u16::from(usbd.windexh.read().windexh().bits()) << 8
}
pub fn wvalue(usbd: &USBD) -> u16 {
u16::from(usbd.wvaluel.read().wvaluel().bits())
| u16::from(usbd.wvalueh.read().wvalueh().bits()) << 8
}

View File

@ -0,0 +1,8 @@
[target.thumbv7em-none-eabi]
runner = "dongle-flash"
rustflags = [
"-C", "link-arg=-Tlink.x",
]
[build]
target = "thumbv7em-none-eabi"

54
boards/dongle/Cargo.toml Normal file
View File

@ -0,0 +1,54 @@
[package]
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
edition = "2018"
name = "dongle"
version = "0.1.0"
[dependencies]
consts = { path = "../../common/consts" }
cortex-m-rt = "0.6.12"
cortex-m-rtfm = "0.5.1"
embedded-hal = "0.2.3"
hal = { package = "nrf52840-hal", git = "https://github.com/japaric/nrf-hal", branch = "143+144" }
heapless = "0.5.5"
panic-halt = "0.2.0"
usb-device = "0.2.5"
usbd-serial = "0.1.0"
# optimize code in both profiles
[profile.dev]
codegen-units = 1
debug = 1
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
# faster builds from scratch
[profile.dev.build-override]
codegen-units = 8
debug = false
debug-assertions = false
opt-level = 0
overflow-checks = false
[profile.release.build-override]
codegen-units = 8
debug = false
debug-assertions = false
opt-level = 0
overflow-checks = false
[patch.crates-io.usb-device]
git = "https://github.com/jonas-schievink/usb-device.git"
branch = "inhibit-setaddr-resp"

27
boards/dongle/README.md Normal file
View File

@ -0,0 +1,27 @@
# ` dongle`
Pre-made applications for the nRF52840 Dongle.
These applications will be used in the beginner workshop.
## Getting familiar with the hardware
> TODO review for accessibility
### LEDs
- The green LED (LD1) is connected to pin P0.6
- The red channel of the RGB LED (LD2) is connected to pin P0.8
- The green channel of the RGB LED (LD2) is connected to pin P**1**.9
- The blue channel of the RGB LED (LD2) is connected to pin P0.12
Both LEDs are mounted near the USB connector.
### Buttons
- The Reset button is mounted sideways near the edge of the board that's opposite of the USB connector.
- The SW1 button is connected to pin P**1**.06. This round-ish button is right next to the RESET button but closer to the USB connector.
## References
- [nRF52840 Dongle section on Nordic Semiconductor's info center](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_getting_started%2FUG%2Fgs%2Fdevelop_sw.html&cp=1_0_2)

897
boards/dongle/loopback.hex Executable file
View File

@ -0,0 +1,897 @@
:10100000F8F30320552500002B4500002B45000078
:101010002B4500002B4500002B4500000000000080
:101020000000000000000000000000002B45000050
:101030002B450000000000002B4500002B45000060
:10104000FD2500000D2600002B4500003128000082
:101050002B4500002B4500002B4500002B450000D0
:101060002B4500002B4500002B45000061280000A7
:101070002B4500002B4500002B4500002B450000B0
:101080002B4500002B4500002B4500002B450000A0
:101090002B4500002B4500002B4500002B45000090
:1010A0002B4500002B4500002B4500002B45000080
:1010B0002B4500002B450000000000000000000050
:1010C0002B4500002B4500002B4500002B45000060
:1010D0002B4500002B4500002B4500003D2900005A
:1010E0002B4500002B4500002B45000000000000B0
:1010F000000000002B450000000000002B45000010
:10110000F91700000400000004000000BF180000F0
:10111000FB170000991800006465766963656964CF
:101120003D000000581200000000000058120000AE
:101130000000000000000000200000000800000087
:101140000200000000000000000000000800000095
:101150000300000001000000200000000800000063
:101160000200000000000000000000000800000075
:1011700003000000206368616E6E656C3D2054784A
:10118000506F7765723D2B3864426D0A7411000010
:10119000090000007D1100000F000000726563650A
:1011A0006976656420206279746500009C110000F6
:1011B00009000000A51100000500000073457272CF
:1011C0004F6B20284352433D28290000C2110000E4
:1011D00006000000C811000001000000C911000055
:1011E00001000000000000002000000000000000DE
:1011F00002000000000000000200000000000000EB
:101200000300000001000000200000000C000000AE
:1012100002000000000000000000000006000000C6
:10122000030000002C204C51493D00002412000016
:1012300006000000290A6469646E277420726570D4
:101240006C79202D2D206368616E6E656C2077614E
:101250007320627573790A003030303130323033A8
:101260003034303530363037303830393130313154
:10127000313231333134313531363137313831393A
:101280003230323132323233323432353236323732
:101290003238323933303331333233333334333518
:1012A00033363337333833393430343134323433FE
:1012B00034343435343634373438343935303531E4
:1012C00035323533353435353536353735383539CA
:1012D00036303631363236333634363536363637C2
:1012E00036383639373037313732373337343735A8
:1012F000373637373738373938303831383238338E
:101300003834383538363837383838393930393173
:101310003932393339343935393639373938393959
:1013200030780B01003235323432333232323132DE
:10133000303139313831373136313531343133317B
:081340003231313236000000A9
:10134800F0B503AF2DE9E00F8B688A460969D0E94B
:101358000069012B02D1012903D077E0012940F06F
:10136800D480B9F1000F53D0DAF8141006EB090E47
:10137800019300224B1C3146344602960D4615F95E
:10138800010BB0F1FF3FC6B229DC754505D048789E
:101398008D1C00F03F0C294602E04FF0000C71460E
:1013A80006F01F0BDF2E07D9714508D011F8010B85
:1013B80000F03F080D4605E04CEA8B160FE04FF0B1
:1013C8000008714648EA8C1CF02E06D371450FD0F0
:1013D80011F8010B00F03F060CE04CEA0B362946E9
:1013E800013B17D0121B8E450A440C46C6D10BE0B0
:1013F800002629464FF4E01000EA8B4040EA8C10A2
:101408000644B6F5881FEBD14A46DDE901361AE0F5
:10141800002218E0019BB6F5881F02D14A46029EB9
:1014280011E0029E3AB14A4505D04A4505D2B05668
:1014380010F1400F01DB304601E0002000220028B7
:101448000CBF4A460646012B0CD19146B9F1000F54
:101458000BD0A9F1010009F0030C032807D20020E2
:10146800324625E0DAE9060154E000203BE0ACEB27
:1014780009030020B64632461478557804F0C004B3
:101488009678D178802C05F0C00408BF0130802CF4
:1014980006F0C00408BF0130802C01F0C00108BF6D
:1014A8000130802908BF013004320433E4D1764684
:1014B800BCF1000F17D0117801F0C001802908BFD6
:1014C8000130BCF1010F0ED0517801F0C001802924
:1014D80008BF0130BCF1020F05D0917801F0C001BE
:1014E800802908BF0130DAF80CE0A9EB000171454A
:1014F8000BD2B9F1000F15D0A9F1010109F0030CC5
:10150800032911D2002133462FE0DAF81C104A468D
:10151800DAF81800CB68314603B0BDE8000FBDE823
:10152800F040184700213BE0ACEB09040021B0462D
:1015380033461D785E789A7805F0C005D878802DF6
:1015480006F0C00508BF0131802D02F0C00208BFB7
:101558000131802A00F0C00008BF0131802808BF8F
:10156800013104330434E4D14646BCF1000F17D0EE
:10157800187800F0C000802808BF0131BCF1010FC5
:101588000ED0587800F0C000802808BF0131BCF1A7
:10159800020F05D0987800F0C000802808BF0131FC
:1015A8009AF82000C21E18BF0246A1EB090000EB02
:1015B8000E0100200C46DFE802F0080205020024B4
:1015C800084602E0480801314C08451C013D08D096
:1015D800DAE90602DAF80410126990470028F5D013
:1015E80013E0DAE906014A46CB683146DAF804B076
:1015F800984750B9DAE906650134013C09D02A69EF
:101608003046594690470028F7D00120BDE80E0F14
:10161800F0BD0020FAE7D4D4F0B503AF2DE9000FF0
:101628008DB0D1E9023BD1E900A6D1E9045C0321E0
:101638008DF830102021059100216B4A04910B92FE
:1016480003AA0A920891039006910293C3B1B34585
:1016580088BFB346BBF1000F2DD0DAF8041002683A
:101668005318802B00F2B280DAF80030A1B302449C
:1016780013F8014B01391471026801320260F5E771
:10168800B44588BFB446BCF1000F14D0DAF8041092
:1016980002685318802B00F29980DAF80030E3468C
:1016A800002978D0024413F8014B013914710268FB
:1016B80001320260F4E74FF0000846450AD90AEB08
:1016C800C8005AF838104268DDE90A03DB68984711
:1016D80000287BD100207AE00298544601964FF00A
:1016E800010800F110094FF0000A6646029800EB65
:1016F8008A0148680590087F8DF830008868049052
:10170800D1E9052062B1022A0CD0B04268D205EBBB
:10171800C002364B52689A4204D155F8300000682E
:10172800012200E00022CDE90620C968D9F80000AE
:1017380061B102290CD0B04252D205EBC0012B4A4C
:101748004968914204D155F830000068012100E051
:101758000021CDE9081059F8100CB04240D255F8D4
:10176800301005EBC0004268084604A9904768BBE2
:10177800D84530D204EB0A00D0E90212DDE90A03A9
:10178800DB6898470AF1080A09F1200908F10108FD
:101798000028ABD01AE00DF110093446002605EBFD
:1017A800C60155F836004A684946904770B906F1AF
:1017B8000108D84512D20AEBC600D0E90212DDE9C9
:1017C8000A03DB68984700284646E8D001200DB098
:1017D800BDE8000FF0BDA246019E6EE726466CE705
:1017E800314600F052FCFEDE0011000085200000AA
:1017F8007047E0B502AF0068802909D202687F2AE5
:1018080018D80244117101680131016000203DE0DF
:101818000022B1F5006F01924FF002020CD28B0941
:1018280062F39F118DF80510062161F35F138DF89F
:10183800043028E0012029E00B46B1F5803F62F32F
:101848009F130ED28DF806308B0962F39F13090B94
:101858000E228DF8053062F31F118DF80410032253
:1018680011E08DF80730F02343EA91438DF80430F6
:101878008B09090B62F39F1362F39F1104228DF801
:1018880006308DF8051001A900F018F802B080BDE7
:10189800F0B503AF4DF8048D86B00068EC4691E8CA
:1018A80074416346614683E87441FFF7B5FE06B0AC
:1018B8005DF8048BF0BD006800F000B8D0B502AF49
:1018C80003689C18802C01D90120D0BD42B1034483
:1018D80011F8014B013A1C71036801330360F5E705
:1018E8000020D0BD80B56F46017839B1032902D1F7
:1018F8004178012904D100F0C3FBFEDE00214170CC
:10190800064901220978C9B2002908BF03220270DA
:101918004270B1FA81F0400980BD00BF94FD0320F8
:1019280080B56F46C8B0DFF8CCA601210AF10500E2
:10193800D0E84F2F002A40F03E83C0E8421F002A1B
:10194800F6D10AF104000121D0E84F2F002A40F017
:101958003F83C0E8421F002AF6D1D14E0DF1200B7B
:10196800DFF8A01632204FF000080922C6F8FC0064
:101978005846CDF82080FFF7A1FFCA480DF1F409B9
:1019880042AC022541683A9100683D90DFF87C0638
:10199800DFF874164591CDE943193AA9429129A96E
:1019A8002E95CDE92C54CDE92A50DFF8640629900C
:1019B8005846FFF731FEDFF85C16D6F8FC00012622
:1019C8002E96CDE92C8400F07F00CDE92A5829AD68
:1019D800CDE94291DFF84016299129468DF8F400A7
:1019E8005846FFF719FE05F12A00029005F1290073
:1019F8008DF8BC802C94CDE92AB4CB46019005F132
:101A08002400DFF84096039005F13400049005F1B6
:101A18002C00059005F11000299406909DF8BC0053
:101A2800DFE810F006009F029F0222003B00090039
:101A38008DF8C0801DE09DF8D400DFE810F03D016E
:101A4800930292024301070051004B009DF8DC000D
:101A5800810700F04181032840F084829DF8DD0071
:101A6800012840F03B817DE29DF8C000012800F08C
:101A78007982DAF8100060B101685AE8042F8242CE
:101A880004D14AE80412002AF3D117E0BFF32F8FDC
:101A9800EFE703208DF8C0005EE29DF8CC00DFE898
:101AA80010F03900610260023D00060013009DF845
:101AB800D80081071FD0032844D053E20221C17007
:101AC8002D908DF8C0601AE09DF8E00080B10328E1
:101AD80071D047E29DF8D800012840F0898141E2A1
:101AE8009DF8E00048B1032840F03C82379D09E1A9
:101AF800DDE934015DE0349823E0369D01E12A9860
:101B080050F8041B02F03CFC06988DF8CC8031900C
:101B18002B983090ADF8D080319832900598FFF727
:101B2800E1FE18B103208DF8CC0089E04FF48070F5
:101B3800ADF8CD00012000064ED003988DF8D8806E
:101B4800349035909AF80600C0B210B102286FD0D0
:101B58001AE202F0A9FB5FFA80FBBBF1000F58D034
:101B6800BBF1010F64D0BBF1020F40F0FE81359844
:101B78004B49006800680330C1F8F8004A488AF801
:101B88000760066002F046FCCDE9420120460221CA
:101B980001F052FFDFF88814204601F067FF019931
:101BA8000DF1F40B02988DF8D8608DF8E08035912E
:101BB8003490CDE936019AF8070000F00300DFE819
:101BC80010F0E30104001400210005208DF8CC007A
:101BD80003208DF8E00033E0314801688DF8CC60CF
:101BE8009DF8CD0080F001002CE08AF80780369837
:101BF80080F800802C48D0F8F803379900F00100ED
:101C0800087001E08AF807808DF8E0609DF8CE0042
:101C180091E702F043FB02F0FDFBCDE942012046CB
:101C2800022101F009FFDFF8FC13204601F01EFF36
:101C380004200DF1F40B8DF8CC0003208DF8D800AA
:101C48000021022082B2022A01D1042084E1ADF8E9
:101C5800B800ADF8BA102D99CA78023AD3B2032B5E
:101C680006D351FA82F212798DF8C120012200E0E0
:101C7800002200048DF8C02001D00020CEE011F829
:101C8800030F821E51FA82F0D2B2013152086AB1B2
:101C98000B78013A057801F8015B00F80139F6E79D
:101CA8000C140040600000100810004006988DF8E1
:101CB800D48032902C983190DDE931013390ADF821
:101CC800D88034910498FFF70DFE08B1032040E155
:101CD800CDF8DC80339836908DF8DD809AF80800CE
:101CE8000121C0B2022808BF032102288DF8DC10A8
:101CF8008DF8DD1001D104202BE1059D8DF8E080E1
:101D080036953795002100208DF80861012905D006
:101D1800625C013106FA02F21043F7E708B1C349E1
:101D280008609AF8060000F00300012800F0088116
:101D3800A8BB02208AF8060002F06CFBCDE942013C
:101D48002046022101F078FEB949204601F08EFEB6
:101D58002868AB4900680330C1F8F800AF4801466D
:101D68004FF48040C1F80003D1F8F80140F4801026
:101D7800C1F8F8019AF80700C0B201280FD1032072
:101D88008AF80700A548466002F044FBCDE9420105
:101D98002046022101F050FE2046A649CEE002F07E
:101DA80083FAC5B2002D00F0BD80012D00F0C88077
:101DB800022D40F0EC8099484FF000618AF80860E5
:101DC800C0F8FC12466202F025FBCDE9420120462C
:101DD800022101F031FE9849204601F047FE2046D5
:101DE8008DF8086102F042F98DF8E0608DF8D8802E
:101DF8009AF808000122C1B2012908BF032201296B
:101E08008DF8D82001D10620A3E06FF0030151FA24
:101E180080F0B0FA80F08DF8D460400907902A98D5
:101E2800016801394A1C01D00160FAE72D990226A0
:101E3800824A4D46DFF80892C97847960239CDE9BB
:101E4800458BCDE943680DF1E8084292A7F13902C4
:101E580007F8391CC9B2CDF80091CDE93E583D923A
:101E68004A1E2A9818BF0122012978493B92764ACE
:101E780008BF11463A912146FFF7CEFBBDF8B800DE
:101E8800734A01284FF0020008BF03203B90BDF8B9
:101E9800BA10704808BF104627F82E1C6E4947969E
:101EA800CDE9456B01264491032143916B49429149
:101EB8006B493A904091A7F12E012A98CDE93E91BD
:101EC8002146CDF8F480A9464FF00008FFF7A4FB9F
:101ED8009DF8C00098B19DF8C10007F8190C2A9820
:101EE80060494796CDE9458BCDE943684291A7F112
:101EF8001901CDF8F8903D912146FFF78DFB2A98FE
:101F080002225949FFF7DAFC079800283FF4F7AD99
:101F18002A9821225549FFF7D1FC2A9802C802F0D5
:101F28002FFAECE502F0BAF902F074FACDE94201B1
:101F38002046022101F080FD4D49204601F096FD22
:101F480020468DF8086102F091F803208DF8E00032
:101F580005208DF8D40005208DF8BC0002F0E9FAC0
:101F68005CE5FEDE00F08CF8FEDE02F053FA42ACCF
:101F7800CDE942010021204601F05EFD2A492046B4
:101F880001F074FD584610E001F03CFFFEDE02F05F
:101F980041FA42ACCDE942010021204601F04CFD56
:101FA8003449204601F062FD2846214602F098F99E
:101FB80002F0D2F9FEDEBFF32F8F02F02BFA08AC45
:101FC800CDE908010021204601F036FD0A490BE061
:101FD800BFF32F8F02F01EFA08ACCDE908010021EB
:101FE800204601F029FD0549204601F03FFD02F099
:101FF80099FAFEDE98FD0320590000004E0000000B
:102008000C14004018110000832400003411000053
:1020180024110000D94100008C1100000810004074
:10202800500000004F0000005100000080E100E077
:10203800520000005300000055000000AC110000E1
:10204800491300000724000058120000BC110000CA
:10205800BD110000C0110000E4110000CC11000007
:10206800592300002C120000341200003612000020
:10207800540000005600000002F055BA0068FEE760
:1020880080B56F4600F006F8FEDE80B56F4600F0BA
:1020980001F8FEDE80B56F4602F0B6F9FEDED4D454
:1020A800F0B503AF2DE9000F83B0D0F800E09246F9
:1020B800BD680A461EF001014FF02B0B01EB050627
:1020C80008BF4FF4881B1C4680465FEA4E7103D454
:1020D8004FF0000A002254E0BAF1000F0BD0AAF129
:1020E80001010AF0030C032907D200211346BCF1B1
:1020F800000F2BD142E0002140E08DE87000ACEBEE
:102108000A060021914613461A785D78987802F0FD
:10211800C002DC78802A05F0C00208BF0131802A9D
:1021280000F0C00008BF0131802804F0C00008BFDB
:102138000131802808BF013104330436E4D1DDE9D8
:1021480000454A46029EBCF1000F17D0187800F0EF
:10215800C000802808BF0131BCF1010F0ED05878AB
:1021680000F0C000802808BF0131BCF1020F05D083
:10217800987800F0C000802808BF013106EB0A00FB
:10218800461AD8F80800012818D1D8F80C90B1459B
:1021980014D95FEA0E702AD40292A9EB060198F8C6
:1021A800202000208946032A08BF0122DFE802F028
:1021B800490245024FF00009084642E040465946A8
:1021C800534600F09EF828B10126304603B0BDE81A
:1021D800000FF0BDD8F81C102A46D8F81800CB68B4
:1021E800214603B0BDE8000FBDE8F0401847D8F815
:1021F800040059460296012601903020C8F80400D0
:10220800534698F820000090404688F8206000F077
:1022180078F80028D9D198F82020AA460298032AED
:1022280008BF0122A9EB000100200D46DFE802F0FB
:102238003A0237020025084634E0480801314FEADF
:102248005109461C013E08D0D8E90602D8F8041006
:10225800126990470028F5D0B6E7029A40465946D9
:102268005346D8F8046000F04CF80028ACD1D8E9FF
:1022780006012A46CB68214698470028A4D109F1CF
:102288000105D8E9069834460126013D2FD0D8F833
:1022980010204846214690470028F6D095E7480880
:1022A80001314D08461C013E08D0D8E90602D8F88D
:1022B8000410126990470028F5D085E7D8E906028E
:1022C8002146D3685246D8F80440984700287FF43E
:1022D8007BAFD8E9069A01350126013D09D0DAF825
:1022E80010204846214690470028F6D06DE7002682
:1022F8006BE70098002688F820000198C8F80400C9
:1023080063E7F0B503AF4DF804BD1C461546064615
:10231800B1F5881F08D0D6E906021269904718B1AE
:1023280001205DF804BBF0BD4DB1D6E90601224697
:10233800CB6829465DF804BBBDE8F040184700208B
:102348005DF804BBF0BD80B56F46FFF7A3FEFEDE67
:10235800F0B503AF4DF804BDA2B0068802AD0C4637
:10236800802106F00F0000F157020A2838BF00F15B
:102378003002284602F032F90020B0EB161F28D0B0
:10238800310901F00F0101F157020A2938BF01F1A3
:102398003002B0EB162F8DF886201DD0300A00F0E1
:1023A8000F0000F157010A2838BF00F13001002062
:1023B800B0EB163F8DF8851011D0300B00F15701A6
:1023C8000A2838BF00F130018DF884107C200421E0
:1023D80007E07F20012104E07E20022101E07D202A
:1023E8000321009141F220312B18C0F20001204650
:1023F8000222FFF755FE22B05DF804BBF0BD80B5A0
:102408006F468CB00A460178642915D348F21F50EC
:10241800C5F2EB10A1FB00036FF063005B0903FB3F
:10242800001041F25821C0F20001C0B231F810008A
:10243800ADF82D00242003E00A2906D226200B46F9
:1024480003F1300102AB195408E041F25820C0F200
:10245800000030F81100ADF82D002520C0F127014B
:10246800009102A90B1841F258211046C0F2000150
:102478000022FFF715FE0CB080BDB0B502AFA2B0C8
:1024880000680DF1080E8C468123002200F00F0130
:1024980001F157040A2938BF01F130040EEB03019A
:1024A800B2EB101F01F8024C30D0040904F00F04FD
:1024B80004F157050A2C38BF04F13005B2EB102F90
:1024C80001F8035C27D0040A04F00F0404F157054F
:1024D8000A2C38BF04F13005B2EB103F01F8045C58
:1024E8001ED0040B043B04F00F0404F157050A2C1A
:1024F80038BF04F1300501F8055C010CB2EB104F50
:102508000846C3D1581E81280ED31CE0981E013BF3
:10251800812809D317E0D81E023B812804D312E092
:10252800181F033B81280ED2C3F18101009141F2AB
:1025380020310EEB0003C0F2000160460222FFF7D3
:10254800AFFD22B0B0BDFFF7FEFEFEDE80B56F46E0
:102558001B481D4A016841F080710160194800213B
:102568004160016811400131016018480121C0F83B
:102578001015016016488160016016481649091A4D
:1025880021F0030102F079F814481549091A21F0DD
:102598000302144901F0D1FF01F0C7FF12484FF4BC
:1025A8008A51124C124D01600161AC4202D201CC39
:1025B8008047FAE7FFF760FDFFF7B2F9FEDE00BFDC
:1025C800FCED00E0001000E0FEFFFF000800004006
:1025D80000B0004000FC0320ACFD0320ACFD03204C
:1025E8002CFE0320644700000805005054470000F3
:1025F8006447000080B56F4601F018FCBDE88040D4
:1026080001F042BCF0B503AF2DE9C00B01F002FFA9
:102618006C46CDE900012046042101F00DFA794904
:10262800204601F023FA784A906B000605D0002076
:102638009063D2F80402400406D41068000607D05C
:102648004FF0040810461BE091464FF002081AE0CC
:10265800104650F8481F090602D04FF000080FE056
:10266800104650F80C1F090602D04FF0010807E089
:10267800104650F86C1F090600F0C0804FF00308A0
:1026880091460021016001F0C5FE6E460446CDE981
:1026980000010D463046032101F0CEF95B49304672
:1026A80001F0E4F928880C22411C6FF35F202254C2
:1026B80030462980564901F0D9F9DFE808F003725D
:1026C8002B482100DDE9000100250A88531C6FF31F
:1026D8005F2285540B8001F09DFE6C46CDE9000118
:1026E8002046022101F0A8F94E49204601F0BEF922
:1026F8004FF000600421C9F808024648857161E07E
:10270800DDE9000104240A88531C6FF35F22845416
:102718000B8070E0DDE9000102240A883D4D531C5E
:102728006FF35F2284540B80A879000666D101F00C
:1027380071FE6C46CDE900012046022101F07CF9CA
:102748003649204601F092F90120A87153E0DDE9ED
:102758000012032013885C1C6FF35F23C8542D49B3
:1027680014808A79D2B2022A48D14FF48042C9F83B
:102778000422D9F8002122F48012C9F800214FF070
:102788000062C9F8082200228A71087201F042FE2C
:102798006C46CDE900012046022101F04DF91E49A1
:1027A80026E0DDE9000101240A88531C6FF35F224B
:1027B800845418480B808179C9B2012904D00229B0
:1027C8001CD10221017216E0C179C9B2012915D1C3
:1027D8000021C9F86C1081710221C17101F01AFE43
:1027E8006C46CDE900012046022101F025F90C498B
:1027F800204601F03BF9BDE80C0BF0BD01F002FBEF
:10280800FEDE00BF40000000001100404100000053
:102818004600000098FD032045000000430000002A
:102828004400000042000000DCB504AF01F0F2FDF6
:102838006C46CDE900012046042101F0FDF8044969
:10284800204601F013F9034800210160DCBD00BFF8
:102858003F00000018310040DCB504AF2F48016884
:10286800C90741D1C16BC90722D000212F4CC163D0
:102878002D490A88931C013204EA03332C4C236047
:102888000A8091B20C22B1FBF2F202EB4202A1EBF8
:10289800820103290CD8DFE801F002070207214969
:1028A8004FF480724A6003E01E494FF480720A6058
:1028B800016CC9071FBF002101644FF40031C0F843
:1028C8000412416CC9071FBF002141644FF48021E5
:1028D800C0F80412816CC9071FBF002181644FF43E
:1028E8000021C0F80412DCBD01F094FD6C46CDE96E
:1028F80000012046002101F09FF80949204601F017
:10290800B5F808484FF480510160402101604FF448
:102918008071416001F006FEFEDE00BF04B1004098
:102928003E0000000805005000FC032000F0FF00F6
:1029380040B50040F0B503AF2DE9000F93B001F0AA
:1029480069FD0AACCDE90A010421204601F074F8BA
:10295800DFF8641E204601F089F8D84AD2F85401FD
:10296800000604D002F5AA704FF007093DE01046B2
:1029780050F8FC1F090602D04FF0080935E0D2F8DC
:102988002401000604D002F592704FF003092CE0F0
:10299800D2F85801000604D002F5AC704FF00409D3
:1029A80023E0D2F85C01000604D002F5AE704FF0C7
:1029B80005091AE0D2F82C01000604D002F5967039
:1029C8004FF0000911E0D2F83801000604D002F5F2
:1029D8009C704FF0010908E0D2F81001000600F0E1
:1029E8005F8402F588704FF0020900219246016069
:1029F80001F010FD0AAE0446CDE90A010D46304645
:102A0800032101F019F8DFF8B41D304601F02EF863
:102A180028880C22411C6FF35F20225430462980FD
:102A2800DFF89C1D01F022F80820DFE809F005090D
:102A38000B070F110D13140000200CE003200AE00F
:102A4800012008E0022006E0062004E0042002E05D
:102A5800052000E00720DDE90A121388DFF8648DFD
:102A68005C1C6FF35F23C854148098F80200811E21
:102A7800CAB2032A28BF0121C9B2012911D00229EB
:102A880014D1DFE819F009000E01AE00BA004700C2
:102A98001F019401F5009B01DFF82C0D00214181F5
:102AA800D0E1B9F1070F08BF002802D000F0FCFF01
:102AB800FEDE01F00FF80005F8D5012088F80200C5
:102AC80001F0A8FC0AACCDE90A010221204600F079
:102AD800B3FFDFF8841E204600F0C8FF98F8000016
:102AE800002800F0AF8198F80100002800F0AA81C2
:102AF80098F80200002800F0A581042088F8020058
:102B08000020A8F8000013B0BDE8000FBDE8F040B1
:102B180001F030B8DFF8480E40890028C6D1684E69
:102B280001220025D6F800905FFA89F0C109B2EBBE
:102B3800D01F0C4618BF0224B5EBD01F08BF0C46A7
:102B4800D6F804A0B368022CF068D9B241EA002193
:102B58000991D6F810E07169B269F6695FFA8EFCDE
:102B68004CEA012BD2B242EA06220892CDF81C9018
:102B780040F03B818DF83050CDE90A555546DDE9E6
:102B8800089601F047FC0DACCDE90D010021204667
:102B980000F052FFDFF87C1E204600F067FF079820
:102BA800214601F079F82846214601F075F8DFF84A
:102BB800685E30462146A84758462146A8474846F9
:102BC8002146A8473D490120086501F023FC0AACCD
:102BD800CDE90A010221204600F02EFFDFF83C1E55
:102BE8002DE101F017FC0AACCDE90A0102212046CB
:102BF80000F022FFDFF8381E21E1DFF8646D7089EC
:102C0800012800F0118102287FF450AFD14601F06D
:102C180001FC0AACCDE90A010221204600F00CFFB4
:102C2800DFF8FC1D204600F021FFB089002800F0E5
:102C38000D81234DD5F880014030C5F88001B48955
:102C4800412CC0F0068101F0E5FB0AACCDE90A0190
:102C58000221204600F0F0FEDFF8CC1D204600F0EF
:102C680005FF4020214601F017F8B08940380AE1F5
:102C780000F030FFC10500F1C480800540F169868D
:102C880001F0C8FB0AACCDE90A010221204600F098
:102C9800D3FEDFF8E01C204600F0E8FE08480021DB
:102CA800BFE001F0B7FB0AACCDE90A0102212046DA
:102CB80000F0C2FEDFF87C1DC1E000BF04700240D6
:102CC80080740240DAF86854CAF86854680708D56E
:102CD800DFF88C0C0078C0B202287FF4E7AE00F071
:102CE80061FFA8072AD501F095FB0AACCDE90A01D6
:102CF8000221204600F0A0FEDFF87C1C204600F0F0
:102D0800B5FE01F087FB0AACCDE90A010221204695
:102D180000F092FEDFF8641C204600F0A7FEDFF802
:102D2800400C01210170DFF83C0C8188C08889B211
:102D380080B288421EBFD94880210160680318D537
:102D4800DB48866A804601F065FB0AACCDE90A01DA
:102D58000221204600F070FEDFF8241C204600F017
:102D680085FE06F07F00214600F096FF0020C8F897
:102D7800280028030DD501F04DFB0AACCDE90A0166
:102D88000221204600F058FEDFF8F81B204600F02C
:102D98006DFE280756D501F03DFB0AACCDE90A01C6
:102DA8000221204600F048FEDFF8DC1B47E013B0A4
:102DB800BDE8000FBDE8F04000F0F4BE01F02AFBCA
:102DC8000AACCDE90A010221204600F035FEDFF801
:102DD800981B204600F04AFEDFF8840B4178022950
:102DE80080F0B785002141702CE0C9F34112002A18
:102DF80051D0022A4CD0012A7FF4BCAE4FF4807225
:102E08004AE001F007FB0AACCDE90A01022120469D
:102E180000F012FEDFF8541B204600F027FEA448FD
:102E28000121C0F8AC100DE001F0F4FA0AACCDE9CC
:102E38000A010221204600F0FFFDDFF8F01B2046C2
:102E480000F014FE13B0BDE8000FF0BD0020708143
:102E5800F8E701F0DFFA0DF12808CDE90A010221AF
:102E6800404600F0E9FDDFF8BC1B404600F0FEFDDF
:102E78002046414600F010FF00F072FF04F07F008A
:102E8800C5F884010020B0810120C9F80000D9E705
:102E98004FF4007200E0002209F00F06032E3FF6FF
:102EA80069AE04F00104049306940023DFE806F0F9
:102EB8000A0205084FF4803304E04FF4003301E0C0
:102EC8004FF4403342EA0304C4F30122260A1C0CDF
:102ED800012A059337D00396002A069E7FF44AAE4E
:102EE8005FFA8AF255460C2ACDF808B000F25782EC
:102EF800DFE812F00D00390155021D0155020A01E3
:102F0800BA00EB0056016801AF019C01A100002E38
:102F180000F045820998000402BF089880B2022890
:102F280040F03D82022C00F01582012C00F01F8237
:102F3800002C40F034820298000440F030824FF0B8
:102F4800000E042052E106995FFA8AF0049A554669
:102F580022283AD0DDF82090212821D0202808BF47
:102F6800002948D0059A002406F00300ADF8404037
:102F78000F94CDE900B940EA1220099E2A4641EA99
:102F8800002111A8334601F05FFA9DF84400022899
:102F980040F026828DF830400320CDE90A442DE226
:102FA8000029DFD0012CDDD10998000404BF1FFAE5
:102FB80089F00728D6D11FFA8BF0B0F5807F28BF9B
:102FC8004FF0000CCED2022027E0DDF82090002937
:102FD800C8D1012C08BF5FEA0940C3D1099880B263
:102FE80003289CBF1FFA8BF0FF28BBD862F3082C7C
:102FF800C2F3400011E0012CB4D10998000404BFC9
:103008001FFA89F00728ADD11FFA8BF0B0F5807F41
:1030180028BF4FF0000CA5D203208DF82A000F9886
:10302800ADF828C0CDF82B00BDF84000ADF82F0052
:103038000120099EE2E1002E00F0B181022C40F04F
:10304800AE810998000402BF089880B2022840F0B7
:10305800A681029801F02FFAC2B2022A00F09F81DD
:1030680000F0010E0A20C1E0002E00F09881002C2B
:1030780040F0958101F034FAC1B20C2900F08F813B
:103088004FF0FF3151FA80F2062A00F288810FF2E0
:10309800100304210220D3E812F000BF04E200E08C
:1030A8000D005801A9008201820174017E0100BF50
:1030B800807402400498000604BF02985FEA00404A
:1030C80040F06D81012100F035BC56EA040140F062
:1030D800668101F005FAC0B20C2800F0608103286F
:1030E80000F02981022800F02881069E0128554613
:1030F80040F055810498000604BF02985FEA00403A
:1031080040F04D8101211EE1002E40F048810998D0
:1031180080B27F2800F24381002C40F0408108985B
:1031280002990843000440F03A8105205CE0002E33
:1031380040F035810898000440F03181099880B248
:10314800002800F0C280022800F0CE80012808BFC5
:10315800002C40F024810298000440F020814FF0B8
:10316800000E082042E0002E40F01981032C00F0E8
:1031780016810898000440F01281099880B200284E
:1031880000F0CD80012808BF002C40F0088102988B
:10319800000440F004814FF0020E0020002125E0D9
:1031A800002E00F0FC80089880B2012808BF002C8F
:1031B80040F0F580099802990843000440F0EF8038
:1031C800012013E0002E40F0EA80099880B2FF2821
:1031D80000F2E580002C40F0E2800898029908434C
:1031E800000440F0DC800620DDF810E0049AC9B243
:1031F800120642EA01415FFA8EF241EA02210844CE
:10320800CDF82900002002998DF828000898C1EA15
:103218000040CDF82D009DF82A00BDF82810BDF813
:103228002F80DDF82B4041EA004634E1002E40F0C3
:10323800B680012C40F0B380089A120401BF000246
:10324800090208435FEA004040F0A9800920049978
:10325800CCE7002E00F0A380029884F0010300213F
:1032680080B2B0F5807F4FF0000038BF0120099A86
:1032780092B21A43089B9BB283F001031A43B2FA35
:1032880082F25209104008BF8646002800F0878065
:103298000022032004920022029200220892A5E74D
:1032A800DFF8BC060078C0B201287FF4CBADDFF8A8
:1032B800B4068188C08889B280B288423FF4C2AD22
:1032C80092464FF00609FFF793BB022C67D102988C
:1032D80001F0F1F80491C1B2022960D000F00101B7
:1032E8004FF0010E082081E744EA0E00000656D18F
:1032F8006FF0020050FA81F24FF0020E0820032A04
:10330800FFF474AF069ECAB2012A3FF46FAF554668
:10331800022A44D14FF0020E0820022166E7022C4F
:103328003DD1029801F0C7F8C2B2022A37D000F0A6
:10333800010E00205AE7022105E0069E5FEA0B40D5
:1033480055462CD100210720F5E20298000426D129
:103358000021EFE2029801F0AEF80491C1B202290F
:103368001DD000F001014FF0010E04203EE7029845
:1033780080B2B0F5807F12D2714604204FF0020E61
:1033880034E70498000604BF02985FEA004006D1BB
:10339800022002212AE70298000400F0CA8203985A
:1033A8002A460599DDF808B000F00300CDF800B012
:1033B80040EA1120DDF82090CDF8049046EA00217B
:1033C800099E11A8334601F03FF89DF844000228F1
:1033D80006D100208DF83000CDE90A0003200DE069
:1033E8001198CDF83500BDF84800ADF839000D98B2
:1033F8000A90DDF83700CDF82B00022003283FF4AF
:10340800C0AB9DF82A10BDF82820DDF82B4042EA11
:103418000146F0B30128DFF8480528D04078022893
:1034280073D104F47F00C6F30745B0F5003F40F0C0
:10343800F68006F47F40B0F5807F40F0318100F0DF
:10344800E9FF0AAECDE90A010221304600F0F4FA9C
:10345800DFF84415304600F009FB2846314600F0F5
:103468001BFC2046314600F01AFCFFF7ABBB407846
:10347800022856D1300600F0DD8000F0CBFF0AAC00
:10348800CDE90A010021204600F0D6FADFF8101530
:103498003CE2BDF82F8026F07F41C1F307200228C7
:1034A80072D00C0C052848D0062840F0AC8000F0FB
:1034B800B1FF0AAECDE90A010221304600F0BCFA9C
:1034C800DFF8F414304600F0D1FA2046314600F017
:1034D800E3FBDFF88C144878002800F062810128AB
:1034E80000F00A81002C00F0E081C878A04200F0CA
:1034F800EE8100F08FFF0AACCDE90A0100212046D9
:1035080000F09AFADFF8B41400E200F083FF0AAC86
:10351800CDE90A010021204600F08EFADFF86C148C
:10352800F4E100F077FF0AACCDE90A01002120465A
:1035380000F082FADFF86414E8E100F06BFF0AADEE
:10354800CDE90A010221284600F076FADFF888144E
:10355800284600F08BFA2046294600F09DFBDFF84C
:1035680000144878002800F00A81012800F0AE8095
:1035780000F050FF0AACCDE90A010021204600F016
:103588005BFADFF85814C1E100F044FF0AAECDE958
:103598000A010221304600F04FFADFF85014304695
:1035A80000F064FA4046314600F076FBE0B20428A9
:1035B80000F2C281DFE810F0050015012901C00101
:1035C8000A01240A00F026FF0AAECDE90A01022109
:1035D800304600F031FADFF82814304600F046FA99
:1035E8002046314600F058FB200600F0948100F098
:1035F80011FF0AACCDE90A010021204600F01CFAAF
:10360800DFF8001482E100F005FF0AACCDE90A01F9
:103618000021204600F010FADFF8F41376E100F0FC
:10362800F9FE0AACCDE90A010021204600F004FAAF
:10363800D7496BE16FF0010050FAA6F0C1B2022938
:1036480028BF0220C6F30745C0B2002800F01C813D
:10365800012800F0E58000F0DDFE0AACCDE90A01A2
:103668000221204600F0E8F9CE49204600F0FEF994
:1036780005F00100214600F00FFBC6F300202146AB
:1036880000F00AFB00F0C6FE0AACCDE90A010221EF
:10369800204600F0D1F9C449204600F0E7F916E1C8
:1036A80000F0B8FE0AAECDE90A010221304600F06A
:1036B800C3F9B849304600F0D9F92846314600F038
:1036C800EBFA2046314600F0E7FA00E1002C00F062
:1036D80005818878A0423FF4B5AB01208C70487012
:1036E80000F098FE0AACCDE90A010221204600F05C
:1036F800A3F9BB49FFF7A3BB002C00F0E880012C1D
:1037080040F0FA808E780D4600F084FE0AACCDE9D0
:103718000A0102214FF00209204600F08DF9A9495B
:10372800204600F0A3F992494FF001080F2085F8D0
:103738000380AE7085F801900E46C1F8900009200C
:10374800C1F894000020C86200F064FE0AACCDE91C
:103758000A010221204600F06FF99B49204600F03B
:1037680085F90A2021460A2500F096FA9748C6F8F6
:1037780094017A48C6F89851C0F80480A7E0002C54
:103788003FF460AB01208C70487000F043FE0AAC37
:10379800CDE90A010221204600F04EF99249FFF7CF
:1037A8004EBB00F037FE0AACCDE90A0100212046E5
:1037B80000F042F98749A9E000000000010000007C
:1037C8003D00000020FE032098FD032000F022FEAB
:1037D8000AACCDE90A010021204600F02DF9844900
:1037E80094E000F017FE0AACCDE90A010221204658
:1037F80000F022F98049204600F038F9B8F1120F9C
:1038080028BF4FF012087D488BE000F003FE0AAC99
:10381800CDE90A010121204600F00EF9754975E04D
:1038280000F0F8FD0AACCDE90A010221204600F0BB
:1038380003F95E49204600F019F9494948890028EA
:103848007FF434A901254D8100F0E4FD0AACCDE9EF
:103858000A010221204600F0EFF85549204600F001
:1038680005F9434908F10300C1F880020720C1F8AF
:1038780084023A480146D0F8FC0140F00200C1F841
:10388800FC018D64FFF7DEBA00F0C4FD0AACCDE997
:103898000A010221204600F0CFF84649204600F0F0
:1038A800E5F808F1030007213CE08D780E4600F0AA
:1038B800B1FD0AACCDE90A010221204600F0BCF8AE
:1038C8003F49204600F0D2F80120B570707013B05F
:1038D800BDE8000FBDE8F04000F03CBA0020487099
:1038E80000F098FD0AACCDE90A010221204600F05B
:1038F800A3F83C49FFF7A3BA00F08CFD0AACCDE968
:103908000A010021204600F097F83149204600F0CE
:10391800ADF8FFF757B93C48B8F14B0F28BF4FF047
:103928004B08414613B0BDE8000FBDE8F04000F079
:10393800B9B90321022059E400F06CFD0AACCDE9C5
:103948000A010021204600F077F83049204600F0AF
:103958008DF800F08BF9FEDE0300000004700240D1
:1039680098FD0320ACFD03200600000004000000C1
:1039780080740240050000002C0000002D000000AB
:103988002E0000002F00000030000000230000007F
:103998002400000026000000250000001C00000094
:1039A800220000001D0000001E0000002000000092
:1039B800210000001F000000150000001800000092
:1039C8001B000000190000001A00000014FE03206C
:1039D8001700000016000000100000001100000091
:1039E800140000001300000012000000090000008D
:1039F8000F0000000C0000000B000000B4FD0320C5
:103A08000D0000000E000000C6FD03200A000000A3
:103A18000800000007000000A73C00002700000085
:103A2800280000002A000000290000002B000000E8
:103A38003100000032000000F0B503AF4DF804BDBE
:103A4800094A1268D0E90034258892096E1C6FF380
:103A58005F2559552680D0E9003118465DF804BB2A
:103A6800BDE8F04000F09AB9041000E0F0B503AFEB
:103A78004DF804BD8AB27F2A0AD3D0E9003441F058
:103A8800800125886E1C6FF35F255955D109268062
:103A9800D0E9002003885C1C6FF35F23D1540480B5
:103AA8005DF804BBF0BDD4D4E0B502AF00F092F9E4
:103AB80000F0B0FC6C46CDE9000120460021FFF77C
:103AC800BBFF03492046FFF7D1FF00F02BFDFEDEC8
:103AD8003B000000F0B503AF2DE9F00B2248D0F809
:103AE8000080C0F8008000F095FC01AC0546CDE9E7
:103AF80001010E4620460321FFF79EFF1B49204681
:103B0800FFF7B4FF30880A22411C6FF35F202A5464
:103B1800204631801649FFF7A9FF029E40F2FF11A7
:103B280027F81A8C358828466FF35F2088420DD114
:103B3800019CA7F11A09012204F2FF11484600F07E
:103B48007BFC09F101002146012204E001990222CF
:103B58000144A7F11A0000F06FFCA81C3080404611
:103B680004B0BDE8000BF0BD007402403A0000004C
:103B78005A000000DCB504AF08480121016000F0DC
:103B880049FC6C46CDE9000120460221FFF754FFAD
:103B980003492046FFF76AFFDCBD00BF04750240F9
:103BA80039000000F0B503AF2DE9C007DFF8A4A085
:103BB800BAF80450BAF80600401B01041CD084B2BD
:103BC8003F2C28BF3F2454FA85F081B2E8B2B1F502
:103BD800807F13D9DAF80090C0F58076DFF8788016
:103BE80009EB00013246404600F0A5FCA11B08EB9A
:103BF80006008AB2494606E0012625E0DAF80010F8
:103C080022460144144800F096FC2819AAF804003A
:103C180020064FF0010617D000F0FCFB6D46CDE9F9
:103C2800000128460221FFF707FF0C492846FFF745
:103C38001DFF2046294600F02FF809480649016073
:103C4800446008480660022607480670BDE80C076D
:103C5800F0BD00BFACFD032000F4032038000000D5
:103C6800287602400C70024098FD0320E0B502AFB0
:103C780000F0B0F800F0CEFB6C46CDE9000120461C
:103C88000021FFF7D9FE03492046FFF7EFFE00F0B9
:103C980049FCFEDE35000000C0B200F069B880B211
:103CA80000F066B8F0B503AF4DF8048D82B0234E2E
:103CB8008046708990BB234C88B20D4640280AD9AB
:103CC800206820F004002060A5F14000B0810220A7
:103CD8004025708104E000F043F80220C6F80A008D
:103CE80000F098FB6E46CDE9000130460221FFF74F
:103CF800A3FE15493046FFF7B9FE28463146FFF7BF
:103D0800CBFF124905F07F0048601148C1F80080D8
:103D18000121215002B05DF8048BF0BD00F07AFB60
:103D28006C46CDE9000120460021FFF785FE0449D5
:103D38002046FFF79BFE00F0F5FBFEDE98FD032012
:103D48003300000000720240340000000076024098
:103D580004FEFFFF014801210160704750700240D6
:103D68000248016841F00401016070470072024096
:103D7800F0B503AF4DF804BD0246D1E900031C8835
:103D88000626651C6FF35F2406551D80D1E90001E6
:103D98005DF804BBBDE8F04000F000B8F0B503AF33
:103DA8004DF804BD0C880023661C42F080056FF3B3
:103DB8005F24B3EBD21F08BF02F07F05B3EBD21F1D
:103DC80005554FEAD21534462A460E80ECD15DF8E7
:103DD80004BBF0BDDCB504AF08480021016000F069
:103DE80019FB6C46CDE9000120460221FFF724FEAD
:103DF80003492046FFF73AFEDCBD00BF04750240C8
:103E080037000000E0B502AF00F004FB6C46CDE9D6
:103E1800000120460021FFF70FFE03492046FFF767
:103E280025FE00F07FFBFEDE3C000000DCB504AFA1
:103E380000F0F0FA6C46CDE9000120460421FFF7B6
:103E4800FBFD0D492046FFF711FE0C4801680906E5
:103E580011D00021016000F0DDFA6C46CDE90001C7
:103E680020460221FFF7E8FD05492046FFF7FEFD41
:103E7800044801210170DCBD4B0000000001004036
:103E88004C00000094FD0320F0B503AF2DE9C00BF2
:103E980000F0C0FA6C46CDE9000120460421FFF786
:103EA800CBFD5D492046FFF7E1FD5C484FF0000877
:103EB8000168090602D04FF000090FE0014651F8E9
:103EC800042F120603D04FF00109084606E050F807
:103ED800081F090600F095804FF00209C0F800801D
:103EE80000F098FA6E460446CDE900010D463046CA
:103EF8000321FFF7A1FD4A493046FFF7B7FD28889F
:103F08000C22411C6FF35F2022543046298045491A
:103F1800FFF7ACFDDDE900010A88531C6FF35F224F
:103F280000F802900B80404DA878811ECAB2032A7F
:103F380028BF0121C9B271B1012967D12A78002AA5
:103F4800114618BF01217ABB98F001022CD101213A
:103F58002970002144E0B8F1000F4FD1B9F1000FEA
:103F680057D1334849F2753103220160C0F8142152
:103F7800016001212F48016000F04CFA6C46CDE940
:103F8800000120460221FFF757FD2B492046FFF785
:103F98006DFD2A4800210078A5F80110C0B200285C
:103FA80018BF0120287029E0687890BB18EA010042
:103FB80024D1B9F1020F2CD10120687000F02AFA3F
:103FC8006C46CDE9000120460221FFF735FD17496F
:103FD8002046FFF74BFD2978A878B1FA81F1490905
:103FE80060B159B9687848B10420A87000202880C9
:103FF800BDE80C0BBDE8F040FFF7BCBDBDE80C0BFD
:10400800F0BD4FF003094FF001088CE7FFF72EFED3
:10401800FEDEFFF749FDFEDE470000001C01004000
:10402800020000004A00000020FE032049000000B2
:1040380000EC0640007502404800000094FD032093
:1040480007484FF4803101604FF48051C0F83C12AA
:104058004FF4807103480160034800F007B800BFBF
:1040680004B300400C05005022130000D0B502AF85
:10407800002201230021012A05D0845C013203FAC1
:1040880004F42143F7E709B101480160D0BD00BF3E
:1040980000E100E0B0B502AF154D0024B4F5C67FCD
:1040A80004D0281900F036F88434F7E711480F21B6
:1040B80001234160082101600F4981608121C160AD
:1040C8000E490A6863F302020A6040F202214FF4C3
:1040D800822281620A49C162002101630949425072
:1040E80009490A4A42500A48BDE8B040FFF7BEBF36
:1040F80040F503200C150040080000066C1600402F
:1041080021100100F4FCFFFFF8FDFFFF094004083F
:104118002313000009490A69026051E8043F9342E9
:1041280007D141E80402002A1A4608BF00F0FFB987
:10413800F2E7BFF32F8F1A46EEE700BF98FD032082
:10414800BCB504AF4FF080400125056000F062F96E
:104158006C46CDE9000120460221FFF76DFC0549B8
:104168002046FFF783FC044805600448BDE8BC40CE
:10417800FFF77CBF4D00000004030040241300003B
:10418800B0B502AF0C4D0024B4F5C67F04D0281991
:10419800FFF7C0FF8434F7E70848012101604FF4B6
:1041A800607101600648074901600748FFF75EFF34
:1041B800064880210160B0BD04FC032004030040D0
:1041C800047302402194C0012413000004E100E0BC
:1041D800007800F07F02052A1ED00A281ED00F287A
:1041E8001ED014281ED019281ED01E281ED0232801
:1041F8001ED028281ED02D281ED032281ED03728A1
:104208001ED03C281ED041281ED046281ED04B2840
:104218000CBF134A114A1AE01F4A18E01D4A16E05B
:104228001B4A14E0194A12E0174A10E0154A0EE03A
:10423800134A0CE0114A0AE00F4A08E00D4A06E06A
:104248000B4A04E0094A02E0074A00E0054AD1E9BE
:104258000601CB6811460222184700BF431300002D
:104268002513000027130000291300002B1300005A
:104278002D1300002F13000031130000331300002A
:104288003513000037130000391300003B130000FA
:104298003D1300003F1300004113000001480121B5
:1042A8000160704704100040BCB504AF0A480468B8
:1042B80000F0B0F86D46CDE9000128460321FFF76C
:1042C800BBFB06492846FFF7D1FB2046294600F0EC
:1042D80007F8204602B0B0BD501500405800000055
:1042E800F0B503AF4DF804BD0C460546D1E9000111
:1042F8000C260A88531C6FF35F22865420460B80D5
:104308001449FFF7B3FB05F00F010820DFE801F0BF
:10431800070D0F091521212121110B13160000206B
:104328000CE003200AE0062008E0012006E0022055
:1043380004E0052002E0072000E00420D4E9001290
:1043480013885C1C6FF35F23C85414805DF804BBAA
:10435800F0BDFEDE57000000E0B502AF00F05AF8ED
:104368006C46CDE9000120460021FFF765FB0349B3
:104378002046FFF77BFB00F0D5F8FEDE3600000094
:10438800E1B3F0B503AF2DE90007DFF8749082467A
:104398000C46B9F80650B9F80400401B00F58070C7
:1043A800010424D080B2A04238BF044654FA85F0F4
:1043B800E9B280B2B0F5807F0FD9D9F80080C1F595
:1043C800807608EB01005146324600F0B4F8A01B95
:1043D8000AEB060182B2404604E0D9F80000224602
:1043E8000844514600F0A7F82819A9F8060005481E
:1043F80080210160BDE80007BDE8F040704700BFBC
:10440800ACFD032004E200E080B56F4600F08AF8B6
:10441800FEDED4D4064905480968C9B20029054911
:1044280018BF00F5007018BF0231704700F803206C
:1044380004ED00E090FD03202AB110F8013B013A99
:1044480001F8013BF8E77047F0B503AF4DF804BD3C
:10445800D6B201F00104C1F30745D7E90212062ECE
:1044680011D00A2E20D1012D08BF002C1CD1120416
:104478001AD18AB2FF2A17D80171002183700170FE
:10448800190A417014E0012D0ED16CB18CB2FF2CC9
:104498000AD803F47F44B4F5085F05D101710121FE
:1044A80042804370017003E0002181800221016095
:1044B8005DF804BBF0BD024680B2FF2801D9002197
:1044C80007E0C2F30310002128B1082801D1012018
:1044D80002E002207047002002F00F017047D4D498
:1044E8004FF0FF3151FA80F10C200A2918D8DFE883
:1044F80001F006080A0C0E101214171716000120F6
:104508007047022070470320704704207047052039
:1045180070470620704707207047082070470B2017
:104528007047FFBEAABE62B6704772B6704740BFFA
:10453800704720BF704700F0A3B8042A38BFFFF7C0
:10454800FABFB0B502AFA2F1040C012303EB9C0340
:1045580013F003030ED08E4604465EF8045B012B6D
:1045680044F8045B1FD1634620467146BCF10C0F2A
:1045780004D212E01346BCF10C0F0ED30D68103BA9
:104588000560032B4D6845608D688560CD6801F135
:104598001001C56000F11000F0D802F00302BDE878
:1045A800B040FFF7C8BF4D68022B456007D1A2F1A4
:1045B800080308310830BCF10C0FDFD2EDE78B6837
:1045C8000C318360A2F10C030C30BCF10C0FD5D276
:1045D800E3E70B4611461A4600F08FB81346D2B2ED
:1045E800042938BFFFF7F5BFD0B502AFA1F1040E1B
:1045F80042EA0363012443EA024304EB9E0443EACC
:10460800022314F0030410D08446012C4CF8043B18
:1046180010D174466046BEF10C0F1ED201F00301A2
:10462800BDE8D0406046FFF7D4BF0C46BEF10C0F82
:1046380013D2F3E7022C436007D10830A1F1080434
:104648008446BEF10C0F08D2E8E783600C30A1F174
:104658000C048446BEF10C0FE0D3C0E90033103CD3
:10466800C0E902331030032CF7D8844601F0030167
:10467800BDE8D0406046FFF7ACBF0022FFF7AEBFF1
:10468800F0B503AF4DF8048DA2B3531E02F0030C2E
:10469800032B14D20022BCF1000F2BD08B5CBCF191
:1046A800010F835426D0531CBCF1020FCE5CC654B4
:1046B80020D00232895C81545DF8048BF0BDACEBEC
:1046C800020E01F10108441C6FF0030208EB020618
:1046D800A5180432F378EB7033792B7173796B7109
:1046E800B379AB710EEB02030433EFD10432BCF1A2
:1046F800000FD3D15DF8048BF0BDD0B502AF12B373
:10470800531E02F0030C032B01D200220FE0ACEB86
:10471800020E421C6FF00303D4180433A1716171B7
:104728002171E1700EEB03040434F5D11A1DBCF1BC
:10473800000F1ABF8154BCF1010FD0BD0244BCF177
:0C474800020F517000D1D0BD9170D0BDA7
:1047540089410000494100009D400000494000009B
:1047640040F403200000000012010002EF020140A7
:104774002020090300010000000109024B0002018E
:104784000080FA080B000202020100090400000183
:104794000202010005240010010524010301042480
:1047A40002020524060001070581034000200904D4
:1047B4000100020A0000000705020240000007058C
:1047C4008202400000000000A1200000000002005E
:1047D4000300000000000280250000000008000023
:04000003000025557F
:00000001FF

75
boards/dongle/loopback.rs Normal file
View File

@ -0,0 +1,75 @@
// f6c27c0a5af464795d1a64c45fa1b3039f44a62a
#![deny(unused_must_use)]
#![no_main]
#![no_std]
use core::fmt::Write as _;
use hal::{
radio::{self, Channel, Packet},
usbd,
};
use heapless::{consts, String};
use panic_abort as _;
#[no_mangle]
fn main() -> ! {
let mut stx = usbd::serial();
let (mut rtx, mut rrx) = radio::claim(Channel::_20);
let mut output = String::<consts::U64>::new();
output.push_str("deviceid=").ok();
write!(output, "{:08x}{:08x}", hal::deviceid1(), hal::deviceid0()).ok();
write!(output, " channel={} TxPower=+8dBm\n", rtx.channel()).ok();
let task = async {
let mut packet = Packet::new().await;
stx.write(output.as_bytes());
loop {
let crcres = rrx.read(&mut packet).await;
let lqi = if packet.len() >= 3 {
Some(packet.lqi())
} else {
// packet is too small; LQI is not valid
None
};
let mut busy = false;
if crcres.is_ok() {
// packet.reverse();
busy = rtx.write(&packet).await.is_err();
}
output.clear();
let len = packet.len();
write!(
&mut output,
"received {} byte{}",
len,
if len == 1 { "" } else { "s" }
)
.ok();
let (res, crc) = match crcres {
Ok(x) => ("Ok", x),
Err(x) => ("Err", x),
};
write!(&mut output, " (CRC={}({:#06x})", res, crc).ok();
if let Some(lqi) = lqi {
write!(&mut output, ", LQI={}", lqi).ok();
}
output.push_str(")\n").ok();
if busy {
output.push_str("didn't reply -- channel was busy\n").ok();
stx.write(output.as_bytes());
}
stx.write(output.as_bytes());
}
};
executor::run!(task)
}

11
boards/dongle/memory.x Normal file
View File

@ -0,0 +1,11 @@
MEMORY
{
/* Bootloader is split in 2 parts: the first part lives in the range
0..0x1000; the second part lives at the end of the 1 MB Flash. The range
selected here collides with neither */
FLASH : ORIGIN = 0x1000, LENGTH = 0x7f000
/* The bootloader uses the first 8 bytes of RAM to preserve state so don't
touch them */
RAM : ORIGIN = 0x20000008, LENGTH = 0x3fff8
}

View File

@ -0,0 +1,203 @@
//! `radio` & `usb` apps merged into a single application
#![no_std]
#![no_main]
use core::fmt::Write as _;
use core::sync::atomic::{AtomicBool, Ordering};
use embedded_hal::digital::v2::{OutputPin as _, StatefulOutputPin as _};
use hal::{
clocks::{Clocks, ExternalOscillator, Internal, LfOscStopped},
gpio::{
p0::{self, P0_06, P0_12},
Level, Output, PushPull,
},
ieee802154::{Packet, Radio},
target::USBD,
usbd::Usbd,
};
use heapless::String;
use panic_halt as _; // panic handler
use usb_device::{bus::UsbBusAllocator, prelude::*};
use usbd_serial::{SerialPort, USB_CLASS_CDC};
// TODO make channel configurable via a single `const`ant
#[rtfm::app(device = hal::target, peripherals = true)]
const APP: () = {
struct Resources {
blue: P0_12<Output<PushPull>>,
configured: AtomicBool,
green: P0_06<Output<PushPull>>,
radio: Radio<'static>,
serial_port: SerialPort<'static, Usbd<'static>>,
usb_dev: UsbDevice<'static, Usbd<'static>>,
}
#[init]
fn init(cx: init::Context) -> init::LateResources {
static mut EP_BUF: [u8; 1024] = [0; 1024];
static mut CLOCKS: Option<Clocks<ExternalOscillator, Internal, LfOscStopped>> = None;
static mut USB_BUS: Option<UsbBusAllocator<Usbd<'static>>> = None;
let periph = cx.device;
let port = p0::Parts::new(periph.P0);
// P0.6: green LED indicates USB is working
// P0.12: blue LED is toggled on each new packet
let green = port.p0_06.into_push_pull_output(Level::High);
let blue = port.p0_12.into_push_pull_output(Level::High);
let clocks = Clocks::new(periph.CLOCK);
// extend lifetime to `'static`
let clocks = CLOCKS.get_or_insert(clocks.enable_ext_hfosc());
let mut radio = Radio::init(periph.RADIO, clocks);
// set TX power to its maximum value
radio.set_txpower(8);
// these loops should always terminate
// detected USB power? (device is USB powered)
while !periph
.POWER
.usbregstatus
.read()
.vbusdetect()
.is_vbus_present()
{}
// wait until USB 3.3V supply (internal regulator) is stable
while !periph
.POWER
.events_usbpwrrdy
.read()
.events_usbpwrrdy()
.bit_is_clear()
{}
// enable all interrupts
periph.USBD.intenset.write(|w| {
w.endepin0().set_bit();
w.endepin1().set_bit();
w.endepin2().set_bit();
w.endepout0().set_bit();
w.endepout1().set_bit();
w.endepout2().set_bit();
w.ep0datadone().set_bit();
w.ep0setup().set_bit();
w.epdata().set_bit();
w.usbevent().set_bit();
w.usbreset().set_bit();
w.sof().set_bit();
w
});
let usb_bus = USB_BUS.get_or_insert(Usbd::new_alloc(periph.USBD, EP_BUF, clocks));
let serial_port = SerialPort::new(usb_bus);
let usb_dev = UsbDeviceBuilder::new(usb_bus, UsbVidPid(consts::VID, consts::PID))
.device_class(USB_CLASS_CDC)
.max_packet_size_0(64) // (makes control transfers 8x faster)
.build();
init::LateResources {
blue,
green,
configured: AtomicBool::new(false),
radio,
usb_dev,
serial_port,
}
}
#[idle(resources = [blue, &configured, radio, serial_port])]
fn idle(cx: idle::Context) -> ! {
let blue = cx.resources.blue;
let configured = cx.resources.configured;
let mut serial_port = cx.resources.serial_port;
let radio = cx.resources.radio;
// TODO run Radio in parallel to the USBD task
let mut packet = Packet::new();
let mut buf = String::<heapless::consts::U64>::new();
loop {
// let res = radio.recv(&mut packet);
// let lqi = packet.lqi();
// let len = packet.len();
// if res.is_ok() {
// if blue.is_set_low() == Ok(true) {
// blue.set_high().ok();
// } else {
// blue.set_low().ok();
// }
// // loopback the packet
// radio.send(&packet);
// }
let is_configured = configured.load(Ordering::Relaxed);
if is_configured {
blue.set_low().ok();
// buf.clear();
// // TODO switch to a single `uwriteln!`
// if res.is_ok() {
// buf.push_str("CRC OK").ok()
// } else {
// buf.push_str("BAD CRC").ok()
// };
// buf.push_str(" - LQI: ").ok();
// write!(buf, "{}", lqi).ok();
// buf.push_str(" - payload: ").ok();
// write!(buf, "{}", len).ok();
// buf.push_str(" bytes\n").ok();
// serial_port.lock(|port| port.write(buf.as_bytes()).ok());
}
}
}
#[task(binds = USBD, resources = [&configured, green, usb_dev, serial_port])]
fn usbd(cx: usbd::Context) {
let configured = cx.resources.configured;
let green = cx.resources.green;
let serial_port = cx.resources.serial_port;
let usb_dev = cx.resources.usb_dev;
usb_dev.poll(&mut [serial_port]);
if !configured.load(Ordering::Relaxed) {
if usb_dev.state() == UsbDeviceState::Configured {
// off
green.set_high().ok();
configured.store(true, Ordering::Relaxed);
let _ = serial_port.write(b"bChannel: 20\nTX power: +8 dBm\n");
}
} else {
// blink until configured
if green.is_set_low() == Ok(true) {
green.set_high().ok();
} else {
green.set_low().ok();
}
}
// FIXME `hal::Usbd` is not clearing all EVENTS?
let usbd: USBD = unsafe { core::mem::transmute(()) };
if usbd.events_epdata.read().bits() != 0 && usbd.epdatastatus.read().bits() == 0 {
usbd.events_epdata.reset();
}
if usbd.events_sof.read().bits() != 0 {
usbd.events_sof.reset();
}
// usbd.events_endepout[0].reset();
// usbd.events_endepout[1].reset();
// usbd.events_endepout[2].reset();
// usbd.events_ep0setup.reset();
// usbd.events_ep0datadone.reset();
// usbd.events_epdata.reset();
// usbd.events_usbevent.reset();
// usbd.events_usbreset.reset();
}
};

View File

@ -0,0 +1,63 @@
//! Test program, mainly for debugging purposes
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use embedded_hal::digital::v2::{OutputPin as _, StatefulOutputPin as _};
use hal::{
clocks::Clocks,
gpio::{p0, Level},
ieee802154::{Packet, Radio},
target as pac,
};
use panic_halt as _;
#[entry]
fn main() -> ! {
let periph = pac::Peripherals::take().unwrap();
let clocks = Clocks::new(periph.CLOCK);
let clocks = clocks.enable_ext_hfosc();
let port = p0::Parts::new(periph.P0);
// NOTE LEDs turn on when the output level is low (0V)
let mut green = port.p0_06.into_push_pull_output(Level::High); // LD1
let mut blue = port.p0_12.into_push_pull_output(Level::High); // LD2 (RGB LED)
let mut red = port.p0_08.into_push_pull_output(Level::High); // LD2 (RGB LED)
let mut radio = Radio::init(periph.RADIO, &clocks);
// turn on green LED to indicate the radio has been initialized
green.set_low().ok();
// set TX power to its maximum value
radio.set_txpower(8);
let mut packet = Packet::new();
loop {
let res = radio.recv(&mut packet);
if res.is_ok() {
// CRC check passed
// clear visual error state (turn off the red LED)
red.set_high().ok();
// toggle blue LED on each successfully received packet
if blue.is_set_low() == Ok(true) {
blue.set_high().ok();
} else {
blue.set_low().ok();
}
// return packet with the contents unchanged
radio.send(&packet);
} else {
// CRC check failed
// indicate error using the red LED
red.set_low().ok();
}
}
}

View File

@ -0,0 +1,96 @@
//! Test program used to debug the `serial-term` tool
#![no_main]
#![no_std]
use cortex_m_rt::entry;
use embedded_hal::digital::v2::OutputPin as _;
use hal::{
clocks::Clocks,
gpio::{p0, Level},
target as pac,
usbd::Usbd,
};
use panic_halt as _;
use usb_device::device::{UsbDeviceBuilder, UsbDeviceState, UsbVidPid};
use usbd_serial::{SerialPort, USB_CLASS_CDC};
#[entry]
fn main() -> ! {
static mut EP_BUF: [u8; 1024] = [0; 1024];
let periph = pac::Peripherals::take().unwrap();
let clocks = Clocks::new(periph.CLOCK);
let clocks = clocks.enable_ext_hfosc();
let port = p0::Parts::new(periph.P0);
// NOTE LEDs turn on when the output level is low (0V)
let mut green = port.p0_06.into_push_pull_output(Level::High); // LD1
let mut blue = port.p0_12.into_push_pull_output(Level::High); // LD2 (RGB LED)
let mut red = port.p0_08.into_push_pull_output(Level::High); // LD2 (RGB LED)
// these loops should always terminate
// detected USB power? (device is USB powered)
while !periph
.POWER
.usbregstatus
.read()
.vbusdetect()
.is_vbus_present()
{
continue;
}
// wait until USB 3.3V supply (internal regulator) is stable
while !periph
.POWER
.events_usbpwrrdy
.read()
.events_usbpwrrdy()
.bit_is_clear()
{
continue;
}
// turn on green LED to show the program is working
green.set_low().ok();
let usb_bus = Usbd::new_alloc(periph.USBD, EP_BUF, &clocks);
let mut serial_port = SerialPort::new(&usb_bus);
let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(consts::VID, consts::PID))
.device_class(USB_CLASS_CDC)
.max_packet_size_0(64) // (makes control transfers 8x faster)
.build();
let mut buf = [0; 64];
let mut once = true;
loop {
if !usb_dev.poll(&mut [&mut serial_port]) {
continue;
}
if usb_dev.state() == UsbDeviceState::Configured {
// turn on the blue LED to show the device has been configured
blue.set_low().ok();
if once {
once = false;
serial_port.write(b"Hello, world!\n").ok();
serial_port.flush().ok();
}
if serial_port.read(&mut buf).is_ok() {
// received data from the PC. I'm interpreting this as an error but it may actually
// be part of the CDC/ACM protocol? -- we never send data to the device
red.set_low().ok();
}
} else {
// otherwise turn it off
blue.set_high().ok();
}
}
}

1
common/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
Cargo.lock

9
common/consts/Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "consts"
version = "0.1.0"
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

5
common/consts/src/lib.rs Normal file
View File

@ -0,0 +1,5 @@
#![no_std]
// Workshop date
pub const VID: u16 = 0x2020;
pub const PID: u16 = 0x0715;

View File

@ -0,0 +1,9 @@
[package]
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
edition = "2018"
name = "panic-log"
version = "0.1.0"
[dependencies]
cortex-m = "0.6.2"
log = "0.4.8"

View File

@ -0,0 +1,14 @@
#![no_std]
use core::panic::PanicInfo;
use cortex_m::asm;
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
log::error!("{}", info);
loop {
asm::bkpt()
}
}

1021
tools/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

6
tools/Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[workspace]
members = [
"dk-run",
"dongle-flash",
"serial-term",
]

17
tools/dk-run/Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
edition = "2018"
name = "dk-run"
version = "0.1.0"
[dependencies]
anyhow = "1.0.31"
arrayref = "0.3.6"
ctrlc = "3.1.4"
env_logger = "0.7.1"
gimli = "0.21.0"
log = "0.4.8"
probe-rs = "0.6.2"
probe-rs-rtt = "0.1.0"
rustc-demangle = "0.1.16"
xmas-elf = "0.7.0"

503
tools/dk-run/src/main.rs Normal file
View File

@ -0,0 +1,503 @@
use core::{
cmp,
convert::{TryFrom, TryInto},
mem,
ops::Range,
sync::atomic::{AtomicBool, Ordering},
};
use std::{
collections::{btree_map, BTreeMap},
env, fs,
io::{self, Write as _},
path::Path,
process,
rc::Rc,
};
use anyhow::{anyhow, bail};
use arrayref::array_ref;
use gimli::{
read::{CfaRule, DebugFrame, UnwindSection},
BaseAddresses, EndianSlice, LittleEndian, RegisterRule, UninitializedUnwindContext,
};
use probe_rs::{
flashing::{self, Format},
Core, CoreRegisterAddress, Probe,
};
use probe_rs_rtt::{Rtt, ScanRegion};
use xmas_elf::{program::Type, sections::SectionData, symbol_table::Entry, ElfFile};
fn main() -> Result<(), anyhow::Error> {
notmain().map(|code| process::exit(code))
}
fn notmain() -> Result<i32, anyhow::Error> {
env_logger::init();
let args = env::args().skip(1 /* program name */).collect::<Vec<_>>();
if args.len() != 1 {
bail!("expected exactly one argument")
}
let path = &args[0];
let bytes = fs::read(path)?;
let elf = ElfFile::new(&bytes).map_err(|s| anyhow!("{}", s))?;
// sections used in cortex-m-rt
// NOTE we won't load `.uninit` so it is not included here
// NOTE we don't load `.bss` because the app (cortex-m-rt) will zero it
let candidates = [".vector_table", ".text", ".rodata"];
let text = elf
.section_iter()
.zip(0..)
.filter_map(|(sect, shndx)| {
if sect.get_name(&elf) == Ok(".text") {
Some(shndx)
} else {
None
}
})
.next();
let mut debug_frame = None;
let mut range_names = None;
let mut rtt = None;
let mut sections = vec![];
let mut dotdata = None;
let mut registers = None;
for sect in elf.section_iter() {
if let Ok(name) = sect.get_name(&elf) {
if name == ".debug_frame" {
debug_frame = Some(sect.raw_data(&elf));
continue;
}
if name == ".symtab" {
if let Ok(symtab) = sect.get_data(&elf) {
let (rn, rtt_) = range_names_from(&elf, symtab, text)?;
range_names = Some(rn);
rtt = rtt_;
}
}
let size = sect.size();
// skip empty sections
if candidates.contains(&name) && size != 0 {
let start = sect.address();
if size % 4 != 0 || start % 4 != 0 {
// we could support unaligned sections but let's not do that now
bail!("section `{}` is not 4-byte aligned", name);
}
let start = start.try_into()?;
let data = sect
.raw_data(&elf)
.chunks_exact(4)
.map(|chunk| u32::from_le_bytes(*array_ref!(chunk, 0, 4)))
.collect::<Vec<_>>();
if name == ".vector_table" {
registers = Some(Registers {
vtor: start,
// Initial stack pointer
sp: data[0],
// Reset handler
pc: data[1],
});
} else if name == ".data" {
// don't put .data in the `sections` variable; it is specially handled
dotdata = Some(Data {
phys: start,
virt: start,
data,
});
continue;
}
sections.push(Section { start, data });
}
}
}
if let Some(data) = dotdata.as_mut() {
// patch up `.data` physical address
let mut patched = false;
for ph in elf.program_iter() {
if ph.get_type() == Ok(Type::Load) {
if u32::try_from(ph.virtual_addr())? == data.virt {
patched = true;
data.phys = ph.physical_addr().try_into()?;
break;
}
}
}
if !patched {
bail!("couldn't extract `.data` physical address from the ELF");
}
}
let registers = registers.ok_or_else(|| anyhow!("`.vector_table` section is missing"))?;
let probes = Probe::list_all();
if probes.is_empty() {
// TODO improve error message
bail!("nRF52840 Development Kit appears to not be connected")
}
log::debug!("found {} probes", probes.len());
let probe = probes[0].open()?;
log::info!("opened probe");
let sess = probe.attach("nRF52840_xxAA")?;
log::info!("started session");
let core = sess.attach_to_core(0)?;
log::info!("attached to core");
core.reset_and_halt()?;
log::info!("reset and halted the core");
// load program into memory
for section in &sections {
core.write_32(section.start, &section.data)?;
}
if let Some(section) = dotdata {
core.write_32(section.phys, &section.data)?;
}
// adjust registers
// this is the link register reset value; it indicates the end of the call stack
if registers.vtor >= 0x2000_0000 {
// program lives in RAM
core.write_core_reg(LR, LR_END)?;
core.write_core_reg(SP, registers.sp)?;
core.write_core_reg(PC, registers.pc)?;
core.write_word_32(VTOR, registers.vtor)?;
log::info!("loaded program into RAM");
core.run()?;
} else {
// XXX the device may have already loaded SP and PC at this point in this case?
// program lives in Flash
flashing::download_file(&sess, Path::new(path), Format::Elf)?;
log::info!("flashed program");
core.reset()?;
}
// run
let core = Rc::new(core);
let mut rtt = Rtt::attach_region(
core.clone(),
&sess,
&ScanRegion::Exact(rtt.ok_or_else(|| anyhow!("RTT control block not found"))?),
)?;
let channel = rtt
.up_channels()
.take(0)
.ok_or_else(|| anyhow!("RTT up channel 0 not found"))?;
static CONTINUE: AtomicBool = AtomicBool::new(true);
ctrlc::set_handler(|| {
CONTINUE.store(false, Ordering::Relaxed);
})?;
// wait for breakpoint
let stdout = io::stdout();
let mut stdout = stdout.lock();
let mut read_buf = [0; 1024];
let mut was_halted = false;
while CONTINUE.load(Ordering::Relaxed) {
let n = channel.read(&mut read_buf)?;
if n != 0 {
stdout.write_all(&read_buf[..n])?;
}
let is_halted = core.core_halted()?;
if is_halted && was_halted {
break;
}
was_halted = is_halted;
}
drop(stdout);
// Ctrl-C was pressed; stop the microcontroller
if !CONTINUE.load(Ordering::Relaxed) {
core.halt()?;
}
let pc = core.read_core_reg(PC)?;
let debug_frame = debug_frame.ok_or_else(|| anyhow!("`.debug_frame` section not found"))?;
let range_names = range_names.ok_or_else(|| anyhow!("`.symtab` section not found"))?;
// print backtrace
backtrace(&core, pc, debug_frame, &range_names)?;
core.reset_and_halt()?;
Ok(0)
}
fn backtrace(
core: &Core,
mut pc: u32,
debug_frame: &[u8],
range_names: &RangeNames,
) -> Result<(), anyhow::Error> {
fn gimli2probe(reg: &gimli::Register) -> CoreRegisterAddress {
CoreRegisterAddress(reg.0)
}
struct Registers<'c> {
cache: BTreeMap<u16, u32>,
core: &'c Core,
}
impl<'c> Registers<'c> {
fn new(lr: u32, sp: u32, core: &'c Core) -> Self {
let mut cache = BTreeMap::new();
cache.insert(LR.0, lr);
cache.insert(SP.0, sp);
Self { cache, core }
}
fn get(&mut self, reg: CoreRegisterAddress) -> Result<u32, anyhow::Error> {
Ok(match self.cache.entry(reg.0) {
btree_map::Entry::Occupied(entry) => *entry.get(),
btree_map::Entry::Vacant(entry) => *entry.insert(self.core.read_core_reg(reg)?),
})
}
fn insert(&mut self, reg: CoreRegisterAddress, val: u32) {
self.cache.insert(reg.0, val);
}
fn update_cfa(
&mut self,
rule: &CfaRule<EndianSlice<LittleEndian>>,
) -> Result</* cfa_changed: */ bool, anyhow::Error> {
match rule {
CfaRule::RegisterAndOffset { register, offset } => {
let cfa = (i64::from(self.get(gimli2probe(register))?) + offset) as u32;
let ok = self.cache.get(&SP.0) != Some(&cfa);
self.cache.insert(SP.0, cfa);
Ok(ok)
}
// NOTE not encountered in practice so far
CfaRule::Expression(_) => todo!("CfaRule::Expression"),
}
}
fn update(
&mut self,
reg: &gimli::Register,
rule: &RegisterRule<EndianSlice<LittleEndian>>,
) -> Result<(), anyhow::Error> {
match rule {
RegisterRule::Undefined => unreachable!(),
RegisterRule::Offset(offset) => {
let cfa = self.get(SP)?;
let addr = (i64::from(cfa) + offset) as u32;
self.cache.insert(reg.0, self.core.read_word_32(addr)?);
}
_ => unimplemented!(),
}
Ok(())
}
}
let mut debug_frame = DebugFrame::new(debug_frame, LittleEndian);
// 32-bit ARM -- this defaults to the host's address size which is likely going to be 8
debug_frame.set_address_size(mem::size_of::<u32>() as u8);
let sp = core.read_core_reg(SP)?;
let lr = core.read_core_reg(LR)?;
// statically linked binary -- there are no relative addresses
let bases = &BaseAddresses::default();
let ctx = &mut UninitializedUnwindContext::new();
let mut frame = 0;
let mut registers = Registers::new(lr, sp, core);
println!("stack backtrace:");
loop {
println!(
"{:>4}: {:#010x} - {}",
frame,
pc,
range_names
.binary_search_by(|rn| if rn.0.contains(&pc) {
cmp::Ordering::Equal
} else if pc < rn.0.start {
cmp::Ordering::Greater
} else {
cmp::Ordering::Less
})
.map(|idx| &*range_names[idx].1)
.unwrap_or("<unknown>")
);
let fde = debug_frame.fde_for_address(bases, pc.into(), DebugFrame::cie_from_offset)?;
let uwt_row = fde.unwind_info_for_address(&debug_frame, bases, ctx, pc.into())?;
let cfa_changed = registers.update_cfa(uwt_row.cfa())?;
for (reg, rule) in uwt_row.registers() {
registers.update(reg, rule)?;
}
let lr = registers.get(LR)?;
if lr == LR_END {
break;
}
if !cfa_changed && lr == pc {
println!("error: the stack appears to be corrupted beyond this point");
return Ok(());
}
if lr > 0xffff_fff0 {
println!(" <exception entry>");
let sp = registers.get(SP)?;
let stacked = Stacked::read(core, sp)?;
registers.insert(LR, stacked.lr);
// adjust the stack pointer for stacked registers
registers.insert(SP, sp + mem::size_of::<Stacked>() as u32);
pc = stacked.pc;
} else {
if lr & 1 == 0 {
bail!("bug? LR ({:#010x}) didn't have the Thumb bit set", lr)
}
pc = lr & !1;
}
frame += 1;
}
Ok(())
}
/// Registers stacked on exception entry
// XXX assumes that the floating pointer registers are NOT stacked (which may not be the case for HF
// targets)
#[derive(Debug)]
struct Stacked {
r0: u32,
r1: u32,
r2: u32,
r3: u32,
r12: u32,
lr: u32,
pc: u32,
xpsr: u32,
}
impl Stacked {
fn read(core: &Core, sp: u32) -> Result<Self, anyhow::Error> {
let mut registers = [0; 8];
core.read_32(sp, &mut registers)?;
Ok(Stacked {
r0: registers[0],
r1: registers[1],
r2: registers[2],
r3: registers[3],
r12: registers[4],
lr: registers[5],
pc: registers[6],
xpsr: registers[7],
})
}
}
// FIXME this might already exist in the DWARF data; we should just use that
/// Map from PC ranges to demangled Rust names
type RangeNames = Vec<(Range<u32>, String)>;
type Shndx = u16;
fn range_names_from(
elf: &ElfFile,
sd: SectionData,
text: Option<Shndx>,
) -> Result<(RangeNames, Option<u32>), anyhow::Error> {
let mut range_names = vec![];
let mut rtt = None;
if let SectionData::SymbolTable32(entries) = sd {
for entry in entries {
if let Ok(name) = entry.get_name(elf) {
if name == "_SEGGER_RTT" {
rtt = Some(entry.value() as u32);
}
if Some(entry.shndx()) == text && entry.size() != 0 {
let mut name = rustc_demangle::demangle(name).to_string();
// clear the thumb bit
let start = entry.value() as u32 & !1;
// strip the hash (e.g. `::hd881d91ced85c2b0`)
let hash_len = "::hd881d91ced85c2b0".len();
if let Some(pos) = name.len().checked_sub(hash_len) {
let maybe_hash = &name[pos..];
if maybe_hash.starts_with("::h") {
// FIXME avoid this allocation
name = name[..pos].to_string();
}
}
range_names.push((start..start + entry.size() as u32, name));
}
}
}
}
range_names.sort_unstable_by(|a, b| a.0.start.cmp(&b.0.start));
Ok((range_names, rtt))
}
const LR: CoreRegisterAddress = CoreRegisterAddress(14);
const PC: CoreRegisterAddress = CoreRegisterAddress(15);
const SP: CoreRegisterAddress = CoreRegisterAddress(13);
const VTOR: u32 = 0xE000_ED08;
const LR_END: u32 = 0xFFFF_FFFF;
/// ELF section to be loaded onto the target
#[derive(Debug)]
struct Section {
start: u32,
data: Vec<u32>,
}
/// The .data section has a physical address different that its virtual address; we want to load the
/// data into the physical address (which would normally be in FLASH) so that cortex-m-rt doesn't
/// corrupt the variables in .data during initialization -- it would be best if we could disable
/// cortex-m-rt's `init_data` call but there's no such feature
#[derive(Debug)]
struct Data {
phys: u32,
virt: u32,
data: Vec<u32>,
}
/// Registers to update before running the program
struct Registers {
sp: u32,
pc: u32,
vtor: u32,
}

View File

@ -0,0 +1,12 @@
[package]
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
edition = "2018"
name = "dongle-flash"
version = "0.1.0"
[dependencies]
anyhow = "1.0.31"
ihex = "1.1.2"
serialport = "3.3.0"
tempfile = "3.1.0"
xmas-elf = "0.7.0"

View File

@ -0,0 +1,149 @@
use core::convert::TryFrom;
use std::{
env, fs,
path::Path,
process::{Command, Stdio},
};
use anyhow::{anyhow, bail, ensure};
use ihex::record::Record;
use serialport::SerialPortType;
use tempfile::TempDir;
use xmas_elf::{
program::{SegmentData, Type},
ElfFile,
};
const VID: u16 = 0x1915;
const PID: u16 = 0x521f;
fn main() -> Result<(), anyhow::Error> {
let args = env::args().skip(1 /* program name */).collect::<Vec<_>>();
ensure!(args.len() == 1, "expected exactly one argument");
let dongle = serialport::available_ports()?
.into_iter()
.filter(|info| match &info.port_type {
SerialPortType::UsbPort(info) => info.vid == VID && info.pid == PID,
_ => false,
})
.next()
.ok_or_else(|| {
anyhow!("nRF52840 Dongle not found or it's not in bootloader mode (red LED should be blinking)\n
connect the Dongle to your laptop or PC and press the reset button to put it in bootloader mode\n
if the red LED was blinking and you got this message then the device wasn't correctly enumerated; remove it and try again")
})?;
let path = Path::new(&args[0]);
let filename = Path::new(
path.file_name()
.ok_or_else(|| anyhow!("{} has no file name", path.display()))?,
);
// NOTE assume that files with `.hex` extension are already in the IHEX format
let is_hex = path.extension().map(|ext| ext == "hex").unwrap_or(false);
let dir = TempDir::new()?;
let ihex = if is_hex {
path.to_owned()
} else {
// ELF -> IHEX
// here we map the ELF loadable segments -- these correspond to sections like `.text`, `.rodata`
// and `.data` (initial values) -- to ihex records
let bytes = fs::read(path)?;
let elf_file = ElfFile::new(&bytes).map_err(anyhow::Error::msg)?;
let mut records = vec![];
for ph in elf_file.program_iter() {
if ph.get_type() == Ok(Type::Load) {
let start = ph.offset();
match ph.get_data(&elf_file).map_err(anyhow::Error::msg)? {
SegmentData::Undefined(bytes) => {
// this is what `objcopy -O ihex` uses and it works with `nrfutil`
const RECORD_SIZE: usize = 16;
for (i, chunk) in bytes.chunks(RECORD_SIZE).enumerate() {
let offset =
u16::try_from(start as usize + i * RECORD_SIZE).map_err(|_| {
anyhow!(
"ELF with loadable addresses outside \
the 16-bit address space are not supported"
)
})?;
records.push(Record::Data {
offset,
value: chunk.to_owned(),
})
}
}
_ => bail!("unexpected segment data at {:#010x}", start),
}
}
}
// NOTE `objcopy` adds a `StartSegmentAddress` record before EOF but it doesn't seem to be
// needed -- AFAICT that record matches the ELF's entry point address
records.push(Record::EndOfFile);
let ihex = dir.path().join(filename).with_extension("hex");
fs::write(
&ihex,
ihex::writer::create_object_file_representation(&records)?,
)?;
println!("ELF -> iHex conversion DONE");
ihex
};
let dfu = ihex.with_extension("zip");
println!("packaging iHex using nrfutil ...");
// IHEX -> DFU package
// silence this command; it's quite noisy
let output = Command::new("nrfutil")
.args(&["pkg", "generate", "--application"])
.arg(ihex)
.args(&[
"--hw-version",
"52",
"--sd-req",
"0x00",
"--application-version",
"0",
])
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.arg(&dfu)
.output()?;
if !output.status.success() {
// do show its output if it failed
if !output.stdout.is_empty() {
println!("{}", String::from_utf8_lossy(&output.stdout));
}
if !output.stderr.is_empty() {
eprintln!("{}", String::from_utf8_lossy(&output.stderr));
}
bail!("`nrfutil package` failed");
}
println!("DONE");
// flash DFU
let status = Command::new("nrfutil")
.args(&["dfu", "usb-serial", "-pkg"])
.arg(dfu)
.args(&["-p", &dongle.port_name])
.status()?;
if !status.success() {
bail!("`nrfutil dfu` failed");
}
Ok(())
}

View File

@ -0,0 +1,11 @@
[package]
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>"]
edition = "2018"
name = "serial-term"
version = "0.1.0"
[dependencies]
anyhow = "1.0.30"
consts = { path = "../../common/consts" }
ctrlc = "3.1.4"
serialport = "3.3.0"

View File

@ -0,0 +1,59 @@
use core::sync::atomic::{AtomicBool, Ordering};
use std::{
fs::OpenOptions,
io::{self, Read as _, Write as _},
};
use serialport::SerialPortType;
#[cfg(target_os = "windows")]
compiler_error!("FIXME(Windows)");
fn main() -> Result<(), anyhow::Error> {
let mut once = true;
let dongle = loop {
if let Some(dongle) = serialport::available_ports()?
.into_iter()
.filter(|info| match &info.port_type {
SerialPortType::UsbPort(usb) => usb.vid == consts::VID,
_ => false,
})
.next()
{
break dongle;
} else if once {
once = false;
eprintln!("(waiting for the Dongle to be connected)");
}
};
// FIXME works with ttyUSB devices but not with ttyACM devices (?)
// let mut port = serialport::open(&dongle.port_name)?;
let mut port = OpenOptions::new()
.read(true)
.write(true)
.open(&dongle.port_name)?;
static CONTINUE: AtomicBool = AtomicBool::new(true);
// properly close the serial device on Ctrl-C
ctrlc::set_handler(|| CONTINUE.store(false, Ordering::Relaxed))?;
let stdout = io::stdout();
let mut stdout = stdout.lock();
let mut read_buf = [0; 64];
while CONTINUE.load(Ordering::Relaxed) {
// if port.bytes_to_read()? != 0 {
let n = port.read(&mut read_buf)?;
stdout.write_all(&read_buf[..n])?;
stdout.flush()?;
// } else {
// time span between consecutive FS USB packets
// thread::sleep(Duration::from_millis(1));
// }
}
eprintln!("(closing the serial port)");
Ok(())
}