エンジニアが開発する組込みソフトウェアとは

エンジニアリングレベルで、組込みソフトウェアと汎用ソフトウェアを区別するものは何か

初めて組込み製品を出荷するチームは、デスクトップアプリケーションと同じようにソフトウェアを扱いがちです。機能を追加し、ベンチでテストし、動作したら出荷します。その後、デバイスは生産環境で3週間稼働し、ロックアップします。クラッシュログもスタックトレースもありません。ただシステムがフリーズしただけで、電源の再投入が必要です。このパターンが組込み製品開発で繰り返されるのは、組込みソフトウェアを支配するルールがアプリケーションソフトウェアを支配するルールとは異なり、その違いは持続的な実世界の条件下でしか現れないからです。

リソース制約を第一級設計制約として

組込みソフトウェアは、シリコンによって設定されたハードリミット(固定RAM、限定されたフラッシュ、仮想メモリなし、安全性クリティカルなコンテキストでは動的ヒープなし)の下で実行されます。これらは設計完了後に達成すべきパフォーマンス目標ではなく、最初からあらゆる設計上の選択を形作る境界線です。

スタック深度解析はその一例です。デスクトップシステムでは、スタックオーバーフローは回復可能な例外です。合計8KBのRAMしか持たないマイクロコントローラでは、予想よりも深いコールチェーンが隣接するメモリを静かに破損させ、スタックの問題とは全く似ていない障害を引き起こします。エンジニアはタスクごとにスタック容量を予算計上し、静的解析ツールで最悪ケースのコール深度を測定し、違反があれば警告ではなくブロックする問題として扱います。

静的メモリ割り当ては、同様のロジックに従います。コンパイル時にすべてのバッファを割り当てるということは、ファームウェアがハードウェアで実行される前に、リンカが合計が利用可能なRAMに収まることを検証できることを意味します。その決定性は、それが犠牲にする柔軟性に値します。

目標は後で最適化することではなく、設計時にリソース制限を可視化することです。リソース制限は、修正するのにまだ安価な時期です。

ハードウェア結合実行モデル

組み込みソフトウェアは、コンパイルされたハードウェアのコンテキストでのみ正しいです。アプリケーションは、特定のメモリアドレスにあるUARTステータスレジスタを読み取ります。特定のデータシートで定義されたビットフィールドを使用してタイマーペリフェラルを構成します。特定のベクターオフセットに登録されたハンドラーに割り込みをルーティングします。MCUを変更すると、それらのどれも有効であり続けることは保証されません。

この緊密な結合は、効率を可能にします。ハードウェアレジスタに直接アクセスするコードは、複数の抽象化レイヤーを経由するコードよりも高速に実行され、メモリ使用量も少なくなります。トレードオフは、再設計コストです。ハードウェアリビジョンがペリフェラルのベースアドレスを変更したり、新しいMCUが異なる割り込みコントローラーを使用したりすると、古いレイアウトを想定していたソフトウェアは、しばしば明白ではない方法で破損します。クリーンなHAL境界に投資するチームは、初期費用としてわずかなオーバーヘッドを支払い、ハードウェアリビジョン中のより大きなコストを回避します。

決定論とリアルタイム動作を正しさの基準として

Webアプリケーションにおける50ミリ秒のレイテンスパイクは、UXの問題です。モーターコントローラーでは、同じスパイクがハードウェアを損傷する可能性があります。組み込みソフトウェアの正しさには、時間的な正しさも含まれます。デッドラインを満たすことは、仕様の一部であり、ストレッチゴールではありません。

3つの実行モデルが設計空間を定義します。ハードリアルタイムシステムは、例外なくすべてのデッドラインを満たす必要があります。デッドラインの遅延は、定義上、システム障害となります。ソフトリアルタイムシステムは、時折の遅延を許容しますが、正常に劣化します。ベストエフォートシステムは、スループットをレイテンシよりも優先し、可変タイミングを受け入れます。早期に間違ったモデルを選択すると、後で高額な手戻りが発生します。ベストエフォートとして設計されたシステムが、後でハードリアルタイム保証を必要とする場合、通常はチューニングパスではなく、完全なアーキテクチャ変更が必要になります。これらのスケジューリング決定が開発ワークフローにどのように関連するかについては、 組み込みソフトウェア開発ライフサイクルとツールチェーン。

実行中のシステムにおける組み込みソフトウェアの構造

レイヤードソフトウェアモデル:HAL、ミドルウェア、アプリケーション

適切に構造化された組み込みソフトウェアは、3つのレイヤーに懸念事項を分離し、各レイヤーは定義されたジョブと隣接レイヤーへの定義されたインターフェイスを持ちます。

  • HAL(ハードウェア抽象化レイヤー): すべてのペリフェラルレジスタアクセスを所有します。このレイヤーより上のものは、ハードウェアアドレスに直接触れることはありません。
  • ミドルウェア: プロトコルスタック、RTOSサービス、ファイルシステム。ハードウェア非依存、製品非依存。
  • アプリケーション層: ミドルウェアおよびHAL APIを呼び出すことで、製品の動作を実装します。

より深い抽象化は、ハードウェアが変更された際のポーティングコストを削減します。ただし、各レイヤー境界は関数呼び出しのオーバーヘッドとスタック深度を追加します。RAM 16KB、48MHzで動作するCortex-M0では、そのオーバーヘッドは測定可能です。リソースが制約されたターゲットで作業するエンジニアは、そのヘッドルームを回復するために、HALとミドルウェアを単一のレイヤーにまとめることがあります。正しい答えは、ハードウェアが変更される頻度と、リソース予算が実際にどれだけタイトかによって異なります。

ベアメタル対RTOSベース実行アーキテクチャ

2つのランタイムモデルがほとんどの組み込みシステムをカバーします。ベアメタルファームウェアは、スーパーループ(状態をポーリングしてハンドラーを呼び出すwhile(1))を実行するか、完全に割り込みに依存します。RTOSベースシステムは、プリエンプティブスケジューラの下で複数のタスクを実行し、優先度レベル、タスク間キュー、セマフォが並行作業を管理します。

要因 ベアメタル RTOSベース
オーバーヘッド 最小 スケジューラ + コンテキストスイッチコスト
同時実行性 単一実行パス 複数の優先度付きタスク
デッドラインの多様性 均一なデッドラインの場合に機能する デッドラインが大きく異なる場合に必要
デバッグの複雑さ 低い 高い(競合状態、優先度逆転)

選択は、好みにではなく、タスク数とデッドラインの多様性によって決まります。単一の通信チャネルを持つシングルセンサーデータロガーは、ベアメタルでクリーンに実行されます。ディスプレイ、CANバス、USBスタック、およびセーフティウォッチドッグを同時に管理するデバイスは、それらの懸念を分離してスケジューリング可能にするためにRTOSを必要とします。RTOSターゲットでの具体的なプログラミングパターンについては、 RTOSターゲットのための組み込みシステムプログラミングパターンを参照してください。

ブートローダーは追加機能ではなく、構造的コンポーネントとして

SWD programming cable connected to PCB debug header on electronics assembly bench

ブートローダーはアプリケーションの前に実行されます。コアハードウェアを初期化し、アプリケーションイメージを検証し、その検証が成功した場合にのみ制御を転送します。プロジェクトの後半でブートローダーを追加するエンジニア、あるいは「単純な」製品のためにそれを完全にスキップするエンジニアは、フィールドアップデートでデバイスが破損した場合にリカバリパスがなく、そのギャップを発見することがよくあります。

ブートローダーの境界は、アップデートサーフェスを定義します。その境界より上にあるものはすべて、ブートローダー自体に触れることなく、OTA(Over-The-Air)またはプログラミングインターフェイス経由でアップデートできます。それより下にあるものは、物理的な接続と完全なリフラッシュが必要です。その境界を間違えると、重要な初期化コードをアップデートリスクに過剰にさらすか、アップデート可能な必要があるアプリケーションコードを過小評価することになります。詳細なブートローダー設計パターンについては、 組み込みソフトウェア開発ガイドを参照してください。

実際のハードウェアで動作する組み込みソフトウェアの作成:主要な実装上の決定

main() より前のスタートアップコードとシステム初期化

アプリケーションは~で開始しません main()その関数が呼び出される前に、スタートアップコードが実行されます。スタックポインタの設定、初期化済み変数のフラッシュからRAMへのコピー、BSSセグメントのゼロクリア、システムクロックの設定を行います。多くのMCUでは、ウォッチドッグはハードウェアデフォルトで有効になっており、クロック設定が完了する前にサービスを実行するか無効にしないと、システムはリセットされてしまいます。 main() 到達することはありません。

ベンダー提供のスタートアップファイルは、標準構成ではこれらのほとんどを正しく処理します。プロジェクトでデフォルト以外のメモリレイアウト、デフォルトのタイムアウト時間よりも安定に時間がかかる外部オシレータ、またはBSSセクションを誤って配置するカスタムリンカースクリプトを使用すると、問題が発生します。スタートアップコードをブラックボックスとして扱うエンジニアは、初期化の失敗によって原因不明のハードフォルトが発生した場合に、診断に時間を浪費します。スタートアップファイルを一度読み、その内容を理解し、アプリケーションコードを追加する前にクロックツリーを確認することで、後でのデバッグ時間を大幅に節約できます。

割り込みサービスルーチン(ISR)の設計と共有データハザード

Firmware engineer reading oscilloscope ISR timing waveform on embedded development bench

ISRは、組み込みソフトウェアがハードウェアイベントにリアルタイムで応答する方法です。その設計は、システムのレイテンシとデータ整合性に直接影響します。ISRは短く保ちます。ブロッキングしないようにします。ISR内で費やされたサイクルはすべて、メイン実行コンテキストが実行できないサイクルです。

ISRとメインループ間の共有データは、慎重な取り扱いが必要です。ISR内で変更され、メインループで読み取られる変数は、 volatile コンパイラがレジスタに古い値をキャッシュするのを防ぐために宣言する必要があります。MCUのネイティブワードサイズよりも大きい変数では、メインループでの単一の読み取りはアトミックではない可能性があります。ISRは、2つのロード命令の間で上位バイトを更新できます。読み取りの周囲の割り込みを無効にすることが、回避策ではなく正しい修正方法です。

ISRから遅延処理に作業を移動する(ISRでフラグを設定し、メインループまたは低優先度タスクで作業を処理する)ことで、応答性が向上します。ISRと処理タスクの間のリングバッファは、UART受信処理の一般的なパターンです。トレードオフは、複雑さの追加です。バッファにはオーバーフロー検出が必要であり、処理タスクにはデータが待機していることを知る方法が必要です。

制約のあるターゲット向けのメモリ管理戦略

動的割り当て( malloc と free )は、ほとんどの組み込みターゲットで利用可能ですが、本番の組み込みソフトウェアでは、これらを完全に回避することがよくあります。数ヶ月間実行されているシステムでのヒープ断片化は、稼働数週間後にのみ現れる割り当て失敗を引き起こす可能性があり、これはフィールドでの再現が困難で、診断がさらに困難な種類の障害です。また、割り当て時間は非決定的であるため、ハードリアルタイム要件と競合します。

本番システムでは、動的割り当てを置き換える3つの戦略があります。

  • 静的割り当て: すべてのバッファをコンパイル時に宣言します。リンカーが適合を確認します。実行時のオーバーヘッドはゼロです。
  • メモリプール: 事前に割り当てられた領域から割り当てられる固定サイズのブロック。割り当て時間は一定です。プール内での断片化は不可能です。
  • スタックベースの割り当て: タスクまたは関数のスタック上のローカル変数。戻り時に自動的に解放されます。短命でサイズが限定されたデータに適しています。

静的割り当ては、固定された予測可能なデータフローを持つシステムに適しています。メモリプールは、ヒープリスクなしでメッセージバッファの割り当てと解放など、動的な動作を必要とするシステムに適しています。スタック割り当ては一時的な計算に適しています。ほとんどの本番システムでは、これら3つすべてが、データの寿命とサイズの予測可能性によって割り当てられて使用されます。これらの戦略が特にディスプレイ駆動型およびHMIシステムにどのように適用されるかについては、 リソース制約のあるHMIシステムのプログラミングモデルを参照してください。

組み込みソフトウェア: エンジニアリングの疑問に直接答える

組み込みソフトウェアとファームウェアは同じものですか?
ファームウェアは組み込みソフトウェアの部分集合です。ファームウェアは、非揮発性メモリに格納され、ハードウェアを最も低いレベルで初期化および制御するソフトウェアを指します。組み込みソフトウェアは、ファームウェア、ミドルウェア、および制約のあるハードウェア上で実行されるアプリケーションレイヤーを含む、より広範なカテゴリです。プロジェクトのスコープを決定する際には、この区別が重要になります。ファームウェアエンジニアと組み込みアプリケーションエンジニアは、同じ製品でも異なるタスクを担当する可能性があります。

組み込みソフトウェアはオペレーティングシステムなしで実行できますか?
はい — 上記のシステムアーキテクチャセクションのベアメタル対RTOSの議論を参照してください。安全性クリティカルなコントローラーや単純なセンサーノードを含む多くの本番組み込みシステムは、RTOSのオーバーヘッドがシステムの複雑さによって正当化されないため、設計上ベアメタルで実行されます。

組み込みソフトウェアをアプリケーションソフトウェアよりもデバッグしにくくしている要因は何ですか?
3つの要因が難易度を増大させます。ターゲットでの表示出力が限定的またはまったくないこと。デバッグプリントステートメントを挿入すると変化するリアルタイムの動作。シミュレータでは再現しないハードウェア依存の障害。JTAG/SWDデバッグインターフェイスとロジックアナライザーが主なツールです — 詳細については、 組み込みターゲット向けのデバッグおよびトレースツール ガイドで説明しています。

組み込みソフトウェアにはカスタムコンパイラやツールチェーンが必要ですか?
カスタムコンパイラではなく、常にターゲット固有のコンパイラです。組み込みソフトウェアは、ホストマシン上でARM Cortex-M、RISC-Vなどの異なるターゲットアーキテクチャ向けにクロスコンパイルされます。リンカースクリプトは、ターゲットのメモリマップと正確に一致する必要があります。正しいリンカースクリプトを持たない汎用コンパイラは、起動コードと割り込みベクターテーブルが間違ったアドレスに配置されるため、起動しないバイナリを生成します。

本番の組み込みソフトウェア開発では、起動コードからメモリ戦略、フィールドアップデート設計に至るまで、あらゆるレイヤーでこのようなプロセス規律が要求されます。いずれかのレイヤーのギャップは、修正コストが最も高くなる長時間の実行後にのみ表面化する傾向があります。STONE HMIは、産業用HMIシステム向けの本番グレードのファームウェアを開発しています。組み込み開発パートナーを評価しているプロジェクトチームにとって、ファームウェアレベルでのプロセス規律は、デリバリーリスクの直接的な指標となります。ブートローダー設計、ISRの安全性、メモリ戦略などを決定事項ではなくデフォルトとして扱うパートナーは、フィールドで機能するシステムを生成する可能性が低くなります。