STM32でI2C通信やる方法（1）

# ライブラリからデータシートの間
I2C通信のセンサを「ライブラリ」でしか動かしたことがない人って（僕を含めて）結構いる気がする。サクッと動かすならライブラリは便利だけど、そもそもライブラリがなかったり、Arduino(.ino)でしか作られていなかったりするケースは多い。ライブラリがないという理由でデバイスを使うのを諦めたり、マイコンを限定したりするのは非常に悔しい！ので、自力で動かせるようになりたい。

最終目標はデータシートのみを頼りにSTM32のI2Cドライバを書くことだが、いきなりは無理なのでそのためのステップを以下に書いた。
ステップ１ ArduinoサンプルコードをSTM32で書き直す。
ステップ２ ArduinoライブラリをSTM32で書き直す。
ステップ３ データシートを見ながら自力でコーディングする。

サンプルコードとライブラリの違いは、個人が書いたものか公式が書いたものか。個人が書いたものは行数が短く構造が比較的単純なので書き直しやすい。公式はレイヤ毎にファイルを分けていたり複雑な事が多いので解読が大変な印象。

# MPU9250+STM32を動かす
MPU-9250搭載 IMUピッチ変換基板　をSTM32で動かす。
開発環境はIAR Embededを使っているが別に何でも良い。ただしCubeMXは必須。
### 用意するもの
・STM32F401 Nucleo
・IMUピッチ変換基板
https://www.switch-science.com/catalog/2845/

STM32の動作電圧が5V、IMUの電圧が3.3Vなので、レベル変換モジュールが必要だと思っていたが、**STM32の出力ピンは3.3V**らしく、レベル変換は不要だった。
※変換基盤を挟んだことが原因で丸1日動かないなーと格闘していた。

### STM32のI2C準備
CubeMXを使って、STM32のI2Cピンを設定する。
色々なサイトに書いてあるので割愛。リクエストがあれば書く。
https://qiita.com/AkimotoMasane/items/da77eddc61f4ca7b8616

### 接続
|STM32 |MPU9250  |役割  |
|---|---|---|
|PB8  |SCL  |SCL  |
|PB9  |SDA  |SDA  |
|3.3V  |VDD  |MPUの電源  |
|GND  |GND  |GND  |

![20200405_235112.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581044/93669a14-6feb-5aba-bf90-0853b266c07c.jpeg)


### Arduinoのコード解読
今回サンプルコードとして、以下のサイトを参考にさせて頂いた。
http://akiracing.com/2018/02/04/how_to_use_mpu9250/
無断転載禁止のため、参考元のArduinoのソースコードは記載しない。

肝心のI2Cデータ取得処理だが、ざっくり言うと以下の流れ。
(1) I2C通信を開始する
(2) MPUのスリープモードを解除
(3) 加速度センサの測定レンジの設定
(4) センサのデータを読み出す

(1)～(3)をmain()の初期化部分、(4)を周期的に実行されるwhileループ等に書く。それだけでいい。(1)～(4)はそれぞれ**1行**しかないから楽勝だ。順番に見ていく。
※実際には(4)で読み出されるのは生値なので、読み出した値から加速度を出す計算が必要になるが、そこは調べたりコピペで何とかなる。通信できることが大事だ。

#### (1) I2C通信を開始する
Arduinoの場合はWire.begin()という関数が用意されているのでそれを呼ぶだけで良いが、STM32の場合はそれすらしなくて良い。CubeMX様が以下の関数をコーディングしてくれている。

```c:main.c
MX_I2C1_Init();
```

これでI2Cの初期化はOK。ちなみにArduinoのWire.begin()の場合は、自身をマスターとする場合は引数に何も指定しない。STM32の場合は、マスターにするかどうかはCubeMXのConfigrationで設定するもので、ソースでは指定しない。

#### (2) MPUのスリープモードを解除
MPU9250を使用するためには奴を目覚めさせる必要がある。[スイッチサイエンスのサイト](https://www.switch-science.com/catalog/2845/)から参照できるレジスタマップを読むと、レジスタ PWR_MGMT_1(0x6B)に「0」がセットされるとスリープが解除されることが書いてある。詳しいことは今は理解しない。大事なのはArduinoで書かれたスリープモード解除処理（レジスタ書き込み）をSTM32で書き直す方法だ。

STM32では、I2Cデバイスにデータを書き込む（送信する）関数と、読み出す（受信する）関数がそれぞれ用意されている。

__書き込み関数__

```c:main.c
HAL_StatusTypeDef HAL_I2C_Mem_Write(
I2C_HandleTypeDef *hi2c,   // 第一引数：I2C構造体
uint16_t DevAddress,      // 第二引数：スレーブデバイスのアドレス
uint16_t MemAddress,      // 第三引数：スレーブデバイスのレジスタアドレス
uint16_t MemAddSize,      // 第四引数：スレーブデバイスのレジスタサイズ
uint8_t *pData,           // 第五引数：書き込みデータへのポインタ
uint16_t Size,            // 第六引数：書き込みデータサイズ
uint32_t Timeout)        // 第七引数：タイムアウト時間
{}
```

スリープモード解除は、スレーブデバイスのレジスタへの書き込みなので、上記の関数を呼び出せばよい。コールは以下のように記述する。

```c:main.c
HAL_I2C_Mem_Write(&hi2c1,I2CADRESS,0x6b,I2C_MEMADD_SIZE_8BIT,(uint8_t*)ret,0x01,100);
```

第一引数はCubeMX御用達の構造体、第二引数はIMUのI2Cアドレス（ただし1bitシフトが必要 [^1]）、第三引数はIMUのPWR_MGMT_1レジスタのアドレス(0x6B)、第四引数はレジスタは1バイトなので1バイト、第五引数は書き込み値なので自分で定義した配列ret[1]={0}のアドレス、第六引数は書き込みデータサイズなので1バイト。

#### (3) 加速度センサの測定レンジの設定
加速度センサの測定レンジを設定する。
(2)同様、I2C書き込みの関数を使用する。

```c:main.c
HAL_I2C_Mem_Write(&hi2c1,I2CADRESS,0x1c,I2C_MEMADD_SIZE_8BIT,(uint8_t*)ret,0x01,100);
```

(2)と違うのは、第三引数をACCEL_CONFIG(0x1c)に変更したところと、第五引数の書き込み値を0x18にしたところ。

#### (4) センサのデータを読み出す
いよいよデータ読み出しとなるので、読み出しの関数を使う。

__読み出し関数__

```c:main.c
HAL_StatusTypeDef HAL_I2C_Mem_Read(
I2C_HandleTypeDef *hi2c, // I2C構造体
uint16_t DevAddress, // スレーブデバイスのアドレス
uint16_t MemAddress, // スレーブデバイスのレジスタアドレス
uint16_t MemAddSize, // スレーブデバイスのレジスタサイズ
uint8_t *pData, // 読み出しデータへのポインタ
uint16_t Size, // 読み出しデータサイズ
uint32_t Timeout // タイムアウト時間
){}
```

書き込み関数と引数の形式は変わらない。 

```c:main.c
HAL_I2C_Mem_Read(&hi2c1, I2CADRESS, 0x3b,I2C_MEMADD_SIZE_8BIT,(uint8_t*)ret ,14,100);
```

ACCEL_XOUT_H14(0x3b)から14バイト分を配列retに代入する。

### コード
```c:main.c
int main(void)
{
  // 省略
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_I2C1_Init();

/* USER CODE BEGIN 2 */
  HAL_I2C_Mem_Write(&hi2c1,I2CADRESS,0x6b,I2C_MEMADD_SIZE_8BIT,(uint8_t*)ret,0x01,100);```

ret[0] = 0x18;
HAL_I2C_Mem_Write(&hi2c1,I2CADRESS,0x1c,I2C_MEMADD_SIZE_8BIT,(uint8_t*)ret,0x01,100);```
  
/* USER CODE BEGIN WHILE */
  while (1)
  {
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
    ret [0] = 0x00;
    HAL_I2C_Mem_Read(&hi2c1, I2CADRESS, 0x3b,I2C_MEMADD_SIZE_8BIT,(uint8_t*)ret ,14,100); //0x3bから，14バイト分をaccGyroDataにいれる
   // 省略(加速度の計算を行う)
  }
}
```
とにかく取得できたからヨシ！！

[^1]: https://gsmcustomeffects.hatenablog.com/entry/2017/04/03/023111