42: WIP: Chapter2 updates r=jamesmunns a=thejpster

* Minor tidy-ups
* Section on Memory Mapped registers
* TODO: Section on semi-hosting


Co-authored-by: Jonathan 'theJPster' Pallant <github@thejpster.org.uk>
This commit is contained in:
bors[bot]
2018-10-15 08:54:09 +00:00
7 changed files with 163 additions and 15 deletions

View File

@@ -21,6 +21,7 @@ more information and coordination
- [Getting started](./start.md)
- [QEMU](./start/qemu.md)
- [Hardware](./start/hardware.md)
- [Memory-mapped Registers](./start/registers.md)
- [Panicking](./start/panicking.md)
- [Exceptions](./start/exceptions.md)
- [IO](./start/io.md)

View File

@@ -57,7 +57,7 @@ This book generally assumes that youre reading it front-to-back. Later
chapters build on concepts in earlier chapters, and earlier chapters may
not dig into details on a topic, revisiting the topic in a later chapter.
This book will be using the [STMF3DISCOVERY] development board from
This book will be using the [STM32F3DISCOVERY] development board from
STMicroelectronics for the majority of the examples contained within. This board
is based on the ARM Cortex-M architecture, and while basic functionality is
common across most CPUs based on this architecture, peripherals and other
@@ -65,10 +65,10 @@ implementation details of Microcontrollers are different between different
vendors, and often even different between Microcontroller families from the same
vendor.
For this reason, we suggest purchasing the [STMF3DISCOVERY] development board
For this reason, we suggest purchasing the [STM32F3DISCOVERY] development board
for the purpose of following the exmaples in this book.
[STMF3DISCOVERY]: http://www.st.com/en/evaluation-tools/stm32f3discovery.html
[STM32F3DISCOVERY]: http://www.st.com/en/evaluation-tools/stm32f3discovery.html
> **HEADS UP** Until the official release of this book, which is planned to
> coincide with the 2018 edition release of the Rust Programming Language,

View File

@@ -5,4 +5,10 @@
> for discussion of this section.
In this section we'll walk you through the process of writing, building,
flashing and debugging embedded programs.
flashing and debugging embedded programs. You will be able to try most of the
examples without any special hardware as we will show you the basics using
QEMU, a popular open-source hardware emulator. The only section where hardware
is required is, naturally enough, the [Hardware](./start/hardware.md) section,
where we use use OpenOCD to program an [STM32F3DISCOVERY].
[STM32F3DISCOVERY]: http://www.st.com/en/evaluation-tools/stm32f3discovery.html

View File

@@ -34,10 +34,11 @@ This board contains an STM32F303VCT6 microcontroller. This microcontroller has:
## Configuring
We'll start from scratch with a fresh template instance. Refer to [previous
section] for a refresher on how to do this without `cargo-generate`.
We'll start from scratch with a fresh template instance. Refer to the
[previous section on QEMU] for a refresher on how to do this without
`cargo-generate`.
[previous section]: /start/qemu.html
[previous section on QEMU]: /start/qemu.md
``` console
$ cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart
@@ -79,7 +80,9 @@ MEMORY
```
There's no step three. You can now cross compile programs using `cargo build`
and inspect the binaries using `cargo-binutils` as you did before.
and inspect the binaries using `cargo-binutils` as you did before. The
`cortex-m-rt` crate handles all the magic required to get your chip running,
as helpfully, pretty much all Cortex-M CPUs boot in the same fashion.
``` console
$ cargo build --example hello
@@ -99,7 +102,7 @@ time, however, the server will be OpenOCD.
As done during the [verify] section connect the discovery board to your laptop /
PC and check that the ST-LINK header is populated.
[verify]: /intro/install/verify.html
[verify]: /intro/install/verify.md
On a terminal run `openocd` to connect to the ST-LINK on the discovery board.
Run this command from the root of the template; `openocd` will pick up the

View File

@@ -1,6 +0,0 @@
# Panics
> **NOTE** You can follow this section *without* hardware, i.e. using QEMU.
> **TODO** Cover the `panic_handler` attribute and `panic_handler` crates like
> `panic_halt` and `panic_semihosting`. Mention

143
src/start/registers.md Normal file
View File

@@ -0,0 +1,143 @@
# Memory-mapped Registers
Embedded systems can only get so far by executing normal Rust code and moving
data around in RAM. If we want to get any information into or out of our
system (be that blinking an LED, detecting a button press or communicating
with an off-chip peripheral on some sort of bus) we're going to have to do dip
in to the world of 'memory mapped registers'.
If you look at the main circuit board in an old-fashioned home computer from
the 1970s or 1980s (and actually, the desktop PCs of yesterday are not so far
removed from the embedded systems of today) you would expect to see:
* A processor
* A RAM chip
* A ROM chip
* An I/O controller
The RAM chip, ROM chip and I/O controller would be joined to the processor
through a series of parallel traces known as a 'bus'. This bus carries address
information, which selects which device on the bus the processor wishes to
communicate with, and a data bus which carries the actual data. In our
embedded microcontrollers, the same principles apply - it's just that
everything is packed on to a single piece of sillicon.
In earlier chapters, we were talking about RAM being located at address
`0x2000_0000`. This is a 32-bit number because the ARM Cortex-M processor
cores have a 32-bit address bus. If our RAM was 64 KiB long (i.e. with a
maximum address of 0xFFFF) then addresses `0x2000_0000` to `0x2000_FFFF` would
correspond to our RAM. When we write to a variable which lives at address
`0x2000_1234`, what happens internally is that some logic detects the upper
portion of the address (0x2000 in this example) and then activates the RAM so
that it can act upon the lower portion of the address (0x1234 in this case).
Going back to our home computer example, our I/O controller needs to operate
in the same fashion as the RAM, as it sits on the same bus. Here though,
instead of having a full 64 Ki (65,536) addressable locations, it might only
have three or four addressable locations. These locations are known as
*memory-mapped registers*. By writing data to these registers, the processor
can affect the operation of the hardware. What happens when you do this is
entirely down to the design of the peripheral. For example, on an I/O
peripheral, each bit of one register might correspond the output level of an
I/O pin allowing us to turn on some LEDs, while some other register might
allow us to set whether each pin is an Input pin or an Output pin. On a UART
peripheral, we might instead expect to see one register which lets us set the
baud rate of our serial connection, one for data we wish to send over the
serial connection and another which lets us read any buffered data that has
been received.
Let's take the 'SysTick' peripheral - a simple timer which comes with every
Cortex-M processor core. Typically you'll be looking these up in the chip
manufacturer's data sheet or *Technical Reference Manual*, but this example is
common to all ARM Cortex-M cores, let's look in the [ARM reference manual]. we
see there are four registers:
[ARM reference manual]: http://infocenter.arm.com/help/topic/com.arm.doc.dui0553a/Babieigh.html
| Offset | Name | Description | Width |
|--------|-------------|-----------------------------|--------|
| 0x00 | SYST_CSR | Control and Status Register | 32 bits|
| 0x04 | SYST_RVR | Reload Value Register | 32 bits|
| 0x08 | SYST_CVR | Current Value Register | 32 bits|
| 0x0C | SYST_CALIB | Calibration Value Regsister | 32 bits|
In Rust, we can represent a collection of registers in exactly the same way as we do in C - with a `struct`.
```rust
#[repr(C)]
struct SysTick {
pub csr: u32,
pub rvr: u32,
pub cvr: u32,
pub calib: u32,
}
```
The qualifier `#[repr(C)]` tells the Rust compiler to lay this structure out
like a C compiler would. That's very important, as Rust allows structure
fields to be re-ordered, while C does not. You can imagine the debugging we'd
have to do if these fields were silently re-arranged by the compiler! We then
have our four 32-bit fields, which should correspond to the table above. But
of course, this `struct` is of no use by itself - we need a variable.
```rust
let systick = 0xE000_E010 as *mut SysTick;
let time = unsafe { (*systick).cvr };
```
Now, there are a couple of problems with this approach.
1. We have to use unsafe every time we want to access our Peripheral.
2. We've got no way of specifying which registers are read-only or read-write.
3. Any piece of code anywhere in your program could access the hardware
through this structure.
4. Most importantly, it doesn't actually work...
Now, the problem is that compilers are clever. If you make two writes to the
same piece of RAM, one after the other, the compiler can notice this and just
skip the first write entirely. In C, we can mark variables as `volatile` to
ensure that every read or write occurs as intended. In Rust, we instead mark
the *accesses* as volatie, not the variable.
```rust
let systick = unsafe { &mut *(0xE000_E010 as *mut SysTick) };
let time = unsafe { std::ptr::read_volatile(&mut systick.cvr) };
```
So, we've fixed one of our four problems, but now we have even more `unsafe`
code! Fortunately, there's a third party crate which can help -
[`volatile_register`].
[`volatile_register`]: https://crates.io/crates/volatile_register
```rust
use volatile_register::{RW, RO};
#[repr(C)]
struct SysTick {
pub csr: RW<u32>,
pub rvr: RW<u32>,
pub cvr: RW<u32>,
pub calib: RO<u32>,
}
fn get_systick() -> &'static mut SysTick {
unsafe { &mut *(0xE000_E010 as *mut SysTick) }
}
fn test() {
let systick = get_systick();
let time = systick.cvr.read();
unsafe { systick.rvr.write(time) };
}
```
Now, the volatile accesses are performed automatically through the `read` and
`write` methods. It's still `unsafe` to perform writes, but to be fair,
hardware is a bunch of mutable state and there's no way for the compiler to
know whether these writes are actually safe, so this is a good default
position. We can always wrap this `struct` into a higher level API which
verifies when these writes are safe - more on that in the chapter on [Static
Guarantees].
[Static Guarantees]: /static-guarantees/static-guarantees.md

1
src/unsorted.md Normal file
View File

@@ -0,0 +1 @@
# Unsorted topics