- ベストアンサー
組込みC開発
お世話になります。 組込みでARMコアのCPUをチョイスしての仕事を任されました。 開発ツールはKeilというIDEで、C言語でOS使用しないで開発をしています。 C言語はかれこれ2年以上使用しており、文法上の事などはほぼマスター しており、上記のIDEでソースをコンパイルしてコンパイルエラーもなく 60kbytesほどのコードが生成されます。 ただ、このプログラムをターゲットCPUへダウンロードして使うと コードとは異なる動きをするときがあります。 これは、小規模なプログラムでは経験したことの無いことです。 たとえば、思った動きにならないときに、ソースを少しいじっただけで 今度は例外が発生して止まったりします。 割込みは内部タイマー、外部(ボタン等)、UARTなど計4種類を 設定していますが、どうもこのあたりが怪しいと考えています。 たとえばUARTでPCからデータを送って、ボタンを押す、などの アクションを起こすと、データアボートやプリフェッチアボート が発生したりしますので。 ちなみに、割込み処理は、IDEがサンプルソースでつけているものを includeして使っています。 また、CでのプログラムでMISRA-C参考書も購入してチェックして おります。 大規模なプログラミングは今回が初めてなのですが、こうもソース を少しさわっただけで、例外が発生したりする(コード上問題ない と考えています)ので、現状は記述の仕方を変えてみたりして 対応しています。 こうした規模の大きい組込み系でのプログラミングで、注意する点 や、常識的な部分があればお教えいただきたく思います。 よろしくお願いいたします。
- みんなの回答 (9)
- 専門家の回答
質問者が選んだベストアンサー
#3のzwiです。 >地道にトレースしてどこでどう無限ループに達するのか、Abortへジャンプしてしまうのかを見ていくことが、組込み系でのデバッグ、ということになるのでしょうか。 再現性が高いだけでも恵まれてますよ! めったに出ないバグなんか、泣きそうになります(>_<) 最悪アセンブラレベルでのトレースが必要になるかも知れませんが、とりあえずスタックの情報から流れが追えませんか? 暴走先にどうやって飛んできたのかのを調べるには、スタックに詰まれている情報を追ってみるのが一番です。 他のテクニックとしては、配列でリングバッファを作ってプログラムの要所要所の通過マークを格納していくって技もあります。流れがおかしくなったらリングバッファの通過マークの順番を見れば分かりますよね。 あとは、CPUのクロックを落として様子みてみるとか、ARMあたりだとPLLで簡単に変更できるので簡単に実験できると思います。 話を聞いている限りでは、次のような不具合が可能性としてあります。 (1)ノイズでRAMまたはレジスタの内容が破壊される。ハード? (2)CPUキャッシュが何らかの原因で破壊される。ハード? (3)コンパイラのバグで、とんでもない命令が実行される。コンパイラ? (4)スタックが破壊される。ハードまたはソフトが原因。 あたりですかね。 >KeilというツールはもともとKeilという会社のコンパイラが搭載されていたわけですが、この会社、英国ARM社の傘下に入り、ARMが提供するコンパイラと同等のコンパイラを搭載するようになりました。 >あるWebでみかけたので気になるのが、ARMコアのコンパイラのチョイスでGNU,IAR,Keilが評価対象とされて、OverallでIARが勝利。次点でGNU、最後がKeilだったと記憶しております。 いまさらながら、IARのほうにしとけば、思っています^^;;; どこのコンパイラもバグがあるのです。運が悪いと引っかかりますね。それも締め切り間際に(^^ゞ まだバグだと確定したわけでもないですよね。周辺操作のタイミングのミスかも知れませんよ。 >pin1にボタン入力の割込み信号が入ると仮定して、本体CPUは常に30μs周期のtimer1の割込みで主に計算処理が中心の無限ループをさせています。 >ボタンが押された=pin1がHighからLowに変化したところで、まずフラグをたてます(flag = 1) >次に30μsのtimer1による無限ループ中に >if(flag == 1) counter++; >として、pin1の割込み発生からループが何回、回ったかをカウント。 >このカウントの値がインクリメントされ、既定の回数をカウントしてもなお、pin1がLowであれば、内部的にONを確定します。そしてflag = 2とします。 >次にボタンを離す行為の監視ですが、ONの監視と同様の処理をしています。 なんか複雑ですね。それにチャタリングはスイッチによりますが10ms以上は続きますので、400から500カウントぐらいしないと確定できませんね。 私がやるとしたら、1ms毎ごとぐらいにしかチェックしません。例えば1ms毎20回チェックして同じ状態が20回続けばスイッチの変化を確定とします。それ以外のときは前のスイッチ状態が継続しているものとします。 volatileは、コンパイラの最適化を抑止してレジスタ処理のみとなること避けるための修飾子です。 プログラムの流れの外(割り込み等)で変数の値が変化する場合は、その変数にvolatileを付ける必要があります。 下記サイトを参考にしてください。 http://www.kumikomi.net/article/explanation/2003/10kumi/13.html >・RAM 領域を定数で初期化するコードが、オブジェクト上に存在する。 Cのスタートアップルーチンが組み込まれていれば、ルーチン次第ですがRAM上の変数の初期化をしてくれています。通常アセンブラで書かれていますが、リンクしていませんか?
その他の回答 (8)
- 麻野 なぎ(@AsanoNagi)
- ベストアンサー率45% (763/1670)
No. 7 です。 > ・RAM 領域を定数で初期化するコードが、オブジェクト上に存在する。 オブジェクトではなくて、オブジェクトファイルでした。 組み込みの場合、最終的に ROM に焼き込むことになりますが、そのもとになるファイルは、フォーマットに差があっても、 xxxx番地のデータはyy という情報の集まりです。 この、xxxx番地が ROM 上にある場合は、問題ないのですが、開発環境やら、の関係で、RAM 上の番地を指定してしまうことがあります。 たとえば、 static int i = 50; のような指定があると、 (i の番地)は、50 のようなデータがオブジェクトファイル上にできます。 エミュレータがこれを読み込むと、ごく自然に RAM 領域に初期値を割り当ててしまいます。ところが、マイコンに焼き込む段階では、RAM 領域への焼き込みはできませんから(できたとしても、電源を切れば消えますから)初期化されなかったことになるというものです。 const なしの static や グローバル変数に初期化すると、こうなるケースがあります(開発環境・オプションにより)
お礼
ご回答ありがとうございます。 非常に参考になりました。
- 麻野 なぎ(@AsanoNagi)
- ベストアンサー率45% (763/1670)
組み込み特有というか、エミュレータが絡んでくると、RAMの取り扱いが問題になることがあります。 ツールによっては変数全般も。 ・初期化されていない変数がある これは、組み込みにかかわらず問題になる項目です。 組み込みに場合、エミュレータ上で動かすと、エミュレータ自体が、きれいにRAMを初期化してしまうので、問題が表面化しない場合があります。 ・RAM 領域を定数で初期化するコードが、オブジェクト上に存在する。 これは、主に、コンパイル時に指定するメモリの割り当てに絡んできます。 変数を初期化するコードで、ツールによっては、それを、単純に、指定された番地に埋め込むことがあります。(特にトップレベルのグローバル変数や、スタティック変数) ところがその変数がRAMだと、いわゆる、ROM への焼き込みができませんから、初期化しているつもりが初期化されていなかったりします。 こういうケースも時にはあります。
お礼
具体的なご回答ありがとうございます。 最初のころは変数の初期化をしなかったせいもあり、うまく動いていなかった点がありましたが、いまではしっかりと初期化を心がけております。 >・RAM 領域を定数で初期化するコードが、オブジェクト上に存在する。 ご説明がいまひとつ、理解しかねるのですが、const で宣言した変数は以前も確認したことがありまして、ROMのアドレスに配置されていました。 グローバル変数については、再確認してみたいと思います。
- jacta
- ベストアンサー率26% (845/3158)
> 組込み系では、こうした対応策が一般的なのでしょうか? > Cの文法上は問題ないけれども、optimizeしてみたり、ソースの書き方を > 変更してみたりして、実機で(なんとか)動くようにするような対策です 確かにコンパイラの最適化バグに遭遇することはときどきありますが... 書かれている内容を読む限り、おそらくコンパイラの最適化バグではないような気がします。 単にvolatile修飾子を忘れているとか、きちんとタイミングを合わせなければならないのに、適当に書いてたまたま動いていただけとか、そんなところではないでしょうか。 たまたま動いていた場合には、ボードの個体差によって、動いたり動かなかったりします。量産した際に、ロットによって動かなかったりすると目も当てられません。
補足
具体的なご回答、ありがとうございます。 volatile修飾子についてなのですが、揮発性の変数(電源オフで忘れる変数)については、必ずつけることが必要なのでしょうか? 現状では long i = 0; char a ='\0'; みたいに、volatileをつけていません。
- zwi
- ベストアンサー率56% (730/1282)
あと、組み込みは初めてですよね? スイッチの入力は、チャタリング対策されてますか? ハードかソフトでチャタリングの対策をしていないと、スイッチの値を読み間違うのは当たり前なんですが。 http://elm-chan.org/docs/tec/te01.html これは、データアボートやプリフェッチアボートとは別の問題です。
お礼
具体的なご回答をありがとうございます。 チャタリングに関しましては当然対策しております。 ただ、自分としてはちょっと変わった方法ではないかと考えています。 pin1にボタン入力の割込み信号が入ると仮定して、本体CPUは常に30μs周期のtimer1の割込みで主に計算処理が中心の無限ループをさせています。 ボタンが押された=pin1がHighからLowに変化したところで、まずフラグをたてます(flag = 1) 次に30μsのtimer1による無限ループ中に if(flag == 1) counter++; として、pin1の割込み発生からループが何回、回ったかをカウント。 このカウントの値がインクリメントされ、既定の回数をカウントしてもなお、pin1がLowであれば、内部的にONを確定します。そしてflag = 2とします。 次にボタンを離す行為の監視ですが、ONの監視と同様の処理をしています。 ここで、多重割込みの問題が発生しますが、timer1の割込み処理中のみ、他の割り込みも発生すれば受け付けるようにしています。他の割込み処理中はすべて他の割込みを禁止しています。
- zwi
- ベストアンサー率56% (730/1282)
>下記にも記載しましたが、Optimization levelを操作することで今回は回避できそうな気配です^^; そんな方法で問題を回避しても無駄です。 趣味で作っているならともかく、仕事でそんなことをしたら酷い目にあいますよ! 量産したら動かない、現場ではちゃんと動かない、でも社内ではちゃんと動くってオチが待っていそうです。 もし、Optimization levelだけで回避できるとしたら、コンパイラのバグが原因です。どこのコードに問題があるか確認しておかないと本当に回避しているか保障できません。 私の勘では、ハードの異常動作な気がするんですけどね。オプティマイズレベルで周辺回路の操作タイミングが変わりますから。
お礼
ご回答・ご叱責ありがとうございます。 確かに、最適化しない状態で動かないのに、最適化処理したとたんに意図した動きになるとは、ちょっと理にかなわないですよね。 KeilというツールはもともとKeilという会社のコンパイラが搭載されていたわけですが、この会社、英国ARM社の傘下に入り、ARMが提供するコンパイラと同等のコンパイラを搭載するようになりました。 あるWebでみかけたので気になるのが、ARMコアのコンパイラのチョイスでGNU,IAR,Keilが評価対象とされて、OverallでIARが勝利。次点でGNU、最後がKeilだったと記憶しております。 いまさらながら、IARのほうにしとけば、思っています^^;;;
- zwi
- ベストアンサー率56% (730/1282)
「プリフェッチアボート」は普通ありえない例外です。 ノイズなどが原因でハードウェアが誤動作していないでしょうか? データアボートやプリフェッチアボートの頻発がソフトウェアが原因とは思えません。パスコンを追加したりしたら直りませんかね? >たとえばUARTでPCからデータを送って、ボタンを押す、などのアクションを起こすと とてもノイズが原因っぽいですよね。 後は、ARMチップが、どこのメーカー製かわかりませんが、エラッタの資料を見て問題となるようなことをしていないか確認をしてみてください。たちの悪いエラッタに引っかかっている可能性もあります。
お礼
ご回答ありがとうございます。 エラータも確認しました。特にひっかかりそうな項目は無かったです。 回路的には、とっかかりにユニバーサルで組んだいたものでも、回路図からパターン引いて試作基盤を作成したものの上でも同じように症状が出ています。 地道にトレースしてどこでどう無限ループに達するのか、Abortへジャンプしてしまうのかを見ていくことが、組込み系でのデバッグ、ということになるのでしょうか。
- R32C
- ベストアンサー率39% (115/290)
実機で、暴走するなら、デバッガーのリアルタイムトレース で、動きを確認してみてはいかがでしょうか? この辺が組み込み独自の方法なのかもしれませんね。 Keilの開発環境については、よく知りませんが、リアルタイムトレース は使えるですよね。 ARMコそのものはオンチップでリアルタイム トレース機能のサポートがあるので、対応デバッガで使えるはずです。 今お使いのツールでもしサポートしていないなら、使えるツールを 用意してみたほうがいいと思います。
お礼
ご回答ありがとうございます。 下記にも記載しましたが、Optimization levelを操作することで今回は 回避できそうな気配です^^; 実はトレースも購入して使用しているのですが、いまいち使い切れてない 状態です。 トレースで確認してみると、ボタンAが引き金となり、変な場所で 無限ループが発生したりしています。 変な場所とは、プログラムエリア外の部分です。そのかわりタイマの イベントだけはしっかり働いてたりしています^^; スタックポインタが上書きされるから関数がもどらない状態が発生 しているんだと思うのですが、タイマ割込み処理以外の割込み処理では 他の割り込み処理を禁止させています。 パソコン上でのプログラム経験が長いので、このあたりの動作が どうもしっくりきません^^;;;
- jacta
- ベストアンサー率26% (845/3158)
> ただ、このプログラムをターゲットCPUへダウンロードして使うと > コードとは異なる動きをするときがあります。 期待した動作とどのように異なるのでしょうか? > たとえば、思った動きにならないときに、ソースを少しいじっただけで > 今度は例外が発生して止まったりします。 発生した例外の要因は何でしょうか?
お礼
ご回答ありがとうございます。 基本的には内部タイマをトリガとして、 単純な処理を数十μSごとに処理させています。 異なる動きとは、たとえばボタンを押すと、定期的な上記の処理から 割込みを使い、メニューモードへ飛ばします。 ボタンは4つあり、割込み時にどのピンに接続されたかをレジスタで 知ることができるようになっています。うちAボタンでメニューへ飛ぶ ボタンとします。 異常なときは、Aボタンを押しても、割込み処理の中でボタンAが 押されたと判断されず、いつもの定期運転をしている状態です。 しかしながら、割り込みが入ったことだけは認識します。 実際に、割込み処理させているところにハードウェアブレークを セットすると、たしかに引っかかってきます。 ただし、レジスタの内容を見ると、どのピンがトリガになったか わからず、スルーしてしまいます。 この件で、自分なりにいろいろ試しているのですが、 コンパイルのOptimizationをいじると、正常動作するOptimizatioin levelがあったりすることも確認しました。 組込み系では、こうした対応策が一般的なのでしょうか? Cの文法上は問題ないけれども、optimizeしてみたり、ソースの書き方を 変更してみたりして、実機で(なんとか)動くようにするような対策です。
お礼
御礼遅れて申し訳ございません。 また、詳細なご意見を頂戴しまして、大変感謝いたしております。 結局は、スイッチの入力処理を疑い、かつトレースもしながらデバッグして いっております。zwi様の言われるスイッチ処理も試そうかと考えています。 いずれにしても、長々とお付き合いくださり、ありがとうございました。