From fd4d7c513914b6a58e13939442d09918b4eaa92d Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 9 Nov 2018 15:40:05 +0000 Subject: [PATCH 1/9] Moved the start/registers.md content into peripherals/ --- src/peripherals/a-first-attempt.md | 180 ++++++++++++++++++----------- src/peripherals/peripherals.md | 17 ++- src/start/registers.md | 122 +------------------ 3 files changed, 125 insertions(+), 194 deletions(-) diff --git a/src/peripherals/a-first-attempt.md b/src/peripherals/a-first-attempt.md index 1c55952..12f55b0 100644 --- a/src/peripherals/a-first-attempt.md +++ b/src/peripherals/a-first-attempt.md @@ -1,97 +1,139 @@ # A First Attempt -## Arbitrary Memory Locations and Rust +## The Registers -Although Rust is capable of interacting with arbitrary memory locations, dereferencing any pointer is considered an `unsafe` operation. The most direct way to expose reading from or writing to a peripheral would look something like this: +Let's look at 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 Register | 32 bits| + +## The C Approach + +In Rust, we can represent a collection of registers in exactly the same way as we do in C - with a `struct`. ```rust -use core::ptr; -const SER_PORT_SPEED_REG: *mut u32 = 0x4000_1000 as _; - -fn read_serial_port_speed() -> u32 { - unsafe { // <-- :( - ptr::read_volatile(SER_PORT_SPEED_REG) - } -} -fn write_serial_port_speed(val: u32) { - unsafe { // <-- :( - ptr::write_volatile(SER_PORT_SPEED_REG, val); - } +#[repr(C)] +struct SysTick { + pub csr: u32, + pub rvr: u32, + pub cvr: u32, + pub calib: u32, } ``` -Although this works, it is subjectively a little messy, so the first reaction might be to wrap these related things into a `struct` to better organize them. A second attempt could come up with something like this: +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! With this qualifier in place, we have our four 32-bit fields which correspond to the table above. But of course, this `struct` is of no use by itself - we need a variable. ```rust -use core::ptr; +let systick = 0xE000_E010 as *mut SysTick; +let time = unsafe { (*systick).cvr }; +``` -struct SerialPort; +## Volatile Accesses -impl SerialPort { - // Private Constants (addresses) - const SER_PORT_SPEED_REG: *mut u32 = 0x4000_1000 as _; +Now, there are a couple of problems with the approach above. - // Public Constants (enumerated values) - pub const SER_PORT_SPEED_8MBPS: u32 = 0x8000_0000; - pub const SER_PORT_SPEED_125KBPS: u32 = 0x0200_0000; +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... - fn new() -> SerialPort { - SerialPort - } +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 volatile, not the variable. - fn read_speed(&self) -> u32 { - unsafe { - ptr::read_volatile(Self::SER_PORT_SPEED_REG) +```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 get_time() -> u32 { + let systick = get_systick(); + systick.cvr.read() +} +``` + +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. + +## The Rusty Wrapper + +We need to wrap this `struct` up into a higher-layer API that is safe for our users to call. As the driver author, we manually verify the unsafe code is correct, and then present a safe API for our users so they don't have to worry about it (provided they trust us to get it right!). + +One example might be: + +```rust +use volatile_register::{RW, RO}; + +pub struct SystemTimer { + p: &'static mut RegisterBlock +} + +#[repr(C)] +struct RegisterBlock { + pub csr: RW, + pub rvr: RW, + pub cvr: RW, + pub calib: RO, +} + +impl SystemTimer { + pub fn new() -> SystemTimer { + SystemTimer { + p: unsafe { &mut *(0xE000_E010 as *mut RegisterBlock) } } } - fn write_speed(&mut self, val: u32) { - unsafe { - ptr::write_volatile(Self::SER_PORT_SPEED_REG, val); - } + pub fn get_time(&self) -> u32 { + self.p.cvr.read() + } + + pub fn set_reload(&mut self, reload_value: u32) { + unsafe { self.p.rvr.write(reload_value) } } } + +pub fn example_usage() -> String { + let mut st = SystemTimer::new(); + st.set_reload(0); + format!("Time is now 0x{:08x}", st.get_time()) +} ``` -And this is a little better! We've hidden that random looking memory address, and presented something that feels a little more rusty. We can even use our new interface: +Now, the problem with this approach is that the following code is perfectly acceptable to the compiler: ```rust -fn do_something() { - let mut serial = SerialPort::new(); +fn thread1() { + let mut st = SystemTimer::new(); + st.set_reload(2000); +} - let speed = serial.read_speed(); - // Do some work - serial.write_speed(speed * 2); +fn thread2() { + let mut st = SystemTimer::new(); + st.set_reload(1000); } ``` -But the problem with this is that our `SerialPort` struct could be created anywhere. By creating multiple instances of `SerialPort`, we would create aliased mutable pointers, which are typically avoided in Rust. - -Consider the following example: - -```rust -fn do_something() { - let mut serial = SerialPort::new(); - let speed = serial.read_speed(); - - // Be careful, we have to go slow! - if speed != SerialPort::SER_PORT_SPEED_LO { - serial.write_speed(SerialPort::SER_PORT_SPEED_LO) - } - // First, send some pre-data - something_else(); - // Okay, lets send some slow data - // ... -} - -fn something_else() { - let mut serial = SerialPort::new(); - // We gotta go fast for this! - serial.write_speed(SerialPort::SER_PORT_SPEED_HI); - // send some data... -} -``` - -In this case, if we were only looking at the code in `do_something()`, we would think that we are definitely sending our serial data slowly, and would be confused why our embedded code is not working as expected. - -In this case, it is easy to see where the error was introduced. However, once this code is spread out over multiple modules, drivers, developers, and days, it gets easier and easier to make these kinds of mistakes. +Our `&mut self` argument to the `set_reload` function checks that there are no other references to *that* particular `SystemTimer` struct, but they don't stop the user creating a second `SystemTimer` which points to the exact same peripheral! Code written in this fashion will work if the author is diligent enough to spot all of these 'duplicate' driver instances, but once the code is spread out over multiple modules, drivers, developers, and days, it gets easier and easier to make these kinds of mistakes. diff --git a/src/peripherals/peripherals.md b/src/peripherals/peripherals.md index 6762fe2..d423571 100644 --- a/src/peripherals/peripherals.md +++ b/src/peripherals/peripherals.md @@ -2,21 +2,30 @@ ## What are Peripherals? +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 dip into the world of Peripherals and their 'memory mapped registers'. + Most Microcontrollers have more than just a CPU, RAM, or Flash Memory - they contain sections of silicon which are used for interacting with systems outside of the microcontroller, as well as directly and indirectly interacting with their surroundings in the world via sensors, motor controllers, or human interfaces such as a display or keyboard. These components are collectively known as Peripherals. These peripherals are useful because they allow a developer to offload processing to them, avoiding having to handle everything in software. Similar to how a desktop developer would offload graphics processing to a video card, embedded developers can offload some tasks to peripherals allowing the CPU to spend it's time doing something else important, or doing nothing in order to save power. +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 (the peripheral in this system) 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 silicon. + However, unlike graphics cards, which typically have a Software API like Vulkan, Metal, or OpenGL, peripherals are exposed to our Microcontroller with a hardware interface, which is mapped to a chunk of the memory. ## Linear and Real Memory Space -On a microcontroller, writing some data to an arbitrary address, such as `0x4000_0000` or `0x0000_0000`, may be a completely valid action. +On a microcontroller, writing some data to some other arbitrary address, such as `0x4000_0000` or `0x0000_0000`, may also be a completely valid action. On a desktop system, access to memory is tightly controlled by the MMU, or Memory Management Unit. This component has two major responsibilities: enforcing access permission to sections of memory (preventing one thread from reading or modifying the memory of another thread); and re-mapping segments of the physical memory to virtual memory ranges used in software. Microcontrollers do not typically have an MMU, and instead only use real physical addresses in software. -Although 32 bit microcontrollers have a real and linear address space from `0x0000_0000`, and `0xFFFF_FFFF`, they generally only use a few hundred kilobytes of that range for actual memory. This leaves a significant amount of address space remaining. - -Rather than ignore that remaining space, Microcontroller designers instead mapped the interface for peripherals in certain memory locations. This ends up looking something like this: +Although 32 bit microcontrollers have a real and linear address space from `0x0000_0000`, and `0xFFFF_FFFF`, they generally only use a few hundred kilobytes of that range for actual memory. This leaves a significant amount of address space remaining. In earlier chapters, we were talking about RAM being located at address `0x2000_0000`. 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). On a Cortex-M we also have our Flash ROM mapped in at address `0x0000_0000` up to, say, address `0x0007_FFFF` (if we have a 512 KiB Flash ROM). Rather than ignore all remaining space between these two regions, Microcontroller designers instead mapped the interface for peripherals in certain memory locations. This ends up looking something like this: ![](../assets/nrf52-memory-map.png) diff --git a/src/start/registers.md b/src/start/registers.md index cdbbe85..fcddcfe 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -1,35 +1,10 @@ # 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 dip -into 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 silicon. -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, @@ -46,98 +21,3 @@ 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 Register | 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 volatile, 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 d37a2a40303f095ec699a9956ce6a69840e575de Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 9 Nov 2018 16:00:27 +0000 Subject: [PATCH 2/9] Added introduction to arch/pac/hal/board crates in start/registers. --- src/peripherals/peripherals.md | 2 -- src/start/registers.md | 31 ++++++++++++++----------------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/peripherals/peripherals.md b/src/peripherals/peripherals.md index d423571..b5a01a1 100644 --- a/src/peripherals/peripherals.md +++ b/src/peripherals/peripherals.md @@ -2,8 +2,6 @@ ## What are Peripherals? -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 dip into the world of Peripherals and their 'memory mapped registers'. - Most Microcontrollers have more than just a CPU, RAM, or Flash Memory - they contain sections of silicon which are used for interacting with systems outside of the microcontroller, as well as directly and indirectly interacting with their surroundings in the world via sensors, motor controllers, or human interfaces such as a display or keyboard. These components are collectively known as Peripherals. These peripherals are useful because they allow a developer to offload processing to them, avoiding having to handle everything in software. Similar to how a desktop developer would offload graphics processing to a video card, embedded developers can offload some tasks to peripherals allowing the CPU to spend it's time doing something else important, or doing nothing in order to save power. diff --git a/src/start/registers.md b/src/start/registers.md index fcddcfe..1045b08 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -1,23 +1,20 @@ -# Memory-mapped Registers +# Memory Mapped Registers +> The getting started chapter is about "how do I" and "what can I do with this". Later chapters cover "how does this works" and "why does it look like this". So start/peripherals.md should cover one of the cortex-m peripheral APIs, e.g. peripheral::SYST. Specifically it should cover: taking peripherals into the current scope (skipping explaining why is done like that), the low level API (read / write on individual registers: e.g. SYST.rvr.write) and the high level API (SYST.enable_counter, which is just some registers reads / writes). +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 dip into the world of Peripherals and their 'memory mapped registers'. +You may well find that the code you need to access the peripherals in your microcontroller has already been written, at one of the following levels: +* Microarchitecture Crate - This sort of crate handles any useful routines common to the processor core your microcontroller is using, as well as any peripherals that are common to all microcontrollers that use that particular type of processor core. For example the [cortex-m] crate gives you functions to enable and disable interrupts, which are the same for all Cortex-M based microcontrollers. It also gives you access to the 'SysTick' peripheral included with all Cortex-M based microcontrollers. +* Peripheral Access Crate (PAC) - This sort of crate is a thin wrapper over the various memory-wrapper registers defined for your particular part-number of microcontroller you are using. For example, [tm4c123x] for the Texas Instruments Tiva-C TM4C123 series, or [stm32f30x] for the ST-Micro STM32F30x series. Here, you'll be interacting with the registers directly, following each peripheral's operating instructions given in your microcontroller's Technical Reference Manual. +* HAL Crate - These crates offer a more user-friendly API for your particular processor, often by implementing some common traits defined in [embedded-hal]. For example, this crate might offer a `Serial` struct, with a constructor that takes an appropriate set of GPIO pins and a board rate, and offers some sort of `write_byte` method for sending data. See the chapter on [Portability] for more information on [embedded-hal]. +* Board Crate - These crates go one step further than a HAL Crate by pre-configuring various peripherals and GPIO pins to suit the specific developer kit or board you are using, such as [F3] for the STM32F3DISCOVERY board. - - -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 to 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. +[cortex-m]: https://crates.io/crates/cortex-m +[tm4c123x]: https://crates.io/crates/tm4c123x +[stm32f30x]: https://crates.io/crates/stm32f30x +[embedded-hal]: https://crates.io/crates/embedded-hal +[Portability]: ../portability/portability.md +[F3]: https://crates.io/crates/f3 From dbadee697512ae199b4e6f0de64c9a6d6423e1a4 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 9 Nov 2018 16:35:31 +0000 Subject: [PATCH 3/9] Added examples of using basic crates. --- src/start/registers.md | 101 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/src/start/registers.md b/src/start/registers.md index 1045b08..19f98b1 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -18,3 +18,104 @@ You may well find that the code you need to access the peripherals in your micro [Portability]: ../portability/portability.md [F3]: https://crates.io/crates/f3 + +## Starting at the bottom + +Let's look at the SysTick peripheral that's common to all Cortex-M based microcontrollers. We can find a pretty low-level API in the [cortex-m] crate, and we can use it like this: + +```rust +use cortex_m::peripheral::{syst, Peripherals}; +use cortex_m_rt::entry; + +#[entry] +fn main() { + let mut peripherals = Peripherals::take().unwrap(); + let mut systick = peripherals.SYST; + systick.set_clock_source(syst::SystClkSource::Core); + systick.clear_current(); + systick.enable_counter(); + while systick.get_current() < 1_000 { + // Loop + } +} +``` + +The methods on the `SYST` struct map pretty closely to the functionality defined by the ARM Technical Reference Manual for this peripheral. There's nothing in this API about 'delaying for X milliseconds' - we have to crudely implement that ourselves using a `while` loop. Note that we can't access our `SYST` struct until we have called `Peripherals::take()` - this is a special routine that guarantees that there is only one `SYST` structure in our entire program. For more on that, see the [Peripherals] section. + +[Peripherals]: ../peripherals/peripherals.md + +## Using a chip crate + +We won't get very far with our embedded software development if we restrict ourselves to only the basic peripherals included with every Cortex-M. At some point, we're going to need to write some code that's specific to the particular microcontroller we're using. In this example, let's assume we have an Texas Instruments TM4C123 - a middling 80MHz Cortex-M4 with 256 KiB of Flash. We're going to pull in the [tm4c123x] crate to make use of this chip. + +```rust +#![no_std] +#![no_main] + +extern crate panic_halt; // panic handler + +use cortex_m_rt::entry; +use tm4c123x; + +#[entry] +pub fn init() -> (Delay, Leds) { + let cp = cortex_m::Peripherals::take().unwrap(); + let p = tm4c123x::Peripherals::take().unwrap(); + + let pwm = p.PWM0; + pwm.ctl.write(|w| w.globalsync0().clear_bit()); + // Mode = 1 => Count up/down mode + pwm._2_ctl.write(|w| w.enable().set_bit().mode().set_bit()); + pwm._2_gena.write(|w| w.actcmpau().zero().actcmpad().one()); + // 528 cycles (264 up and down) = 4 loops per video line (2112 cycles) + pwm._2_load.write(|w| unsafe { w.load().bits(263) }); + pwm._2_cmpa.write(|w| unsafe { w.compa().bits(64) }); + pwm.enable.write(|w| w.pwm4en().set_bit()); +} + +``` + +We've access the `PWM0` peripheral in exactly the same as as we access the `SYST` peripheral earlier, except we called `tm4c123x::Peripherals::take()`. As this crate was auto-generated using [svd2rust], the access methods for our register fields take a closure, rather than a numeric argument. While this looks like a lot of code, the Rust compiler can use it to perform a bunch of checks for us, but then generate machine-code which is pretty close to hand-written assembler! + +### Reading + +The `read()` method takes a closure with a single argument. Typically we call this `r`. This argument then gives read-only access to the various sub-fields within this register, as defined by the manufacturer's SVD file for this chip. You can find all the methods available on the 'r' for this particular register, in this particular peripheral, on this particular chip, in the [tm4c123x documentation]. + +```rust +if pwm.ctl.read(|r| r.globalsync0().is_set()) { + // Do a thing +} +``` + +### Writing + +The `write()` method takes a closure with a single argument. Typically we call this `w`. This argument then gives read-write access to the various sub-fields within this register, as defined by the manufacturer's SVD file for this chip. Again, you can find all the methods available on the 'w' for this particular register, in this particular peripheral, on this particular chip, in the [tm4c123x documentation]. Note that all of the sub-fields that we do not set will be set to a default value for us - any existing content in the register will be lost. + +```rust +pwm.ctl.write(|w| w.globalsync0().clear_bit()); +``` + +### Modifying + +If we wish to change only on particular sub-field in this register and leave the other sub-fields unchanged, we can use the `modify` function. This function takes a closure with two arguments - one for reading and one for writing. Typically we call these `r` and `w` respectively. The `r` argument can be used to inspect the current contents of the register, and the `w` argument can be used to modify the register contents. + +```rust +pwm.ctl.modify(|r, w| w.globalsync0().clear_bit()); +``` + +The `modify` function really shows the power of closures here. In C, we'd have to read into some temporary value, modify the correct bits and then write the value back. This mean's there's considerable scope for error: + +```C +uint32_t temp = pwm0.ctl.read(); +temp |= PWM0_CTL_GLOBALSYNC0; +pwm0.ctl.write(temp); +uint32_t temp2 = pwm0.enable.read(); +temp2 |= PWM0_ENABLE_PWM4EN; +pwm0.enable.write(temp); // Uh oh! Wrong variable! +``` + +[svd2rust]: https://crates.io/crates/svd2rust +[tm4c123x documentation]: https://docs.rs/tm4c123x/0.7.0/tm4c123x/pwm0/ctl/struct.R.html + + +## Using a HAL crate From 5f7d0e9416b407fa13854f9587ef3b92f45466d7 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 9 Nov 2018 21:14:06 +0000 Subject: [PATCH 4/9] Add a HAL example. --- src/start/registers.md | 79 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 7 deletions(-) diff --git a/src/start/registers.md b/src/start/registers.md index 19f98b1..8d72abf 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -8,7 +8,7 @@ You may well find that the code you need to access the peripherals in your micro * Microarchitecture Crate - This sort of crate handles any useful routines common to the processor core your microcontroller is using, as well as any peripherals that are common to all microcontrollers that use that particular type of processor core. For example the [cortex-m] crate gives you functions to enable and disable interrupts, which are the same for all Cortex-M based microcontrollers. It also gives you access to the 'SysTick' peripheral included with all Cortex-M based microcontrollers. * Peripheral Access Crate (PAC) - This sort of crate is a thin wrapper over the various memory-wrapper registers defined for your particular part-number of microcontroller you are using. For example, [tm4c123x] for the Texas Instruments Tiva-C TM4C123 series, or [stm32f30x] for the ST-Micro STM32F30x series. Here, you'll be interacting with the registers directly, following each peripheral's operating instructions given in your microcontroller's Technical Reference Manual. -* HAL Crate - These crates offer a more user-friendly API for your particular processor, often by implementing some common traits defined in [embedded-hal]. For example, this crate might offer a `Serial` struct, with a constructor that takes an appropriate set of GPIO pins and a board rate, and offers some sort of `write_byte` method for sending data. See the chapter on [Portability] for more information on [embedded-hal]. +* HAL Crate - These crates offer a more user-friendly API for your particular processor, often by implementing some common traits defined in [embedded-hal]. For example, this crate might offer a `Serial` struct, with a constructor that takes an appropriate set of GPIO pins and a board rate, and offers some sort of `write_byte` function for sending data. See the chapter on [Portability] for more information on [embedded-hal]. * Board Crate - These crates go one step further than a HAL Crate by pre-configuring various peripherals and GPIO pins to suit the specific developer kit or board you are using, such as [F3] for the STM32F3DISCOVERY board. [cortex-m]: https://crates.io/crates/cortex-m @@ -40,11 +40,11 @@ fn main() { } ``` -The methods on the `SYST` struct map pretty closely to the functionality defined by the ARM Technical Reference Manual for this peripheral. There's nothing in this API about 'delaying for X milliseconds' - we have to crudely implement that ourselves using a `while` loop. Note that we can't access our `SYST` struct until we have called `Peripherals::take()` - this is a special routine that guarantees that there is only one `SYST` structure in our entire program. For more on that, see the [Peripherals] section. +The functions on the `SYST` struct map pretty closely to the functionality defined by the ARM Technical Reference Manual for this peripheral. There's nothing in this API about 'delaying for X milliseconds' - we have to crudely implement that ourselves using a `while` loop. Note that we can't access our `SYST` struct until we have called `Peripherals::take()` - this is a special routine that guarantees that there is only one `SYST` structure in our entire program. For more on that, see the [Peripherals] section. [Peripherals]: ../peripherals/peripherals.md -## Using a chip crate +## Using a Peripheral Access Crate (PAC) We won't get very far with our embedded software development if we restrict ourselves to only the basic peripherals included with every Cortex-M. At some point, we're going to need to write some code that's specific to the particular microcontroller we're using. In this example, let's assume we have an Texas Instruments TM4C123 - a middling 80MHz Cortex-M4 with 256 KiB of Flash. We're going to pull in the [tm4c123x] crate to make use of this chip. @@ -75,11 +75,11 @@ pub fn init() -> (Delay, Leds) { ``` -We've access the `PWM0` peripheral in exactly the same as as we access the `SYST` peripheral earlier, except we called `tm4c123x::Peripherals::take()`. As this crate was auto-generated using [svd2rust], the access methods for our register fields take a closure, rather than a numeric argument. While this looks like a lot of code, the Rust compiler can use it to perform a bunch of checks for us, but then generate machine-code which is pretty close to hand-written assembler! +We've access the `PWM0` peripheral in exactly the same as as we access the `SYST` peripheral earlier, except we called `tm4c123x::Peripherals::take()`. As this crate was auto-generated using [svd2rust], the access functions for our register fields take a closure, rather than a numeric argument. While this looks like a lot of code, the Rust compiler can use it to perform a bunch of checks for us, but then generate machine-code which is pretty close to hand-written assembler! Where the auto-generated code isn't able to determine that all possible arguments to a particular accessor function are valid (for example, if the SVD defines the register as 32-bit but doesn't say if some of those 32-bit values have a special meaning), then the function is marked as `unsafe`. We can see this in the example above when setting the `load` and `compa` sub-fields using the `bits()` function. ### Reading -The `read()` method takes a closure with a single argument. Typically we call this `r`. This argument then gives read-only access to the various sub-fields within this register, as defined by the manufacturer's SVD file for this chip. You can find all the methods available on the 'r' for this particular register, in this particular peripheral, on this particular chip, in the [tm4c123x documentation]. +The `read()` function takes a closure with a single argument. Typically we call this `r`. This argument then gives read-only access to the various sub-fields within this register, as defined by the manufacturer's SVD file for this chip. You can find all the functions available on the 'r' for this particular register, in this particular peripheral, on this particular chip, in the [tm4c123x documentation]. ```rust if pwm.ctl.read(|r| r.globalsync0().is_set()) { @@ -89,7 +89,7 @@ if pwm.ctl.read(|r| r.globalsync0().is_set()) { ### Writing -The `write()` method takes a closure with a single argument. Typically we call this `w`. This argument then gives read-write access to the various sub-fields within this register, as defined by the manufacturer's SVD file for this chip. Again, you can find all the methods available on the 'w' for this particular register, in this particular peripheral, on this particular chip, in the [tm4c123x documentation]. Note that all of the sub-fields that we do not set will be set to a default value for us - any existing content in the register will be lost. +The `write()` function takes a closure with a single argument. Typically we call this `w`. This argument then gives read-write access to the various sub-fields within this register, as defined by the manufacturer's SVD file for this chip. Again, you can find all the functions available on the 'w' for this particular register, in this particular peripheral, on this particular chip, in the [tm4c123x documentation]. Note that all of the sub-fields that we do not set will be set to a default value for us - any existing content in the register will be lost. ```rust pwm.ctl.write(|w| w.globalsync0().clear_bit()); @@ -117,5 +117,70 @@ pwm0.enable.write(temp); // Uh oh! Wrong variable! [svd2rust]: https://crates.io/crates/svd2rust [tm4c123x documentation]: https://docs.rs/tm4c123x/0.7.0/tm4c123x/pwm0/ctl/struct.R.html - ## Using a HAL crate + +The HAL crate for a chip typically works by implementing a custom Trait for the raw structures exposed by the PAC. Often this trait will define a function called `constrain()` for single peripherals or `split()` for things like GPIO ports with multiple pins. This function will consume the underlying raw peripheral structure and return a new object with a higher-level API. This API may also do things like have the Serial port `new` function require a borrow on some `Clock` structure, which can only be generated by calling the function which configures the PLLs and sets up all the clock frequencies. In this way, it is statically impossible to create a Serial port object without first having configured the clock rates, or for the Serial port object to mis-convert the baud rate into clock ticks. Some crates even define special traits for the states each GPIO pin can be in, requiring the user to put a pin into the correct state (say, by selecting the appropriate Alternate Function Mode) before passing the pin into Peripheral. All with no run-time cost! + +Let's see an example: + +```rust +#![no_std] +#![no_main] + +extern crate panic_halt; // panic handler + +use cortex_m_rt::entry; +use tm4c123x_hal as hal; +use tm4c123x_hal::prelude::*; +use tm4c123x_hal::serial::{NewlineMode, Serial}; +use tm4c123x_hal::sysctl; + +#[entry] +fn main() -> ! { + let p = hal::Peripherals::take().unwrap(); + let cp = hal::CorePeripherals::take().unwrap(); + + // Wrap up the SYSCTL struct into an object with a higher-layer API + let mut sc = p.SYSCTL.constrain(); + // Pick our oscillation settings + sc.clock_setup.oscillator = sysctl::Oscillator::Main( + sysctl::CrystalFrequency::_16mhz, + sysctl::SystemClock::UsePll(sysctl::PllOutputFrequency::_80_00mhz), + ); + // Configure the PLL with those settings + let clocks = sc.clock_setup.freeze(); + + // Wrap up the GPIO_PORTA struct into an object with a higher-layer API. + // Note it needs to borrow `sc.power_control` so it can power up the GPIO + // peripheral automatically. + let mut porta = p.GPIO_PORTA.split(&sc.power_control); + + // Activate the UART. + let uart = Serial::uart0( + p.UART0, + // The transmit pin + porta + .pa1 + .into_af_push_pull::(&mut porta.control), + // The receive pin + porta + .pa0 + .into_af_push_pull::(&mut porta.control), + // No RTS or CTS required + (), + (), + // The baud rate + 115200_u32.bps(), + // Output handling + NewlineMode::SwapLFtoCRLF, + // We need the clock rates to calculate the baud rate divisors + &clocks, + // We need this to power up the UART peripheral + &sc.power_control, + ); + + loop { + writeln!(uart, "Hello, World!\r\n").unwrap(); + } +} +``` From 35869101045d3161e32d4566c781fd756d07c833 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 9 Nov 2018 21:20:05 +0000 Subject: [PATCH 5/9] Spelling fixes. --- src/start/registers.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/start/registers.md b/src/start/registers.md index 8d72abf..b0be1c1 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -4,10 +4,10 @@ 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 dip into the world of Peripherals and their 'memory mapped registers'. -You may well find that the code you need to access the peripherals in your microcontroller has already been written, at one of the following levels: +You may well find that the code you need to access the peripherals in your micro-controller has already been written, at one of the following levels: -* Microarchitecture Crate - This sort of crate handles any useful routines common to the processor core your microcontroller is using, as well as any peripherals that are common to all microcontrollers that use that particular type of processor core. For example the [cortex-m] crate gives you functions to enable and disable interrupts, which are the same for all Cortex-M based microcontrollers. It also gives you access to the 'SysTick' peripheral included with all Cortex-M based microcontrollers. -* Peripheral Access Crate (PAC) - This sort of crate is a thin wrapper over the various memory-wrapper registers defined for your particular part-number of microcontroller you are using. For example, [tm4c123x] for the Texas Instruments Tiva-C TM4C123 series, or [stm32f30x] for the ST-Micro STM32F30x series. Here, you'll be interacting with the registers directly, following each peripheral's operating instructions given in your microcontroller's Technical Reference Manual. +* Micro-architecture Crate - This sort of crate handles any useful routines common to the processor core your microcontroller is using, as well as any peripherals that are common to all micro-controllers that use that particular type of processor core. For example the [cortex-m] crate gives you functions to enable and disable interrupts, which are the same for all Cortex-M based micro-controllers. It also gives you access to the 'SysTick' peripheral included with all Cortex-M based micro-controllers. +* Peripheral Access Crate (PAC) - This sort of crate is a thin wrapper over the various memory-wrapper registers defined for your particular part-number of micro-controller you are using. For example, [tm4c123x] for the Texas Instruments Tiva-C TM4C123 series, or [stm32f30x] for the ST-Micro STM32F30x series. Here, you'll be interacting with the registers directly, following each peripheral's operating instructions given in your micro-controller's Technical Reference Manual. * HAL Crate - These crates offer a more user-friendly API for your particular processor, often by implementing some common traits defined in [embedded-hal]. For example, this crate might offer a `Serial` struct, with a constructor that takes an appropriate set of GPIO pins and a board rate, and offers some sort of `write_byte` function for sending data. See the chapter on [Portability] for more information on [embedded-hal]. * Board Crate - These crates go one step further than a HAL Crate by pre-configuring various peripherals and GPIO pins to suit the specific developer kit or board you are using, such as [F3] for the STM32F3DISCOVERY board. @@ -21,7 +21,7 @@ You may well find that the code you need to access the peripherals in your micro ## Starting at the bottom -Let's look at the SysTick peripheral that's common to all Cortex-M based microcontrollers. We can find a pretty low-level API in the [cortex-m] crate, and we can use it like this: +Let's look at the SysTick peripheral that's common to all Cortex-M based micro-controllers. We can find a pretty low-level API in the [cortex-m] crate, and we can use it like this: ```rust use cortex_m::peripheral::{syst, Peripherals}; @@ -46,7 +46,7 @@ The functions on the `SYST` struct map pretty closely to the functionality defin ## Using a Peripheral Access Crate (PAC) -We won't get very far with our embedded software development if we restrict ourselves to only the basic peripherals included with every Cortex-M. At some point, we're going to need to write some code that's specific to the particular microcontroller we're using. In this example, let's assume we have an Texas Instruments TM4C123 - a middling 80MHz Cortex-M4 with 256 KiB of Flash. We're going to pull in the [tm4c123x] crate to make use of this chip. +We won't get very far with our embedded software development if we restrict ourselves to only the basic peripherals included with every Cortex-M. At some point, we're going to need to write some code that's specific to the particular micro-controller we're using. In this example, let's assume we have an Texas Instruments TM4C123 - a middling 80MHz Cortex-M4 with 256 KiB of Flash. We're going to pull in the [tm4c123x] crate to make use of this chip. ```rust #![no_std] @@ -103,7 +103,7 @@ If we wish to change only on particular sub-field in this register and leave the pwm.ctl.modify(|r, w| w.globalsync0().clear_bit()); ``` -The `modify` function really shows the power of closures here. In C, we'd have to read into some temporary value, modify the correct bits and then write the value back. This mean's there's considerable scope for error: +The `modify` function really shows the power of closures here. In C, we'd have to read into some temporary value, modify the correct bits and then write the value back. This means there's considerable scope for error: ```C uint32_t temp = pwm0.ctl.read(); From 96289d7fca270553ddd78541b7491a0fd8b19e70 Mon Sep 17 00:00:00 2001 From: Jonathan Pallant Date: Tue, 13 Nov 2018 17:54:58 +0000 Subject: [PATCH 6/9] Fix "read() does not take a closure". --- src/start/registers.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/start/registers.md b/src/start/registers.md index b0be1c1..c92597e 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -79,17 +79,17 @@ We've access the `PWM0` peripheral in exactly the same as as we access the `SYST ### Reading -The `read()` function takes a closure with a single argument. Typically we call this `r`. This argument then gives read-only access to the various sub-fields within this register, as defined by the manufacturer's SVD file for this chip. You can find all the functions available on the 'r' for this particular register, in this particular peripheral, on this particular chip, in the [tm4c123x documentation]. +The `read()` function returns an object which gives read-only access to the various sub-fields within this register, as defined by the manufacturer's SVD file for this chip. You can find all the functions available on special `R` return type for this particular register, in this particular peripheral, on this particular chip, in the [tm4c123x documentation][tm4c123x documentation R]. ```rust -if pwm.ctl.read(|r| r.globalsync0().is_set()) { +if pwm.ctl.read().globalsync0().is_set() { // Do a thing } ``` ### Writing -The `write()` function takes a closure with a single argument. Typically we call this `w`. This argument then gives read-write access to the various sub-fields within this register, as defined by the manufacturer's SVD file for this chip. Again, you can find all the functions available on the 'w' for this particular register, in this particular peripheral, on this particular chip, in the [tm4c123x documentation]. Note that all of the sub-fields that we do not set will be set to a default value for us - any existing content in the register will be lost. +The `write()` function takes a closure with a single argument. Typically we call this `w`. This argument then gives read-write access to the various sub-fields within this register, as defined by the manufacturer's SVD file for this chip. Again, you can find all the functions available on the 'w' for this particular register, in this particular peripheral, on this particular chip, in the [tm4c123x documentation][tm4c123x Documentation W]. Note that all of the sub-fields that we do not set will be set to a default value for us - any existing content in the register will be lost. ```rust pwm.ctl.write(|w| w.globalsync0().clear_bit()); @@ -115,7 +115,8 @@ pwm0.enable.write(temp); // Uh oh! Wrong variable! ``` [svd2rust]: https://crates.io/crates/svd2rust -[tm4c123x documentation]: https://docs.rs/tm4c123x/0.7.0/tm4c123x/pwm0/ctl/struct.R.html +[tm4c123x documentation R]: https://docs.rs/tm4c123x/0.7.0/tm4c123x/pwm0/ctl/struct.R.html +[tm4c123x documentation W]: https://docs.rs/tm4c123x/0.7.0/tm4c123x/pwm0/ctl/struct.W.html ## Using a HAL crate From 1e5eec0d09a382391898d426604ee933d4a7a8ea Mon Sep 17 00:00:00 2001 From: Jonathan Pallant Date: Tue, 13 Nov 2018 17:55:38 +0000 Subject: [PATCH 7/9] Remove note to self. --- src/start/registers.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/start/registers.md b/src/start/registers.md index c92597e..b36491b 100644 --- a/src/start/registers.md +++ b/src/start/registers.md @@ -1,7 +1,5 @@ # Memory Mapped Registers -> The getting started chapter is about "how do I" and "what can I do with this". Later chapters cover "how does this works" and "why does it look like this". So start/peripherals.md should cover one of the cortex-m peripheral APIs, e.g. peripheral::SYST. Specifically it should cover: taking peripherals into the current scope (skipping explaining why is done like that), the low level API (read / write on individual registers: e.g. SYST.rvr.write) and the high level API (SYST.enable_counter, which is just some registers reads / writes). - 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 dip into the world of Peripherals and their 'memory mapped registers'. You may well find that the code you need to access the peripherals in your micro-controller has already been written, at one of the following levels: From c9465d790b8a0f3379b1e9741988d5ecfe97e420 Mon Sep 17 00:00:00 2001 From: Jonathan Pallant Date: Tue, 13 Nov 2018 18:01:22 +0000 Subject: [PATCH 8/9] Change reload to be a large value. --- src/peripherals/a-first-attempt.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/peripherals/a-first-attempt.md b/src/peripherals/a-first-attempt.md index 12f55b0..85c21cd 100644 --- a/src/peripherals/a-first-attempt.md +++ b/src/peripherals/a-first-attempt.md @@ -117,7 +117,7 @@ impl SystemTimer { pub fn example_usage() -> String { let mut st = SystemTimer::new(); - st.set_reload(0); + st.set_reload(0x00FF_FFFF); format!("Time is now 0x{:08x}", st.get_time()) } ``` @@ -127,12 +127,12 @@ Now, the problem with this approach is that the following code is perfectly acce ```rust fn thread1() { let mut st = SystemTimer::new(); - st.set_reload(2000); + st.set_reload(2000); } fn thread2() { let mut st = SystemTimer::new(); - st.set_reload(1000); + st.set_reload(1000); } ``` From 6248d8fca168f4e3be3f91f2268d8e9f0cc4cf57 Mon Sep 17 00:00:00 2001 From: James Munns Date: Tue, 13 Nov 2018 19:31:23 +0100 Subject: [PATCH 9/9] Remove Rusty chapter, is now covered by `src/start/registers.md` --- src/SUMMARY.md | 1 - src/peripherals/rusty.md | 1 - 2 files changed, 2 deletions(-) delete mode 100644 src/peripherals/rusty.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index db6fce2..f503001 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -29,7 +29,6 @@ more information and coordination - [A first attempt in Rust](./peripherals/a-first-attempt.md) - [The Borrow Checker](./peripherals/borrowck.md) - [Singletons](./peripherals/singletons.md) - - [Peripherals in Rust](./peripherals/rusty.md) - [Static Guarantees](./static-guarantees/static-guarantees.md) - [Typestate Programming](./typestate-programming/typestate-programming.md) diff --git a/src/peripherals/rusty.md b/src/peripherals/rusty.md deleted file mode 100644 index ffbac8e..0000000 --- a/src/peripherals/rusty.md +++ /dev/null @@ -1 +0,0 @@ -# Peripherals in Rust