Arduinoで揮発性変数を適切に使用する方法は?

ArduinoUnoで小さなプロジェクトを行っていました。デフホイールシステムがどれだけ前進するかを測定するためにエンコーダーを使用しているので、割り込みが必要でした。私のロボットは前進するだけです。したがって、各エンコーダーから1つのチャネルのみを使用します。 2つの割り込みルーチンは次のとおりです。

ISR (INT0_vect){ encoderRPos = encoderRPos + 1; } ISR (INT1_vect){ encoderLPos = encoderLPos + 1; } 

変数encoderRPosencoderLPosのタイプはvolatile intです。割り込みルーチンで変更される変数は、揮発性タイプである必要があることを理解しています。これは、これらの変数を使用するコードの他の部分に、いつでも変更される可能性があることを警告するためです。

しかし、私のコードで起こったことは少し奇妙で、説明できませんでした。左ホイールの移動距離を計算する方法は次のとおりです。

 #define distancePerCount 0.056196868 float SR = distancePerCount * (encoderRPos - encoderRPosPrev); float SL = distancePerCount * (encoderLPos - encoderLPosPrev); encoderRPosPrev = encoderRPos; encoderLPosPrev = encoderLPos; 

しかし、シリアルモニターに次のように印刷すると、異常が発生します。

ここに画像の説明を入力

3番目を見ると列、(SL)値がしばらくの間高すぎます。これは、すべての計算を混乱させます。

取得したSLの値を取得した場合、取得できる唯一の手がかりです( 3682)、これは常に定数であり、(encodeLPos - encoderLPosPrev)を計算して戻すと、unsigned intの最大値に近い65519.66が得られます。 。つまり、(encoderLPos - encoderLPosPrev)がオーバーフローを引き起こしているのに対し、差が取られた値は両方とも約5000程度です!

そして私はそれを解決することができました。運。これが私がコードを変更した方法です:

 static int encoderRPosPrev = 0; static int encoderLPosPrev = 0; int diffL = (encoderLPos - encoderLPosPrev); int diffR = (encoderRPos - encoderRPosPrev); float SR = distancePerCount * diffR; float SL = distancePerCount * diffL; encoderRPosPrev = encoderRPos; encoderLPosPrev = encoderLPos; 

何が起こったのか理解できません。知っておくべき揮発性変数について何かありますか?

更新: ここにコード全体があります。そして、受け入れられた回答で提案されたものに変更した後、それは非常にうまく機能しています。

コメント

  • あなたの質問は、出力は…他の列は何ですか?質問を編集して列見出しを追加してください
  • @ jwpat7読者を混乱させるだけなので、意図的に削除しました。しかし、質問はすでにMajenkoによって十分に回答されています。
  • スニペットから詳細な回答を提供するのは難しいです。could you explain why it is not happening randomly but at a specific time every time I run the code? Also why does it give the particular value? -コード全体を見れば、おそらくそうすることができます。その間、これを読んでください: gammon.com.au/interrupts
  • @NickGammonどうぞ: paste.ubuntu.com / 14085127
  • 3683 / .056196868 = 65537なので、間違ったタイミングでインクリメントされたように見えますね?割り込みで変更される可能性のある変数にアクセスしていますそのコードで複数回使用されるため、割り込みがオフのときにローカルコピーを取得する方が、はるかに安全です。

回答

クリティカルセクションについて学ぶ必要があります。

内容おそらく起こっているのは、計算の途中で割り込みルーチンによって変数が変更されていることです。 「修正」により、揮発性変数を使用した計算に費やす時間が短縮されるため、衝突が発生する可能性が低くなります。

割り込みを無効にして、揮発性変数をローカル変数にコピーする必要があります。短い期間。

cli(); int l = encoderLpos; int r = encoderRpos; sei(); 

Arduinoは8ビットCPUであるため、16ビット値に対して数学演算を実行するには複数のアセンブリ命令が必要です。単純な加算のために多くの命令を使用すると、浮動小数点はさらに悪化します。除算と乗算はかなり多くを使用します。割り込みは、その命令のリストの間に発生する機会がたくさんあります。このような割り当てを行ってから、計算で新しいローカル変数を使用することにより、揮発性変数を処理するために必要な命令が最小限に抑えられます。割り当て中に割り込みをオフにすることで、変数の使用中に変数を変更できないことが保証されます。このコードスニペットはクリティカルセクションと呼ばれます。

コメント

  • これはまさにその通りかもしれませんが、なぜランダムに発生するのではなく、コードを実行するたびに特定の時間に発生するのか説明していただけますか?特定の値は?
  • cli / seiへの優れたリファレンスです。 nongnu.org/avr-libc/user-manual/ … 。メモリバリアを使用すると、上記のコードでは揮発性宣言は実際には必要ありません。このテーマに関する楽しい読み物をいくつか紹介します。 kernel .org / doc / Documentation / volatile-considered-harmful.txt
  • @MikaelPatelいいですが、MCUには関係ありません。この状況では、コンパイラが'が使用されていないと判断したインスタンスを最適化しないようにするために揮発性が必要です(値は変更されません)。 cli / seiは、操作アトミックWRTを実行する唯一の他のスレッド(割り込み)にするためにあります。
  • volatileを使用して、または使用せずにコードをコンパイルしましたか?しかし、クリティカルセクション(cli / sei)があります。私が議論しようとしているのは、メモリバリアの概念と、それが変数を揮発性として宣言する必要があるコンパイラからの揮発性アクセス(および正しい順序付け)をどのように提供するかです。ほとんどのプログラマーは、ISRでアクセスされる変数は揮発性として宣言する必要があると教えられていますが、この話にはそれだけではありません。
  • コンパイラーは'とは思いません。 cli()とsei()が何をするのか、そしてそれが'最適化されるべきではない変数の最適化などにどのように影響するかについて多くの概念があります。 sei()とcli()が行うのは、レジスタ内のグローバル割り込み有効フラグを操作することだけです。コードの流れには何もしません。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です