RustでAVRを動かす

こんにちは、naotacoです。これはIoTLT Advent Calendar 2017の12日目の記事です。

IoTといえばモダンな環境がたくさんあるので目移りしてしまいますが、ほかの皆様とネタがかぶってしまうので、初心に返って素のAVRマイコンをいじりたいと思います。Rustで。

AVRマイコンとは

AVRマイコンというのはArduinoのど真ん中に乗っているマイコンのことで、安くて高性能で使いやすい 1ので、マイコンおじさんに愛され続けているやつです。言い換えると、AVRにプログラムをあらかじめ書き込んでおいて、さらに便利な電源とか周辺回路をセットにして基板に乗せたものがArduinoですね。

現代においてこれを素のまま使うというのはただの苦行というか趣味でしかないのですが、ネタかぶりを避けるためには犠牲がつきものです。虎穴に入らずんばなんとやら。勉強もかねてこれでやっていきます。

ちなみに昔はAVRの開発をアセンブリでやる人が多かったようですが、最近はC言語で書かれるのが一般的だと思います。Atmel謹製のIDEもあるので、いまから普通に始める人はそちらを使うとよいでしょう。

Rust on AVR

そうはいってもマイコンを動かすためにC言語を書くというのは全然おもしろくないので、今回はRustを使ってAVRを動かす方向で考えます。なぜRustなのかというと、最近私がRustを勉強しはじめたところだからです。

何も考えず始めたんですが、やってみると低レベルプログラミングを安全に低コストでできるようにするというポリシーがとても組み込み向きだなと思っています。もうC/C++はいやだお・・・。コンパイル時に変数の生存期間を決定してチェックしてくれたりするので、安全性が高いですがコンパイルを通すのが大変です(初心者並みの感想)。あと大規模なコードになってくるとどうなるのかよくわかりません。

さてそんなRustですが、当然デフォルトでAVRターゲットのクロスコンパイルができるわけはありません。それ用にコンパイラを用意してやる必要があります。

avr-rust

avr-rust: https://github.com/avr-rust/rust

avr-rustというのがあり、RustでAVRを動かせると書いてあるので試してみることにします。AVR-LLVMというのがあるんですね。avr-rustもアレだけど、LLVMさんマジパネェ。

例によってこいつ自身をコンパイルする必要がありますが、さすがにこれをWindowsでこれをコンパイルしにいく根性はありません。とりあえずUbuntu on Windows環境で試します。つまり、Windows上のUbuntuでRustのコードをAVR向けにクロスコンパイルしようとしている状況です。

基本的にはREADMEにある通りでよいのですが、Windows上のUbuntuだからか足りないパッケージがけっこうあります。また/optへのインストールも失敗したので、インストール先は自分のhome下にしました。あと、rustupとcargo, xargoが必要なので入れておきます。

# Ubuntu on Windows10 ですが、普通のでも多分同じようにやればできる
# packages
sudo apt-get install build-essential python cmake libffi-dev cargo

# rustup. インストール後に表示される場所にパスを通す.
curl https://sh.rustup.rs -sSf | sh
# xargo
cargo install xargo

# avr-rust
git clone https://github.com/avr-rust/rust.git
mkdir build && cd build

../rust/configure \
  --enable-debug \
  --disable-docs \
  --enable-llvm-assertions \
  --enable-debug-assertions \
  --enable-optimize \
  --prefix=~/.avr-rust # インストール先はhome下

make
make install
rustup toolchain link avr-toolchain $(realpath $(find . -name 'stage1'))
rustup default avr-toolchain

Windows 10 (build 17025)ならこれでavr-rustがコンパイルできると思います。依存submoduleがめちゃくちゃ多いので、makeのうちsubmoduleのアップデートに死ぬほど時間がかかります。いろんなパッケージがないとかで詰まりまくったので、おとなしく普通のUbuntuでやればよかったという後悔があります。

~/.avr-rust/binにrustcなどができていれば成功です。

Blink in Rust

コンパイラだけできてもしょうがないので、Lチカをやりましょう。

blink: https://github.com/avr-rust/blink

$ git clone git@github.com:avr-rust/blink.git
$ XARGO_RUST_SRC=~/.avr-rust/ rustup run avr-toolchain xargo build --target avr-atmega328p --release

   Compiling arduino v0.1.0
   Compiling blink v0.1.0 (file:///home/naotaco/blink)
    Finished release [optimized] target(s) in 0.60 secs

かっこいいですね。target/avr-atmega328p/blink.elf ができています。

$ tree
.
├── avr-atmega328p.json
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── src
│   └── main.rs
├── target
│   ├── avr-atmega328p
│   │   └── release
│   │       ├── blink.d
│   │       ├── blink.elf ★これ
│   │       ├── ...
│   │       └── native
│   └── release
│       ├── ...
│       └── native
└── Xargo.toml

15 directories, 13 files

焼く

さて、いよいよ焼く段ですが、今回はUSBaspという太古のAVRライタを使用します。細かいことは過去エントリを読んで頂くとして、USB接続して焼きます。というところまでやって気づいたんですが、Ubuntu on WindowsではUSBデバイスの使用はサポートされていないようです。

仕方なくWindowsにドライバを入れようとしましたが何をやってもだめでした。結局普通のLinux Mintマシンを出してきて、そこにavrdudeなどを入れて焼くことにします(最初から全部Linuxでやれよ)。

# ここから本当のUbuntu, ただしWindowsでも動くはず、ドライバさえ当たれば、、
sudo apt-get install gcc-avr binutils-avr gdb-avr avr-libc avrdude

さっきできたelfファイルはそのままだと焼けないGNU executableなので、これをIntel HEXという形式に変換してやる必要があります。

avr-objcopy -j .text -j .data -O ihex blink.elf blink.hex

するとblink.hexというファイルができ、これをavrdudeで焼くことができます。

avrdude -c usbasp -p atmega328p -U flash:w:blink.hex
avrdude: warning: cannot set sck period. please check for usbasp firmware update.
avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.02s

(略)

Writing | ################################################## | 100% 1.21s

(略)

Reading | ################################################## | 100% 1.12s

avrdude: verifying ...
avrdude: 254 bytes of flash verified

avrdude: safemode: Fuses OK (H:07, E:D9, L:62)

avrdude done.  Thank you.

Warningが出まくっていますが、焼けます。

光ったあああああああ

ちなみに、Atmega328p用の設定ファイルしかリポジトリに含まれていないため、そのままだとほかのマイコン用にはコンパイルできません。とりあえず、同じ構造で容量違いのAtmega168で試そうと、jsonを2カ所、Xargo.tomlを1カ所、マイコン名を328から168に変更してコンパイルしたところAtmega168でも動作させることができました。

コード

さてこのサンプルリポジトリにあるLチカのコードですが、普段見慣れているシンプルなRustのコードとは似ても似つかない禍々しいオーラを放っていることが見て取れます。no_stdアトリビュートが指定されているので普通の関数は何一つ使えません。またループの実装にasm(インラインアセンブリ)が使われていて不気味ですね。随所にあるwrite_volatileを囲うunsafeもおぞましい。ちなみにデフォルトだとsmall_delayのループ回数が400000になっていますが、これだと長すぎるので上の動画を撮るときには0を一つ取っています。

コードもこんな感じですし使えるライブラリも皆無ですので、残念ながら実用にはほど遠いようです。このような状況を切り開いていける本物のエンジニア各位におかれましては、冬休みの自由研究の題材にいかがでしょうか。私はおとなしくLinux/Windowsで書いていくことにします。

まとめ

ここまで書いておいていま気づいたんですが、Internetにつながるところまで進めませんでした。ごめんなさい。

明日は@shiogenさんの順番です。お楽しみにどうぞ。

Notes:

  1. アセンブリが書きやすいとかTTLレベルでflash書き込みができるとかそういうことですので、Web上でJSを書いたらLEDが光るとかそういうのとはちょっと違います。