From fe9c33102be20078d3dbcc379d52ef30ac7987d5 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Wed, 10 Oct 2018 18:37:49 +0100 Subject: [PATCH 1/8] Add chapter on memory mapped registers. --- src/SUMMARY.md | 1 + src/start/registers.md | 90 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 src/start/registers.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 0ee7ea1..f72b662 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -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) diff --git a/src/start/registers.md b/src/start/registers.md new file mode 100644 index 0000000..75cedbd --- /dev/null +++ b/src/start/registers.md @@ -0,0 +1,90 @@ +# 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, + pub rvr: RW, + pub cvr: RW, + pub calib: RO, +} + +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 From 0f0937b4a1833ce0ed8bd99494f9b0712819f4b7 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Wed, 10 Oct 2018 18:38:00 +0100 Subject: [PATCH 2/8] Remove unused file. --- src/start/panics.md | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 src/start/panics.md diff --git a/src/start/panics.md b/src/start/panics.md deleted file mode 100644 index cc8faee..0000000 --- a/src/start/panics.md +++ /dev/null @@ -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 From e33c18123191095e8ca8db5b3e4bba0b8b524ae6 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Wed, 10 Oct 2018 18:38:10 +0100 Subject: [PATCH 3/8] This file gets auto-generated. --- src/unsorted.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/unsorted.md diff --git a/src/unsorted.md b/src/unsorted.md new file mode 100644 index 0000000..4be6654 --- /dev/null +++ b/src/unsorted.md @@ -0,0 +1 @@ +# Unsorted topics From bd5dffefe3cbb35b2213f24eb384db197e488805 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Wed, 10 Oct 2018 18:39:58 +0100 Subject: [PATCH 4/8] Minor tidy-ups. --- src/start/hardware.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/start/hardware.md b/src/start/hardware.md index 4187d19..aad7472 100644 --- a/src/start/hardware.md +++ b/src/start/hardware.md @@ -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 From 1d2b0f3adf02d92b95ab9a9276919c81e59b33f4 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Wed, 10 Oct 2018 18:40:04 +0100 Subject: [PATCH 5/8] Extend intro. --- src/start.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/start.md b/src/start.md index 3fcc00d..4a6be86 100644 --- a/src/start.md +++ b/src/start.md @@ -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 From 0309f7828ba5f2e7aa7648dbcb4ee0ded5096d44 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Wed, 10 Oct 2018 18:40:23 +0100 Subject: [PATCH 6/8] Fix name of board. --- src/intro/introduction.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/intro/introduction.md b/src/intro/introduction.md index a223043..6cfd89b 100644 --- a/src/intro/introduction.md +++ b/src/intro/introduction.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, From 4ce35a9c583b6231b60f1d19a0ee61bec1a886e5 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Wed, 10 Oct 2018 18:42:40 +0100 Subject: [PATCH 7/8] Add line-wrapping. --- src/start/registers.md | 75 +++++++++++++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/src/start/registers.md b/src/start/registers.md index 75cedbd..c931cc6 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -1,21 +1,56 @@ # 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'. +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: +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. +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). +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. +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: +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 @@ -38,7 +73,12 @@ struct SysTick { } ``` -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. +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; @@ -49,17 +89,24 @@ 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. +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. +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`]. +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 @@ -85,6 +132,12 @@ fn test() { } ``` -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]. +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 From 2945185b5f7f018b4fe59ee0f07d4011d608689f Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Thu, 11 Oct 2018 12:22:18 +0100 Subject: [PATCH 8/8] Fix internal link - I think. --- src/start/registers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/start/registers.md b/src/start/registers.md index c931cc6..1b77855 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -140,4 +140,4 @@ 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 +[Static Guarantees]: /static-guarantees/static-guarantees.md