STM32でのノンブロッキングI2C実装

私が見つけることができるほとんどの記事は本質的に "XXXマイクロコントローラ上のダミーのためのI2C"で、確認イベントを待つ間にCPUをブロックすることを提案します。 の場合(コメントと説明はロシア語ですが、実際には関係ありません)。これが私が話している「ブロッキング」の例です。

I2C_GenerateSTART(HMC5883L_I2C, ENABLE);
/* wait for confirmation */
while (!I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_MODE_SELECT));

これらの while ループは、基本的に各操作の後にあります。最後に、私の質問は - これらのwhileループでMCUを "ハング"させずにI2Cを実装する方法です。非常に高いレベルでは、whileの代わりに割り込みを使用しなければならないことを理解していますが、どのコード例も思い付くことはできません。それを手伝ってください。

3
パフォーマンスの向上と処理する割り込み数の削減のために、I²Cと一緒にDMAを使用することもできます。
追加された 著者 StackOverflowed,
@AlexeyMalevこれはAVR用のノンブロッキングI2C実装の例です(STM32にも同様の原則が当てはまります)。 github com/scttnlsn/avr-twi この特定の実装では、割り込みを使用してステートマシンを管理し、送信が終了したときにコールバック関数を呼び出します。
追加された 著者 shians,

5 答え

もちろんです。

  1. 共有バッファで区切られた上限下限の組み合わせでドライバを設定します。 lower 関数は、割り込みイベントに応答し、ハードウェアにサービスを提供するために必要な即時作業を処理し、共有バッファにデータを追加する(レシーバの場合)、割り込み駆動型のコードです。 upper 関数は、通常のコードによって呼び出され、発信バッファに追加するデータを受け取るか、または共有バッファから次のビットのデータを抽出してハードウェアの保守を続けます。それ以外の場合は、受信バッファからデータをチェックして抽出します。このように組み合わせて、ニーズに合わせて適切なバッファリングを行うことで、この調整は問題なく1日中実行でき、ハードウェアの保守からメインコードを分離します。
  2. 割り込みドライバにステートマシンを使用します。各割り込みは現在の状態を読み取り、1ステップ処理した後、別の状態に遷移するか、同じ状態に留まり、終了します。これは必要に応じて複雑にも単純にもなり得ます。多くの場合、これはタイマーイベントによって発生します。しかし、そうである必要はありません。
  3. 協調的なオペレーティングシステムを作成します。これは、malloc()を使用して割り振られたいくつかの小さいスタックをセットアップして、当面のタスクで完了したときに関数が協調してswitch()関数を呼び出すことを可能にするのと同じくらい簡単です。 I2C関数用に別のスレッドを設定しましたが、今は何もしないと判断した場合は、単にswitch()を呼び出して別のスレッドにスタックを変更します。これは、戻ってきて、行ったswitch()呼び出しから戻るまで、ラウンドロビンで実行できます。それからあなたはあなたのwhile-conditionに戻り、それがまたチェックします。まだ何もしなければ、whileは再度switch()を呼び出します。このようにして、あなたのコードを保守するのがそれほど複雑になることはなく、あなたが必要としているところにswitch()呼び出しを挿入するのは簡単です。関数呼び出しの境界でスタック間を遷移しているだけなので、ライブラリ関数が割り込まれることは不可能であるため、ライブラリ関数の横取りについて心配する必要もありません。これにより、実装が非常に簡単になります。
  4. 先制スレッド。これは#3によく似ていますが、switch()関数を呼び出す必要がない点が異なります。プリエンプションはタイマーに基づいて行われるか、スレッドも同様にその時間を解放することを自由に選択できます。ここでの困難は、中断することができないコンパイラによって生成された特定の命令シーケンスがあるかもしれないライブラリルーチンや特殊なハードウェアの横取りを扱うことです。たとえば、同じメモリマップアドレスの下位バイト)

私は最初の2つの選択肢がおそらくあなたのより良い賭けだと思います。ただし、他の人が書いたライブラリコードにどれだけ依存しているかによって、多くのことが決まります。彼らのライブラリコードが、上位レベルのコンポーネントと下位レベルのコンポーネントに分割されるように設計されていない可能性はかなりあります。また、タイマーイベントや他のステートマシンベースのイベントに基づいてそれらを呼び出すことができない可能性もあります。

私は、ライブラリが仕事をする方法が嫌ならば(ビジー待機ループのみ)、仕事をするために私自身のコードを書いているのに行き詰まったと思う傾向があります。つまり、上記の方法を自由に使用できます。

ただし、ライブラリコードをよく調べて、ビジーウェイト動作を回避できる機能がすでにあるかどうかを確認する必要があります。ライブラリが別の何かをサポートしている可能性があります。念のため、最初に確認する場所です。そうでない場合は、既存の関数を upper / lower ドライバ分割の一部として使用するか、またはステートマシン駆動プロセスとして使用することをお勧めします。それを解決することも可能です。

今すぐに思い浮かぶ他の提案はありません。

4
追加された
すみません、でも私は投票しなければなりません。この長い答えでは、STM32で割り込み駆動型I²Cステートマシンを実装する方法について に関する有用な情報が得られないようです。
追加された 著者 StackOverflowed,
@AlexeyMalevその階乗コードはおそらくメイン関数の中にあるはずです。それは上位レベルのコードを呼び出します。下位レベルの コードでは、途中で中断が必要なことは行われません。メインコードが上位コードを呼び出している間、またはそれを呼び出した後に処理している間にさらに処理が行われても、割り込みは処理され、バッファに値が書き込まれます。たとえあなたが計算をしていてもそれは止まりません。他の作業が完了していない場合は、さらにバッファデータを確認する必要はありません。あなたが追いつくことができないならば、とにかく問題を抱えています。
追加された 著者 ina,
@AlexeyMalev最も長い計算作業はメインコードにあります。あなたが時間の間にする必要があるより短いビットがあるなら、あなたはそれらの短いタスクを成し遂げるためにあなたの長い計算コードを中断するタイマーイベントにそれらを設定することができます。これらの時限の短いタスクは、バッファされたデータを取得するために、ドライバの上位レベルの半分を呼び出すこともできます。タイミング図をレイアウトするだけの問題です。最も長い計算時間とすべての割り込み時間に十分な時間間隔を空けてください。それがあなたのメインプロセスです。ただし、私の#3オプションを使用して、switch()呼び出しでメインコードをソルトすることができます。
追加された 著者 ina,
@AlexeyMalev私は特にSTM32環境を知りませんが、私は一般的に当てはまる、ブロッキングのない、すぐにそうするような広い答えを提供しようとしました。
追加された 著者 ina,
#1の考えが間違っていたら、訂正してください - たとえば、ある数の階乗を計算するなど、I2Cバスからデータをいくつか読み取り、重い数学を実行するコードがあるとします。ここにはスレッドがないので、階乗を計算する私のメインコードは時々「一時停止」して(高水準言語の割り込み処理スレッドに譲る代わりに)割り込みバッファによって追加されたバッファにデータがあるかどうかチェックするべきです?
追加された 著者 enumaris,
私はアイデアを得たと思います、ありがとう。
追加された 著者 enumaris,
問題は、ノンブロッキングのi2cインタラクションをどのように実装するか、オプションとして割り込みに言及したことでした。可能なアプローチの1つだけで割り込みベース。
追加された 著者 enumaris,
@ジョンノノ、私はJimmyBのコメントを参照していた、気にしないでください:)
追加された 著者 enumaris,

理由

Well, the 理由 is simple: blocking is just easy, and at first glance it seems to work. Woe to you if you want to do something else, meanwhile.

それで、私はSTM32を知らないので、多くの詳細に入ることなしに、あなたは一般的にあなたの必要性に応じて2つの方法でこの問題から出ることができます。

I2C_GenerateSTART(HMC5883L_I2C, ENABLE);
/* wait for confirmation */
while (!I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_MODE_SELECT));

ノンブロッキングに変換

while ループすべてに対してタイムアウトを実装します。これの意味は:

I2C_GenerateSTART(HMC5883L_I2C, ENABLE);
/* wait for confirmation */
static unsigned long start = now();
while (!I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_MODE_SELECT) && now()-start < TIMEOUT);  
if (now()-start >= TIMEOUT) { return ERROR_TIMEOUT; }

(もちろんこれは疑似コードです、あなたはアイデアを得ます。必要に応じてあなたのコーディング設定を最適化するか調整してください。)

スタックを上っていくときにリターンコードをチェックし、タイムアウト処理を行う正しい場所を選ぶ必要があります。グローバル変数 i2c_timeout_occured = 1 などを設定すると、あまりにも多くの引数を渡さなくても、それ以上のI2C呼び出しをすばやく中止できるので便利です。

うまくいけば、この変更はかなり無痛です。

裏返し

その代わりに、そのイベントを待っている間に他の処理を実際に行う必要がある場合は、内部whileループを完全に削除する必要があります。あなたはこのようにします:

void main_loop() {
   do_i2c_stuff();   //must never block
   do_other_stuff();
   ...
}

// Must never block. Assuming all I2C_... functions do not block either.
void do_i2c_stuff() {
  static int state=...;

  if (state==0) {
    I2C_GenerateSTART(HMC5883L_I2C, ENABLE);
    state=1;
  } else if (state==1) {
    if (I2C_CheckEvent(HMC5883L_I2C, I2C_EVENT_MASTER_MODE_SELECT)) 
      state=2;
  } else ...
}

他のロジックによっては、必ずしもそれほど複雑ではありません。あなたがプログラミングしていることを見失うことがないように、適切なインデント/コメント/フォーマットで多くのことができます。

これが機能する方法は、ステートマシンを作成することです。元のコードを見ると、このようになります。

non-blocking code
while (!nonblocking_function_call1());
non-blocking code
while (!nonblocking_function_call2());

それをステートマシンに変換するには、それぞれに1つのステートがあります。

state 0: non-blocking code
state 1: nonblocking_function_call1()
state 2: non-blocking code
state 3: nonblocking_function_call2()

次に、上の例に示すように、このコードを無限ループ(メインループ)で呼び出し、現在の状態に一致するコード(静的な state 変数で追跡)のみを実行します。ノンブロッキングコードは些​​細なもので、以前と変わっていません。ブロックコードはブロックされないバリエーションに置き換えられますが、終了したときにのみ state を更新します。

Note that the individual while loops are gone; you have replaced them by the fact that you have your top level main loop anyways, which calls your state machine repeatedly.

最初の解決策のように最も内側のブロッキング関数を単純に適応させることはできないので、この解決策は多くのレガシーコードがあるときは面倒なことがあります。あなたが新鮮なコードを書き始めて、最初からこのようにするとき、それは輝きます。 µCが実行できる他の多くのことと組み合わせてください(たとえば、ボタンを押すのを待つなど)。このやり方でずっと慣れれば、あなたは任意のマルチタスク能力を無料で手に入れることができます。

割り込み

Frankly, for something like this (i.e., just get rid of infinite blocking) I would try hard to stay away from 割り込み unless you have extreme timing needs. 割り込み make it complicated, fast, you may not have enough of them anyway, and it will boil down to quite similar code anyways, as you don't want to do much more inside the interrupt except set some flags.

4
追加された
ここでは良い(次善の)解決策 - なんらかの割り込みが常に必要だと思いました。
追加された 著者 Ryan Cavanaugh,
全体的なアイディアは、 while ループを取り除くことでした。私はあなたの最後のコードサンプルについて質問します - あなたが正確に何を提案しているかわかりません。問題は、 - I2C_CheckEvent がしばらくしてから true を返してブロックされないことです。つまり、一般的には一度呼び出すだけでは不十分です。あなたがお勧めします。そのことについて少し説明してください。
追加された 著者 enumaris,
@AlexeyMalev、私のソリューションは無限の while ループを取り除きます。最初のものはまだそれらを持っていますが、タイムアウトを追加します。 2つ目はそれらを完全に取り除きます(とにかく、永久ループで実行されるトップレベルのループがあると仮定します)。要求に応じて説明を追加しました。 i2C_CheckEvent は頻繁に呼び出されますが、結果に関係なく、メソッドはすぐに戻ります。そのため、プログラムの他の部分にも実行する時間が与えられます。
追加された 著者 benesch,

これがSTM32で利用可能な\ $ \ small I ^ 2C \ $割り込み要求のリストです。私はあなたが使っている正確なチップを知らないので、違いがあるかどうかあなたのリファレンスマニュアルをチェックすることをお勧めします、しかし私はそうなることは疑います。

enter image description here

サンプルコードをそのまま使用するには、ノンブロッキングバージョンが必要な場合は、 ITEVFEN コントロールビットで開始ビット送信(マスター)イベントを有効にし、< ISR内のcode> SB イベントフラグ。

3
追加された

STM32Cube ライブラリに例があります。お使いのコントローラファミリに適したもの(例: STM32CubeF4 または STM32CubeL1 )を入手し、プロジェクトで Examples/I2C/I2C_TwoBoards_ComDMA を探します。 subdir

3
追加された
ご覧ください、ありがとう。
追加された 著者 enumaris,

@BenceKaulicsがデータシートから抜粋したことを考えると、割り込みサービスルーチン(ISR)の擬似コードは次のようになります。

i2c_event_isr() {
  switch( i2c_event ) {

    case master_start_bit_sent: 

      send_address(...);
      break;

    case master_address_sent:
    case data_byte_finished:

      if ( has_more_data() ) {
        send_next_data_byte(); 
      } else {
        send_stop_condition();
      }

      break;
    ...
  }
}
2
追加された