STMicro は、STM32 コントローラの開発環境として HAL (Hardware Abstraction Layer) というライブラリを用意しています。これは、コントローラごとに違うレジスタ等の設定をライブラリの中に隠してコントローラが変わっても同じアプリコードが使えるようにするための抽象化ライブラリです。ソフトウェアエンジニアとして結構長年仕事してきましたけどその経験から得た知見「汎用化のためのライブラリは遅い」がここでも当てはまるか気になります。というのも前回の記事の最後の方に「GPIO書き込みがなんか遅いかも」的なことを書いたのですがやはりどうも引っかかる。ということでもう少し調べました。以下が HAL の GPIO ピンに書きこむ API です。
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{
/* Check the parameters */
assert_param(IS_GPIO_PIN(GPIO_Pin));
assert_param(IS_GPIO_PIN_ACTION(PinState));
if (PinState != GPIO_PIN_RESET)
{
GPIOx->BSRR = (uint32_t)GPIO_Pin;
}
else
{
GPIOx->BRR = (uint32_t)GPIO_Pin;
}
}
C誤使用を検知するためのパラメータチェックをした後 GPIO セットレジスタまたはリセットレジスタに値を書き込んでいます。そして以下のように実行には 1μS 強かかります。システムクロックが 48MHz に設定してあるのでこの関数の実行に約 50 サイクル。いややっぱりいくらなんでもかかりすぎでしょう。

誤使用を検知するのも「汎用」なら大事ですし Arduino の digital write に慣れている人にはこの API のインタフェースは使い勝手が良いかもしれません。でも、やりたいことはレジスタに値を書くことだけでしょう?無駄が多すぎない?ということで、必要な部分だけを以下のように切り出して同じことをさせてみると…
CAN_EN_GPIO_Port->BSRR = (uint32_t) CAN_EN_Pin;
CAN_EN_GPIO_Port->BRR = (uint32_t) CAN_EN_Pin;
CAN_EN_GPIO_Port->BSRR = (uint32_t) CAN_EN_Pin;
CAN_EN_GPIO_Port->BRR = (uint32_t) CAN_EN_Pin;
CAN_EN_GPIO_Port->BSRR = (uint32_t) CAN_EN_Pin;
CAN_EN_GPIO_Port->BRR = (uint32_t) CAN_EN_Pin;
C実行は 150ns 弱、10倍近く速くなりました。ARM の条件分岐実行って遅いのかな?あるいは条件分岐全般これぐらい時間がかかるのかもしれませんが。気になるので他のプロセッサでも後日実験するかもしれません。

HAL は良くないといいたいわけではなく、上のコードはまだ HAL の抽象化の恩恵にはあずかっています。BSRR と BRR はピンごとに違うわけで正しいレジスタとビットを探してくる手間は HAL 任せなわけです。今構築中の CAN インタフェースライブラリも「汎用」なので HAL の恩恵は大きいです。それでもひと手間加えるだけでこれだけ実行が速くなるわけです。こうなると、SPI の制御だって HAL 任せでいいの?という疑問が湧いてきます。
ということで、HAL の SPI 用 API 関数のうち一番使うのが簡単な HAL_SPI_TransmitReceive() を使った場合、1バイト送受信では以下のように開始から終了まで約 23μS かかります。

これはまあいいとして、バイト数が多くなると送信に隙間が空くことがわかっています。18バイト送受信したらどうなるでしょうか?

95μS … は 0.1ms … ちょーっとよろしくない気がしてきます。ということで、オリジナルの API 関数をコピーしてきて自分の用途ではいらないコードを削ってみました。あとアルゴリズムが遅そうだったので直してみました。以下が1バイト送受信の場合

約 7μS、かなり速くなります。それでは、18バイト送受信の場合は?

こちらもだいぶん速くなりました。STM32C0F6P6 の SPI には2バイトを一度に送る機能がありそれを使って2バイトずつデータを送っているのですがペリフェラルとのやり取りにどうしても時間がかかるらしく、SPI 送信 FIFO にデータを入れるたびに 3μS ぐらいの遅延が出るようです。この隙間がなければもっと速くなりそうなのですが、この送受信方式ではハードウェアの制約でこれ以上は速くならないかもしれません。
この隙間は送受信のバイト数が多くなるほど痛くなってくるわけですが、STMicro も手をこまねいているわけではありません。STM32 は DMA を使って SPI の送受信をする機能も用意しています。そちらも試してみました。
下のスクリーンショットは、HAL の DMA 送受信 API を使った場合です。1バイト送受信の場合

47 μS、むしろ遅いのか。18バイト送受信の場合はどうでしょうか?

70μS、バイト間の隙間はなくなりましたが、速度の向上は少しだけです。DMA は始まれば効率よく送信できますがセットアップにはより時間がかかるようです。もっとたくさんのバイト数を送らないと利点が生かせないかもしれません。でも CAN 用途ではこの程度の長さなのですよね。どうも座りが悪いです。帯に短したすきに長し。
HAL DMA 送受信でも自分の使い方ではいらないコードを消した実装を試してみました。1バイト送受信の場合

40μS、劇的な速度向上はありません。ハードウェアの制約っぽい感じがします。18バイト送受信の場合

66μS、うーんこの、飛行機に乗るために成田空港に行く感じ。
SPI アクセス関数をどうするか決めるのも悩ましいですが、この実験の過程で USART も鬼遅いのが垣間見えています。もしかして、STM32 はプロセッサでふんだんに計算して周辺デバイスにはたまにアクセスする程度の用途を想定している?そして応答時間よりスループット重視?うーん、Rings じゃん。でも Analog3 では制御用途だからはまりが悪いかもしれません。
今まで PSoC はどうなのか詳しく見ていなかったのですが、断然気になりだしました。そもそも根本的に 32ビットコントローラは制御用途に向いているのでしょうか?というわけで、STM32 の検討はいったん中断、PSoC で同じような実験をして比較してみたいと思います。PSoC ちょっと高いのだけどもなあ。AVR も高い。悩ましいです。