Rust
microbit

Rustでmicro:bit

micro:bitをRustで動かしてみようという記事です。

P1010730_3.jpg


使用機材など


  • micro:bit

  • MacOSX 10.13.6

  • Rust 1.33.0-nightly

  • OpenOCD

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

$ brew cask install gcc-arm-embedded

$ brew install minicom openocd

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

$ rustup update

$ rustup target add thumbv6m-none-eabi


OpenOCD

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

$ 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

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


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は以下のようになります。


Cargo.toml

[dependencies]

panic-abort = "~0.3"
microbit="~0.6"
cortex-m-rt="~0.6"

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


.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向けのバイナリがビルドできます。

$ cargo build

$ ls target/thumbv6m-none-eabi/debug/first
target/thumbv6m-none-eabi/debug/first*

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

$ 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がないので一手間必要です。


Cargo.toml

[dependencies]

cortex-m-semihosting = "*"

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が付いていますので、これをチカチカさせてみます。


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を光らせます。


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)でUART通信の例もやりたかったのですが、どうもうまく動かず。ソースだけ置いておきます。

(シリアル通信で何かの値を受信できてそうな感じでしたが、ppmとるところはかなり適当で、このままだと正しい値を取り出せていませんのでご注意を。)

#![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円くらいで手に入れることができ、気軽に遊ぶことができますので、一度試してみてはいかがでしょうか😀


参考文献