takashiskiのブログ

覚書の殴り書き

STM32でI2Cスレーブ書く時にはまった箇所

STM32G0を使ってI2Cスレーブデバイス作ろうとしました。 サンプルコードがほぼ皆無で、わずかに存在するコードでも部分的に動いたり動かなかったりという状態でした。 わかったことが揮発しないうちに書いときます。

貴重なサンプルコード

割り込みI2Cのコードは以下の二つしかないと思います。

これと、stm32g0xx_hal_i2c.c を読んで雰囲気で使い方を見ていくしかありませんでした。

サンプルコードを実装した挙動

AdafruitsのQtPy RP2040とCircuitPythonを使って動作確認をしました。

  • readかwriteに対して1回だけ返答がある
  • それ以降応答が全くなくなる

I2C割り込みを再度有効にする箇所はmain()内に置かないとダメっぽい?

サンプルコードではHAL_I2C_SlaveTxCpltCallback()HAL_I2C_SlaveRxCpltCallback()の中でHAL_I2C_EnableListen_IT()を呼んでいます。

色々試したところ、この二つの関数内で呼んでも再度割り込みを有効化することができないような挙動でした。 このCallback関数も割り込み処理の一部なのでしょうか?

フォーラム等を参考にした結果、両関数内ではフラグを立てる処理だけを行い、main()内のwhileループの中でフラグを見て再有効化処理をすると動作しました。

__IO uint8_t Xfer_Complete=0;
void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c){
    Xfer_Complete = 1;
}

void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c){
    Xfer_Complete = 1;
}
  /* USER CODE BEGIN WHILE */
  while (1)
  {
      if(Xfer_Complete == 1){
            HAL_I2C_EnableListen_IT(&hi2c2);
            Xfer_Complete=0;
      }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

フォーラムの上記内容があった投稿では「HAL_I2C_AddrCallback()の中では処理振り分けがうまく行かないからmain()に移動する必要がある」みたいなことも書いてありましたが逆にこっちは特に挙動に変化はなかったです。

I2Cに使うピンのGPIO settings>Maximum output speedをvery highにする

デフォルトだとlowですが、どうやらlowだとダメみたいです。 ひょっとしたら、これは使おうとしたボードがI2C用端子を用意しているのに外部プルアップ抵抗がされておらず、i2cスレーブ側にもプルアップ抵抗を用意していなかったので内部プルアップ抵抗を使ったことが理由かもしれません。

この設定を見直すきっかけになったのは以下の記事でした。

CubeIDEによるI2C設定にご注意 – DSP空挺団

なお、highだと連続してread/writeをすると相変わらず応答なしになってしまいました。 read/writeのあとに0.01秒くらい待ちをいれないとうまく動かなかったです。

veryhighにしたら連続して呼び出しても問題なくなりました。

その他

なんかTimer使ってタイムアウト処理を考えるといいらしいので元気だったら考えます。