From c1932fceb69d5068e20f4c1fcbc5fc87580c9da6 Mon Sep 17 00:00:00 2001 From: Mirabellensaft Date: Mon, 19 Apr 2021 14:36:01 +0200 Subject: [PATCH 1/5] clarify rtic-expansion output --- embedded-workshop-book/src/rtic-hello.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embedded-workshop-book/src/rtic-hello.md b/embedded-workshop-book/src/rtic-hello.md index ec14358..26d2a7a 100644 --- a/embedded-workshop-book/src/rtic-hello.md +++ b/embedded-workshop-book/src/rtic-hello.md @@ -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() -> ! { From 80c890b7855cb2a88e0b8599fa366b6d76472167 Mon Sep 17 00:00:00 2001 From: Mirabellensaft Date: Tue, 20 Apr 2021 13:36:17 +0200 Subject: [PATCH 2/5] changed order of items to be the same as in the code example --- embedded-workshop-book/src/usb-events.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embedded-workshop-book/src/usb-events.md b/embedded-workshop-book/src/usb-events.md index 38c1d92..c9e32c0 100644 --- a/embedded-workshop-book/src/usb-events.md +++ b/embedded-workshop-book/src/usb-events.md @@ -14,10 +14,10 @@ Go to `fn on_event`, line 39. In this section you'll need to implement the follo - `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. +- `EP0SETUP`. The USBD peripheral has detected the SETUP stage of a control transfer. If you get to this point move to the next section. + When you are done you should see this output: ``` console From 86f1fefcc2f949cf4a19348e6fefbeb799ff1ef5 Mon Sep 17 00:00:00 2001 From: Mirabellensaft Date: Tue, 20 Apr 2021 14:38:38 +0200 Subject: [PATCH 3/5] change assignment: more concise, less like a trap --- advanced/firmware/src/bin/usb-1.rs | 6 ++---- embedded-workshop-book/src/usb-events.md | 22 ++++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/advanced/firmware/src/bin/usb-1.rs b/advanced/firmware/src/bin/usb-1.rs index 62d2b02..6bd8504 100644 --- a/advanced/firmware/src/bin/usb-1.rs +++ b/advanced/firmware/src/bin/usb-1.rs @@ -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!(), } } diff --git a/embedded-workshop-book/src/usb-events.md b/embedded-workshop-book/src/usb-events.md index c9e32c0..5761bb1 100644 --- a/embedded-workshop-book/src/usb-events.md +++ b/embedded-workshop-book/src/usb-events.md @@ -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. - -- `EP0DATADONE`. The USBD peripheral is signaling the end of the DATA stage of a control transfer. You won't encounter this event just yet. - -- `EP0SETUP`. The USBD peripheral has detected the SETUP stage of a control transfer. If you get to this point move to the next section. - -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. From e7a872b12009ef1efb676cd2341a486f6c7c9a08 Mon Sep 17 00:00:00 2001 From: Mirabellensaft Date: Wed, 21 Apr 2021 14:28:28 +0200 Subject: [PATCH 4/5] re organise info --- embedded-workshop-book/src/setup-stage.md | 58 ++++++++++++++--------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/embedded-workshop-book/src/setup-stage.md b/embedded-workshop-book/src/setup-stage.md index a301a30..7e1303d 100644 --- a/embedded-workshop-book/src/setup-stage.md +++ b/embedded-workshop-book/src/setup-stage.md @@ -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 From 429769f1b104a9a78542a08df0e4583069a6e4a9 Mon Sep 17 00:00:00 2001 From: Mirabellensaft Date: Wed, 21 Apr 2021 15:38:22 +0200 Subject: [PATCH 5/5] restructure assignment and information --- embedded-workshop-book/src/data-stage.md | 26 ++++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/embedded-workshop-book/src/data-stage.md b/embedded-workshop-book/src/data-stage.md index 0277231..1700fe4 100644 --- a/embedded-workshop-book/src/data-stage.md +++ b/embedded-workshop-book/src/data-stage.md @@ -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):