didactic changes in advanced material

This commit is contained in:
Mirabellensaft 2020-07-15 16:28:37 +02:00
parent 2f805453fb
commit 7318f993a4
11 changed files with 38 additions and 21 deletions

View file

@ -2,10 +2,11 @@
We'll be using the `dk` Hardware Abstraction Layer. It's good to have its API documentation handy. You can generate the documentation for that crate from the command line:
✅ Run the following command from within the `advanced/firmware` folder. It will open the generated documentation in your default web browser.
``` console
$ cargo doc -p dk --open
```
Run this command from within the `advanced/firmware` folder. It will open the generated documentation in your default web browser.
> NOTE if you are using Safari and the documentation is hard to read due to missing CSS, try opening it in a different browser.
> NOTE: If you are using Safari and the documentation is hard to read due to missing CSS, try opening it in a different browser.

View file

@ -1,5 +1,7 @@
# Control Transfers
Before we continue we need to discuss how data transfers work under the USB protocol.
The control pipe handles *control transfers*, a special kind of data transfer used by the host to issue *requests*. A control transfer is a data transfer that occurs in three stages: a SETUP stage, an optional DATA stage and a STATUS stage.
During the SETUP stage the host sends 8 bytes of data that identify the control request. Depending on the issued request there may be a DATA stage or not; during the DATA stage data is transferred either from the device to the host or the other way around. During the STATUS stage the device acknowledges, or not, the whole control request.

View file

@ -1,9 +1,11 @@
# Dealing with Registers
Open the `src/bin/events.rs` file.
In this and the next section we'll look into RTIC's event handling features. To explore these features we'll use the action of connecting a USB cable to the DK's port J2 as the event we'd like to handle.
✅ Open the `src/bin/events.rs` file.
We'll read the code and explain, what it does.
The example application enables the signaling of this "USB power" event in the `init` function. This is done using the low level register API generated by the [`svd2rust`] tool. The register API was generated from a SVD (System View Description) file, a file that describes all the peripherals and registers, and their memory layout, on a device. In our case the device was the nRF52840; a sample SVD file for this microcontroller can be found [here][nrf52840.svd].
[`svd2rust`]: https://crates.io/crates/svd2rust

View file

@ -2,7 +2,7 @@
Below the `idle` function you'll see a `#[task]` handler, a function. This *task* is bound to the POWER_CLOCK interrupt signal and will be executed, function-call style, every time the interrupt signal is raised by the hardware.
"Run" the `events` application. Then connect a micro-USB cable to your PC/laptop then connect the other end to the DK (port J3). You'll see the "POWER event occurred" message after the cable is connected.
✅ Run the `events` application. Then connect a micro-USB cable to your PC/laptop then connect the other end to the DK (port J3). You'll see the "POWER event occurred" message after the cable is connected.
Note that all tasks will be prioritized over the `idle` function so the execution of `idle` will be interrupted (paused) by the `on_power_event` task. When the `on_power_event` task finishes (returns) the execution of the `idle` will be resumed. This will become more obvious in the next section.

View file

@ -1,14 +1,16 @@
# Hello, world!
First, open the `tools/dk-run` folder and run `cargo install --path . -f` to install the `dk-run` tool.
In this section, we'll set up the integration in VS Code and run the first program.
Next open the `advanced/firmware` folder in VS Code and open the `src/bin/hello.rs` file from the `advanced/apps` folder.
✅ Open the `tools/dk-run` folder and run `cargo install --path . -f` to install the `dk-run` tool.
✅ Open the `advanced/firmware` folder in VS Code and open the `src/bin/hello.rs` file from the `advanced/apps` folder.
> Note: To ensure full Rust-Analyzer support, do not open the whole `embedded-trainings-2020` folder.
Give Rust Analyzer some time to analyze the file and its dependency graph. When it's done, a "Run" button will appear over the `main` function. If it doesn't appear on its own, type something in the file, delete and save. This should trigger a re-load.
Click the "Run" button to run the application on the microcontroller.
Click the "Run" button to run the application on the microcontroller.
If you are not using VS code run the `cargo run --bin hello` command from the `advanced/firmware` folder.

View file

@ -1,6 +1,9 @@
# Listing USB Devices
In the `tools` folder you'll find `usb-list`: a minimal cross-platform version of the `lsusb` tool. Run it (`cargo run` from `tools/usb-list`) to list all USB devices.
In the `tools` folder you'll find `usb-list`: a minimal cross-platform version of the `lsusb` tool.
✅ To list all USB devices, run the progam using `cargo run` from `tools/usb-list`.
``` console
$ cargo run

View file

@ -2,7 +2,7 @@
RTIC, Real Time on Integrated Circuits, is a framework for building evented, time sensitive applications.
Open the `src/bin/rtic-hello.rs` file.
Open the `src/bin/rtic-hello.rs` file.
RTIC applications are written in RTIC's Domain Specific Language (DSL). The DSL extends Rust syntax with custom attributes like `#[init]` and `#[idle]`.
@ -10,7 +10,9 @@ RTIC makes a clearer distinction between the application's initialization phase,
`rtic::app` is a procedural macro that generates extra Rust code, in addition to the user's functions. The fully expanded version of the macro can be found in the file `target/rtic-expansion.rs`. This file will contain the expansion of the procedural macro for the last compiled RTIC application.
If you build the `rtic-hello` example and look at the generated `rtic-expansion.rs` file you can confirm that interrupts are disabled during the execution of the `init` function:
✅ Build the `rtic-hello` example and look at the generated `rtic-expansion.rs` file.
The generated code should look like this. Note that interrupts are disabled during the execution of the `init` function:
``` rust
fn main() -> ! {

View file

@ -12,7 +12,9 @@ So that's what we'll do here. In `advanced/common/usb/lib.rs` you'll find starte
The definition of `Descriptor::Configuration` as well as the associated test has been "commented out" using an `#[cfg(TODO)]` attribute because it is not handled by the firmware yet. Delete the `#[cfg(TODO)]` so that the unit tests can access it. This pattern is used for enum members and test functions throughout this workshop, so keep it in mind should you see it again.
Your task now is to parse the data of this SETUP stage. We will start with the GET_DESCRIPTOR request, which is described in detail in section 9.4.3 of the USB specification. All the constants you will need are described in Tables 9-3, 9-4 and 9-5.
✅ Parse the data of this SETUP stage.
Start with the GET_DESCRIPTOR request, which is described in detail in section 9.4.3 of the USB specification. All the constants you will need are described in Tables 9-3, 9-4 and 9-5.
The fields of a GET_DESCRIPTOR request are as follows:
- `bmRequestType` is 0b10000000

View file

@ -2,11 +2,13 @@
Now let's say we want to change the previous program to count how many times the USB cable (port J3) has been connected and disconnected.
✅ Open the `src/bin/resource.rs` file.
Tasks run from start to finish, like functions, in response to events. To preserve some state between the different executions of a task we can add a *resource* to the task. In RTIC, resources are the mechanism used to *share* data between different tasks in a memory safe manner but they can also be used to hold task state.
To get the desired behavior we'll want to store some counter in the state of the `on_power_event` task.
Open the `src/bin/resource.rs` file. The starter code shows the syntax to declare a resource, the `Resources` struct, and the syntax to associate a resource to a task, the `resources` list in the `#[task]` attribute.
The starter code shows the syntax to declare a resource, the `Resources` struct, and the syntax to associate a resource to a task, the `resources` list in the `#[task]` attribute.
In the starter code a resource is used to *move* (by value) the POWER peripheral from `init` to the `on_power_event` task. The POWER peripheral then becomes part of the state of the `on_power_event` task and can be persistently accessed throughout calls to `on_power_event()` through a *reference*. The resources of a task are available via the `Context` argument of the task.
@ -16,7 +18,7 @@ We have moved the POWER peripheral into the task because we want to clear the US
Also note that in the starter code the `idle` function has been modified. Pay attention to the logs when you run the starter code.
Your task in this section will be to modify the program so that it prints the number of times the USB cable has been connected to the DK every time the cable is connected, as shown below.
✅ Modify the program so that it prints the number of times the USB cable has been connected to the DK every time the cable is connected, as shown below.
``` console
(..)

View file

@ -1,8 +1,8 @@
# USB Enumeration
The USB protocol is complex so we'll leave out many details and focus only on the concepts required to get enumeration and configuration working. There are also several USB specific terms so we recommend checking chapter 2, "Terms and Abbreviations", of the USB specification (linked at the bottom of this document) every now and then.
A USB device, like the nRF52840, 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.
So what is enumeration? A USB device, like the nRF52840, 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.
The USB protocol is complex so we'll leave out many details and focus only on the concepts required to get enumeration and configuration working. There are also several USB specific terms so we recommend checking chapter 2, "Terms and Abbreviations", of the USB specification (linked at the bottom of this document) every now and then.
Each OS may perform the enumeration process slightly differently but the process will always involve these host actions:
@ -12,6 +12,6 @@ Each OS may perform the enumeration process slightly differently but the process
These host actions will be perceived as *events* by the nRF52840. During this workshop, we will gradually parse and handle these events and learn more about Embedded Rust along the way.
There are more USB concepts involved that we'll need to cover like descriptors, configurations, interfaces and endpoints but for now let's see how to handle USB events.
There are more USB concepts involved that we'll need to cover, like descriptors, configurations, interfaces and endpoints but for now let's see how to handle USB events.
For each step of the course, we've prepared a `usb-<n>.rs` file that gives you a base structure and hints on how to proceed. The matching `usb-<n>-solution.rs` contains a sample solution should you need it. Switch from `usb-<n>.rs` to `usb-<n+1>.rs` when instructed and continue working from there.

View file

@ -2,11 +2,13 @@
The USBD peripheral on the nRF52840 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.
Open the `firmware/src/bin/usb-1.rs` file. In this starter code the USBD peripheral is initialized in `init` and a task, named `main`, is bound to the interrupt signal USBD. This task will be called every time a new USBD event needs to be handled. The `main` task uses `usbd::next_event()` to check all the event registers; if any event is set (occurred) then the function returns the event, represented by the `Event` enum, wrapped in the `Some` variant. This `Event` is then passed to the `on_event` function for further processing.
Open the `firmware/src/bin/usb-1.rs` file.
Connect the USB cable to the port J3 then run the starter code.
In this starter code the USBD peripheral is initialized in `init` and a task, named `main`, is bound to the interrupt signal USBD. This task will be called every time a new USBD event needs to be handled. The `main` task uses `usbd::next_event()` to check all the event registers; if any event is set (occurred) then the function returns the event, represented by the `Event` enum, wrapped in the `Some` variant. This `Event` is then passed to the `on_event` function for further processing.
In this section as a warm-up exercise you'll need to deal with the following USB events until you reach the EP0SETUP event.
✅ Connect the USB cable to the port J3 then run the starter code.
Go to `fn on_event`, line 39. In this section you'll need to implement the following USB events until you reach the EP0SETUP 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. Since we are currently not dealing with any other state, you can handle this state by doing nothing.
@ -26,4 +28,3 @@ Do not overthink this exercise; it is not a trick question. There is very little
You can find the solution in the `usb-1-solution.rs` file.
Before we continue we need to discuss how data transfers work under the USB protocol.