発生した問題

 新人ファームウエア開発者のAさんは,ある開発プロジェクトでCDなどを出し入れするメディア・ローディング機構の制御プログラム設計を担当していました。このソフトウエア・モジュールは,位置決めセンサ回路からの割り込み信号をトリガに,マイコンの外部端子を使ってモータを起動/停止させることになっていました(図2-1)。

 設計したモジュールは単体テストでは正常に動作していたのですが,他のモジュールとの結合テストを行うと,ごくまれにモータが正常な位置で停止せずオーバーランしてしまう,という不具合が発生してしまいました。

原因と対策

 Aさんは,まず外部端子制御のために用意されたライブラリ関数を調べてみました(図2-2)。ライブラリ関数では,論理和(|=)やビット否定( ̄),論理積(&-)などのビット演算子を利用して,マイコンの外部端子出力状態を設定する入出力ポート・レジスタの対応ビットに“H”をセットする処理,および“L”にクリアする処理が実装されていました。モータの起動については,当該ビット(ビット2)に“H”や“L”に設定する関数を利用しており,特に異常を引き起こすようには見えませんでした。

 そこでモータが停止しなかったときのレジスタの状態をデバッガでモニターしたところ,ビットを“L”にクリアする関数が呼び出されたにもかかわらず,該当のビットが“H”のままになっている様子が観測できました。

 Aさんは,正しく動作するはずのライブラリ関数を利用しているにもかかわらず,なぜこのような現象が起こってしまうのか分からなくなり,先輩エンジニアのTさんに助けを求めました。Tさんはライブラリ関数のアセンブル結果を眺め,入出力ポートへのビット設定には4ステップのCPU命令が必要であることを教えてくれました(図2-3)。また,同じマイコン・ポートの別の外部端子にアクセスする他のソフトウエア・モジュールがないかを確認したところ,動作モードを表示するLEDの駆動制御に利用されていることが分かりました。さらに,LED駆動モジュールはタイマからの周期割り込みで起動され,モータ制御と同様のライブラリ関数を使用して外部端子を制御していること,モータ制御処理よりも割り込みの優先度が低いことも判明しました。そこでアセンブリ言語レベルの動作シミュレーションを,机上で行ってみることにしました。

 図2-4のように,最初にLED駆動モジュールの割り込み処理の中でLEDをオンにするライブラリ関数が呼ばれ,3ステップ目(or)まで進んだところで優先度の高いモータ停止制御の割り込み処理が開始された場合,モータ停止制御割り込みが終了した直後に外部端子が“L”になります。ところが中断していたLED駆動モジュールの割り込み処理が再開され,4ステップ目が実行されたところでモータ制御用の外部端子は“H”に戻ってしまいます。つまり,LED駆動モジュールの割り込み処理でいったん読み出された入出力ポートの出力データは,モータ制御用外部端子の状態が“H”のときの値であり,この値にビット演算を施して再びレジスタに書き戻されていたため,モータ制御用の端子の値が古い値で上書きされていたのです。

 対策方法についてTさんにアドバイスを求めると,次のようなアイデアを提案してくれました。

  • (a)ライブラリ関数の実行中に割り込みを禁止する割り込みマスクなどの排他制御を行う。
  • (b)ポートへのアクセスを割り込みハンドラ内での処理ではなく,リアルタイムOSなどを使って同じポートへのアクセスを単一スレッド化することで,複数のモジュールによるリソースへのアクセスの競合を防ぐ。ただし,イベントが発生してから処理が完了するまで時間が厳密に規定されている場合には,スケジューリングに注意が必要である。
  • (c)目的やアクセス周期などに応じて,使用する外部端子のポートを分割する。

 今回のプロジェクトでは,ハードウエアの変更が時期的に難しいこと,リアルタイムOSを利用していないことなどから,(a)の割り込みマスクで排他制御を行って不具合を解決しました。

技術者必修の基本

 マイコンのポート制御にはレジスタを使用する場合が多いのですが,これらのレジスタは8,16,32ビット単位でアクセスすることが一般的です。特にRISCマイコンの多くはビット演算の際に,メモリマップされた入出力ポート・レジスタのデータを,汎用レジスタに取り込んでから演算を施し元の場所に戻すので,数ステップのCPU命令が必要になります。このため,複数のソフトウエア・モジュールが同じポートにアクセスする場合は,トラブルを防ぐために何らかの排他制御が必須になります。逆にCISCマイコンでは,上記のような処理を一つのCPU命令で実行できてしまうので,このような不具合は起こりにくいでしょう。

 さらにポート制御だけに限らず,複数のタイマや複数チャネルのDMA(direct memory access)を,それぞれ単一のレジスタを用いて制御している場合にも,同様の配慮が必要です。使用するマイコンのアーキテクチャをよく理解し,ソフトウエアを設計することが肝要です。

 このようなミスは,ベテランでもよくやってしまいます。ビット演算などは,C言語では1ラインで記述できてしまう簡単な処理なので、気が付きにくいのかもしれません。またRISCマイコンだと起こりやすいので,よく使うマイコンがCISCからRISCに変わったときに注意しましょう。

【訂正】記事掲載当初,図2-4に間違いがありました。リストの最初の行に「r2」とあるのは「r4」の,最後の行に「@0r4」とあるのは「@r4」の間違いでした。記事は既に修正済みです。お詫びして訂正いたします。