D言語で動くのか
D言語は複数のコンパイラ実装が存在しており、リファレンスコンパイラであるDMDではできないのですが、コンパイラバックエンドがGCCのGDCやLLVMのLDCではCortex M4向けにバイナリを吐くことが可能です。今回はLDCを使って開発しました。
理屈の上ではできることはわかるのですが、実際に動くものを作るというのは大きな違いがあり、なかなか簡単にいかなかったりします。とはいえLDCはすでにThumb2 targetをサポートしていたので、特にコンパイラをいじるようなこともなくLチカまではすんなり書くことができた記憶があります。
ソースコードはGitHubで公開しています。
- https://github.com/kubo39/cortexm
- https://github.com/kubo39/stm32f407discovery
- https://github.com/kubo39/semihosting
D言語だとなにが嬉しいのか・辛いのか
組み込みでよく使われる言語というとAssemblyやC言語になります。これらの言語と比較してという話でいうと、
- より強力な型チェックが存在すること
- テンプレートによる柔軟なメタプログラミングが可能
- UFCSでメソッドチェーンっぽく記述できる
といったあたりが個人的に嬉しいです。またffiがあるので(今回はしていないのですが)C言語のライブラリをD側から呼び出すことも可能です。
反面、エコシステムが充実していないこと(そもそも今回はスタートアップルーチンから全て自作しています)、組み込みプログラミングにおけるDの作法を意識せずに書いてしまうとかなりの確率でエラーになってしまうことなどがあります。
例えばD言語は全てのグローバル変数が普通に書いてしまうとTLS領域に置かれるのですが、これはLibcの __tls_get_addr
関数を使うようにコンパイラがコード生成するのでリンカエラーに遭遇してしまうでしょう。スタブを置いてもよいのですが、そもそも組み込みプログラミングでTLS領域を用意する必要は今の所ないので __gshared
というキーワードを使って明示的に .data
あるいは .bss
領域に配置されるようにしたほうがよいでしょう。こういった知識を暗黙に要求する場面が多々あるので、初心者からするとすこし敷居が高くなってしまうかもしれません。
どのくらいできるのか
現状だとLチカ、割り込み、タイマー制御、arm semihostingでホスト側に書き込むといったことはできます。Lチカのプログラムは以下のように記述できます。そこそこシンプルに記述できているのではないでしょうか。
import stm32f407discovery;
import stm32f407discovery.led;
import stm32f407discovery.timer;
@nogc:
nothrow:
extern (C) void main()
{
pragma(LDC_never_inline);
auto tim2 = powerOn!"TIM2"();
initLED();
tim2.pause();
tim2.setPrescaler(7999);
while (true)
{
auto ticks = 1000;
foreach (led; LEDS)
{
led.on();
tim2.delay(ticks);
led.off();
tim2.delay(ticks);
}
}
}
void delay(Tim* tim, uint ticks)
{
tim.setAutoreload(ticks);
tim.resume();
while (!tim.isUpdated())
{
}
tim.clearUpdateFlag();
}
他の言語の選択肢
もちろん、AssemblyとC言語以外の選択肢がD言語以外にないわけではありません。MicroPythonは有名ですし、Rustも http://blog.japaric.io/ のように活動的な人がいます。
マイナー言語でも 今回のアドベントカレンダーで紹介された Nimや、 さらにAdaで 書いている 人もいたりします。
個人的にはコンパイル型の静的型付き言語が好みなので、D言語・Rust・Nimあたりに頑張ってもらえたらなあ。。といった感じです。この中だとRustがユーザコミュニティやライブラリの充実度で一歩リードしているのではないでしょうか。
宣伝
C93にサークル参加して、今回の話をチュートリアル形式で進めるようなものを書いてます。興味があれば是非。