STM32 HALを使ってI2Cでメモリーにアクセスする

STM32 HALを使ってI2Cでメモリーにアクセスする

今回はHALのI2Cを使ってメモリー(EEPROM)にアクセスしてみます。
以前I2Cを使った気圧センサーの記事を書きましたが、EEPROMの方がより実用的かも知れません。

投稿時の開発環境を記しておきます。

PC:Windows10 OS
IDE: STM32CubeIDE Version1.3.0
Configurator: STM32CubeMX Version5.6.0
Board: STM32Nucleo-F401RE

I2C通信の概要

・フィリップス(現NXP)セミコンダクターズが開発した通信方式
・SCLとSDAの2本の通信ラインを使って双方向で通信が可能
・通信速度は100k, 1Mbits/secなどいくつか選択肢がある
・マスタとスレーブが存在し、マルチマスタでの構築も可能

I2Cの詳しい仕様については こちら を見て頂ければと思います。

スレーブとなるターゲット

STM32(をマスタ)で制御し、スレーブとなるデバイスメモリーはマイクロチップ社製の 24LC256 を使ってみました。

EEPROM は Electrically Erased PROM の略で電気的に消去可能な不揮発性メモリーです。

アプリケーションで設定値を保存したい場合などに使うことができます。

データシートは DataSheet 24LC256 を確認してください。

やや雑談になってしまいますが、

24の部分がI2Cインターフェースを表しています。
25だとSPIです。

LCの部分は使用電圧だったり、通信速度だったりするようです。
LCでは 2.5~5.5V ですから、3.3Vで使用可能です。
256はサイズで 256Kbit (32K x 8bit) を表しています。

ピン番号の説明をしておきますと、

1:A0
2:A1
3:A2

1~3番はアドレス指定するピンになっています。
これにより最大8つまでのデバイスを、ひとつのI2Cバスにつなぐこどができます。
今回は1つのデバイスだけをつなぐので、これらを全てGNDにつないでおきます。

4:GND

GNDピンです。

5:SDA

マイコンのSDAをつなぎます。

6:SCL

マイコンのSCLをつなぎます。

7:WP

ハイアクティブのライトプロテクトです。
プロテクトしたら書き込みができないので、GNDにつなぎます。

8:VCC

3.3Vをつなぎます。

接続

下表のとおりに接続します。
SDA と SCL の信号は3.3Vにプルアップしてください。
今回は 10kΩ でプルアップしました。

プロジェクトを作成する

IDEを起動し、File- New – STM32 Project を選択し、Target Selection ウィンドウが出たら Board Selector タブを選択し Boards List から NUCLEO-F401RE を選択し Next ボタンを押します。

Project 名に F401I2cHAlMem と入力し、Finishボタンを押します。
Initialize all peripherals with their default Mode ? と聞いてくるので Yesを押します。
This kind of project is associated with the STM32CubeMx perspective. Do you want to open this perspective now ? と聞いてくるので Yesを押します。

コードジェネレーターでI2Cインターフェースを追加する

今回使うI2Cの端子はPB8とPB9です。(画像の左上付近の赤枠で囲った部分)

PB8を右クリックして選択し、I2C1_SCL を選択します。
PB9を右クリックして選択し、I2C1_SDA を選択します。

端子の色がグレーから黄色にかわることを確認します。

そして Pinout & Configuration – Categories – Connectivity – I2C1 を選択し、
I2C1 Mode and Configuration – Mode の I2C のリストから I2C を選択します。

Project Explorer で F401I2cHAlMem を選択し、Project – Build Project でビルドします。

端子の色が黄色から緑色にかわります。

また、main.cに MX_I2C1_Init()が生成されていることを確認します。

アクセスのしかた

まず最初に指定するコントロールバイトについて見ていきましょう。
このデバイスの場合、上位4ビットは 1010 のパターンになります。
A2-A0はGNDに接続するので、000 のパターンになります。
素の状態のアドレスは 0x50 ですが、ご存じの通りI2Cでは左に1ビットシフトした状態で使うので
0xa0 を使い、更に最下位のビットは read時には 1、write時には 0 にします。
ただし read時に 1 をセットする役目は HAL の関数がやってくれるのでプログラム上で意識する必要はありません。

コントロールバイトは16進表記で以下のようになります。

read時: 0xa1
write時: 0xa0

バイトライト

バイト単位で書き込みを行うフォーマットは以下の通りです。
このデバイスは32KByteなので16ビットアドレスの最上位ビットは意味を持たないために、Don’t cate となっています。

ページライト

ページ単位で書き込みを行うフォーマットは以下の通りです。
1ページは64バイトです。

ランダムリード

ランダムに読むフォーマットは以下の通りです。

シーケンシャルリード

シーケンシャルに読むフォーマットは以下の通りです。

コードを追加する

MX_I2C1_Init()関数内で完結するコードを書いてみました。

static void MX_I2C1_Init(void)
{
  /* USER CODE BEGIN I2C1_Init 0 */
  /* USER CODE END I2C1_Init 0 */
  /* USER CODE BEGIN I2C1_Init 1 */
  /* USER CODE END I2C1_Init 1 */
  hi2c1.Instance = I2C1;
  hi2c1.Init.ClockSpeed = 100000;
  hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
  hi2c1.Init.OwnAddress1 = 0;
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c1.Init.OwnAddress2 = 0;
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN I2C1_Init 2 */

  uint8_t sbuf[128] = {0};
  uint8_t rbuf[128] = {0};
  HAL_StatusTypeDef s;

#define DEVICE_ADDR 0xa0
#define BYTES_PER_PAGE 64

  for (int i = 0; i < BYTES_PER_PAGE; i++)
  {
	  sbuf[i] = i;
  }
  s = HAL_I2C_Mem_Write(&hi2c1, DEVICE_ADDR, 0, I2C_MEMADD_SIZE_16BIT, sbuf, BYTES_PER_PAGE, 1000);
  s = HAL_I2C_Mem_Read(&hi2c1, DEVICE_ADDR, 0, I2C_MEMADD_SIZE_16BIT, rbuf, BYTES_PER_PAGE, 1000);

  /* USER CODE END I2C1_Init 2 */
}

コードの解説

Memoryリード・ライト専用のAPIがあるようなので使ってみました。
HAL_I2C_Mem_Write() と HAL_I2C_Mem_Read() です。

第1引数:インスタンス(構造体へのポインタ)
第2引数:I2Cのデバイスアドレス(左に1ビットシフトした状態の値)
第3引数:メモリーのアドレス
第4引数:メモリーアドレスのサイズ
第5引数:リードまたはライトするバッファ
第6引数:リードまたはライトするバイト数
第7引数:タイムアウト値[msec]

HALのマニュアルが英語なのは仕方ないとして、パラメータの情報とか、もっと詳しく書いて欲しいところです。
例えば第4引数には何を設定すれば良いのかピンと来ませんでした。

4番目の引数には I2C_MEMADD_SIZE_16BIT を指定しました。

ここにいくつを設定すれば良いのかマニュアルを読んだだけでは気がつかないかも知れません。
私はHALのAPI関数の中を覗いてみて理解できました。

上のフォーマットではアドレスが2バイト(16ビット)なので、その場合には I2C_MEMADD_SIZE_16BIT を指定します。

24LC01 とか 24LC02 の場合には容量が少ないのでアドレスが1バイト(8ビット)ですみます。
その場合には I2C_MEMADD_SIZE_8BIT を指定します。

ですけど今時、わざわざ容量の少ないデバイスを選ぶ必要もないと思います。
24LC256でも100円程度で購入できますので。。

もうひとつの注意点は6バイト目の引数 BYTES_PER_PAGE が 64を超えないようにすることです。
64を超えると、リード用に指定したバッファの先頭から上書きされてしまいます。
(このメモリの場合1ページのバイト数が 64 です)

メモリーのアドレス 0~63番地に対して 0~63を書き、読み直しています。
書いた値が受信バッファに正しく読めれば成功です。

ランダムとかシーケンシャルとか意識することなく使うことができました。

I2Cメモリーの場合には関数1つで読み書きできるので、まあまあ扱いやすいのではないでしょうか。

今回は配線作業もあるので、少々時間がかかりました。

皆さまの環境でも、うまく読み書きできましたか?

よろしければ I2Cで気圧センサーを使う の記事もご覧ください。

HALカテゴリの最新記事