From 52b530ed2c5075ece379624f72ec6a2ed29e03c7 Mon Sep 17 00:00:00 2001 From: Michael Droogleever Date: Sat, 18 Aug 2018 01:46:27 +0200 Subject: [PATCH] Multiple changes Fix #1 Add serial chapter Small fixes everywhere --- Cargo.toml | 2 + README.md | 3 ++ src/README.md | 40 ++++++++------ src/SUMMARY.md | 36 ++++++++----- src/choice/00.00.README.md | 10 ++-- src/getting-started/02.00.FLASH.md | 11 ++-- src/hello-world/00.00.README.md | 3 ++ src/hello-world/01.00.SEMIHOSTING.md | 4 +- src/hello-world/02.00.UART.md | 20 ++++--- src/hello-world/02.01.NIX.md | 11 ++++ src/hello-world/03.00.LED.md | 31 +++++++++-- src/hello-world/src/main.rs | 1 + src/serial/.gdbinit | 12 +++++ src/serial/00.00.README.md | 9 ++++ src/serial/01.00.ECHO.md | 19 +++++++ src/serial/01.01.THEORY.md | 78 ++++++++++++++++++++++++++++ src/serial/01.02.ECHO.md | 3 ++ src/setup/WINDOWS.md | 2 - 18 files changed, 235 insertions(+), 60 deletions(-) create mode 100644 src/serial/.gdbinit create mode 100644 src/serial/00.00.README.md create mode 100644 src/serial/01.00.ECHO.md create mode 100644 src/serial/01.01.THEORY.md create mode 100644 src/serial/01.02.ECHO.md diff --git a/Cargo.toml b/Cargo.toml index cbd82a3..6fdec78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,8 @@ members = [ "src/getting-started", "src/hello-world", "src/display", + "src/microbit", + "src/serial", ] [profile.dev] diff --git a/README.md b/README.md index ab03d3d..5569e94 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ > Learn embedded software in Rust on the micro:bit +[**droogmic.github.io/microrust**](https://droogmic.github.io/microrust/) + ## License The documentation is licensed under @@ -23,6 +25,7 @@ at your option. ### Contribution +Contribution is welcome by either submitting an issue or a pull request. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions. diff --git a/src/README.md b/src/README.md index 7b45cd9..d6003f5 100644 --- a/src/README.md +++ b/src/README.md @@ -5,14 +5,34 @@ [Rust]: https://www.rust-lang.org/ [microbit]: https://microbit.org/ -This book is an introductory course on microcontroller-based embedded systems that uses Rust as the -teaching language (rather than the usual C/C++), and the micro:bit as target system. +This book is an introductory course on microcontroller-based embedded systems +that uses Rust as the teaching language (rather than the usual C/C++), +and the micro:bit as the target system. + +## Approach + +- Beginner friendly. + No previous experience with microcontrollers or embedded systems is required. + +- Hands on. + *You* will be doing most of the work here. + When possible, pages will end on a problem for you to solve, with the solution on the next page. + There are plenty of exercises to put the theory into practice. + +- Standard. + We'll make plenty use of standard tooling and processes to ease development + so you can apply the skills learnt to any Rust embedded project. + Fixing compiler errors, debugging with GDB, and logging will be introduced early on. + Using LEDs as a debugging mechanism has no place here. ## Scope -The following topics will be covered: +The following topics are covered in the core chapters: - How to write, build, flash and debug an embedded program. +- Basic operation of a GPIO, ubiquitous in microcontrollers. + +The rest of the chapters are independent, only requiring the core knowledge: - Functionality ("peripherals") commonly found in microcontrollers: - Digital input and output, including buttons and LEDs @@ -26,20 +46,6 @@ The following topics will be covered: -## Approach - -- Beginner friendly. - No previous experience with microcontrollers or embedded systems is required. - -- Hands on. - *You* will be doing most of the work here. - When possible, pages will end on a problem for you to solve, with the solution on the next page. - Plenty of exercises to put the theory into practice. - -- Standard. We'll make plenty use of standard tooling and processes to ease development. - Fixing compiler errors, debugging with GDB, and logging will be introduced early on. - Using LEDs as a debugging mechanism has no place here. - ## Non-goals What's out of scope for this book: diff --git a/src/SUMMARY.md b/src/SUMMARY.md index c4d63be..6e35e87 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -23,10 +23,29 @@ - [Serial communication](hello-world/02.00.UART.md) - [*nix](hello-world/02.01.NIX.md) - [Windows](hello-world/02.02.WINDOWS.md) - - [LED](hello-world/03.00.LED.md) + - [GPIO and LEDs](hello-world/03.00.LED.md) - [Solution](hello-world/03.01.SOLUTION.md) -- [Choose your own adventure](choice/00.00.README.md) +- [Choose Your Own Adventure](choice/00.00.README.md) + +- [WIP - micro:bit HAL basics](microbit/00.00.README.md) + - [WIP - Buttons](microbit/01.00.BUTTONS.md) + - [WIP - Delays](microbit/02.00.DELAY.md) + - [WIP - Display](microbit/03.00.DISPLAY.md) + +- [WIP - Serial UART](serial/00.00.README.md) + - [Echo Server](serial/01.00.ECHO.md) + - [Theory](serial/01.01.THEORY.md) + - [Solution](serial/01.02.ECHO.md) + - [Exercises](serial/02.00.md) + - [Reverse echo](serial/02.01.md) + - [Solution](serial/02.01.SOLUTION.md) + - [Countdown](serial/02.02.md) + - [Solution](serial/02.02.SOLUTION.md) + - [Display echo](serial/02.03.md) + - [Solution](serial/02.03.SOLUTION.md) + - [Quiz](serial/02.04.md) + - [Solution](serial/02.04.SOLUTION.md) - [LED display](display/00.00.README.md) - [Theory](display/01.00.THEORY.md) @@ -43,7 +62,7 @@ - [WIP - Real time](rtfm/00.00.README.md) -- [WIP - HAL](hal/00.00.README.md) +- [WIP - Creating a HAL](hal/00.00.README.md) @@ -53,14 +72,3 @@ [GDB cheatsheet](appendix/gdb.md) [General troubleshooting](appendix/troubleshooting.md) - - - - diff --git a/src/choice/00.00.README.md b/src/choice/00.00.README.md index f26d558..0b987f9 100644 --- a/src/choice/00.00.README.md +++ b/src/choice/00.00.README.md @@ -3,11 +3,9 @@ At this point of the book, you know the basics to get started with embedded development with Rust. -The following chapters are all independent of each other. -This means they can be done in any order, -and the only required knowledge is found in the chapters before this. +The following chapters are more independent of each other, and can be done in any order; +the only required knowledge is found in the chapters before this. +If attempting the exerises, it is best to follow the book in order to pace the difficulty. -Feel free to either follow the rest of the book in order, or choose a chapter which interests you. - -> A large portion of this book is still unfinished and we would love your support. +> A large portion of this book is still unfinished and I would love your support. > Please submit an issue to request a new section, and a pull request to add a section. \ No newline at end of file diff --git a/src/getting-started/02.00.FLASH.md b/src/getting-started/02.00.FLASH.md index 6a0ad95..d07fd90 100644 --- a/src/getting-started/02.00.FLASH.md +++ b/src/getting-started/02.00.FLASH.md @@ -17,7 +17,7 @@ We need to give OCD the name of the interfaces we are using: ``` console $ # All -$ # Windows: remember that you need an extra `-s %PATH_TO_OPENOCD%\share\scripts` +$ # Windows: remember that you need an extra `-s %PATH_TO_OPENOCD%\\scripts` $ openocd -f interface/cmsis-dap.cfg -f target/nrf51.cfg ``` @@ -79,7 +79,7 @@ Reading symbols from target/thumbv6m-none-eabi/debug/rustled...done. This only opens a GDB shell. To actually connect to the OpenOCD GDB server, use the following command within the GDB shell: -``` +``` gdb (gdb) target remote :3333 Remote debugging using :3333 0x00000000 in ?? () @@ -98,7 +98,7 @@ After entering this command, you'll see new output in the OpenOCD terminal: Almost there. To flash the device, we'll use the `load` command inside the GDB shell: -``` +``` gdb (gdb) load Loading section .vector_table, size 0x188 lma 0x8000000 Loading section .text, size 0x38a lma 0x8000188 @@ -133,7 +133,7 @@ And that's it. You'll also see new output in the OpenOCD terminal. Our program is loaded, we can now run it! -``` +``` gdb (gdb) continue Continuing. ``` @@ -148,7 +148,8 @@ Before we move on though, we are going to add one more file to our project. This will automate the last few steps so we don't need to repeatedly do the same actions in gdb: `.gdbinit` -``` + +``` gdbinit # Connects GDB to OpenOCD server port target remote :3333 # (optional) Unmangle function names when debugging diff --git a/src/hello-world/00.00.README.md b/src/hello-world/00.00.README.md index 11dbb0c..b706d71 100644 --- a/src/hello-world/00.00.README.md +++ b/src/hello-world/00.00.README.md @@ -1,3 +1,6 @@ # Hello world In this chapter, we will discuss the basic I/O of embedded development in rust. + +After this chapter,you should have all the neccesary basic knowledge to do embedded development in Rust, +with anything remaining being solution specific. diff --git a/src/hello-world/01.00.SEMIHOSTING.md b/src/hello-world/01.00.SEMIHOSTING.md index 4462983..f291321 100644 --- a/src/hello-world/01.00.SEMIHOSTING.md +++ b/src/hello-world/01.00.SEMIHOSTING.md @@ -63,7 +63,7 @@ panicked at 'test-panic', src/hello-world/src/main.rs:27:5 ## stdout -Finally, this is how to write to stdout, although writing to stderr is just as easy. +Finally, this is how to write to stdout. ``` rust extern crate cortex_m_semihosting as sh; @@ -76,4 +76,4 @@ use sh::hio; writeln!(hio::hstdout().unwrap(), "Init").unwrap(); ``` -And that's it! +Writing to stderr is just as easy. diff --git a/src/hello-world/02.00.UART.md b/src/hello-world/02.00.UART.md index f59112e..ae13bb8 100644 --- a/src/hello-world/02.00.UART.md +++ b/src/hello-world/02.00.UART.md @@ -12,9 +12,7 @@ The data rate is called the _baud rate_, and we will use 115200bps. ## USB -The micro:bit allows us to transmit and receive this serial communication over USB -with no additional hardware, -along with our flashing and debugging activities. +The micro:bit allows us to transmit and receive this serial communication over USB with no additional hardware. ## Tooling @@ -35,19 +33,19 @@ if let Some(p) = microbit::Peripherals::take() { // Configure RX and TX pins accordingly let tx = gpio.pin24.into_push_pull_output().downgrade(); let rx = gpio.pin25.into_floating_input().downgrade(); + // Configure serial communication let (mut tx, _) = serial::Serial::uart0(p.UART0, tx, rx, BAUD115200).split(); + // Write string with newline and carriage return + // This could also be a format string let _ = write!(tx, "serial test\n\r"); } ``` +In minicom/PuTTY you should see: + ``` -Welcome to minicom 2.7.1 - -OPTIONS: I18n -Compiled on Jun 5 2018, 10:54:41. -Port /dev/ttyACM0, 19:50:57 - -Press CTRL-A Z for help on special keys - serial test ``` + +This is a very simple introduction to using the UART as one way serial logging. +The chapter on UART serial communication goes into much more detail. diff --git a/src/hello-world/02.01.NIX.md b/src/hello-world/02.01.NIX.md index 934f3f4..ebc7296 100644 --- a/src/hello-world/02.01.NIX.md +++ b/src/hello-world/02.01.NIX.md @@ -66,6 +66,17 @@ $ minicom -D /dev/ttyUSB0 -b 115200 This tells `minicom` to open the serial device at `/dev/ttyUSB0` and set its baud rate to 115200. A text-based user interface (TUI) will pop out. +``` +Welcome to minicom 2.7.1 + +OPTIONS: I18n +Compiled on Jun 5 2018, 10:54:41. +Port /dev/ttyACM0, 19:50:57 + +Press CTRL-A Z for help on special keys + +``` + You can now send data using the keyboard! Go ahead and type something. Note that the TUI *won't* echo back what you type (nothing will happen when you type) but you'll see TX (red) LED on the serial module blink with each keystroke. diff --git a/src/hello-world/03.00.LED.md b/src/hello-world/03.00.LED.md index 72753ab..63007cd 100644 --- a/src/hello-world/03.00.LED.md +++ b/src/hello-world/03.00.LED.md @@ -1,17 +1,39 @@ -# LED +# GPIO and LEDs + +## GPIO + +> GPIO: General purpose input-output + +The GPIO is a block of pins found on nearly all microcontrollers. +As the name implies, they are general-purpose, configureable, analog or digital, input or output, electrical pins. +Exactly what features each pin has on a given microcontroller will require looking at a datasheet. + +> Analog vs Digital: Analog signals carry data in their amplitude as they continuously vary over time, +> whereas digital signals have a fixed rate and fixed amplitudes. Digital signals are usually just 0 or 1, i.e. 0V or 3.3V + +## LED + +> LED: Light emitting diode Let us now turn on an LED! But how? -Well, first we should look at the [documentation of our crate][microbit], +Many integrated periperals like LEDs and buttons are already connected to certain GPIO pins, +so lighting up an LED can be as simple as configuring a GPIO pin to be a digital output. + +First we should look at the [documentation of our crate][microbit], and you should be able to figure out how to get access to the gpio, and set individual pins high and low: [microbit]: https://docs.rs/microbit/0.5.1/microbit/ ``` rust +// This takes singleton ownership of the micro:bit's peripherals if let Some(p) = microbit::Peripherals::take() { + // Take the micro:bit's GPIO let mut gpio = p.GPIO.split(); + // Take pin 1 of the GPIO, and configure it as a digital output let mut pin1 = gpio.pin1.into_push_pull_output(); + // Set pin 1 high pin1.set_high(); } ``` @@ -22,12 +44,15 @@ On the first sheet you should find a diagram with a grid of numbered LEDs. > If you do not know much about electronics: > Each row and column (labelled ROW and COL) represent a GPIO output pin. -> The components labelled LED are light emitting diodes. +> The components labelled are LEDs. > LEDs only let current flow one way, and only emit light when current is flowing. > If a row is set high, high voltage, and a column is set low, low voltage, > the LED at the point that they cross will have a potential difference across it; > current will flow and it will light up. +As you can see, the micro:bit's display LEDs are a bit more complicated than being connected to a single pin. +Each LED is connected to 2 pins, where one needs to be high, and the other low for the LED to light up. + The 5x5 array of LEDs are actually wired up as a 3x9 array (3 rows by 9 columns), with 2 missing. This is usually done to make the circuit design easier. diff --git a/src/hello-world/src/main.rs b/src/hello-world/src/main.rs index 4214c4e..919ecc2 100644 --- a/src/hello-world/src/main.rs +++ b/src/hello-world/src/main.rs @@ -46,6 +46,7 @@ fn main() -> ! { let _ = gpio.pin4.into_push_pull_output(); // Set row high (column starts low) led.set_high(); + // Write string with newline and carriage return write!(tx, "serial - LED on\r\n"); } panic!("End"); diff --git a/src/serial/.gdbinit b/src/serial/.gdbinit new file mode 100644 index 0000000..b70f137 --- /dev/null +++ b/src/serial/.gdbinit @@ -0,0 +1,12 @@ +# Connects GDB to OpenOCD server port +target remote :3333 +# (optional) Unmangle function names when debugging +set print asm-demangle on +# Enable semihosting +monitor arm semihosting enable +# Load your program, breaks at entry +load +# (optional) Add breakpoint at function +break serial::main +# Continue with execution +continue \ No newline at end of file diff --git a/src/serial/00.00.README.md b/src/serial/00.00.README.md new file mode 100644 index 0000000..0d5dc44 --- /dev/null +++ b/src/serial/00.00.README.md @@ -0,0 +1,9 @@ +# WIP - UART serial server + +In the first section of this book we saw how to do a simple debug print using serial. +This is useful for logging and debugging, but does not cover the full potential of the UART peripheral. + +## Input and output + +The simultaneous input and output capabilities of the UART allow for both your computer and the micro:bit to act as a server. +They can receive a transmission, process it, perform some action, and send a response. diff --git a/src/serial/01.00.ECHO.md b/src/serial/01.00.ECHO.md new file mode 100644 index 0000000..dfb5214 --- /dev/null +++ b/src/serial/01.00.ECHO.md @@ -0,0 +1,19 @@ +# Echo Server + +An echo server, is probably the simplest server we could make. +It should receive a message, and echo it back to the sender. + +We earlier said that minicom/PuTTY would transmit any keystrokes we send, +and display and data received, +so the end result should be the familiar experience of typing and seeing the letters typed appear as expected. + +## Flow + +1. The character `a` is typed +2. minicom/PuTTY encodes the `a` character's unicode code point (`097` in decimal) as a word in a serial frame +3. The frame is transmitted to the micro:bit over USB +4. The micro:bit software decodes the frame to get the word +5. The microbit software re-encodes the word to get a (new but identical) frame +6. The frame is transmitted to the computer over USB +7. minicom/PuTTY decodes the frame's word as a unicode code point +8. The letter `a` is displayed diff --git a/src/serial/01.01.THEORY.md b/src/serial/01.01.THEORY.md new file mode 100644 index 0000000..35d7a20 --- /dev/null +++ b/src/serial/01.01.THEORY.md @@ -0,0 +1,78 @@ +# Serial Theory + +The micro:bit core crate implements the embedded_hal::serial::Write and embedded_hal::serial::Read traits for the tx and rx pins respectively. + +## `writeln!` and Carriage Return + +In the introduction page on serial communication, I brushed over this: + +``` rust +// Write string with newline and carriage return +let _ = write!(tx, "serial test\r\n"); +``` + +A naïve assumption would be to try the seemingly more correct `writeln!` macro: + +``` rust +// Write string with newline and carriage return +let _ = writeln!(tx, "serial test"); +``` + +This will usually fail to do what is intended, +as multiple writes will only print one line in PuTTY, +and produce the following in minicom: + +``` +serial test + serial test + serial test + serial test +``` + +Your choices are to either configure minicom and PuTTY appropriately or use `write!` with `\r\n`. + +### Control Characters + +The control characters operate based on a print head, as used in [teleprinters][tty]. + +`\r` - Carriage Return - The print head is moves left to the start of the line. +`\n` - Line Feed - The print head moves down once to a new line. + +[tty]: https://en.wikipedia.org/wiki/Teleprinter + +### `writeln!` macro + +The `writeln!` macro should append a new line, +but he [documentation for core::writeln][doc] says: + +> On all platforms, the newline is the LINE FEED character (\n/U+000A) alone (no additional CARRIAGE RETURN (\r/U+000D). + +[doc]: https://doc.rust-lang.org/core/macro.writeln.html + +### minicom + +CTRL-A + Z will tell you that CTRL-A + U will add a carriage return. +This will add a carriage return to a received `\n` + +### PuTTY + +In PuTTY, you can enable enable `Implicit LF in every CR` under Terminal options. + +## Blocking + +Behind the scenes, `embedded_hal::serial` uses the nb crate to allow for blocking and non-blocking operation. +This is implemented in embedded_hal crates by returning nb::Error::WouldBlock +when a read or write action cannot be performed immediately. +In this chapter, we will only be using read and write as simple blocking calls. + +### `block!` + +The `block!` macro provided by the crate continuously calls the expression +contained until it no longer returns Error::WouldBlock. + +## Tx - `embedded_hal::serial::Write` or `core::fmt::Write` + +The `write!` and `writeln!` macros call `write_str` of the `core::fmt::Write` trait which is implemented for Tx. +`write_str` is implemented as a blocking call to `write` of the `embedded_hal::serial::Write` trait. + +This means `write!(tx, "a")` is equivalent to `block!(tx.write(b'a'))`. diff --git a/src/serial/01.02.ECHO.md b/src/serial/01.02.ECHO.md new file mode 100644 index 0000000..efa993c --- /dev/null +++ b/src/serial/01.02.ECHO.md @@ -0,0 +1,3 @@ +# Echo Solution + +{{#include src/main.rs}} \ No newline at end of file diff --git a/src/setup/WINDOWS.md b/src/setup/WINDOWS.md index 14fed22..ee008aa 100644 --- a/src/setup/WINDOWS.md +++ b/src/setup/WINDOWS.md @@ -1,7 +1,5 @@ # Windows -> UNTESTED: please submit an issue if you can confirm this works. - ## `arm-none-eabi-*` ARM provides `.exe` installers for Windows. Grab one from [here][gcc], and follow the instructions.