Development progress

- many small fixes
- add hello-world
- fix source code
This commit is contained in:
Michael Droogleever
2018-07-21 22:22:56 +02:00
parent ffc7b9dfec
commit c5de62831e
22 changed files with 497 additions and 118 deletions

9
.cargo/config Normal file
View File

@@ -0,0 +1,9 @@
[target.thumbv6m-none-eabi]
runner = "arm-none-eabi-gdb"
rustflags = [
"-C", "link-arg=-Wl,-Tlink.x",
"-C", "link-arg=-nostartfiles",
]
[build]
target = "thumbv6m-none-eabi"

6
.gdbinit Normal file
View File

@@ -0,0 +1,6 @@
target remote :3333
monitor arm semihosting enable
set print asm-demangle on
load
#break main
continue

View File

@@ -1,6 +1,7 @@
[workspace]
members = [
"src/getting-started",
"src/hello-world",
]
[profile.dev]

View File

@@ -13,14 +13,17 @@
- [Verify the installation](setup/VERIFY.md)
- [Getting started](getting-started/00.00.README.md)
- [Building](getting-started/01.00.PROJECT.md)
- [Building](getting-started/01.00.BUILD.md)
- [Flashing](getting-started/02.00.FLASH.md)
- [LED](getting-started/03.00.LED.md)
- [Debugging](getting-started/04.00.DEBUG.md)
- [Debugging](getting-started/03.00.DEBUG.md)
- [WIP - Hello world](hello-world/00.00.README.md)
- [Semi-hosting](hello-world/01.00.SEMIHOSTING.md)
- [UART](hello-world/02.00.UART.md)
- [Hello world](hello-world/00.00.README.md)
- [Semihosting](hello-world/01.00.SEMIHOSTING.md)
- [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)
- [Solution](hello-world/03.01.SOLUTION.md)
- [Choose your own adventure](choice/00.00.README.md)
@@ -46,6 +49,8 @@
[Explore](appendix/explore.md)
[GDB cheatsheet](appendix/gdb.md)
[General troubleshooting](appendix/troubleshooting.md)
<!-- - [LED roulette](05-led-roulette/README.md)

15
src/appendix/gdb.md Normal file
View File

@@ -0,0 +1,15 @@
# GDB cheatsheet
| Short | Command | Action |
|:---------- |:---------------- |:------------------------------------------- |
| b? | break | Add breakpoint |
| | continue | |
| | step | |
| | stepi | |
| | print | |
| | info locals | |
| | layout src | |
| | tui disable | |
| | layout asm | |
| | dissasmble /m | |
| | monitor reset halt| |

View File

@@ -166,3 +166,17 @@ $ rustup update nightly
$ rustup target add thumbv7em-none-eabihf
```
## Build problems
### `error: language item required, but not found: \`eh_personality\``
#### Cause
The `eh_personality` language item is used to implement stack unwinding in case a panic occurs.
#### Fix
You need to use the correct target
by using `--target thumbv6m-none-eabi`
or modifying `.cargo/config`

View File

@@ -86,7 +86,9 @@ error: cannot find macro `println!` in this scope
```
`println` is a macro found in the std crate.
We don't need it at the moment, so we can remove it and try to compile again:
We don't need it at the moment, so we can remove it and try to build again.
## Build 3
```
error: language item required, but not found: `panic_impl`
@@ -124,7 +126,7 @@ fn main() {
}
```
## Build 3
## Build 4
``` console
$ cargo build --target thumbv6m-none-eabi
@@ -237,7 +239,7 @@ It will allow us to debug the code running on our micro:bit, from your computer.
Now, all you need to do is run `$ cargo build`,
and cargo will automatically add `--target thumbv6m-none-eabi`.
## Build 4
## Build 5
`Cargo.toml`
```
@@ -296,7 +298,7 @@ fn main() -> ! {
}
```
## Build 5
## Build 6
```
error: linking with `arm-none-eabi-gcc` failed: exit code: 1
@@ -313,12 +315,12 @@ We mentioned something a little earlier about memory.x file.
To save you the hassle of scouring the internet for one or creating your own, you can copy it over into your project:
``` console
cp ../getting-started/memory.x
cp ../../memory.x ./
```
> Often a board support crate will already include this, so this step will not be necessary.
## Build 6
## Build 7
```
error: linking with `arm-none-eabi-gcc` failed: exit code: 1
@@ -356,22 +358,22 @@ extern crate microbit;
use rt::ExceptionFrame;
entry!(main);
fn main() -> ! {
loop {}
}
exception!(HardFault, hard_fault);
fn hard_fault(ef: &ExceptionFrame) -> ! {
panic!("{:#?}", ef);
}
exception!(*, default_handler);
fn default_handler(irqn: i16) {
panic!("Unhandled exception (IRQn = {})", irqn);
}
entry!(main);
fn main() -> ! {
loop {}
}
```
It is all a bit ugly, but fortunately it only needs to be done once.

View File

@@ -4,21 +4,14 @@ Flashing is the process of moving our program into the microcontroller's (persis
In this case, our `rustled` program will be the only program in the microcontroller memory. By this I mean that there's nothing else running on the microcontroller: no OS, no daemon, nothing. `rustled` has full control over the device. This is what is meant by *bare-metal* programming.
> OS: operating system
<dl>
<dt>OS</dt>
<dd>operating system</dd>
<dt>Daemon</dt>
<dd>program running in the background</dd>
</dl>
> Daemon: program running in the background
<!-- Onto the actual flashing. First thing we need is to do is launch OpenOCD. We did that in the previous section but this time we'll run the command inside a temporary directory (/tmp on *nix; %TEMP% on Windows). -->
Connect the mirco:bit to your computer and run the following commands on a new terminal.
<!-- ``` console
$ # *nix
$ cd /tmp
$ # Windows
$ cd %TEMP%
``` -->
Connect the micro:bit to your computer and run the following commands on a new terminal.
We need to give OCD the name of the interfaces we are using:
@@ -156,45 +149,16 @@ This will automate the last few steps so we don't need to repeatedly do the same
`.gdbinit`
```
# Connects GDB to OpenOCD server port
target remote :3333
# (optional) Unmangle function names when debugging
set print asm-demangle on
# Load your program, breaks at entry
load
# (optional) Add breakpoint at function
break rustled::main
# Continue with execution
continue
```
## LED
Let us now turn on an LED! But how?
Well, first we should look at the documentation of our crate,
and you should be able to figure out how to get access to the gpio,
and set individual pins high and low:
``` rust
if let Some(p) = microbit::Peripherals::take() {
let mut gpio = p.GPIO.split();
let mut pin1 = gpio.pin1.into_push_pull_output();
pin1.set_high();
}
```
Next we need to see how these pins are hooked up,
for that we need [the micro:bit schematics][schematics] linked to at the bottom of [the hardware overview][hw].
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;
> 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,
> so current will flow and it will 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.
The fifth sheet shows how each row and column correspond to each GPIO pin.
[hw]: http://tech.microbit.org/hardware/
[schematics]: https://github.com/bbcmicrobit/hardware/blob/master/SCH_BBC-Microbit_V1.3B.pdf
You should now have enough information to try and turn on an LED.
Now we can learn how to debug code on the micro:bit.

View File

@@ -1,5 +1,22 @@
# Debugging
## Setup
Before we start, let's add some code to debug:
``` rust
// -- snip --
entry!(main);
fn main() -> ! {
let _y;
let x = 42;
_y = x;
loop {}
}
```
## GDB session
We are already inside a debugging session so let's debug our program.
After the `load` command, our program is stopped at its *entry point*. This is indicated by the
@@ -76,13 +93,15 @@ $3 = 134219052
$4 = (i32 *) 0x10001fd8
```
As expected, `x` contains the value `42`. `y`, however, contains the value `134219052` (?). Because
`_y` has not been initialized yet, it contains some garbage value.
As expected, `x` contains the value `42`.
`_y` however, contains the value `134219052` (?).
Because `_y` has not been initialized yet, it contains some garbage value.
The command `print &x` prints the address of the variable `x`. The interesting bit here is that GDB
output shows the type of the reference: `i32*`, a pointer to an `i32` value. Another interesting
thing is that the addresses of `x` and `_y` are very close to each other: their addresses are just
`4` bytes apart.
The command `print &x` prints the address of the variable `x`.
The interesting bit here is that GDB output shows the type of the reference:
`i32*`, a pointer to an `i32` value.
Another interesting thing is that the addresses of `x` and `_y` are very close to each other:
their addresses are just `4` bytes apart.
Instead of printing the local variables one by one, you can also use the `info locals` command:
@@ -116,8 +135,8 @@ command and advance one instruction at a time using `stepi`.
(gdb) layout asm
```
If you are not using the TUI mode, you can use the `disassemble /m` command to disassemble the
program around the line you are currently at.
If you are not using the TUI mode,
you can use the `disassemble /m` command to disassemble the program around the line you are currently at.
```
(gdb) disassemble /m
@@ -145,8 +164,8 @@ End of assembler dump.
See the fat arrow `=>` on the left side? It shows the instruction the processor will execute next.
If not inside the TUI mode on each `stepi` command GDB will print the statement, the line number
*and* the address of the instruction the processor will execute next.
If not inside the TUI mode on each `stepi` command GDB will print the statement,
the line number *and* the address of the instruction the processor will execute next.
```
(gdb) stepi
@@ -156,7 +175,8 @@ If not inside the TUI mode on each `stepi` command GDB will print the statement,
0x08000194 17 loop {}
```
One last trick before we move to something more interesting. Enter the following commands into GDB:
One last trick before we move to something more interesting.
Enter the following commands into GDB:
```
(gdb) monitor reset halt
@@ -176,17 +196,16 @@ Breakpoint 1, led_roulette::main () at src/main.rs:8
We are now back at the beginning of `main`!
`monitor reset halt` will reset the microcontroller and stop it right at the program entry point.
The following `continue` command will let the program run freely until it reaches the `main`
function that has a breakpoint on it.
The following `continue` command will let the program run freely until it reaches the `main` function that has a breakpoint on it.
This combo is handy when you, by mistake, skipped over a part of the program that you were
interested in inspecting. You can easily roll back the state of your program back to its very
beginning.
This combo is handy when you, by mistake,
skipped over a part of the program that you were interested in inspecting.
You can easily roll back the state of your program back to its very beginning.
> **The fine print**: This `reset` command doesn't clear or touch RAM. That memory will retain its
> values from the previous run. That shouldn't be a problem though, unless your program behavior
> depends of the value of *uninitialized* variables but that's the definition of Undefined Behavior
> (UB).
> **The fine print**: This `reset` command doesn't clear or touch RAM.
> That memory will retain its values from the previous run.
> That shouldn't be a problem though, unless your program behavior depends of the value of *uninitialized* variables,
> but that's the definition of *undefined behavior* (UB).
We are done with this debug session. You can end it with the `quit` command.
@@ -209,3 +228,9 @@ Ending remote debugging.
Don't close OpenOCD though! We'll use it again and again later on. It's better
just to leave it running.
## What next?
In the next chapter we will learn
how to send messages from the micro:bit to your computer,
as well as howt to control the HAL GPIO.

View File

@@ -1,5 +1,5 @@
[package]
name = "rustled"
name = "start"
version = "0.1.0"
[dependencies]

View File

@@ -1,29 +1,30 @@
#![deny(unsafe_code)]
#![no_std]
#![no_main]
extern crate aux5;
extern crate panic_abort;
extern crate cortex_m_rt as rt;
use aux5::prelude::*;
use aux5::{Delay, Leds};
#[macro_use(entry, exception)]
extern crate microbit;
fn main() {
let (mut delay, mut leds): (Delay, Leds) = aux5::init();
use rt::ExceptionFrame;
let period = 50_u16;
let mut step: usize = 0;
loop {
match step % 2 {
0 => leds[step/2].on(),
1 => {
let wrap_step = ((step + 16 - 3) / 2) % 8;
leds[wrap_step].off();
},
_ => unreachable!(),
}
delay.delay_ms(period);
match step {
15 => step = 0,
_ => step += 1,
}
}
exception!(HardFault, hard_fault);
fn hard_fault(ef: &ExceptionFrame) -> ! {
panic!("{:#?}", ef);
}
exception!(*, default_handler);
fn default_handler(irqn: i16) {
panic!("Unhandled exception (IRQn = {})", irqn);
}
entry!(main);
fn main() -> ! {
let _y;
let x = 42;
_y = x;
loop {}
}

5
src/hello-world/.gdbinit Normal file
View File

@@ -0,0 +1,5 @@
target remote :3333
monitor arm semihosting enable
load
break hello::main
continue

View File

@@ -0,0 +1 @@
# Hello world

View File

@@ -0,0 +1,77 @@
# Semihosting
Semihosting is a feature which allows targets without I/O support to use the I/O of the host.
When the special `BKPT` instruction is reached, the host reads the characters directly from the micro:bit's memory.
## Semihosting is slow
The most important thing to remember about semihosting is that it is slow.
The processor halts entirely for each operation, making each operation take 107 milliseconds.
This means that if you are doing any time sensitive work, you should not use it for logging.
[Check out this blog post for more information.](http://blog.japaric.io/itm/)
## GDB
The first thing to do is to enable semihosting in GDB.
As before, we will add this to `.gdbinit` to avoid typing it every time.
`.gdbinit`
``` gdb
target remote :3333
monitor arm semihosting enable
load
```
## OpenOCD
You may have incorrectly assumed at this point that the outpust would appear in GDB.
Remember that GDB simply connects to OpenOCD to interface with the micro:bit.
OpenOCD is very loud currently,
so it will be quite hard to see the output of our micro:bit in the noise.
Fix this by stopping and restarting it with logging dumped to a file.
``` console
openocd -f interface/cmsis-dap.cfg -f target/nrf51.cfg -l /tmp/openocd.log
```
## Panic
The easiest way to use semihosting is to use it for the `panic!` macro.
`Cargo.toml`
``` toml
panic-semihosting = ""
```
You can then see what happens if you add a `panic!` to your code:
``` rust
fn main() -> ! {
panic!("test-panic");
}
```
```
Open On-Chip Debugger 0.10.0
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
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.
``` rust
extern crate cortex_m_semihosting as sh;
use core::fmt::Write;
use sh::hio;
// -- snip --
let mut stdout = hio::hstdout().unwrap();
stdout.write_str("semitest\n\r").unwrap();
```
And that's it!

View File

@@ -0,0 +1,38 @@
# Serial communication
The micro:bit has a perihperal called UART,
a Universal Asynchronous Receiver/Transmitter.
This is a form of serial communication, data is transferred serially,
i.e. one bit at a a time.
It is asynchronous communication, and there is no clock signal to dictate the bitrate,
intead this is agreed upon beforehand.
The protocol has frames consisting of a start bit, data bits, parity bits, and stop bits.
We will be using 8 bits per frame: 1 start, 6 data and 1 stop.
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.
## Tooling
To read and write to the serial bus from your computer, you will need to configure your tooling:
- [*nix](hello-world/02.01.NIX.html)
- [Windows](hello-world/02.02.WINDOWS.html)
## Code
``` rust
// -- snip --
if let Some(p) = microbit::Peripherals::take() {
let mut gpio = p.GPIO.split();
// Configure RX and TX pins accordingly
let tx = gpio.pin24.into_push_pull_output().downgrade();
let rx = gpio.pin25.into_floating_input().downgrade();
let (mut tx, _) = serial::Serial::uart0(p.UART0, tx, rx, BAUD115200).split();
let _ = write!(tx, "\n\rStarting!\n\r");
}
```

View File

@@ -0,0 +1,83 @@
# *nix tooling
Connect the serial module to your laptop and let's find out what name the OS assigned to it.
``` console
$ dmesg | grep -i tty
(..)
[ +0.000155] usb 3-2: FTDI USB Serial Device converter now attached to ttyUSB0
```
> **NOTE** On macs, the USB device will named like this: `cu.usbserial-*`. Adjust the following
> commands accordingly!
But what's this `ttyUSB0` thing? It's a file of course! Everything is a file in *nix:
``` console
$ ls -l /dev/ttyUSB0
crw-rw---- 1 root uucp 188, 0 Oct 27 00:00 /dev/ttyUSB0
```
You can send out data by simply writing to this file:
``` console
$ echo 'Hello, world!' > /dev/ttyUSB0
```
You should see the TX (red) LED on the serial module blink, just once and very fast!
## minicom
Dealing with serial devices using `echo` is far from ergonomic. So, we'll use the program `minicom`
to interact with the serial device using the keyboard.
We must configure `minicom` before we use it. There are quite a few ways to do that but we'll use a
`.minirc.dfl` file in the home directory. Create a file in `~/.minirc.dfl` with the following
contents:
``` console
$ cat ~/.minirc.dfl
pu baudrate 115200
pu bits 8
pu parity N
pu stopbits 1
pu rtscts No
pu xonxoff No
```
> **NOTE** Make sure this file ends in a newline! Otherwise, `minicom` will fail to read it.
That file should be straightforward to read (except for the last two lines), but nonetheless let's
go over it line by line:
- `pu baudrate 115200`. Sets baud rate to 115200 bps.
- `pu bits 8`. 8 bits per frame.
- `pu parity N`. No parity check.
- `pu stopbits 1`. 1 stop bit.
- `pu rtscts No`. No hardware control flow.
- `pu xonxoff No`. No software control flow.
Once that's in place. We can launch `minicom`
``` console
$ 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.
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.
## `minicom` commands
`minicom` exposes commands via keyboard shortcuts. On Linux, the shortcuts start with `Ctrl+A`. On
mac, the shortcuts start with the `Meta` key. Some useful commands below:
- `Ctrl+A` + `Z`. Minicom Command Summary
- `Ctrl+A` + `C`. Clear the screen
- `Ctrl+A` + `X`. Exit and reset
- `Ctrl+A` + `Q`. Quit with no reset
> **NOTE** mac users: In the above commands, replace `Ctrl+A` with `Meta`.

View File

@@ -0,0 +1,43 @@
# Windows tooling
Before plugging the Serial module, run the following command on the terminal:
``` console
$ mode
```
It will print a list of devices that are connected to your laptop. The ones that start with `COM` in
their names are serial devices. This is the kind of device we'll be working with. Take note of all
the `COM` *ports* `mode` outputs *before* plugging the serial module.
Now, plug the Serial module and run the `mode` command again. You should see a new `COM` port appear
on the list. That's the COM port assigned to the serial module.
Now launch `putty`. A GUI will pop out.
<p align="center">
<img title="PuTTY settings" src="assets/putty-settings.png">
</p>
On the starter screen, which should have the "Session" category open, pick "Serial" as the
"Connection type". On the "Serial line" field enter the `COM` device you got on the previous step,
for example `COM3`.
Next, pick the "Connection/Serial" category from the menu on the left. On this new view, make sure
that the serial port is configured as follows:
- "Speed (baud)": 115200
- "Data bits": 8
- "Stop bits": 1
- "Parity": None
- "Flow control": None
Finally, click the Open button. A console will show up now:
<p align="center">
<img title="PuTTY console" src="assets/putty-console.png">
</p>
If you type on this console, the TX (red) LED on the Serial module should blink. Each key stroke
should make the LED blink once. Note that the console won't echo back what you type so the screen
will remain blank.

View File

@@ -0,0 +1,37 @@
# LED
Let us now turn on an LED! But how?
Well, first we should look at the documentation of our crate,
and you should be able to figure out how to get access to the gpio,
and set individual pins high and low:
``` rust
if let Some(p) = microbit::Peripherals::take() {
let mut gpio = p.GPIO.split();
let mut pin1 = gpio.pin1.into_push_pull_output();
pin1.set_high();
}
```
Next we need to see how these pins are hooked up,
for that we need [the micro:bit schematics][schematics] linked to at the bottom of [the hardware overview][hw].
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;
> 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,
> so current will flow and it will 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.
The fifth sheet shows how each row and column correspond to each GPIO pin.
[hw]: http://tech.microbit.org/hardware/
[schematics]: https://github.com/bbcmicrobit/hardware/blob/master/SCH_BBC-Microbit_V1.3B.pdf
You should now have enough information to try and turn on an LED.

View File

@@ -1,4 +1,4 @@
# LED
# Solution
This is my solution:
@@ -41,5 +41,7 @@ fn default_handler(irqn: i16) {
It is worth noting that pin4 starts low, so does not need to be explicitly set low.
You now know enough to start playing around with the LED display,
but before you do, you should know to debug your Rust code on the micro:bit.
You now know enough to start playing around with the LED display and the GPIO in general.
Before you do,
you should know that the microbit crate already includes an abstraction for the LED display,
and how this is implemented is demonstrated in the [LED display chapter](display/00.00.README.html)

View File

@@ -0,0 +1,9 @@
[package]
name = "hello"
version = "0.1.0"
[dependencies]
cortex-m-rt="~0.5"
cortex-m-semihosting=""
panic-semihosting = "~0.3"
microbit="~0.5"

View File

@@ -0,0 +1,42 @@
#![no_std]
#![no_main]
extern crate panic_semihosting;
extern crate cortex_m_rt as rt;
extern crate cortex_m_semihosting as sh;
#[macro_use(entry, exception)]
extern crate microbit;
use core::fmt::Write;
use rt::ExceptionFrame;
use sh::hio;
exception!(HardFault, hard_fault);
fn hard_fault(ef: &ExceptionFrame) -> ! {
panic!("{:#?}", ef);
}
exception!(*, default_handler);
fn default_handler(irqn: i16) {
panic!("Unhandled exception (IRQn = {})", irqn);
}
entry!(main);
fn main() -> ! {
let mut stdout = hio::hstdout().unwrap();
stdout.write_str("semihosting test\n\r").unwrap();
if let Some(p) = microbit::Peripherals::take() {
let mut gpio = p.GPIO.split();
// Configure RX and TX pins accordingly
let tx = gpio.pin24.into_push_pull_output().downgrade();
let rx = gpio.pin25.into_floating_input().downgrade();
let (mut tx, _) = serial::Serial::uart0(p.UART0, tx, rx, BAUD115200).split();
let _ = write!(tx, "\n\rserial test\n\r");
}
panic!("test-panic");
}