Merge pull request #155 from ferrous-systems/improve-material

Improve material
This commit is contained in:
Tanks Transfeld 2021-04-23 13:19:27 +02:00 committed by GitHub
commit 4d18924e31
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 68 additions and 46 deletions

View file

@ -43,10 +43,8 @@ fn on_event(_usbd: &USBD, event: Event) {
Event::UsbReset => todo!(),
Event::UsbEp0DataDone => todo!(),
// leave this at it is for now.
Event::UsbEp0Setup => {
log::info!("goal reached; move to the next section");
dk::exit()
}
Event::UsbEp0Setup => todo!(),
}
}

View file

@ -2,17 +2,22 @@
The next step is to respond to the GET_DESCRIPTOR request with a device descriptor.
✅ Open the file `src/bin/usb-3.rs`. Implement the response to the GET_DESCRIPTOR request. Use the following guide for assistance.
❗️ Keep the cable connected to the J3 port for the rest of the workshop
To do this we'll use the `dk::usb::Ep0In` abstraction -- we'll look into what the abstraction does in a future section; for now we'll just use it.
✅ Open the file `src/bin/usb-3.rs`.
An instance of this abstraction is available in the `board` value (`#[init]` function). The first step is to make this `Ep0In` instance available to the `on_event` function.
Part of this response is already implemented. We'll go through this.
The `Ep0In` API has two methods: `start` and `end` (also see their API documentation). `start` is used to start a DATA stage; this method takes a *slice of bytes* (`[u8]`) as argument; this argument is the response data. The `end` method needs to be called after `start`, when the EP0DATADONE event is raised, to complete the control transfer. `Ep0In` will automatically issue the STATUS stage that must follow the DATA stage.
To implement responding to a GET_DESCRIPTOR Device request, extend `usb-3.rs` so that it uses `Ep0In` to respond to the `GET_DESCRIPTOR Device` request (and only to that request). The response must be a device descriptor with its fields set to these values:
We'll use the `dk::usb::Ep0In` abstraction. An instance of it is available in the `board` value (`#[init]` function). The first step is to make this `Ep0In` instance available to the `on_event` function.
The `Ep0In` API has two methods: `start` and `end`. `start` is used to start a DATA stage; this method takes a *slice of bytes* (`[u8]`) as argument; this argument is the response data. The `end` method needs to be called after `start`, when the EP0DATADONE event is raised, to complete the control transfer. `Ep0In` will automatically issue the STATUS stage that must follow the DATA stage.
✅ Implement the EP0DATADONE event by calling the `end` method of the `EP0In` API.
✅ Implement the response to the GET_DESCRIPTOR request. Extend `usb-3.rs` so that it uses `Ep0In` to respond to the `GET_DESCRIPTOR Device` request (and only to that request).
**Values of the device descriptor**
- `bLength = 18`, the size of the descriptor (must always be this value)
- `bDescriptorType = 1`, device descriptor type (must always be this value)
@ -25,14 +30,17 @@ To implement responding to a GET_DESCRIPTOR Device request, extend `usb-3.rs` so
- `bNumConfigurations = 1`, must be at least `1` so this is the minimum value
>(\*) the `common` crate refers to the crate in the `advanced/common` folder. It is already part of the `firmware` crate dependencies.
**Use the `usb2::device::Descriptor` abstraction**
Although you can create the device descriptor by hand as an array filled with magic values we *strongly* recommend you use the `usb2::device::Descriptor` abstraction. The crate is already in the dependency list of the project; you can open its API documentation with the following command: `cargo doc -p usb2 --open`.
> NOTE: the `usb2::device::Descriptor` struct does not have `bLength` and `bDescriptorType` fields. Those fields have fixed values according to the USB spec so you cannot modify or set them. When `bytes()` is called on the `Descriptor` value the returned array, the binary representation of the descriptor, will contain those fields set to their correct value.
**The length of the device descriptor**
Note that the device descriptor is 18 bytes long but the host may ask for fewer bytes (see `wlength` field in the SETUP data). In that case you must respond with the amount of bytes the host asked for. The opposite may also happen: `wlength` may be larger than the size of the device descriptor; in this case your answer must be 18 bytes long (do *not* pad the response with zeroes).
The `usb2::device::Descriptor` struct does not have `bLength` and `bDescriptorType` fields. Those fields have fixed values according to the USB spec so you cannot modify or set them. When `bytes()` is called on the `Descriptor` value the returned array, the binary representation of the descriptor, will contain those fields set to their correct value.
Don't forget to also handle the `EP0DATADONE` event!
The device descriptor is 18 bytes long but the host may ask for fewer bytes (see `wlength` field in the SETUP data). In that case you must respond with the amount of bytes the host asked for. The opposite may also happen: `wlength` may be larger than the size of the device descriptor; in this case your answer must be 18 bytes long (do *not* pad the response with zeroes).
**Expected log output**
Once you have successfully responded to the GET_DESCRIPTOR Device request you should get logs like these (if you are logging like `usb-3` does):

View file

@ -12,7 +12,7 @@ RTIC makes a clearer distinction between the application's initialization phase,
✅ 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:
You can use `rustfmt` on `target/rtic-expansion.rs` to make the generated code easier to read. Among other things, the file should contain the following lines. Note that interrupts are disabled during the execution of the `init` function:
``` rust
fn main() -> ! {

View file

@ -7,24 +7,32 @@ In `usb-2.rs`, you will find a short description of each register above the vari
[nrf product spec]: https://infocenter.nordicsemi.com/pdf/nRF52840_PS_v1.1.pdf
## Writing a parser for the data of this SETUP stage.
❗️ Keep the cable connected to the J3 port for the rest of the workshop
✅ Parse GET_DESCRIPTOR requests for DEVICE descriptors.
Modify `Request::parse()` in `advanced/common/usb/src/lib.rs` to recognize a GET_DESCRIPTOR request of type DEVICE so that the `get_descriptor_device` test passes. Note that the parser already handles SET_ADDRESS requests.
**Getting Started:**
**1. Writing code that can be tested**
When you need to write some `no_std` code that does not involve device-specific I/O you should consider writing it as a separate crate. This way, you can test it on your development machine (e.g. `x86_64`) using the standard `cargo test` functionality.
So that's what we'll do here. In `advanced/common/usb/lib.rs` you'll find starter code for writing a `no_std` SETUP data parser. The starter code contains some unit tests; you can run them with `cargo test` (from within the `usb` folder) or you can use Rust Analyzer's "Test" button in VS code.
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.
✅ Parse the data of this SETUP stage.
❗️ Keep the cable connected to the J3 port for the rest of the workshop
Start with the GET_DESCRIPTOR request, which is described in detail in section 9.4.3 of the [USB specification][usb_spec]. All the constants we'll be using are also described in Tables 9-3, 9-4 and 9-5 of the same document.
[usb_spec]: https://www.usb.org/document-library/usb-20-specification
**2. Description of GET_DESCRIPTOR request**
We can recognize a GET_DESCRIPTOR request by the following properties:
- `bmRequestType` is **0b10000000**
- `bRequest` is **6** (i.e. the GET_DESCRIPTOR Request Code, defined in table 9-4 in the USB spec)
**3. Description of DEVICE descriptor requests**
In this task, we only want to parse DEVICE descriptor requests. They have the following properties:
- the descriptor type is **1** (i.e. DEVICE, defined in table 9-5 of the USB spec)
@ -32,29 +40,33 @@ In this task, we only want to parse DEVICE descriptor requests. They have the fo
- the wIndex is **0** for our purposes
- ❗you need to fetch the descriptor type from the high byte of `wValue`, and the descriptor index from the the low byte of `wValue`
Check section 9.4.3 of the [USB specification][usb_spec] for a very detailed description of the requests. All the constants we'll be using are also described in Tables 9-3, 9-4 and 9-5 of the same document.
**4. Remember that you can define binary literals by prefixing them with `0b`.**
**5. You can use bit shifts (`>>`) and casts (`as u8`) to get the high/low bytes of `wValue`.**
**6. Return `Err` if properties aren't met.**
You will also find this information in the `// TODO implement ...` comment in the `Request::parse()` function of `lib.rs` file.
> NOTE: If you'd like to learn more, take a look at Section 9.4.3 Get Descriptor of the USB specification.
To complete the task, proceed like this:
1. **Parse GET_DESCRIPTOR requests for DEVICE descriptors:**
Modify `Request::parse()` in `advanced/common/usb/src/lib.rs` to recognize a GET_DESCRIPTOR request of type DEVICE so that the `get_descriptor_device` test passes. Note that the parser already handles SET_ADDRESS requests.
- remember the GET_DESCRIPTOR fields described at the start of this section
- remember that you can define binary literals by prefixing them with `0b`
- you can use bit shifts (`>>`) and casts (`as u8`) to get the high/low bytes of `wValue`
See `advanced/common/usb/solution-get-descriptor-device.rs` for a solution.
2. **Read incoming request information and pass it to the parser:**
modify `usb-2.rs` to read `USBD` registers and parse the SETUP data when an EPSETUP event is received.
✅ Read incoming request information and pass it to the parser:
modify `usb-2.rs` to read `USBD` registers and parse the SETUP data when an EP0SETUP event is received.
**Getting Started:**
- for a mapping of register names to the `USBD` API, check the entry for `nrf52840_hal::target::usbd` in the documentation you've created using `cargo doc`
- remember that we've learned how to read registers in `events.rs`
- let bmrequesttype = usbd.bmrequesttype.read().bits() as u8;
- remember that we've learned how to read registers in `events.rs`.
- you will need to put together the higher and lower bits of `wlength`, `windex` and `wvalue` to get the whole field
- > Note: If you're using a Mac, you need to catch `SetAddress` requests returned by the parser as these are sent before the first GetDescriptor request. You can handle them by doing nothing.
3. when you have successfully received a GET_DESCRIPTOR request for a Device descriptor you are done. You should see an output like this:
1. when you have successfully received a GET_DESCRIPTOR request for a Device descriptor you are done. You should see an output like this:
``` console
INFO:usb_2 -- USB: UsbReset @ 438.842772ms
@ -67,5 +79,7 @@ INFO:usb_2 -- Goal reached; move to the next section
`wlength` / `length` can vary depending on the OS, USB port (USB 2.0 vs USB 3.0) or the presence of a USB hub so you may see a different value.
You can find a solution to step 1. in `advanced/common/usb/solution-get-descriptor-device.rs`.
You can find a solution to step 2. in `advanced/firmware/src/bin/usb-2-solution.rs`.
You can find a solution to this step in `advanced/firmware/src/bin/usb-2-solution.rs`.
[usb_spec]: https://www.usb.org/document-library/usb-20-specification

View file

@ -10,22 +10,24 @@ In this starter code the USBD peripheral is initialized in `init` and a task, na
❗️ Keep the cable connected to the J3 port for the rest of the workshop
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:
This code will panic because `USBRESET` is not implemented yet.
- `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.
- `EP0SETUP`. The USBD peripheral has detected the SETUP stage of a control transfer. If you get to this point move to the next section.
- `EP0DATADONE`. The USBD peripheral is signaling the end of the DATA stage of a control transfer. You won't encounter this event just yet.
When you are done you should see this output:
✅ Go to `fn on_event`, line 39. In this section you'll need to implement the following USB events `USBRESET` and `EP0SETUP` so that your log output will look like this:
``` console
(..)
INFO:usb_1 -- USB: UsbReset
INFO:usb_1 -- returning to the Default state
INFO:usb_1 -- USB: UsbEp0Setup
INFO:usb_1 -- goal reached; move to the next section
INFO:dk -- `dk::exit() called; exiting ...
```
Do not overthink this exercise; it is not a trick question. There is very little to do and no new functionality to add.
## Help
- `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 adding a log statement to provide information that this event occurred.
- `EP0DATADONE`. The USBD peripheral is signaling the end of the DATA stage of a control transfer. Since you won't encounter this event just yet, you can leave it as it is.
- `EP0SETUP`. The USBD peripheral has detected the SETUP stage of a control transfer. Add a log statement containing "goal reached; move to the next section" and exit the application.
You can find the solution in the `usb-1-solution.rs` file.