【2024年】AVR-Rust 開発環境構築 2 (ruduino, ATmega328P 編)

# 1: はじめに

Rust で AVR マイコンをプログラミングする方法はいくつかあります。

* [avr-rust/ruduino](https://github.com/avr-rust/ruduino/)
  * 感触として、とりあえず Aruduino UNO をそのまま動かしたい方向け
  * 割り込みも使用できます
  * 本記事で紹介する方法です
* [Rahix/avr-hal](https://github.com/Rahix/avr-hal)
  * 感触として、割り込みを意識せず抽象度高めに書きたい方向け
  * 次の記事で紹介している方法です
* [Rahix/avr-device](https://github.com/Rahix/avr-device)
  * 割り込みハンドラなどを具体的に動かすときに便利（抽象的ではない）
  * 次の記事で紹介している方法です

基本的には、公式ガイドブックの通りに進めることで、環境を構築することができますが、一部つまづきポイントがありました。
Rustup や avr-gcc がインストールできれば、どの OS でも同じようなコマンドで操作できます。

私が AVR-Rust 環境構築についてまとめている関連記事は次のとおりです。
1. [Rust nightly コンパイラ, avr-gcc 編](https://qiita.com/BerandaMegane/items/ae2b35d3c8411df5a506)
1. 本記事（ruduino, ATmega328P 編）
1. [avr-hal, atmega-hal, ATmega 編](https://qiita.com/BerandaMegane/items/8d92111170d78d9343ad)

## 1-1: 本資料を活用できる対象者

Rust が初めてでも、ガイドブック読みながらコマンド操作できれば問題ありません。

* AVR マイコンを C/C++ でプログラミングしたことがある方
* シェルやコマンドプロンプトでのコマンド操作にある程度慣れている方

## 1-2: 構築した環境

外部ライブラリとして ruduino を使用しています。他にも avr-hal の作例も調べると出てきます。

* ruduino - [GitHub avr-rust/ruduino](https://github.com/avr-rust/ruduino/tree/master)
  * Aruduino UNO をそのまま Rust で書きたい方向け
  * ATmega328P が使用されている R3 まで対応しており、R4 は対応していません
  * 割り込みも使用できます
* avr-hal - [GitHub Rahix/avr-hal](https://github.com/Rahix/avr-hal)
  * 今回は使用していませんが、ATmega328P 以外の型番で動作させる場合は便利です

その他の環境

* Rust nightly コンパイラ
  * 動いたバージョン
    * rustc 2024-08-04, 1.81 
    * rustc 2024-09-14, 1.83
  * AVR-Rust は非安定版コンパイラ (nightly) でコンパイルする必要があります
  * nightly コンパイラのバージョンは日付で指定しますが、日付によって動いたり、動かなかったりします
  * **バージョンを最新にすると動きませんでした** ので、動かなかった場合は数ヶ月ずつ過去の日付を指定してください
  * 後述の rust-toolchain.toml ファイルによる日付指定が非常に便利です
* AVR ATmega328P-PU  (動作確認したマイコン)
  * <https://akizukidenshi.com/catalog/g/g103142/>
  * ATmega328P 以外にも **ATtiny2313 で動かないか試行錯誤しましたが、うまく動かせません** でした

# 2: Rust nightly コンパイラと avr-gcc インストール手順

こちらの記事で紹介しています。

https://qiita.com/BerandaMegane/items/ae2b35d3c8411df5a506

# 3: ruduino プロジェクトのビルド

Rustup と avr-gcc をインストールしたら、環境構築は完了しているはずです。
ここでは、AVR-Rust が提供しているサンプルをビルドするなどをして、動作を確認します。

## 3-1: ruduino Blink サンプルのビルド

AVR-Rust が提供している Blink サンプルをビルドしてみます。Blink とは、電子工作界隈の Hello World! であるLチカ（LEDチカチカ）のことです。
[GitHub - avr-rust/blink](https://github.com/avr-rust/blink.git) 

### リポジトリクローン

まずはサンプルを GitHub からクローンします。
クローン後は blink ディレクトりが生成されるため、カレントディレクトリを移動します。

```:リポジトリクローン
$ git clone https://github.com/avr-rust/blink.git
$ cd blink
```

ソースコードは src/main.rs にあるため、開いてみます。

```rust:src/main.rs
#![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 UNO であれば、ボード上の LED (PB5) がチカチカするサンプルとなっています。

### ビルド前準備

動作確認につかったマイコンボード（ブレッドボード）は次のような状態です。

* マイコン: ATmega328P
* ヒューズビット: 0x62, 0xD9, 0xFF (出荷時状態 参考: https://www.engbedded.com/fusecalc/)
  * クロック: 1 MHz (内蔵クロック 8 MHz を8分周)

それでは、ビルドしてみましょう。

公式サンプルの ReadMe.md や [AVR-Rust ガイドブック](https://book.avr-rust.org/003-building-a-crate-for-avr.html#make-sure-you-use-the-nightly-version-of-rust-not-the-default-stable-channel) に記載の方法だと、いくつか不具合があったので、少し調整して動かします。

blink ディレクトリ内にある rust-toolchian.toml に、nightly コンパイラのバージョン（日付）を記述します。

```toml: rust-toolchain.toml (変更後)
[toolchain]
channel = "nightly-2024-04-23"  # 変更後
components = ["rust-src"]  # 追加
```

マイコンの動作周波数を設定するため、.cargo/config.toml を変更します。
変更しないと、delay_ms() が指定した時間通りに動作しません。デフォルトは 16MHz のようです。

```toml:.cargo/config.toml (変更後)
[build]
target = "avr-atmega328p.json"

[unstable]
build-std = ["core"]

# Cargo versions before 2021-02-23 won't recognize this: https://github.com/rust-lang/cargo/pull/9175
[env]
AVR_CPU_FREQUENCY_HZ = "1_000_000"  # 16 -> 1 MHz に変更
```

コマンドでビルドします。

```:Ubuntu
# config.toml を設定しているので、次の export コマンドは不要
$ export AVR_CPU_FREQUENCY_HZ=1000000

# AVR-Rust ガイドブック通りでビルドする場合
$ cargo build -Z build-std=core --target avr-atmega328p.json --release

# config.toml を設定しているので、次のコマンドで問題ない
$ cargo build --release
```

`target/avr-atmega328p/release/` ディレクトリにある `blink.elf` をマイコンに書き込むことで、正常にLチカを行うことができました。

私は、AVRDUDESS で書き込みました。

https://github.com/ZakKemble/AVRDUDESS

## 3-2: cargo 新規プロジェクトのビルド

先ほど Blink サンプルの動作を確認しましたが、ビルドに最小限必要なファイルは限られます。
そこで、` $ cargo new [プロジェクト名] ` で作成できるプロジェクトでプログラミングする方法を試しました。

### 必要なファイル

最低限必要なファイルは次のとおりです。

* ruduino-blink ディレクトリ  (プロジェクト)
  * src ディレクトリ
    * main.rs  (ソースコード)
  * .cargo ディレクトリ
    * config.toml  (ビルド時の設定ファイル)
  * Cargo.toml
  * Cargo.lock  (上手く行かないときは blink から流用)
  * avr-atmega328p.json  (ターゲット仕様 JSON ファイル) 
  * rust-toolchain.toml  (コンパイラ設定ファイル)


### 新規プロジェクトの作成

それでは、simple-blink プロジェクトを作成します。

```:コマンド
# 新規プロジェクトの作成
$ cargo new ruduino-blink
# ディレクトリへ移動
$ cd ruduino-blink
```

### Cargo.toml の編集
ruduino への依存関係を追加する必要があるため、dependencies に次の行を追加します。

```toml:Cargo.toml (変更後)
[package]
name = "ruduino-blink"
version = "0.1.0"
edition = "2021"

# 以下の記述を追加（blink サンプルよりコピー）
[dependencies]
ruduino = {  git = "https://github.com/avr-rust/ruduino", branch = "master" }
```

### src/main.rs の編集

もともとは Hello World のためのコードが記述されていますが、Blink サンプルをそのまま上書きしました。

```rust:src/main.rs (変更後)
#![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(500);
    }
}
```

### rust-toolchain.toml の作成

プロジェクトで Nightly コンパイラのバージョンを指定するため、rust-toolchain.toml を次のように新規作成します。

参考: [Rustのrust-toolchain.tomlについて](https://neer-engineer.com/25613ca005b69a600e8c45bc4d2b200f)

```toml:rust-toolchain.toml (新規作成)
[toolchain]
channel = "nightly-2024-04-23"
# channel = "nightly"  # 最新にするならこっちだが、動かなくなることがある
components = ["rust-src"]
```

正しく rust-toolchain.toml ファイルで指定できていれば、次のコマンドで確認できます。
```:rustup show コマンドによる確認
$ rustup show
Default host: x86_64-pc-windows-msvc
rustup home:  C:\Users\user\.rustup

installed toolchains
--------------------

stable-x86_64-pc-windows-msvc (default)
nightly-2024-04-23-x86_64-pc-windows-msvc
nightly-2024-04-24-x86_64-pc-windows-msvc
nightly-2024-04-29-x86_64-pc-windows-msvc
nightly-2024-04-30-x86_64-pc-windows-msvc
nightly-x86_64-pc-windows-msvc

active toolchain
----------------

(↓ ここに記載)
nightly-2024-04-23-x86_64-pc-windows-msvc (overridden by 'D:\user\Documents\Rust\ruduino-blink\rust-toolchain.toml')
rustc 1.79.0-nightly (7f2fc33da 2024-04-22)
```

### ターゲット仕様 JSON ファイルの作成 (ファイル流用)
Blink サンプルにも入っている avr-atmega328p.json ファイルは、たくさんある AVR マイコンの型番を指定するために必要なターゲットファイルです。

次に紹介する JSON ファイルの作成が上手く行かない場合は、Blink サンプルからそのままコピーして流用するのが手っ取り早いかと思います。

### ターゲット仕様 JSON ファイルの作成 (ガイドブック)

~~今のところ成功していませんが、~~ **上手くいく方法がわかったので、** AVR-Rust ガイドブック [3. Building a crate for AVR](https://book.avr-rust.org/003-building-a-crate-for-avr.html) を読みながら作成します。

Google翻訳によると、以下のようなことが記載されています。

* Rust Nightly コンパイラには、`avr-unknown-gnu-atmega328` という ATmega328 用の組み込みターゲットが含まれている
* ATmega328 以外の MCU をターゲットにする場合、組み込みの `avr-unknown-gnu-atmega328` ターゲットをカスタム ターゲット仕様 JSON ファイルにエクスポートし、ニーズに合わせて変更する必要がある
  * このファイルを別のマイクロコントローラをターゲットにするように調整するには:
    * トップレベルの「cpu」:「atmega328」を「cpu」:「YOUR-AVR-VARIANT-NAME」に置き換えます
    * 「-mmcu=atmega328」を「-mmcu=YOUR-AVR-VARIANT-NAME」に置き換えます

  * `rustc --target <JSON FILE PATH>` を介してファイルを Rust に渡すことができます。これにより、生成されたコードが目的のマイクロコントローラに合わせて調整されます

今回使用するのは ATmega328P であり ATmega328 ではないので、ターゲット仕様 JSON ファイルをカスタマイズする必要があります。

まずは、Nightly コンパイラに組み込まれているターゲット仕様 JSON ファイルをエクスポートします。このとき、`--target avr-unknown-gnu-atmega328` は、`atmega328` のままエクスポートします。

```:エクスポート
$ rustc --print target-spec-json -Z unstable-options --target avr-unknown-gnu-atmega328 > avr-atmega328p.json
```

リダイレクトによって、avr-atmega328p.json ファイルが作成されます。
PowerShell で作成した場合、文字コードが UTF16 の状態で作成されることがあるため、その場合は BOM なし UTF8 に変換してください。PowerShell 7 なら、リダイレクト時にエンコーディング指定 (utf8NoBOM) できます。（PowerShell 5 はできなかった）

次の2項目を修正します。
* ファイル中に記載されているマイコン名を atmega328 から atmega328p へ変更する（3個所ありました）
* `"is_builtin": true` を false にする

```json:avr-atmega328p.json (変更後)
{
  "arch": "avr",
  "atomic-cas": false,
  "cpu": "atmega328p",
  "crt-objects-fallback": "false",
  "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",
  "is-builtin": true,
  "late-link-args": {
    "gnu-cc": [
      "-lgcc"
    ],
    "gnu-lld-cc": [
      "-lgcc"
    ]
  },
  "linker": "avr-gcc",
  "linker-flavor": "gnu-cc",
  "llvm-target": "avr-unknown-unknown",
  "max-atomic-width": 16,
  "metadata": {
    "description": null,
    "host_tools": null,
    "std": null,
    "tier": null
  },
  "pre-link-args": {
    "gnu-cc": [
      "-mmcu=atmega328p"
    ],
    "gnu-lld-cc": [
      "-mmcu=atmega328p"
    ]
  },
  "relocation-model": "static",
  "target-c-int-width": "16",
  "target-pointer-width": "16"
}
```

### トラブルシューティング

**記事作成当初のトラブルです。2024-09-15 現在、このトラブルは発生していません。** 

このままビルドすれば動くと思っていましたが、LEDが薄ら光るだけでうまく動きませんでした。
色々試してみたところ、Cargo.lock ファイルに記載されている avr_delay などのクレートのバージョンが異なることが原因とわかりました。

* 動くバージョン avr_delay v0.4.0
* 動かない最新バージョン avr_delay v0.4.2

**blink サンプルの Cargo.lock ファイルを simple-blink プロジェクトに上書きしてください。**

~~Cargo.toml に正しくクレートのバージョンを指定することができれば、このトラブルを解決できそうですが、今のところはできていません。~~

### 基本的なビルド手順

AVR-Rust ガイドブック [3.2. A note about the required Rust -Z build-std=<CRATE,> flag](https://book.avr-rust.org/003.2-note-about-rust-build-std-flag.html) を読みながら、ビルドしていきます。

`--target` オプションには、ターゲット仕様 JSON ファイルを相対パスで指定します。

```:ビルドコマンド
$ cargo build -Z build-std=core --target avr-atmega328p.json --release
```

次のような `.cargo/config.toml` ファイルを作成することで、楽にビルドできるようにします。
なお、環境変数 AVR_CPU_FREQUENCY_HZ は ruduino を利用するときに必要です。別記事で紹介している atmega-hal の場合は不必要です。

```toml:.cargo/config.toml (作成)
[build]
target = "avr-atmega328p.json"

[unstable]
build-std = ["core"]

# マイコン動作周波数を指定
[env]
AVR_CPU_FREQUENCY_HZ = "1_000_000"  # _ で区切ると読みやすい
```

楽なオプション指定でビルドします。

```:ビルドコマンド
$ cargo build --release
```

`target/avr-atmega328p/release/` ディレクトリにある `ruduino-blink.elf` をマイコンに書き込むことで、正常にLチカを行うことができます。

何らかの原因でビルドが上手く行かなかった場合は、中間生成ファイルを消してビルドし直したほうがよい場合もあります。その場合は、生成された target ディレクトリごと削除してください。

# 4: 最後に

この資料を書いているのは、[実践Rustプログラミング入門](https://www.shuwasystem.co.jp/book/9784798061702.html) を読み始めた程度の Rust 言語歴2日目の初心者です。
未だにクレートって何？という状態ではありますが、AVR マイコンを Rust で開発するまでの環境構築をまとめてみました。
AVR マイコンで Rust を動かそうと試行錯誤している人の助けになれば幸いです。私は試行錯誤に2週間かかりました。

ruduino ではなく avr-hal でプログラミングする手順は、続きの記事で紹介しています。

https://qiita.com/BerandaMegane/items/8d92111170d78d9343ad