Rustでmicro:bit

[micro:bit](https://microbit.org/)をRustで動かしてみようという記事です。
![P1010730_3.jpg](https://qiita-image-store.s3.amazonaws.com/0/1775/fbc82233-4af7-c928-78a1-b4b3269b1395.jpeg)

# 使用機材など

* micro:bit
* MacOSX 10.13.6
* Rust 1.33.0-nightly
* OpenOCD

OpenOCDを使用するため、以下のコマンドで事前にインストールしておきます。

```shell-session
$ brew cask install gcc-arm-embedded
$ brew install minicom openocd
```

rustcはtargetに`thumbv6m-none-eabi`が必要ですので、`rustup target add`で追加します。

```shell-session
$ rustup update
$ rustup target add thumbv6m-none-eabi
```

## OpenOCD
ホストマシン（今回はMac）とmicro:bitを接続し、OpenOCDを使ってmicro:bitへのプログラム書き込みを行える状態にしておきます。

```shell-session
$ openocd -f interface/cmsis-dap.cfg -f target/nrf51.cfg
```

以下のような出力で`openocd`が終了しなければ準備完了です。

```
Open On-Chip Debugger 0.10.0
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "swd". To override use 'transport select <transport>'.
cortex_m reset_config sysresetreq
adapter speed: 1000 kHz
Info : CMSIS-DAP: SWD  Supported
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : CMSIS-DAP: FW Version = 1.0
Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 0 TDO = 0 nTRST = 0 nRESET = 1
Info : CMSIS-DAP: Interface ready
Info : clock speed 1000 kHz
Info : SWD DPIDR 0x0bb11477
Info : nrf51.cpu: hardware has 4 breakpoints, 2 watchpoints
```

# 例1
まずはなにもしないプログラムを動かしてみます。

```rust:src/main.rs
#![no_std]
#![no_main]

extern crate cortex_m_rt;
extern crate microbit;
extern crate panic_abort;

use cortex_m_rt::entry;

#[entry]
fn main() -> ! {
    loop {}
}
```

* `thumbv6m-none-eabi`ターゲット向けのstdは存在しないので、`#![no_std]`をつける
* プログラムのエントリポイントを変えるため、`#![no_main]`をつけて、`#[entry]`でエントリポイントを指定する
* エントリポイントで指定した関数（`fn main`）は無限ループに入るので`-> !`をつけておく。

`Cargo.toml`は以下のようになります。

```toml:Cargo.toml
[dependencies]
panic-abort = "~0.3"
microbit="~0.6"
cortex-m-rt="~0.6"
```

`cargo`でビルドする際のパラメータを`.cargo/config`で指定しておきます。

```toml:.cargo/config
[target.thumbv6m-none-eabi]
runner = "arm-none-eabi-gdb"
rustflags = [
    "-C", "link-arg=-Tlink.x",
]

[build]
target = "thumbv6m-none-eabi"
```

これで`cargo build`でmicro:bit向けのバイナリがビルドできます。

```shell-session
$ cargo build
$ ls target/thumbv6m-none-eabi/debug/first
target/thumbv6m-none-eabi/debug/first*
```

最後に、ビルドしたバイナリを`arm-none-eabi-gdb`を使ってmicro:bitに書き込んで動作させます。

```shell-session
$ arm-none-eabi-gdb -q target/thumbv6m-none-eabi/debug/first
Reading symbols from target/thumbv6m-none-eabi/debug/first...done.
(gdb) target remote :3333
Remote debugging using :3333
(gdb) load
Loading section .vector_table, size 0xa8 lma 0x0
Loading section .text, size 0x50 lma 0xa8
Start address 0xac, load size 248
Transfer rate: 355 bytes/sec, 124 bytes/write.
(gdb) continue
```

これでmicro:bit上でプログラムが動いています。（何もしないプログラムなので動いているかどうかわかりずらいですが。）

# OpenOCD上でデバッグ出力
デバッグと言えばprintデバッグですが、stdがないので一手間必要です。

```toml:Cargo.toml
[dependencies]
cortex-m-semihosting = "*"
```

```rust
use core::fmt::Write;
use cortex_m_semihosting::hio;

#[entry]
fn main() -> ! {
    let mut stdout = hio::hstdout().unwrap();
    writeln!(stdout, "hello {}" "world");
}
```


GDB起動後、load前にsemihostingを有効にしておきます。

```
(gdb) target remote :3333
(gdb) monitor arm semihosting enable
(gdb) load
```

これで`writeln!()`がOpenOCD上に出力されます。

# .gdbinit
GDB起動後に毎回コマンドを入力するのはめんどくさいので、`.gdbinit`を作成しておきます。


```
target remote :3333
monitor arm semihosting enable
load
continue
```

起動直後にmainでブレークポイントを貼っておきたい場合はcontinueのかわりにbreak mainを追加しておきます。

```

target remote :3333
monitor arm semihosting enable
load
break main
```

# Hello world
基本的な知識を得たところで、次の例に行ってみます。
micro:bitにはLEDが付いていますので、これをチカチカさせてみます。

```rust:display_helloworld.rs
use microbit::hal::delay::Delay;
use microbit::hal::prelude::*;
use microbit::led;

#[entry]
fn main() -> ! {
    if let Some(p) = microbit::Peripherals::take() {
        let gpio = p.GPIO.split();
        let mut delay = Delay::new(p.TIMER0);

        // display pins
        let col1 = gpio.pin4.into_push_pull_output();
        let col2 = gpio.pin5.into_push_pull_output();
        let col3 = gpio.pin6.into_push_pull_output();
        let col4 = gpio.pin7.into_push_pull_output();
        let col5 = gpio.pin8.into_push_pull_output();
        let col6 = gpio.pin9.into_push_pull_output();
        let col7 = gpio.pin10.into_push_pull_output();
        let col8 = gpio.pin11.into_push_pull_output();
        let col9 = gpio.pin12.into_push_pull_output();
        let row1 = gpio.pin13.into_push_pull_output();
        let row2 = gpio.pin14.into_push_pull_output();
        let row3 = gpio.pin15.into_push_pull_output();

        // setup display
        let mut leds = led::Display::new(
            col1, col2, col3, col4, col5, col6, col7, col8, col9, row1, row2, row3,
        );

        loop {
            display_text(&mut leds, &mut delay, "Hello micro:bit with Rust!!");
            delay.delay_ms(500_u32);
        }
    }

    panic!("panic");
}
```

`display_text()`は以下のような実装になっていて、入力で渡された文字列を1文字ずつ対応する文字でLEDを光らせます。

```rust:src/display_helloworld.rs
fn display_text(leds: &mut led::Display, delay: &mut Delay, text: &str) {
    let ascii: [[[u8; 5]; 5]; 128] = [
        [[0; 5];5], [[0; 5];5], [[0; 5];5], [[0; 5];5], [[0; 5];5], [[0; 5];5], [[0; 5];5], [[0; 5];5], [[0; 5];5], [[0; 5];5],
            :
        [
            [0, 1, 1, 1, 0],
            [0, 1, 0, 1, 0],
            [0, 1, 0, 1, 0],
            [0, 1, 0, 1, 0],
            [0, 1, 1, 1, 0],
        ],  // ascii:0, decimal:48
        [
            [0, 0, 1, 0, 0],
            [0, 0, 1, 0, 0],
            [0, 0, 1, 0, 0],
            [0, 0, 1, 0, 0],
            [0, 0, 1, 0, 0],
        ],
            :
        [
            [0, 0, 0, 0, 0],
            [0, 0, 0, 0, 0],
            [0, 1, 1, 1, 0],
            [0, 0, 1, 0, 0],
            [0, 1, 1, 1, 0],
        ],  // ascii:z, decimal:122
        [[0; 5];5], [[0; 5];5], [[0; 5];5], [[0; 5];5], [[0; 5];5],
    ];

    for c in text.chars() {
        let led_c: [[u8; 5]; 5] = ascii[c as usize];
        // 0.5秒間隔で表示する
        leds.display(delay, led_c, 500);
    }
}
```

`ascii`は128要素のスライスになっていて、LEDで光らせたい文字をそれぞれ定義しています。
LEDは5x5になっていますので、5x5のスライスに文字をそれぞれ定義します。
（今回使わない文字は0で埋めてはしょってます。）

動画はないですが、これをビルドして動かすと、1文字0.5秒間隔で`Hello micro:bit with Rust!!`がLEDに出力されます。

# UART

家にあったCO2センサー（[S300-SV](http://eltsensor.co.kr/2016/jp/products/oem_modules/S-300-3V.html)）でUART通信の例もやりたかったのですが、どうもうまく動かず。ソースだけ置いておきます。
（シリアル通信で何かの値を受信できてそうな感じでしたが、ppmとるところはかなり適当で、このままだと正しい値を取り出せていませんのでご注意を。）

```rust
#![no_std]
#![no_main]

extern crate cortex_m_rt;
extern crate microbit;
extern crate panic_abort;
extern crate cortex_m_semihosting;

use core::fmt::Write;
use cortex_m_semihosting::hio;
use cortex_m_rt::entry;

#[entry]
fn main() -> ! {
    let mut stdout = hio::hstdout().unwrap();

    if let Some(p) = microbit::Peripherals::take() {
        p.GPIO.pin_cnf[0].write(|w| w.pull().pullup().dir().output());
        p.GPIO.pin_cnf[16].write(|w| w.pull().disabled().dir().input());

        p.UART0.pseltxd.write(|w| unsafe { w.bits(0) });
        p.UART0.pselrxd.write(|w| unsafe { w.bits(16) });

        p.UART0.baudrate.write(|w| w.baudrate().baud38400());
        p.UART0.enable.write(|w| w.enable().enabled());

        loop {
            p.UART0.tasks_startrx.write(|w| unsafe { w.bits(1) });

            for _ in 0..20 {
                while p.UART0.events_rxdrdy.read().bits() == 0 {}

                let c = p.UART0.rxd.read().rxd().bits();
                if c == 0x0a {
                    let mut tmp_ppm = 0;
                    for j in 0..6 {
                        let c = p.UART0.rxd.read().bits();
                        if c == 0x20 {
                            break;
                        }
                        tmp_ppm += j * 2;
                    }
                    let _ = writeln!(stdout, "{}[ppm]", tmp_ppm);
                    break;
                }
                let _ = writeln!(stdout, "c: 0x{:x}", c);
            }

            p.UART0.tasks_stoprx.write(|w| unsafe { w.bits(1) });
        }
    }
    panic!("panic");
}
```

# おわりに
ということでRustでmicro:bitをいじってみました。
micro:bitは2000〜3000円くらいで手に入れることができ、気軽に遊ぶことができますので、一度試してみてはいかがでしょうか😀

# 参考文献
* [MicroRust](https://droogmic.github.io/microrust/)
* https://github.com/therealprof/microbit
* [ARM Cortex-M 32ビットマイコンでベアメタル "Safe" Rust - Qiita](https://qiita.com/tatsuya6502/items/7d8aaf3792bdb5b66f93)