チカラの技術

電子工作やプログラミング

STM32CubeのHALをCoIDEに移植してみました


お久しぶりです。ヌル夫です。
元気です!


○HALですよ
イメージ 1

STM32Cubeという新しい開発環境のファームウェアセットが発表されました。
今はSTM32F4のみ対応ですが、これから他のシリーズも対応して行くとの事です。
公式ページからSTM32F4対応のファームウェアを入手しようとするとSTM32Cubeのみリンクされている状態でした。
つまりSTマイクロさんとしては「これからはこちらを使ってね。」という事なのでしょう。

ファームウェアが変わった事で最も影響を受けるのは関数ライブラリです。
旧「Standard Peripheral Driver」から「HAL(hard ware abstraction layer)」へ変更されていますがこの二つは互換性が全くありません。
旧ライブラリで書かれたソースを移植する際、ペリフェラル関数は全て書き直しになると言っても過言ではありません。

つまりHALは今後のSTM32ユーザーの強制必須科目という事ですね。(苦笑)
下記に移植したソースコードを紹介した後、HALの特徴と旧ライブラリとの比較をしたいと思います。



○HALを移植しました
内容は従来と同じですがソースコードを見て頂けば感じは掴めると思います。
フレームワークの「main.c」にLチカ(TIM4のPWM+割り込みによるGPIO制御)のサンプルコードがあります。

CoIDE開発環境
(「4. CoIDEプロジェクトの作成」の中に移植したフレームワークがあります)



○HALの特徴 ー 抽象化

HALは新しいCMSIS規格を意識してペリフェラルの抽象化と移植性の向上を狙ったライブラリです。
抽象化の為、オブジェクト指向を意識して設計されている事が伺えます。

ソースコードを見た方が分かり易いですね。
以下はペリフェラル(GPIOのDポート)にクロックを供給する関数です。

旧STDライブラリ 「RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);」
HALライブラリ  「__GPIOD_CLK_ENABLE();」

上記は同じ処理ですがHALはペリフェラルを抽象化しているのが分かります。
これは良いですね。私はいつもデータシートと首っ引きでこの設定をしていましたが、それでも良く間違えていました。このように抽象化されると非常に助かります。


○HALのコールバック処理

反対に抽象化で分かりにくくなった部分もあります。
旧STDライブラリではIRQハンドラに直接割り込み時の処理を書いていました。
しかし、HALの作法では若干複雑になります。(作法を気にしなければ従来通り書く事も出来ます)
まず、割り込み時の処理は各割り込みに対応したCallback関数に記述します。
(例: 「HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)」 )
ここでHandle型の引数を取りますが、これはペリフェラルの設定時に使ったHandle変数を使います。
このHandle変数は割り込み関数内で値が利用される為、グローバル変数となりますので気をつけて下さい。

このCallback関数は「HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim))」の中で呼ばれます。
割り込みハンドラにはこの関数を登録します。

TIM4の更新イベントを例にまとめると
-----------------------------------------------------
割り込み発生
------------------------------------------------------

↓割り込みハンドラ呼び出し

---------------------------------------------------------
TIM4_IRQHandler(){
    HAL_TIM_IRQHandler(&Tim4Handle);
}
---------------------------------------------------------

↓HAL_TIM_IRQHandlerで割り込みフラグの処理などを行った後、
↓HAL_TIM_PeriodElapsedCallback(&Tim4Handle)を呼び出し

---------------------------------------------------------
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
     //ここにタイマ更新時の処理をユーザーが書く
}
---------------------------------------------------------

なぜこのように多段でコールバック処理をしているかというと、コールバックの呼び出し元を抽象化する為です。
例えばAD変換の場合ならAD変換完了割り込みとDMA転送完了通知の二つのイベントからコールバックが呼ばれています。
AD変換完了の通知処理とコールバック関数が別なので、ユーザはコールバック関数にAD変換されたデータを処理するプログラムを書く事に集中できます。
また、ユーザがポーリングによってAD変換完了を通知する関数を作った場合もその中でコールバック関数を呼べば共通の処理が行われる訳です。
こうして呼び出し元の通知関数で行われるハードウェア処理を抽象化する事で、関数同士の結合を弱め、移植性、保守性、拡張性を向上させています。

ただし関数分割によるオーバーヘッドとグローバル変数を使用するという副作用もあるので、速度やサイズが重要な場合はコールバック関数を使わないほうが良い場合もあると思います。


○HALの特徴まとめ

1.旧ライブラリとの互換性は全く無い

2.ハードウェアの抽象化が行われていて関数が使いやすくなっている

3.コールバック関数による通知関数の抽象化

4.ペリフェラルの初期化コードは「stm32f4xx_hal_msp.c」ファイルに書くのが作法
(ただし自作フレームワークではmain.cだけ変更すれば使えるようにあえて無視している)
(追記:msp.cのテンプレート(void HAL_PPP_MspInit(PPP_HandleTypeDef *hppp))を使って初期化関数を作ってみましたが、グローバル変数と参照渡しの関係で非常にややこしい書き方になってしまうので、しばらくこのテンプレートは無視する事にしました。)

5.抽象化によってコードサイズは増える。3kB(ROM)のプログラムをHALに移植すると8kBとなった。STM32F4においてはペリフェラルコードのサイズは一部に留まるので大きな問題では無いように思うが、今後STM32F0等ROMが少ないマイコンにSTM32Cubeが移植されると問題が表面化しそうです。

HALの理解はSTM32Cubeに含まれているサンプルコードを読むのが一番だと思います。

それでは皆さん、(大変ですが)頑張ってHALに乗り換えましょう!