組み込みシステムの設計を容易なものにするためのハードウェアに依存しない手法の活用:基本的事項

組み込みシステムの設計を容易なものにするためのハードウェアに依存しない手法の活用:基本的事項

著者の連絡先情報

Giacomo Paterniani

Giacomo Paterniani

要約

本稿では、組み込みシステム設計のプロトタイプ・フェーズを加速する手法を解説します。ここでは、ハードウェアに依存しないドライバをセンサーと組み合わせ、組み込みシステム全体の部品選択を大幅に容易なものにする方法を示します。本稿では、部品、組み込みシステムの代表的なソフトウェア構造、およびドライバの実装について説明します。この次の記事「組み込みシステムの設計を容易なものにするためのハードウェアに依存しない手法の活用:ドライバの実装」では実行例を詳細に説明します。

はじめに

ハードウェアに依存しないドライバを用いることで、ハードウェアへの依存性を持たずにセンサーを管理するタイプのマイクロコントローラやプロセッサを選択することが、設計上可能になります。この手法の利点は、センサーの集積化が簡略化されるだけでなく、サプライヤから供給された基本的なソフトウェア層の上にいくつかのソフトウェア層を追加するオプションが与えられることです。本稿では、例として慣性計測ユニット(IMU)センサーを取り上げますが、この手法は他のセンサーや部品にも拡張可能です。ドライバは、Cプログラミング言語で設定され、一般的なマイクロコントローラでテストされます。

部品の選択

IMUセンサーは、多くの場合、モーション検出に用いられ、また、加速度や回転速度を通じて運動の強度を測定するために用いられます。この実験では、ADIS16500 IMUセンサー(図1)が用いられました。このセンサーを用いることで、正確な多軸慣性センシングを産業用システムに統合する場合に、ディスクリート設計に伴う複雑さや投資額と比較して、簡単でコスト効率の高い方法が可能になるためです。

図1 ADIS16500評価用ボード

図1 ADIS16500評価用ボード

主なアプリケーションは次のとおりです。

  • ナビゲーション、安定化、計測器
  • 無人運転車、自動運転車
  • ファクトリ/産業オートメーション、ロボティクス
  • ファクトリ/産業オートメーション、ロボティクス
  • 仮想/拡張現実
  • 動くモノのインターネット

ADIS16500は、高精度で小型のマイクロエレクトロメカニカル・システム(MEMS)IMUで、3軸ジャイロ・センサー、3軸加速度センサー、温度センサーが備わっています。図2を参照してください。感度、バイアス、アライメント、線形加速度(ジャイロ・センサー・バイアス)、振動ポイント(加速度センサーの位置)は、工場でキャリブレーションされています。つまり、センサーによる測定は、広範な条件にわたり正確です。

図2 ADIS16500のブロック図

図2 ADIS16500のブロック図

このインターフェースにより、マイクロコントローラはユーザ制御レジスタとの間で読書きができる他、出力データ・レジスタを読み出して、加速度センサー、ジャイロ・センサー、あるいは温度センサーのデータを取得できます。そのため、インターフェースを管理するのに必要な全てのソフトウェアおよびファームウェアが開発されています。図2には、データ・レディ(DR)ピンが示されています。このピンは、センサーから新しいデータを読み出せるようになった場合にそれを示すデジタル信号です。DRピンは汎用入出力(GPIO)ポートを介する入力と見なせるので、マイクロコントローラで容易に管理できます。

ハードウェアの観点からは、IMUセンサーとマイクロコントローラは、nCS、SCLK、DIN、DOUTの各ピンで構成される4線式インターフェースのSPIインターフェースを用いて接続されます。DRピンはマイクロコントローラのいずれかのGPIOに接続する必要があります。IMUセンサーには電圧源も必要で、その電圧は3V~3.6Vなので、3.3Vで十分です。

組み込みシステムの代表的なソフトウェア構造の理解

組み込みシステムの一般的なソフトウェア構造およびファームウェア構造を理解することは、センサー・ドライバとインターフェース接続する上で必須です。これは設計者にとって、任意のプロジェクトに統合できるだけの柔軟性と容易さを備えるソフトウェア・モジュールを構築する際の助けとなります。更に、設計の際に既存の機能に依拠してより高いレベルの機能を追加できるよう、ドライバをモジュラー方式で実装する必要があります。

組み込みシステムのソフトウェア構造を図3に示します。図3では、アプリケーション・コードを書き込むアプリケーション層から階層が始まっています。このアプリケーション層には、メインのファイル、センサーに依拠するアプリケーション・モジュール、およびプロセッサ構成を管理するペリフェラル・ドライバに依拠するモジュールが含まれます。更に、アプリケーション層内には、マイクロコントローラが処理しなくてはならないタスクに関連した、全てのモジュールがあります。これには例えば、割込みやポーリングを伴うタスクやステート・マシンなどを管理するソフトウェアが全て含まれています。この層レベルは、プロジェクトの種類によって異なるため、異なるプロジェクトには異なるコードが実装されています。アプリケーション層内では、システムの全センサーが初期化され、データシートに従って設定されます。センサーのドライバによって提供される全ての公開機能は呼び出し可能です。例えば、データの出力元となるレジスタの読出しや、設定/キャリブレーションを変更するレジスタへの書込みを行う手順などです。

図3 組み込みシステムのSW/FW構造

図3 組み込みシステムのSW/FW構造

アプリケーション層の下にはセンサーのドライバ層があります。これには2種類のインターフェースがあります。このレベルには、アプリケーション層から呼び出し可能な全ての機能が実装されています。更に、ドライバのヘッダ・ファイル(.h)には、機能のプロトタイプが挿入されています。そのため、センサー・ドライバのヘッダ・ファイルを調べると、ドライバのインターフェースとより高いレベルから呼び出し可能な機能とが理解できます。これより低いレベルの層は、センサーを管理するマイクロコントローラに固有でかつこれに依存するペリフェラル・ドライバとインターフェースされます。ペリフェラル・ドライバには、SPI、I2C、UART、USB、CAN、SPORTなどのマイクロコントローラのペリフェラルを管理するモジュール、あるいは、タイマー、メモリ、ADCなどのプロセッサ内のブロックを管理するモジュールが全て含まれています。これらは下位機能と呼ばれることがあります。ハードウェアに緊密に関連しているためです。例えば、様々なマイクロコントローラを考慮して、SPIドライバはそれぞれ異なっています。例としてADIS16500を調べてみましょう。インターフェースはSPIなので、そのドライバはマイクロコントローラのSPIドライバでカバーされています。このことは、異なるセンサーおよび異なるインターフェースについても同じです。例えば、別のセンサーがI2Cインターフェースであれば、センサーの初期化手順では、マイクロコントローラのI2Cドライバで同様にカバーされます。

センサーのドライバ・レベルの下にはペリフェラル・ドライバがあります。これはマイクロコントローラの種類によって様々です。図3では、ペリフェラル・ドライバと下位ドライバとが分離されています。本質的には、ペリフェラル・ドライバは、使用可能な通信プロトコルを通じて読出しと書込みの機能を提供します。下位ドライバは、信号の物理層を管理するため、設計者が使用するハードウェアに強く依存します。通常、ペリフェラル・ドライバ層と下位ドライバ層は、マイクロコントローラを取り付けている評価用ボードに応じて、マイクロコントローラの統合開発環境(IDE)から視覚ツールを通じて生成されます。

ドライバの実装

ハードウェアに依存しない手法を採用することで、同じドライバを様々なアプリケーションで用いることができ、したがって、様々なマイクロコントローラやプロセッサでも使用できます。この手法は、ドライバの実装方法に依存します。ドライバの実装について理解するために、まず、インターフェース、すなわち図4に示すセンサーのヘッダ・ファイル(adis16500.h)を調べてみましょう。

図4 ADIS16500のヘッダ・ファイル(adis16500.h)のマクロ

図4 ADIS16500のヘッダ・ファイル(adis16500.h)のマクロ

ヘッダ・ファイルには、便利なパブリック・マクロが使用されています。これには、レジスタのアドレス、SPIの最大速度、デフォルトの出力データ・レート(ODR)、ビットマスク、および加速度センサー、ジャイロ・センサー、温度センサーの出力感度が含まれています。感度は、データを表現するビット数(16または32)に関連しています。これらのマクロを図4に示します。例として一部のレジスタのアドレスのみが示されています。本稿で用いたコードは、付録に記載されています。

付録の図3は、adis16500.h を含むあらゆるモジュールで使用できる、パブリック変数およびパブリック型宣言を全て示しています。ここで、新しい型はデータをより効率的に管理できるよう定義されています。例を挙げると、ADIS16500_XL_OUT の型は、軸(x、y、z)ごとに1つずつ、3つの浮動小数点型変数を持つ構造として定義されています。また、センサーを様々な方法で設定できるようにする列挙型もあり、設計の際に要求に最も適う設定を選択できる柔軟性がもたらされます。最も興味深い部分は、ドライバをハードウェアに依存しないようにするセクションです。パブリック変数の部分の冒頭(付録の図3)では、3つの重要な型の定義があります。それは、3つの基本関数、すなわち、SPI送信関数、SPI受信関数、2つのSPIアクセス間で適切なストール時間を設けるために必要な遅延関数へのポインタです。これらのコード行は、ポイントされる関数のプロトタイプも示しています。SPI送信関数は、入力として送信される値へのポインタを受け、送信が成功したかどうかを調べるためにチェックできる値を返します。同じことがSPI受信関数にも言えます。これは変数へのポインタを入力として受け、ここで受信時に読み取った値が保管されます。遅延関数は、浮動小数点型の数を、待機する必要のあるマイクロ秒数の設計値を表す入力として受け、値を返すことはありません(void)。このように、設計時にアプリケーション層(例におけるメイン・ファイル)でこれら3つの関数をその固有のプロトタイプで宣言できます。宣言を終えると、これらのプロトタイプは3つの関数をADIS16500_INIT のプライベート構造のフィールドに割り当てることができます。この最後のステップをより深く理解できるよう、付録の図2に例が示されています。

SPIトランスミッタ関数、SPIレシーバ関数、および遅延関数は、メイン・ファイル、つまりアプリケーション・レベルで静的関数として定義されています。これらは、ペリフェラル・ドライバの関数に依存するため、ハードウェアへの依存性はセンサー・ドライバからは外れます。3つの関数は、関数へのポインタであるこの変数のフィールドに割り当てられます。このように、設計者は、センサー・ドライバのコードを変更せずに、センサーとマイクロコントローラを一括りにできます。設計者がマイクロコントローラを変更する場合には、3つの静的関数内の下位レベルの関数を新しいマイクロコントローラ用に該当する関数に置き換えるようメイン・ファイルを調整するだけで済みます。この手法により、設計者はセンサーのドライバ・コードを変更する必要がないため、ドライバはハードウェアに依存しなくなります。spiSelect、spiReceive、spiUnselect、chThdSleepMicroseconds などの下位レベルの関数は、通常、既にマイクロコントローラのIDEから使用できるようになっています。この場合に使用したマイクロコントローラ評価用ボードは、STM32F469NIH6 Cortex®-M4マイクロコントローラが組み込まれたSDP-K1です。実際に用いたIDEは、無償のArm®開発環境である、ChibiOSです。

付録の図4では、アプリケーション・レベルから呼び出し可能な関数のプロトタイプを示しています。これらのプロトタイプは、付録の図2および図3で説明されている他の全てのソフトウェアおよびファームウェアと共に、センサーのドライバのヘッダ・ファイル(adis16500.h)にあります。まず、ADIS16500_INIT構造へのポインタを入力として受け、初期化が成功したかどうかを示すステータス・コードを返す、初期化関数(adis16500_init)があります。初期化関数の実装は、センサーのドライバのソース・ファイル(adis16500.c)で行われます。付録の図5に、adis16500_init 関数のコードを示します。まず、ADIS16500_PRIV という名の型が定義されます。これには少なくともADIS16500_INIT 構造の全フィールドが含まれます。次いで、その型の_adis16500_priv というプライベート変数が宣言されます。初期化関数内では、アプリケーション層から渡されたADIS16500_INIT 構造の全フィールドが、プライベート変数のフィールド_adis16500_priv に割り当てられます。つまり、センサー・ドライバへのその後の呼び出しは、アプリケーション層で渡されたSPI書込み関数、SPI読出し関数、およびプロセッサ遅延関数を用います。これによってセンサー・ドライバがハードウェアに依存しなくなるので、これは重要なポイントです。設計の際にマイクロコントローラを変更する必要がある場合、adis16500_init 関数に渡す関数を変更するだけで済みます。センサー・ドライバのコード自体を変更する必要はありません。初期化関数の最初は、初期化プロセスがまだ完了していないため、_adis16500_priv 変数の初期化されたフィールドは偽に設定されます。リターン前の関数の終了時に、真に設定されます。設計者が別のパブリック関数(付録の図4)を呼び出すたびに、次のチェックが実行されます。すなわち、_adis16500_priv.initialized が真であれば続行できます。偽であれば、ADIS16500_RET_VAL__ERROR というエラーが直ちに返されます。これは、ユーザが最初にセンサー・ドライバの初期化を行わずに関数を呼び出すことを防止するためです。引き続き初期化関数の説明になりますが、以下の手順が実行されます。

  1. ADIS16500_REG_ PROD_ID レジスタを読み出して、先験的に既知となっている製品IDをチェックします。


  2. ADIS16500_REG_MSC_CTRL レジスタの該当のビット・フィールドにアプリケーション層(main.c)から渡された値を書き込んで、データ・レディ(DR)ピンの極性を設定します。


  3. ADIS16500_REG_MSC_CTRL レジスタの該当のビット・フィールドにアプリケーション層(main.c)から渡された値を書き込んで同期モードを設定します。


  4. ADIS16500_REG_DEC_RATE レジスタにアプリケーション層(main.c)から渡された値を書き込んでデシメーション・レートを設定します。

初期化関数は、レジスタ読出し関数およびレジスタ書込み関数(付録の図6)に依存します。それが、_adis16500_priv 変数への割当て後に上記4つのルーチンを行う理由です。そうでないと、レジスタ読出し関数またはレジスタ書込み関数が呼び出されても、これらのレジスタは、どのSPIトランスミッタ関数、SPIレシーバ関数、プロセッサ遅延関数を用いれば良いかがわからなくなってしまいます。

付録の図4を参照すると、初期化関数の後に呼び出し可能な他のパブリック関数があります。実装されているルーチンの機能性を、下位のルーチンを示しながら、以下に説明します。本稿の後半では、他のドライバの実装関数を詳細に説明します。以下の関数は全て、初期化関数の後にのみ呼び出すことができます。そのため、各関数の最初にダブル・チェックが行われ、センサーが初期化されているかどうかを調べます。もし初期化されていなければ、この手順は直ちにエラーを返します。

  • adis16500_rd_reg_16

この関数は、16ビットのレジスタを読み出すために用いられます。その実装方法は、付録の図6に示されています。入力は、読み出すレジスタのアドレスを表すuint8_t 変数のadと、読出し値を割り当てる場所を表すuint16_t 型の変数へのポインタである*p_reg_val です。SPIプロトコルを介してレジスタの読出しを行うには、2回のSPIアクセスが必要です。最初にアドレスを送信し、次に、アドレス指定されたレジスタの値をリード・バックします。この2回のアクセスの間には、ストール時間が必要です。そのため、遅延関数が必要となります。最初のアクセスでは、読出し/書込みビットを送信します。今の場合は1(R = 1、W = 0)です。また、レジスタ・アドレスを8ビット分シフトして8ビットの0を加えます。したがって、次のシーケンスとなります。

R/W | AD6 | AD5 | AD4 | AD3 | AD2 | AD1 | AD0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |

ここで、ADはアドレスを表し、R/Wは読出し/書込みビットを表します。

一定の遅延後、関数はSPIを介して値を読み出し、それを入力ポインタに渡します。ADIS16500のレジスタには、上位の値(8個の最上位ビット)からなる上位アドレスと、下位の値(8個の下位ビット)からなる下位アドレスがあります。16ビットの値全体(下位および上位)を得るためには、下位アドレスをadとして用いれば十分です。下位アドレスと上位アドレスは連続しているためです。

  • adis16500_wr_reg_16

この関数は、16ビットのレジスタに書込みを行うために用いられます。その実装方法は、付録の図6に示されています。入力は、書き込むレジスタのアドレスを表すuint8_t 型の変数であるadと、レジスタに書き込む値を表すuint16_t 型の変数であるreg_val です。読出し関数と同様、下位アドレス、上位アドレス、および値を考慮する必要があります。そのため、データシートに従い、ADIS16500のレジスタへの書込みには伝送中に2回のSPIアクセスが必要です。最初に0に等しいR/Wビットと下位レジスタ・アドレスを送信し、次に下位の値を送信します。したがって、シーケンスは次のようになります。

R/W | AD6 | AD5 | AD4 | AD3 | AD2 | AD1 | AD0 | D7 | D6| D5 | D4 | D3 | D2 | D1 | D0 |

ここで、Dはデータを表します。

2回目のSPIトランスミッタ・アクセスでは、0に等しいR/Wビットと上位レジスタ・アドレスを送信し、次に上位の値を送信します。したがって、シーケンスは次のようになります。

R/W | AD14 | AD13 | AD12 | AD11 | AD10 | AD9 | AD8 | D15 | D14 | D13 | D12 | D11 | D10 | D9 | D8 |

レジスタ書込み関数とレジスタ読出し関数は、実際にはプライベートとして定義され、そのため、ドライバ・ソフトウェア・モジュール外からは見えず、呼び出すこともできない場合もあります。これらをパブリックとして定義するのは、デバッグを可能にするためです。それにより、設計者は、センサーの任意のレジスタに手早くアクセスして読出しや書込みを行うことができ、問題のトラブルシューティングに役立てることができます。

  • adis16500_rd_acc

この関数は、出力データ・レジスタからx、y、zの加速度データを読み出し、その値を[m/sec2]の単位で返します。その実装方法は、付録の図7に示されています。入力は、単純にx、y、zの加速度の3つのフィールドを組み込んでいる、ADIS16500_XL_OUT 構造へのポインタで、浮動小数点型で表されます。加速度を読み出す方法は、3軸全て同じで、唯一の違いは読み出すレジスタです。軸ごとに固有のレジスタがあります。つまり、x軸はx軸加速度出力データ・レジスタから読み出す必要があり、y軸、z軸も同様です。加速度値は32ビットの値で表されるため、読み出すレジスタは2つになります。1つは最上位16ビット用、他の1つは最下位16ビット用です。そのため、コードを見ると、適切なシフトおよびORビット操作を伴う2つのレジスタ読出しアクセスがあります。これらの操作により、2進数値全体を_tempという名のプライベートなint32_t 変数に保存できます。この時点で、2進数から2の補数への変換が行われます。変換後、この2の補数値は、[LSB/(m/sec2)]を単位とする感度で割り算され、最終値が[m/sec2]を単位とする加速度になります。この値は、入力として渡された構造へのポインタのx、y、zフィールドに登録されます。

  • adis16500_rd_gyro

ジャイロ・センサー読出し関数は、加速度読出し関数と全く同じ作用をします。言うまでもなく、x、y、zジャイロ・センサー・データを[º/sec]単位で読み出します。その実装方法は付録の図8に示されています。関数の入力は、加速度の場合と同様、x、y、zのジャイロ・センサー・データが組み込まれたADIS16500_GYRO_OUT 構造へのポインタで、浮動小数点型で表されます。読み出すレジスタは、ジャイロ・センサー出力データ・レジスタです。この2進数値は32ビットで表され、2の補数値にするために加速度の場合と同じステップが必要です。2進数から2の補数への変換後、値は[LSB/(º/sec)]で表される感度で割り算され、最終的な値は[º/sec]の単位になります。これが、入力として渡される構造へのポインタのx、y、zフィールドに登録されます。

まとめ

本稿では、組み込みシステムの代表的なソフトウェア/ファームウェア・スタックについて説明しました。また、IMUセンサーのドライバの実装を紹介しました。ハードウェアに依存しない手法は、インターフェース(SPI、I2C、UARTなど)が異なっても、様々なセンサーやコンポーネントに繰り返し使える方法を提供します。この次の記事「組み込みシステムの設計を容易なものにするためのハードウェアに依存しない手法の活用:ドライバの実装」では、センサー・ドライバの実装方法について更に詳しく説明します。