micro:bitをRustで動かしてみようという記事です。
使用機材など
- 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
まずはなにもしないプログラムを動かしてみます。
#![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
は以下のようになります。
[dependencies]
panic-abort = "~0.3"
microbit="~0.6"
cortex-m-rt="~0.6"
cargo
でビルドする際のパラメータを.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がないので一手間必要です。
[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が付いていますので、これをチカチカさせてみます。
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を光らせます。
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円くらいで手に入れることができ、気軽に遊ぶことができますので、一度試してみてはいかがでしょうか😀