Arduino といえば、不便便利な IDE とその取り巻きですが、とにかく 開発が簡単なマイコンとして知られています。
本記事では、マイコン開発の基礎であるLチカを、Arduino だからこそできる(?)いろいろな方法で実装してみました。とりあえず今回はボード上の LED を点灯させるだけ(それじゃあチカチカしてないじゃんか、という意見はあると思いますが)。
その方法たちは、独断と偏見によってレベル別に並べてあります。皆さんどうぞご参考に。今回は、Arduino Uno と Arduino Nano で動作を確認しています。
Level 1: 素直にLチカ
よく Web にあるような書き方です。とりあえず Arduino 言語(笑)を使います。至って普通ですね。
#define LED 13
void setup() {
pinMode(LED, OUTPUT);
}
void loop() {
digitalWrite(LED, true);
}
この超絶シンプルな書き方にはデメリットがあって、
- 内部で何をやっているかわからない
- 動作が遅い(Lチカごときで気にするような内容ではないと思いますが)
- 楽しくない
という問題があります。そこでこれ以降の書き方たちです。
Level 1.5: C++として書く
いいか、Arduino言語なんかないんだぞ、と言わんばかりに、C++ で書いてみます。ただ #define
という過去の遺産を constexpr
に変えただけです。
constexpr auto LED = 13;
void setup() {
pinMode(LED, OUTPUT);
}
void loop() {
digitalWrite(LED, true);
}
Arduino IDE では constexpr
がシンタックスハイライトされないんですね。なんということだ。。。
Level 2: レジスタを叩く
上の方法だと楽しくないですよね。ということで 実際に IO レジスタを叩いてみましょう。
まずは LED の接続先を確認します(え?13番ピンじゃないかって??いやいや、そんなわけ無いですよ)。arduino schematic と検索して回路図を見てみます。
ふむふむ、PB5 ですね。わかりました。なので、このポート出力設定と、出力データをいじればいいのです。なので、コードは以下のようになります。
void setup() {
DDRB = bit(5);
}
void loop() {
PORTB = bit(5);
}
まあ至って普通ですね。簡単簡単。
Level 2.5: 制御を乗っ取る
よくよく考えると、void setup()
とか void loop()
とか、どこでどうやって動いているのか不可解ですね。多分以下のような感じなんでしょうけど。
int main() {
init(); // 何らかのライブラリ用初期化関数
setup();
while(true) {
loop();
}
}
でも実際にライブラリ内部のソースコードを見たわけじゃないので納得いきません。そこでこれらの関数の挙動を乗っ取ってしまえばいいと考えました。
参考は手前味噌ですが、↓の記事です。
__attribute__((constructor))
をつければ main
関数が実行される前に実行されます。
void setup() {
// put your setup code here, to run once:
}
void loop() {
// put your main code here, to run repeatedly:
}
__attribute__((constructor))
void f() {
asm volatile(
"sbi 0x04, 5\n\t"
"sbi 0x05, 5\n\t"
);
}
Level 4: 機械語を手書き
アセンブラなんて甘えだ! という人はいると思います。あと、インラインアセンブラが気持ち悪いと思う人もいるでしょう。分かります。
この2つの要求に答えるためには、必然的に機械語を手書きすればいいというところに落ち着きますよね。
これはプログラミングマニュアルにあるビット対応図を利用すれば実現できます。どうするかと言うと...
const uint16_t[]
として機械語を手打ちした配列を作る- 配列の属性として
__attribute__((section(".text")))
とする - その配列ポインタを
void (*)()
(関数ポインタ)にキャストする - 関数を実行する
この手順を踏みます。1. で const
を付けたのに、この変数は RAM 上に配置されるようです。変なの。
あと、AVR は RAM 上のデータは実行できないみたいなので、属性を指定することが必要です。若干ハマりかけました。
それでは、以下が実現したコードです。
void setup() {
// put your setup code here, to run once:
}
void loop() {
// put your main code here, to run repeatedly:
}
__attribute__((section(".text")))
const uint16_t code[] = {
0x9A25, // sbi 0x04, 5
0x9A2D, // sbi 0x05, 5
0x9508 // ret
};
__attribute__((constructor))
void f() {
auto execute = reinterpret_cast<void (*)()>(code);
execute();
}
至ってシンプルですね。でもいちいちキャストした関数ポインタを execute
として束縛するのは...
ということで、f()
を省略して書いてみます。
void f() {
reinterpret_cast<void (*)()>(code)();
}
スッキリしていいですね!!
〆
以上、ただのネタ記事でした。