【Arduino工作〜発展編】RustでATmega328pのプログラムをビルドしてみる


2021/03/22
蛸壺の中の工作室|RustでATmega328pのプログラムをビルドしてみる

以前FreeROSをArduinoにインストールしてLチカさせる方法を紹介しました。

また別のアプローチとして、Rustによる組込プログラミングというのも可能のようです。

今回はRustからどのようにAVRマイコン用のプログラムを使っていくのか考えていきます。


AVR-Rustでより簡単に組込プログラミング

AVR-Rustはかつては有志によるプロジェクトでしたが、その後Rustが公式でサポートする形で2020年7月からオプションのライブラリとして利用できるようになったようです。

このことでcargoコマンド一発でAVRのビルド環境が構築できるようになり、かなり簡単に扱えるようになっています。


toolchainの環境構築

RustでAVR用のプログラムをビルドする前に、Rustの環境を整えます。

パソコンのOS自体はRustが動作すればなんでもOKですが、今回は
Debian/Ubuntu系のLinux相当(apt-getコマンドの利用できるディストリビューション)で試しております。なお、お手元でRustの使える環境であることを前提として、Rustのインストール手順自体はウェブ検索すると山ほど情報が出てきますので省略します。ご了承ください。

さて、まずrustupがビルド時に必須ですので、現在のバージョンを確認します。

            $ rustup --version
rustup 1.23.1 (3df2264a9 2020-11-30)
info: This is the version for the rustup toolchain manager, not the rustc compiler.
info: The currently active `rustc` version is `rustc 1.51.0-nightly (c2de47a9a 2021-01-06)`
        
AVRのプログラムに必要なプログラムを以下のように追加でパッケージインストールします。

            $ sudo apt-get install binutils gcc-avr avr-libc avrdude
        
他のOSへのインストールはここのページに言及されています。

AVR向けのツールチェーンはnightlyにしか組み込まれていませんので、以下のようにrustupでnightlyビルドができるように仕向けます。

            $ rustup update
$ rustup toolchain install nightly
$ rustup component add rust-src --toolchain nightly
$ rustup override set nightly
$ rustc -Vv
rustc 1.51.0-nightly (c2de47a9a 2021-01-06)
binary: rustc
commit-hash: c2de47a9aa4c9812884f341f1852e9c9610f5f7a
commit-date: 2021-01-06
host: x86_64-unknown-linux-gnu
release: 1.51.0-nightly
        
次にビルドターゲットをAVR用に設定しますが、Rustのnightlyに記述されているAVR用ターゲットはavr-unknown-gnu-atmega328一択です。

もしATmega328以外のマイコンをターゲットにするには、カスタムターゲット用のJSONを別途作成する必要があります。

カスタムターゲットの例としてATmega328p用のjsonファイルを作ってみましょう。

            $ rustc --print target-spec-json -Z unstable-options \
    --target avr-unknown-gnu-atmega328 > avr-atmega328p.json
        
すると、avr-atmega328p.jsonという名前で以下のような内容のjsonファイルができると思います。

            {
    "arch": "avr",
    "atomic-cas": false,
    "cpu": "atmega328",
    "data-layout": "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8",
    "eh-frame-header": false,
    "exe-suffix": ".elf",
    "executables": true,
    "is-builtin": true,
    "late-link-args": {
        "gcc": [
        "-lgcc"
        ]
    },
    "linker": "avr-gcc",
    "linker-is-gnu": true,
    "llvm-target": "avr-unknown-unknown",
    "max-atomic-width": 0,
    "pre-link-args": {
        "gcc": [
        "-mmcu=atmega328",
        "-Wl,--as-needed"
        ]
    },
    "target-c-int-width": "16",
    "target-pointer-width": "16"
}
        
基本的にはcpu名とgccの-mmcuの2つをターゲットデバイスに合わせるように変えます。

            {
    //...
    "cpu": "atmega328p",
    //...
    "pre-link-args": {
        "gcc": [
            "-mmcu=atmega328p",
            //...
        ]
    },
    //...
}
        
これでひとまずツールチェーンの構築は完了です。


Rustプログラミング手順

ではRustでのLチカのプログラム例を見ていきます。

このプロジェクトの最低限のフォルダ構造は以下です。

            $ tree
.
├── Cargo.toml
├── src
│   └── main.rs
└── avr-atmega328p.json
        
ビルドターゲット用のjsonは先ほど説明していたファイルですので説明は省略しまして、まずCargo.tomlですが、

            [package]
name = "blink"
version = "0.1.0"
authors = ["Dylan McKay <me@dylanmckay.io>"]
edition = '2018'

[dependencies]
ruduino = "0.3"
        
となっています。

ここで唯一依存性の指定が入っている
ruduinoはArduino Uno向けのクレートです。Arduino UnoはATmega328pマイコンの搭載された製品ですので、このruduinoクレートがそのまま利用できます。

他のAVRマイコンへの実装例もこの
ruduinoのプロジェクトソースコードを眺めて、自作するヒントが掴めるかも知れませんので、色々と勉強してみると良いでしょう。

次にいよいよ
src/main.rsですが、

            #![feature(llvm_asm)]

#![no_std]
#![no_main]

use ruduino::Pin;
use ruduino::cores::current::{port};

#[no_mangle]
pub extern fn main() {
    port::B5::set_output();
    loop {
        port::B5::set_high();
        ruduino::delay::delay_ms(1000);
        port::B5::set_low();
        ruduino::delay::delay_ms(1000);
    }
}
        
となって、いかにもArduino風にプログラミングされいます。

こうスッキリと完結したコードになっているのも、ruduinoからインポートしたメソッドを利用できている恩恵で、一般に自分で一からコードを実装すると、もっと長くなると思います。

如何せんこれで材料が揃いましたので以下のコマンドでビルドします。

            $ export AVR_CPU_FREQUENCY_HZ=16000000
$ cargo build -Z build-std=core --target avr-atmega328p.json --release
        
これでtarget/[デバイスのタイトル]/releaseフォルダ以下に、実行ファイルのblink.elfが吐き出されていたら完了です。

ちなみにArduinoにはビルドインLEDとマイコン側のPB5ピンは接続されているので、Arduino上で試すとこのLEDがチカチカすることが分かります。

あとはavrdudeでマイコンに書き込む手順は別記事で説明した内容と一緒です。

以下のコマンドはAtmel-ICEからマイコンへ書き込むときのコマンド例です。

            $ sudo avrdude -p m328p -c atmelice_isp -P usb -U flash:w:target/avr-atmega328p/release/blink.elf:e
        

書き込み装置の種類でavrdudeのオプションが違いますので、avrdudeのマニュアルなどを参照にプログラムを書き込んでください。

LLVM ERRORで止まる(2021年1月頃の報告)

2021年3月現在で、nightlyのバージョンによっては他のライブラリとの整合性が内部で取れていないのか、llvmでビルド中のバグが報告されています。

参考:LLVM ERROR: Not supported instr

以下のように
compiler_builtinsのエラーという内容です。

            $ cargo build -Z build-std=core --target avr-unknown-gnu-atmega328 --release
   Compiling compiler_builtins v0.1.39
   Compiling nb v1.0.0
   #...
   Compiling embedded-hal v0.2.4
LLVM ERROR: Not supported instr: <MCInst 258 <MCOperand Reg:1> <MCOperand Imm:15> <MCOperand Reg:40>>
error: could not compile `compiler_builtins`

To learn more, run the command again with --verbose.
warning: build failed, waiting for other jobs to finish...
error: build failed
        
現行で最新のnightlyではまだこのバグが残ったままでしたので、バグの出ないnightly-2021-01-07というバージョンを指定して利用するようにします。

            $ rustup toolchain install nightly-2021-01-07
$ rustup component add rust-src --toolchain nightly-2021-01-07
$ rustup override set nightly-2021-01-07
        
として再度cargoビルドしてあげると上手くコンパイルできるはずです。


まとめ

今回はRustでAVRマイコンを実装する場合の手順を紹介していきました。

感触としてはすでにC言語でプログラミングするのと殆ど同じように感じました。

個人的に、Rustで組込プログラミングというのは水面下でかなり流行ってきてる手法ですので、これを気に何かサーボモータを制御して可動する装置を作ってみたくなりました。


参考サイト

The AVR-Rust Guidebook

Arduinoへ直接書き込みする

2020版: RustのArduino向けプログラムでLチカする方法

RustでLチカする2種類の方法(Arduino編)

Raspberry Pi Pico関連

最近話題のRaspberry Pi Picoも同様の手法でRustによるプログラミングが可能ですが、書き込み方法は別の環境を構築する必要があります。

Raspberry Pi Pico Gets supports for Rust, RT-Thread OS and FreeRTOS

記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

電子工作を身近に知っていただけるように、材料調達からDIYのハウツーまで気になったところをできるだけ細かく記事にしてブログ配信してます。