mirror of
https://github.com/nkbai/book.git
synced 2026-08-24 20:23:29 +08:00
Merge #42
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:
@@ -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)
|
||||
|
||||
@@ -57,7 +57,7 @@ This book generally assumes that you’re 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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
143
src/start/registers.md
Normal 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
1
src/unsorted.md
Normal file
@@ -0,0 +1 @@
|
||||
# Unsorted topics
|
||||
Reference in New Issue
Block a user