Initial commit

Much of the content here is a direct port from https://github.com/japaric/discovery
This commit is contained in:
Michael Droogleever
2018-07-02 23:01:03 +02:00
commit 8e05dfd010
32 changed files with 2513 additions and 0 deletions

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"

View File

@@ -0,0 +1,4 @@
target remote :3333
load
break main
continue

View File

@@ -0,0 +1,13 @@
# Getting started
Alright, let's start as you usually would with Rust.
``` console
$ rustup update
```
It is always good to keep your toolchain up to date.
Now let's make a new binary project.
You might not do this often, so it is understandeable to forget.
If you run `$ cargo`, you will be given a hint.

View File

@@ -0,0 +1,388 @@
# New Project
``` console
$ cargo new rustled
Created binary (application) `rustled` project
$ cd rustled
Cargo.toml src
```
This has created a binary crate.
Now we could `$ cargo build` this, and even `$ cargo run` it,
but everything is being compiled for, and run on, your computer.
## Targets
The micro:bit has a different architecture than your computer,
so the first step will be to cross compile for the micro:bit's architecture.
If you were to do an internet search, you would find a [platform support list for Rust][platforms].
Looking into this page, you will find the micro:bit's nRF51822 Cortex-M0 microprocessor:
> `thumbv6m-none-eabi [*] [ ] [ ] Bare Cortex-M0, M0+, M1`
"thumbv6m-none-eabi" is known a a target triple. Note what the star represents:
> These are bare-metal microcontroller targets that only have access to the core library, not std.
To install this target:
``` console
$ rustup target add thumbv6m-none-eabi
```
## Build 1
Now how should we use this? Well, if you were to take a look at `$ cargo build -h`, you would try:
``` console
$ cargo build --target thumbv6m-none-eabi
```
```
error[E0463]: can't find crate for `std`
|
= note: the `thumbv6m-none-eabi` target may not be installed
error: aborting due to previous error
For more information about this error, try `rustc --explain E0463`.
error: Could not compile `rustled`.
To learn more, run the command again with --verbose.
```
The help note is rather unhelpful because we just installed that target.
We also just noted that the thumbv6m-none-eabi target does not include std,
only the core crate, which is has a platform independent subset of the std features.
Why is it still looking for the std crate when we build?
### `no_std`
It turns out, rust will always look for the std crate unless explicitly disabled,
so we will add the no_std attribute
`src/main.rs`
``` rust
#![no_std]
fn main() {
println!("Hello, world!");
}
```
## Build 2
``` console
$ cargo build --target thumbv6m-none-eabi
```
```
error: cannot find macro `println!` in this scope
--> src/main.rs:4:5
|
4 | println!("Hello, world!");
| ^^^^^^^
```
`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:
```
error: language item required, but not found: `panic_impl`
```
This error, is because the panic macro is unimplemented,
when rustc needs it to have an implementation.
### `panic_impl`
We could try and implement the panic macro ourselves,
but it's easier and more portable to use a crate that does it for us.
If we look on [crates.io for the panic-impl keyword][panic] we will find some examples.
Let us pic the simplest one, and add it to our Cargo.toml.
If you have forgotten how to do this, try looking at [the cargo book][cargo].
[platforms]: https://forge.rust-lang.org/platform-support.html
[panic]: https://crates.io/keywords/panic-impl
[cargo]: https://doc.rust-lang.org/stable/cargo/
`Cargo.toml`
```
[dependencies]
panic-abort = "~0.2"
```
`src/main.rs`
```
#![no_std]
extern crate panic_abort;
fn main() {
}
```
## Build 3
``` console
$ cargo build --target thumbv6m-none-eabi
```
```
error: requires `start` lang_item
```
### `no_main`
In the normal command line rust binaries you would be used to making,
executing the binary usually has the operating system start by executing the C runtime library (crt0).
This in turn invokes the Rust runtime, as marked by the `start` language item,
which in turn invokes the main function.
Having enabled `no_std`, as we are targeting on a microcontroller,
neither the crt0 nor the rust runtime are available,
so even implementing `start` would not help us.
We need to replace the operating system entry point.
You could for example name a function after the default entry point, which for linux is `_main`,
and start that way. Note, you would also need to disable [name mangling][nm]:
```
#![no_std]
#![no_main]
#[no_mangle]
pub extern "C" fn _start() -> ! {
loop {}
}
```
[nm]: https://en.wikipedia.org/wiki/Name_mangling
This is the end of the road for trying to get this to work on our own.
At this point we need the help of a board specific crate and a few cargo tweaks to get this working.
## microbit crate
Let us add a dependency on the board crate for the micro:bit.
```
[dependencies]
panic-abort = "~0.2"
microbit="~0.5"
```
The microbit crate has 2 notable dependencies:
### `embedded-hal`
This crate is a HAL implementation crate, where HAL stands for *hardware abstraction layer*.
As rust becomes more and more popular in embedded development,
it is desireable to have as little hardware specific implementation as possible.
For this reason, the `embedded-hal` crate contains a range of hardware abstraction traits which can
be implemented by board specific crates.
### `cortex-m-rt`
This crate implements the minimal startup / runtime for Cortex-M microcontrollers.
Among other things this crate provides:
- the `entry!` macro, to define the entry point of the program.
- the `exception!` macro, to set or override a processor core exception handler.
This crate requires:
- a definition of the specific microcontroller's memory layout as a memory.x file.
- a definition of the hard fault handler
- a definition of the default exception handler
For more detailed information,
you can use the helpful [cortex-m-quickstart crate][qs] and [its documentation][doc].
[qs]: https://docs.rs/crate/cortex-m-quickstart
[doc]: https://docs.rs/cortex-m-quickstart
## cargo config
Before we go any further,
we are going to tweak the cargo's configuration by editing `rustled/.cargo/config`.
For more information, you can read [the documentation here][cargoconfig].
[cargoconfig]: https://doc.rust-lang.org/cargo/reference/config.html
``` toml
# Configure builds for our target
[target.thumbv6m-none-eabi]
# Execute binary using gdb
runner = "arm-none-eabi-gdb"
# Tweak to the linking process required by the cortex-m-rt crate
rustflags = [
"-C", "link-arg=-Tlink.x",
"-C", "link-arg=-nostartfiles",
]
# Automatically select this target when running cargo for this project
[build]
target = "thumbv6m-none-eabi"
```
### arm-none-eabi-gdb
This is a version of gdb (the GNU debugger) for the ARM EABI (embedded application binary interface).
It will allow us to debug the code running on our micro:bit, from your computer.
### Build target
Now, all you need to do is run `$ cargo build`,
and cargo will automatically add `--target thumbv6m-none-eabi`.
## Build 4
`Cargo.toml`
```
[dependencies]
panic-abort = "~0.2"
microbit="~0.5"
```
`src/main.rs`
```
#![no_std]
#![no_main]
extern crate panic_abort;
entry!(main);
fn main() {
}
```
``` console
cargo build
```
```
error[E0308]: mismatched types
--> src/main.rs:9:1
|
8 | entry!(main);
| ^^^^^^^^^^^^^ expected !, found ()
|
= note: expected type `fn() -> !`
found type `fn() {main}`
= note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)
```
## `!` return type
A little known rust feature, so I will forgive you if you do not know what this means.
A return type of `!` means that the function cannot return
An easy way to implement this, is by using an infinite loop.
`src/main.rs`
```
#![no_std]
#![no_main]
extern crate panic_abort;
#[macro_use(entry)]
extern crate microbit;
entry!(main);
fn main() -> ! {
loop {}
}
```
## Build 5
```
error: linking with `arm-none-eabi-gcc` failed: exit code: 1
|
= note: "arm-none-eabi-gcc" "-L" "/home/xxx/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/thumbv6m-none-eabi/lib" "/home/xxx/rust/rustled/target/thumbv6m-none-eabi/debug/deps/rustled-e6053d34b0422141.2yhvr0tmp69gb94x.rcgu.o" "-o"
# SNIP
"/home/xxx/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/thumbv6m-none-eabi/lib/libcore-fb37a4ea1db1e473.rlib" "-Wl,--end-group" "/home/xxx/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/thumbv6m-none-eabi/lib/libcompiler_builtins-f2357c0397dd7e0d.rlib" "-Wl,-Tlink.x" "-nostartfiles" "-Wl,-Bdynamic"
= note: /usr/lib/gcc/arm-none-eabi/8.1.0/../../../../arm-none-eabi/bin/ld: cannot open linker script file memory.x: No such file or directory
collect2: error: ld returned 1 exit status
```
A scary error, but if you look closely you will see `cannot open linker script file memory.x: No such file or directory`.
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
```
> Often a board support crate will already include this, so this step will not be necessary.
## Build 6
```
error: linking with `arm-none-eabi-gcc` failed: exit code: 1
|
= note: "arm-none-eabi-gcc" "-L" "/home/xxx/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/thumbv6m-none-eabi/lib" "/home/xxx/rust/rustled/target/thumbv6m-none-eabi/debug/deps/rustled-e6053d34b0422141.2yhvr0tmp69gb94x.rcgu.o" "-o"
# SNIP
"/home/xxx/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/thumbv6m-none-eabi/lib/libcompiler_builtins-f2357c0397dd7e0d.rlib" "-Wl,-Tlink.x" "-nostartfiles" "-Wl,-Bdynamic"
= note: device.x:1: undefined symbol `DefaultHandler' referenced in expression
collect2: error: ld returned 1 exit status
```
Notice `undefined symbol 'DefaultHandler' referenced in expression`.
We said earlier, as with the memory,
that the hard fault handler and default exception handler both need defining.
`Cargo.toml`
```
[dependencies]
panic-abort = "~0.2"
cortex-m-rt="~0.5"
microbit="~0.5"
```
`src/main.rs`
```
#![no_std]
#![no_main]
extern crate panic_abort;
extern crate cortex_m_rt as rt;
#[macro_use(entry, exception)]
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);
}
```
It is all a bit ugly, but fortunately it only needs to be done once.
If you try building now, you should finally be greeted with `Finished`!
## Build Complete
As a sanity check, let's verify that the produced executable is actually an ARM binary:
``` console
$ file target/thumbv6m-none-eabi/debug/rustled
target/thumbv6m-none-eabi/debug/rustled: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped
^^^ ^^^^
```

View File

@@ -0,0 +1,192 @@
# Flashing
Flashing is the process of moving our program into the microcontroller's (persistent) memory. Once flashed, the microcontroller will execute the flashed program every time it is powered on.
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.
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%
```
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`
$ openocd -f interface/cmsis-dap.cfg -f target/nrf51.cfg
```
The program will block; leave that terminal open.
Now it's a good time to explain what this command is actually doing.
I mentioned that the micro:bit actually has two microcontrollers.
One of them is used as a USB interface and programmer/debugger.
This microcontroller is connected to the target microcontroller using a Serial Wire Debug (SWD) interface
(this interface is an ARM standard so you'll run into it when dealing with other Cortex-M based microcontrollers).
This SWD interface can be used to flash and debug a microcontroller.
It uses the CMSIS-DAP protocol for host debugging of application programs.
It will appear as a USB device when you connect the micro:bit to your laptop.
As for OpenOCD, it's software that provides some services like a *GDB server* on top of USB
devices that expose a debugging protocol like SWD or JTAG.
Onto the actual command: those `.cfg` files we are using instruct OpenOCD to look for
- a CMSIS-DAP USB interface device (`interface/cmsis-dap.cfg`)
- a nRF51XXX microcontroller target (`target/nrf51.cfg`) to be connected to the USB interface.
The OpenOCD output looks like this:
``` console
Open On-Chip Debugger 0.9.0 (2016-04-27-23:18)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
none separate
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : clock speed 950 kHz
Info : STLINK v2 JTAG v27 API v2 SWIM v15 VID 0x0483 PID 0x374B
Info : using stlink api v2
Info : Target voltage: 2.919073
Info : stm32f3x.cpu: hardware has 6 breakpoints, 4 watchpoints
```
The "6 breakpoints, 4 watchpoints" part indicates the debugging features the processor has
available.
I mentioned that OpenOCD provides a GDB server so let's connect to that right now:
``` console
$ arm-none-eabi-gdb -q target/thumbv7em-none-eabihf/debug/led-roulette
Reading symbols from target/thumbv7em-none-eabihf/debug/led-roulette...done.
(gdb)
```
This only opens a GDB shell. To actually connect to the OpenOCD GDB server, use the following
command within the GDB shell:
```
(gdb) target remote :3333
Remote debugging using :3333
0x00000000 in ?? ()
```
By default OpenOCD's GDB server listens on TCP port 3333 (localhost). This command is connecting to
that port.
After entering this command, you'll see new output in the OpenOCD terminal:
``` diff
Info : stm32f3x.cpu: hardware has 6 breakpoints, 4 watchpoints
+Info : accepting 'gdb' connection on tcp/3333
+Info : device id = 0x10036422
+Info : flash size = 256kbytes
```
Almost there. To flash the device, we'll use the `load` command inside the GDB shell:
```
(gdb) load
Loading section .vector_table, size 0x188 lma 0x8000000
Loading section .text, size 0x38a lma 0x8000188
Loading section .rodata, size 0x8 lma 0x8000514
Start address 0x8000188, load size 1306
Transfer rate: 6 KB/sec, 435 bytes/write.
```
And that's it. You'll also see new output in the OpenOCD terminal.
``` diff
Info : flash size = 256kbytes
+Info : Unable to match requested speed 1000 kHz, using 950 kHz
+Info : Unable to match requested speed 1000 kHz, using 950 kHz
+adapter speed: 950 kHz
+target state: halted
+target halted due to debug-request, current mode: Thread
+xPSR: 0x01000000 pc: 0x08000194 msp: 0x2000a000
+Info : Unable to match requested speed 8000 kHz, using 4000 kHz
+Info : Unable to match requested speed 8000 kHz, using 4000 kHz
+adapter speed: 4000 kHz
+target state: halted
+target halted due to breakpoint, current mode: Thread
+xPSR: 0x61000000 pc: 0x2000003a msp: 0x2000a000
+Info : Unable to match requested speed 1000 kHz, using 950 kHz
+Info : Unable to match requested speed 1000 kHz, using 950 kHz
+adapter speed: 950 kHz
+target state: halted
+target halted due to debug-request, current mode: Thread
+xPSR: 0x01000000 pc: 0x08000194 msp: 0x2000a000
```
Our program is loaded, we can now run it!
```
(gdb) continue
Continuing.
```
Continue runs the program until the next breakpoint.
This time it blocks, nothing happens.
This is because all we have in our code is a loop!
## `.gdbinit`
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`
```
target remote :3333
load
```
## 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.
It is worth noting that the 5x5 array of LEDs is wired up as a 9x3 array, 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

@@ -0,0 +1,41 @@
# LED
This is my solution:
``` rust
#![no_std]
#![no_main]
extern crate panic_abort;
extern crate cortex_m_rt as rt;
#[macro_use(entry, exception)]
extern crate microbit;
use rt::ExceptionFrame;
use microbit::hal::prelude::*;
entry!(main);
fn main() -> ! {
if let Some(p) = microbit::Peripherals::take() {
let mut gpio = p.GPIO.split();
let mut led = gpio.pin13.into_push_pull_output();
let _ = gpio.pin4.into_push_pull_output();
led.set_high();
}
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);
}
```
It is worth noting that pin4 starts low, so does not need to be explicitly set low.

View File

@@ -0,0 +1 @@
# Debugging

View File

@@ -0,0 +1,8 @@
[package]
name = "rustled"
version = "0.1.0"
[dependencies]
panic-abort = "~0.2"
cortex-m-rt="~0.5"
microbit="~0.5"

View File

@@ -0,0 +1,11 @@
MEMORY
{
/* NOTE K = KiBi = 1024 bytes */
FLASH : ORIGIN = 0x00000000, LENGTH = 256K
RAM : ORIGIN = 0x20000000, LENGTH = 16K
}
/* This is where the call stack will be allocated. */
/* The stack is of the full descending type. */
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);

View File

@@ -0,0 +1,29 @@
#![deny(unsafe_code)]
#![no_std]
extern crate aux5;
use aux5::prelude::*;
use aux5::{Delay, Leds};
fn main() {
let (mut delay, mut leds): (Delay, Leds) = aux5::init();
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,
}
}
}