HAL設計とハードウェア依存の分離
使っていたマイクロコントローラ(以下、マイコン/MCU)が供給停止になり、代替品への載せ替えを迫られるケースがあります。この事態に直面した際に、修正がドライバ層だけで済むのか、上位まで書き直しになるのかは、設計により大きく左右されます。
ハードウェアに依存する部分を一つの層へ閉じ込める設計、いわゆるHAL(Hardware Abstraction Layer:ハードウェア抽象化層)は、影響範囲を局所化し移植性を高めるうえで有効です。
組み込みでHAL(ハードウェア抽象化層)が必要な理由
ハードウェア依存部を一つの層に閉じ込めることで、マイコンの変更や周辺機能(ペリフェラル)の差し替えによる上位層への影響を抑えられます。保守が楽になるだけではなく、工数を抑えた高品質なテストも実施できます。
マイコン変更・EOLに備える
マイコンのEOL(End of Life:生産終了・保守終了など、ライフサイクル終端)は、考慮すべきリスクの一つです。設計する際には、HALを活用してマイコン(部品)が変わることによる影響を最小限に抑える工夫が求められます。
米国国立標準技術研究所(NIST)の報告書「NIST IR 8501 Semiconductors and Microelectronics Standards」(2023年)は、マイクロエレクトロニクス部品の進化の速さとベンダー市場の変動から、投入時点で部品がすでに陳腐化しているリスクを指摘しました。同報告書はさらに、更新を前提に設計しておくことが陳腐化リスクを下げるとも述べています。HAL境界を持たない設計では、マイコンの変更がそのまま上位の書き換えへ波及することに注意が必要です。
上位アプリケーションのテストを実機に縛られず進めるため
HALを用いて境界を設けるもう一つの効果は、テストの自由度です。HALよりも下層の部分をモックで差し替えれば、上位アプリケーションは実機に縛られずパソコン上で動作します。実機から切り離したテストができるため、継続的インテグレーションでの自動テストにも組み込みやすくなります。
HALの階層とスコープの決め方
HALと一口に言っても、粒度は一つではありません。ペリフェラル単位の低レベルな抽象と機能単位の高レベルな抽象では、対象とするスコープが異なります。抽象を設ける箇所は、保守のしやすさと速度のトレードオフを踏まえて決める必要があります。
ペリフェラル抽象と機能抽象を分けて考える
この記事では、UART、SPI、I2Cといった通信路そのものを扱う層をペリフェラル抽象(低レベル)、アプリケーションから見た機能を扱う層を機能抽象(高レベル)と呼びます。標準化されたドライバAPI仕様でも、この粒度で統一インタフェースが定められています。機能抽象の例としては、センサとの通信やログ出力があげられます。
車載分野ではAUTOSAR(AUTomotive Open System ARchitecture:車載ソフトウェアの業界標準仕様)のように、マイコン直接アクセスを担う層(MCAL:Microcontroller Abstraction Layer)と、より上位の抽象層を分ける考え方が一般的です。二つが同じ層に混在すると、どちらの理由で変更が起きたのか追いにくくなります。
抽象を厚くしすぎるとオーバーヘッドを招く
抽象はコストを伴うことに留意が必要です。ベンダー提供HALでは、パラメータチェック(入力値検査)を行う設計が一般的です(有効/無効を設定できる場合があります)。入力値の検査は堅牢性を高めますが、処理時間は関数を呼び出すたびに加算されます。
ベンダー提供のマニュアルでは、低レベル側について、状態やカウンタを保持する追加のメモリを必要とせず、すべての操作をレジスタの内容変更で実行すると説明しています。
割り込み応答の要求が厳しい処理や、高い頻度で実行される処理まで抽象化すると、オーバーヘッドがリアルタイム性に影響するおそれがあります。抽象は必要な境界だけに絞って設けることが現実的です。
ベンダー提供HALの実例に見る階層モデル
自社でHALを設計する前に、各ベンダーが公開するHALを読んでおくと判断の基準ができます。ここでは標準化されたAPIの位置づけ、高抽象HALと低レベルAPIの2層モデル、HAL設計のアプローチについて解説します。
標準化されたコアアクセスAPIとペリフェラルドライバAPIの位置づけ
特定のプロセッサアーキテクチャ向けに、業界で標準化されたコアアクセスAPIは、プロセッサコアとデバイスペリフェラルへの入口を用意します。割り込みコントローラやシステムタイマ、メモリ保護ユニットへの標準化されたアクセスを定義する層です。これと対になる、ベンダー横断で定義されたペリフェラルドライバAPIは、通信ペリフェラルなどの統一インタフェースを、ミドルウェアと上位アプリケーションが使える形で定めています。コアに近い部分とペリフェラルとで、標準化の単位が異なる点が位置づけの要点です。いずれも対応デバイスや実装提供の有無に依存するため、すべてのマイコンでそのまま使えるわけではありません。
高抽象HALと低レベルAPIの2層モデル
あるベンダーは、同じペリフェラルに対して二つのAPI群を用意しました。高抽象HALは移植性を最大化する抽象化層と位置づけられ、すべてのペリフェラルに提供されます。低レベルAPIは、ハードウェアにより近い軽量で熟練者向けの層として説明され、一部のペリフェラルにだけ用意されました。マニュアルは両者が相互に補完的であり、高抽象側は機能指向のAPIでマイコンとペリフェラルの複雑さを隠すと述べています。この2層は、先に述べたペリフェラル抽象(低レベル)と機能抽象(高レベル)に対応します。この2層モデルは自社HALの設計でも参考になります。
各社SDKに共通するHAL設計のアプローチ
HAL設計のアプローチには、多くのベンダーに共通する考え方や手法があります。あるベンダーの開発キットはペリフェラルドライバ群を提供し、標準化されたドライバAPIに沿った実装例を併載しました。別のベンダーのソフトウェアパッケージは、統一的で直感的なAPIを文書とともに提供すると説明しています。多くのSDKに共通してみられるのは、ハードウェア固有の部分を分け、その上に共通APIを載せる構造です。各ベンダーの切り出し方を見比べることで、設計者は自社HALで境界を置くべき箇所を判断しやすくなります。
自社HAL設計のポイント
ベンダー提供のHALをそのまま使うか自社で開発したHALを用いるかの判断は、製品の要件によります。自社でHALを設計する場合は、インタフェースの絞り方と異常時の扱いや処理方法の統一が重要です。
インタフェースはユースケースにあわせて絞る
汎用性を求めて100個のAPIを並べるより、実際に使う10個に絞って定義するほうが保守は楽になります。使用しないAPIも、テストや文書の作成、あるいは後方互換性の維持のために残さなければならない可能性があるからです。
標準化されたドライバAPI仕様でも、想定する用途にあわせて必要なインタフェースだけが定義されています。ユースケースを先に書き出し、必要なAPIを選定して定義することがおすすめです。
エラー処理・タイムアウト・非同期完了通知を統一する
ベンダー提供のHALは、エラーコードの体系も完了通知の作法もそろっていないことがあります。自社HALでルールを統一することで、上位のロジックを簡潔にできます。
標準化されたインタフェース仕様でも、戻り値とエラー番号の扱いが統一的に規定されています。自社HALでも、エラーコードの型、タイムアウトの指定方法、非同期完了通知の扱いを最初に決めておきましょう。この取り組みにより、あとからドライバを追加した場合でも同じ規則のまま実装できます。
マイコン変更(EOL対応)の乗り換え手順
境界が正しく設定できていれば、マイコンを乗り換える場合でも、影響を境界より下位の層に抑えられます。上位アプリケーションにはほとんど手を入れずに済むケースも多くあります。手順を、差し替える範囲と閉じ込める範囲に分けて見ていきます。
HAL境界の実装だけを差し替える
新しいマイコンで書き換える主な対象は、HAL境界よりも下の実装部分です。新旧とも標準化されたコアアクセスAPIに対応するマイコンであれば、多くの場合、割り込みコントローラやシステムタイマの扱いを変えずに移せます。設計が適切で、上位層が抽象APIにだけ依存する場合は、影響範囲を最小限に抑えられることが多くなります。逆にここで上位層の修正が発生した場合は、境界を越えたハードウェア依存が残っていないか確認する必要があります。
レジスタ差異はドライバ層に閉じ込める
ペリフェラル割り当てやレジスタ定義の差異は、マイコンごとに生じることがあります。この差異をドライバ層の内側へ閉じ込め、HAL境界からは見えないようにすることが要点です。
レジスタを操作する責務が集約されている分、差異を吸収する層は明確です。AUTOSARの層構造でも、マイコン内部に触れる層(MCAL)は下層に固定されています。


