組み込みRust
-
組み込みRustの環境
通常のRustでは、開発対象はOS上で動作するアプリケーションプログラムであるため
OSが提供する機能を使用するstd::* クレートが提供されています。
一方、組み込みRustが実行される環境ではOSが存在しないため、上記の機能は使えません。
そのため、組み込みRustの実行環境ではno_std環境を使います。#![no_std]
ただし no_std環境では(当然ですが) std::* クレートは使えません。
その代わりにcore::* クレートが使えるので、主にこのクレートを使うことになります。use core::*;
-
組み込みRustでの動的メモリの利用
通常のRustでは、StringやVectorなど可変サイズのオブジェクトを扱うため動的メモリ
(ヒープメモリ)を利用しています。
一方組み込みシステムでは、定められた時間内に必ず処理を完了する必要があります。
メモリーの確保・開放の都度リンクを辿る必要のある動的メモリの利用は、処理時間の保証
と相性が良くないため、どうしても必要な場合を除き利用しない方が良いでしょう。 -
対象ハードウェアアーキテクチャの理解
組み込みRustが動作するマイコンには様々なメーカーのものがあり、それぞれのマイコン毎に
・ROMやRAMの各メモリエリアの開始・終了アドレス
・処理開始時のアドレス
が異なるため、それらに応じて実行モジュールの作成方法が変わります。
例えばRaspberry Pi Picoの場合メモリ アドレス範囲 ROM 0x00000000 - 0x0FFFFFFF ROM(FLASH) 0x10000000 - 0x1FFFFFFF RAM 0x20000000 - 0x2FFFFFFF などとなっています。
プログラムを開発する際には、開発対象マイコンに関する情報を入手する必要があります。 -
ベクタテーブルの準備
ベクタテーブルとは、各種例外発生時に呼び出される特定のルーチン(ハンドラ)のアドレス
を格納する配列です。
ベクタテーブルの要素数や配置場所も、マイコンの仕様で定められています。
Raspberry Pi Picoの場合は、ROM内の先頭にあるセカンドブートローダ(256byte)
の直後に存在する必要があります。 -
スタートアップ処理
組み込みRustのプログラムの本体部分はmain()関数に記述されていますが
この本体処理部分の実行を開始する前に、スタートアップ処理が必要になります。
スタートアップ処理では通常
・マイコン本体や周辺装置の初期化
・BSS(Block Started by Symbol)領域の0クリア
・初期値を持つグローバル変数は、初期値はROMに保管されていますが、プログラム実行時
にはRAM上に配置しないと値が書き換えられないため、ROMからRAM領域へコピー
などを行います。
スタートアップ処理は通常リセットハンドラから起動され、上記の処理を行った後
最後にRustのmain()関数にジャンプし、本体処理が開始されます。 -
リンカスクリプトの記述
組み込みRustでは、2-4で述べたような制約事項を満たすように実行モジュールを作成
する必要があります。モジュールはリンカがオブジェクトファイルを結合して作成しますが
上記の制約事項をリンカに指示する為、リンカスクリプトファイルを作成します。
(リンカスクリプトの文法は非常に複雑なので、ここでは内容は割愛します) -
Rustからアセンブラの呼び出し
Rustは高級言語のためCPUのレジスタを直接操作するような低レベルの処理を行う命令が
ありません。しかし割り込みの禁止・解除、プログラムの実行コンテキストのスタックへの
保管や取り出しなどを実現するには、どうしてもCPUのレジスタを直接操作する必要が
出てきます。組み込みRustでは、インラインアセンブラマクロ asm! を使う事で
Rustからアセンブラの命令を発行する事ができます。
また、アセンブラ命令に与えるパラメータを指定することも出来ます。
Discussion