embedded-trainings-2020/embedded-workshop-book/src/uarte-implementation.md

192 lines
6.2 KiB
Markdown
Raw Normal View History

2023-02-21 15:24:57 +00:00
# Write the Uarte implementation
## Step-by-Step Solution
2023-02-27 16:00:21 +00:00
2023-03-07 16:52:41 +00:00
### Step 1: Check Documentation.
2023-02-27 16:00:21 +00:00
The UART protocol requires four pins, they are usually labelled:
2023-02-22 18:11:41 +00:00
* RXD
* TXD
* CTS
* RTS
2023-03-13 12:29:44 +00:00
2023-03-20 14:48:21 +00:00
✅ Check the [User Guide in section 7.2](https://infocenter.nordicsemi.com/topic/ug_nrf52840_dk/UG/dk/vir_com_port.html) to find to find out which pins are reserved for these and what their configuration needs to be.
2023-02-27 16:00:21 +00:00
2023-03-07 16:52:41 +00:00
### Step 2: Explore the `nrf-hal` to find out what needs to be done.
2023-02-22 18:11:41 +00:00
The `nrf52840-hal` is a crate that exports all the `52840` flagged features from the `nrf-hal-common`. Let's take a look at the `nrf52840-hal`'s [Uarte module](https://github.com/nrf-rs/nrf-hal/blob/v0.14.1/nrf-hal-common/src/uarte.rs).
2023-02-22 18:11:41 +00:00
In line 16 we see, that the nRF52840 uses the `hal::pac::UARTE1` peripheral.
In line 44 you find the `struct Uarte<T>(T)`, the interface to a UARTE instance `T`. Besides the instance `T`, the instantiating method takes variables of the following types as arguments: `Pins`, `Parity` and `Baudrate`.
A quick search of the document reveals where to find all of them:
* `Pins`: Line 463
* `Parity` and `Baudrate`: Re-export on line 34
✅ Add the following lines as import:
2023-03-13 12:29:44 +00:00
```
2023-02-22 18:11:41 +00:00
use hal::pac::uarte0::{
baudrate::BAUDRATE_A as Baudrate, config::PARITY_A as Parity};
use hal::uarte;
```
2023-03-17 18:31:07 +00:00
### Step 3: Add `struct Uarte`
2023-02-22 18:11:41 +00:00
✅ Add `struct Uarte` that serves as a wrapper for underlying HAL UART driver.
The struct has one field labelled `inner` which is of type `hal::Uarte` but with the type parameter `T` being set to `hal::pac::UARTE1`.
The main difference between using our `Uarte` object and the underlying HAL `Uarte` object is that *ours* will be pre-configured for the correct pins and baud rate, according to the layout of our nRF52840-DK board and the Virtual COM Port interface chip on the other end of the UART link.
<details>
<summary>Solution</summary>
```rust
pub struct Uarte {
inner: hal::Uarte<hal::pac::UARTE1>,
}
```
</details>
2023-03-07 16:52:41 +00:00
### Step 4: Bring up the peripheral in the `fn init()`
2023-02-22 18:11:41 +00:00
✅ Take a closer look at the definition of the `uarte::Pins` struct in the `nrf-hal`. Compare the pin type configurations with the ones you have already imported in `lib.rs`. Add the ones you're missing.
2023-03-13 12:29:44 +00:00
✅ Create an instance of this struct in `fn init()` with the appropriate pins and configurations. Set the output pin's level to `Level::High`.
2023-03-13 12:29:44 +00:00
Note, that the third and fourth pin are each wrapped in an `Option`.
✅ Create a Uarte driver with `hal::uarte::Uarte::new(...)` and bind it to a variable called `uarte` - we will stash this in our own `Uarte` struct later.
Creating the Uarte driver requires four arguments:
2023-03-07 16:52:41 +00:00
* The `UARTE1` instance can be found in the `periph` variable.
* Your instance of the `uarte::Pins` struct.
* Set parity to `Parity::INCLUDED`
* set the baud rate to `Baudrate::BAUD115200`.
2023-02-22 18:11:41 +00:00
<details>
<summary>Solution</summary>
```rust
let pins = hal::uarte::Pins {
rxd: pins.p0_08.degrade().into_floating_input(),
txd: pins.p0_06.degrade().into_push_pull_output(Level::High),
cts: Some(pins.p0_07.degrade().into_floating_input()),
rts: Some(pins.p0_05.degrade().into_push_pull_output(Level::High)),
};
let uarte = hal::uarte::Uarte::new(periph.UARTE1, pins, Parity::INCLUDED, Baudrate::BAUD115200);
```
</details>
2023-03-07 16:52:41 +00:00
### Step 5: Board struct
2023-02-22 18:11:41 +00:00
✅ Add a field for the `Uarte` struct in the `Board` struct.
2023-03-07 16:52:41 +00:00
add the field to the instance of the `Board` struct in `fn init()`.
<details>
<summary>Solution</summary>
```rust
pub struct Board {
/// LEDs
pub leds: Leds,
/// Buttons
pub buttons: Buttons,
/// Timer
pub timer: Timer,
/// uarte interface
pub uarte: Uarte,
}
// ...
pub fn init() -> Result<Board, ()> {
// ...
Ok(Board {
leds: Leds {
// ...
},
buttons: Buttons {
// ...
},
// 🔼 --- Button Exercise --- 🔼
timer: Timer { inner: timer },
uarte: Uarte { inner: uarte },
})
} else {
Err(())
}
```
</details>
2023-03-07 16:52:41 +00:00
### Step 6: Implementing the `fmt::Write` trait
2023-02-22 18:11:41 +00:00
We want to implement the `fmt::Write` trait so that users can call `write!` on our Uarte object
When implementing this, we can't just write to the `Uarte` instance because a simple write of a string literal would try and read the string literal from flash memory. This does not work because the EasyDMA peripheral in the nRF52 series can only access RAM, not flash.
Instead our implementation must ensure all the strings are copied to a stack allocated buffer and that buffer is passed to the Uarte's `write` method.
2023-03-13 12:29:44 +00:00
✅ Add `use::core::fmt;` to your imports.
✅ Create a public method `write_str`. It takes a mutable reference to self and a `&str` as argument. It returns an `fmt::Result`
2023-02-22 18:11:41 +00:00
✅ Create a buffer. The type is an `array` of 16 u8, set to all 0.
2023-02-27 16:00:21 +00:00
✅ To copy all data into an on-stack buffer, *iterate* over *chunks* of the string and copy them into the buffer (noting that the chunk length may be less than the requested size if you are at the end of the input string).
2023-02-27 16:00:21 +00:00
<details>
<summary>Solution</summary>
2023-02-27 16:00:21 +00:00
```rust
impl fmt::Write for Uarte {
fn write_str(&mut self, s: &str) -> fmt::Result {
// Copy all data into an on-stack buffer so we never try to EasyDMA from
// flash.
let mut buf: [u8; 16] = [0; 16];
for block in s.as_bytes().chunks(16) {
buf[..block.len()].copy_from_slice(block);
self.inner.write(&buf[..block.len()]).map_err(|_| fmt::Error)?;
}
Ok(())
}
2023-02-27 16:00:21 +00:00
}
```
</details>
2023-02-27 16:00:21 +00:00
2023-03-07 16:52:41 +00:00
### Step 7: Connect your computer to the virtual UART
[todo!] [directions for mac present, linux and windows are missing.]
2023-02-27 16:00:21 +00:00
✅ Use the following command to find the address of the nRF52840-DK on your computer.
2023-02-22 18:11:41 +00:00
2023-02-27 16:00:21 +00:00
```
2023-03-13 12:29:44 +00:00
ls /dev/tty.usbmodem*
2023-02-27 16:00:21 +00:00
```
✅ Run the following command to run `screen` with the nRF52840-DK with 115200 baud.
2023-02-27 16:00:21 +00:00
```
2023-03-07 16:52:41 +00:00
screen <address of mc> 115200
2023-02-27 16:00:21 +00:00
```
2023-03-17 18:31:07 +00:00
### Step 8: Run the example.
2023-02-27 16:00:21 +00:00
✅ In another terminal window go into the folder `down-the-stack/apps` and use the following command.
2023-02-27 16:00:21 +00:00
```
cargo run --bin uarte_print
```
2023-02-21 15:24:57 +00:00
2023-03-13 12:29:44 +00:00
On your terminal window where `screen` runs, "Hello, World" should appear.
You need to terminate `screen` manually.