ししかわのマウス研修

STM32をI2Cスレーブにする – ししかわのマウス研修 Part.24

ししかわのマウス研修

ししかわです。 社員研修の一環で、マウスを自作して大会に出場します。

記事一覧

4月はブログの移行などがあり、しばらくご無沙汰でした。 今日から引き続き週1回更新でやっていきます。
前回と今回で、コアモジュール(M5Stack)とマウスモジュール(STM32)それぞれにI2C通信処理を書いて、両者で指令をやりとりできるようにするのが目標です。前回はM5StackをI2Cのマスタとして設定しました。今回はSTM32をI2Cのスレーブとして設定していきます。

今回もSTM32CubeMXを使ってコード雛形を生成し、必要な処理を追記する、という流れで開発します。

私が開発するマイクロマウスのソースコードは、GitHubで順次公開していきます。

meganetaaan/M5Mouse
A micro mouse module made for M5Stack. Contribute to meganetaaan/M5Mouse development by creating an account on GitHub.

STM32CubeMXの設定

まずはSTM32CubeMXからI2Cを有効化します。

  • 左メニューから「Connectivity->I2C1」を選択します
  • 「Mode->I2C」を「I2C」に変更します
    • このとき自動的に適当なピンに機能が割り当てられます(PA6にI2C_SCL、PA7にI2C_SDA)
  • 「Configuration->Parameter Settings」を次の通り変更します
    • Primary slave address: 0x64
      • スレーブを複数繋ぐ場合に「このSTM32」を特定するためのアドレスです。
      • M5Stack側のI2Cバスには既に別のスレーブ(9軸センサMPU9250)が繋がっているので、このアドレスと被らないように設定します。

雛形の出力

「GENERATE CODE」をクリックして雛形を出力します。 生成されたI2C設定の処理がmain.cに出力されています。

/**
  * @brief I2C2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_I2C2_Init(void)
{
  /* USER CODE BEGIN I2C2_Init 0 */
  /* USER CODE END I2C2_Init 0 */
  /* USER CODE BEGIN I2C2_Init 1 */
  /* USER CODE END I2C2_Init 1 */
  hi2c2.Instance = I2C2;
  hi2c2.Init.ClockSpeed = 100000;
  hi2c2.Init.DutyCycle = I2C_DUTYCYCLE_2;
  hi2c2.Init.OwnAddress1 = 0;
  hi2c2.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c2.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c2.Init.OwnAddress2 = 0;
  hi2c2.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c2.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN I2C2_Init 2 */
  /* USER CODE END I2C2_Init 2 */
}

処理の追記

I2Cマスタと信号を送受信する処理を追記していきます。今回は「Who am Iレジスタの読み取り」を実装してみます。Who am Iレジスタは共通のインタフェースを持つI2Cデバイスの型番を識別するなどの用途で使われます。

I2Cスレーブから見るとWho am Iレジスタの読み取り処理は次のようになります。

  • I2Cスレーブ(STM32)はI2Cマスタ(M5Stack)から、1バイトのコマンド信号を受信する
  • 受け取ったコマンドが「WHO_AM_I(0x68)」ならば、
    I2CスレーブはI2Cマスタへ自身のデバイスID(0x01)を送信する

エンコーダやセンサの読み取り処理と同様に「メインループ内で同期的に読み書きをする方法」または「受信時に割り込み処理を発生させる方法」の2通りがあります。今回は簡単のために同期的に読み書きする方法で動作確認をします。

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* 略 */
  /* USER CODE BEGIN 2 */
  uint8_t status = 0;
  uint8_t commandBuffer[4] = {0};
  uint8_t whoami[1] = {WHO_AM_I};
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    // I2Cで同期的に1バイト受け取る
    if (HAL_I2C_Slave_Receive(&hi2c1, (uint8_t *)commandBuffer, 1, 1000) == HAL_OK) {
      switch(commandBuffer[0]) {
        case COMMAND_WHO_AM_I:
          // I2Cで同期的に1バイト送信する
          status = HAL_I2C_Slave_Transmit(&hi2c1, (uint8_t *)whoami, 1, 1000);
          break;
        default:
        ; // Do nothing
      }
    }
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

また、これに合わせてマスタ側の処理も追記します。マスタ側から見ると上記の流れは「スレーブにWHO_AM_Iコマンドを送信し、続けて1バイトのデバイスIDを受け取る」と言えます。

マウスモジュールのドライバクラスのwhoamiメソッドは次のようになります。

export default class Mouse {
// ...
private readByte(register: number): number {
const buf = this.u8a.buffer
this.i2c.write(register)
this.i2c.read(1, buf)
return this.u8a[0]
}
// ...
public whoami(flag: boolean): number {
return this.readByte(REGISTORY.WHO_AM_I)
}
}

このメソッドを使う側の処理もmainに追記します。M5Stackのボタンを押すとデバイスIDを読み取って画面に表示します(詳細はソースを参照)。

動作確認

前回セットアップしたM5StackとSTM32 Blue Pill Boardを2本の信号線(SDA、SCL)で接続し、それぞれを共通のグラウンドに接続します。

プログラムをM5Stack、STM32 BluePill Boardにそれぞれ書き込んで起動します。

以上でI2Cによる通信処理ができました。

なお、特に何の説明もなくM5Stackに顔が表示されていますが、自作のアバター表示ライブラリを使っています。マイクロマウスの本筋から外れるので本連載では紹介しませんが、マイクロマウス大会ではこの顔が走る姿をお披露目できるかもしれません。乞うご期待!

ここまで、基本的な部品(モータ、エンコーダ、センサ)の駆動とM5Stack-STM32間の通信を確認してきました。次回からは本格的にマウスの筐体や基板の制作を進めます。

タイトルとURLをコピーしました