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

187 lines
5.8 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
✅ Check the User Guide in section 7.2 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 the `UARTE1` instance.
2023-03-07 16:52:41 +00:00
The struct has one field labelled `inner`, it contains the `UARTE1` instance: `hal::Uarte<hal::pac::UARTE1>`.
<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 an interface to the UARTE1 instance with `uarte::Uarte::new(...)` that you bind to a variable. This instantiating method takes 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.