目的
ATmega328p向けのLチカプログラムをRustで作成しそれをコンパイルします。
環境
利用するライブラリ:avr-hal(Versionはハッシュ値が885e8ec
のもの)
OS:Ubuntu20.04.1
使用したAVRライター: Arduino Nano Everyをライター化したもの
注意:現在(2021/5/21)、avr-halを利用している回帰処理を含んだプログラムをコンパイルするとき、nightly-2021-01-07
より後のコンパイラではうまく行えないようです。そのためavr-halはnightly-2021-01-07
を使うことを勧めています。
バイナリクレートの作成
ツールチェインの固定
まずは、普通にバイナリクレート(ここではblinkと命名)を作成します。作成後、クレートの直下にrust-toolchain
ファイルを作成し、利用するツールチェインを指定します。
nightly-2021-01-07
Atmega328pをtaget
に指定する
次にrustcではAtmega328pに対応していないため、Custom Target Specificationを用意し、コンパイルするときにそれを参照するよう指示します。
嬉しいことにavr-hal
がCustom Target Specification
を用意してくださってるので、それを使わさせていただきます。このatmega328p.json
をblinkクレート直下に設置します。
Custom Target Specification
が用意できたら.cargo/config
を用いて、コンパイル時にatmega328p.json
を用いるよう指定します。ちなみに.cargo/config
でも.cargo/config.toml
でもいいです1。
また、サポートしていないtarget
でRustを使うためには、そのtarget
用のcoreクレートが必要なため、atmega328p
に向けにcore
をコンパイルしてもらうように設定します。このような用意されているRust標準ライブラリを使わずに、指定したtarget
に合わせてローカルでコンパイルすることをstd Aware Cargo
と言うようです2。
[check]
target = "atmega328p.json"
[build]
target = "atmega328p.json"
[unstable]
build-std = ["core"]
依存関係
最後に、avr-halに従ってCatgo.toml
に依存関係を書いていきます。
[package]
name = "blink"
version = "0.1.0"
authors = ["mutuya"]
edition = "2018"
[dependencies]
panic-halt = "0.2.0"
[dependencies.atmega328p-hal]
git = "https://github.com/Rahix/avr-hal"
rev = "885e8ec"
features = ["rt", "atmega328p"]
# Configure the build for minimal size
[profile.dev]
panic = "abort"
lto = true
opt-level = "s"
[profile.release]
panic = "abort"
codegen-units = 1
debug = true
lto = true
opt-level = "s"
atmega328p-hal
向けにコンパイルする場合において
features = ["rt", "atmega328p"]
がとっても大切です。atmega328p
を忘れてもコンパイル時にわかりやすいエラーが出るためあまり問題にならないと思いますが、rt
を忘れるとちょっと厄介です。理由は後述します。
コードの作成
完成コード
#![no_std]
#![no_main]
extern crate panic_halt;
use atmega328p_hal::clock;
use atmega328p_hal::prelude::*;
use atmega328p_hal::pac::Peripherals;
use atmega328p_hal::delay::Delay;
#[atmega328p_hal::entry]
fn main() -> ! {
let mut delay = Delay::<clock::MHz8>::new();
let peripherals = Peripherals::take().unwrap();
let mut portd = peripherals.PORTD.split();
let mut led = portd.pd4.into_output(&mut portd.ddr);
led.set_low().unwrap();
loop {
delay.delay_ms(500_u16);
led.toggle().unwrap();
}
}
コードの解説
エントリーポイントは次のように書かれます。
#[architecture::entry]
fn main() -> ! {
// do something.
}
このfn main() -> !
はNever Type
と呼ばれるものであり、main()
が何も返さない(≒ 終了しない)ことを表しています3。
#[architecture::entry]
は今回の場合、#[atmega328p_hal::entry]
となります。ここで前述したfeatures = ["rt"]
を忘れているとエラーになるため気をつけてください。
このエラー内容は「はぅぅ。entryが見つからないよぉぉ。」と言うだけで「rt
を指定してください」とは言ってくれず、avr-hal
のクレート内容をくまなく探すことになります。。
Pinの操作
操作したいPinの取得は次のように行います。
use atmega328p_hal::port::portc;
use atmega328p_hal::port::mode::Output;
// 様々なレジスタへのアドレスが組み込まれている構造体Peripheralsを取得する
let peripherals = Peripherals::take().unwrap();
// Peripheralsの中から、GPIO Port Cを制御しているレジスタのアドレスを取り出す。
let mut portc: portc::Parts = peripherals.PORTC.split();
// portcから制御したいPin(ここではPC5)を取り出す。
// でも、portc.pc5は初めはinputとなっているから、outputに変換する。
let mut led: portc::PC5<Output> = portc.pc5.into_output(&mut portc.ddr);
ちなみに、Peripherals
やportc::Parts
、portc::PC5<Output>
はレジスタへのアドレスの情報を持っていますが、値として持っていません。マクロやトレイトを駆使しプログラムとして書き込まれています。本当にすごいと思った(小並感)。
AVRマイコン組み込み初心者には「portc.pc5.into_output(&mut portc.ddr);
の&mut portc.ddr
って何だ?」と思われるかもしれません。AVRマイコンのPinの制御には3つのレジスタが必要で、それぞれDDRレジスタ、PORTレジスタ、PINレジスタと呼ばれています。
DDRレジスタを書き換えることで、Pinを出力で扱うか入力で扱うかを選択できます。PORTレジスタはPinの出力を設定(High or Low)し、PINレジスタではPinがHighの入力を受けているかLowの入力を受けているかが格納されています。
(この&mut portc.ddrなんですが、実は中身は空っぽです。)
操作については、portc::PC5のドキュメントを見れば問題ないと思います。詳細については、こちらをどうぞ。
書き込み
自作ライターを利用します。
書き込みにはavrdude
を用います。インストールは次の通り。
sudo apt install avrdude
書き込み時は次のように実行します。
sudo avrdude -v -c avrisp -p m328p -b 19200 -P $(USB Device) -U flash:w:$(ELF File)
$(USB Device)
は接続しているライター(/dev/ttyACM*
と表されてると思う...)に置き換え、$(ELF File)
はコンパイルの成果物であるblink.elf
を入れる。
カレントディレクトリがblink
で、自作ライターが/dev/ttyACM0
のときは、次のようになる。
sudo avrdude -v -c avrisp -p m328p -b 19200 -P /dev/ttyACM0 -U flash:w:./target/atmega328p/release/blink.elf
もちろん、blink.elf
はDebugでコンパイルしたらdebug
フォルダ内にあるので、適宜、変えること。
メモ、自作ライターの判別
接続しているライターの/dev/ttyACM*
がどうなっているかを判断するには、次のコマンドの結果をライター非接続時と接続時の前後で見比べるのがてっとり速いと思う。
ls /dev/ | grep tty
-
ConfigurationのHierarchical structureのNoteに書いてあります。 ↩
-
build-std
についてはUnstable Featuresを参照してください。std Aware Cargo
を利用するケースについてはここを参照してください。 ↩