STM32Cube HAL I2Cドライバをマスターモードで使用する

背景

STマイクロエレクトロニクスは、各MCUシリーズに対して、特にHAL(Hardware Abstraction Layer)ドライバを含む組み込みファームウェアパッケージを提供しています。このドライバは、MCUとその周辺機器の複雑さを抽象化し、移植性の高い方法で設計された高レベルのAPIセットを提供します。 I2Cインターフェース は、そのような周辺機器の1つです。HAL I2Cドライバを使い始めるための初心者向けガイドは、Shawn Hymelによるこのチュートリアルを参照してください。 I2Cマスターデバイスが使用できる通信機能の概要については、以下をお読みください。

HALドライバは、そのデータ処理機能として3つのプログラミングモデルをサポートしています。ポーリング、割り込み、およびDMAです。ポーリング機能はブロッキングモードで動作し、操作が完了するまで元に戻りません。アプリケーションがハングアップするのを防ぐために、ユーザーは適切なタイムアウト値を指定する必要があります。 割り込みとDMA機能はノンブロッキングモードで動作します。つまり、これらの機能は動作が開始された後に元に戻り、バックグラウンドで動作が継続する間、アプリケーションの実行を継続することができます。ただし、操作完了後に発生する信号を処理するためのコールバック関数を設定し、有効化する必要があります。

ブロッキングモードでI2Cマスターとして動作する場合、スレーブデバイスと通信に使用できるAPI関数は次の4つです。

  • HAL_I2C_Master_Transmit()
  • HAL_I2C_Master_Receive()
  • HAL_I2C_Mem_Write()
  • HAL_I2C_Mem_Read()

ノンブロッキング機能については、割り込みモードとDMAモードが同等の機能を有しています。しかし、このリファレンスガイドでは、ポーリング機能がよりわかりやすいため、ポーリング機能を使用します。

データ送信

マスターデバイスからスレーブデバイスへのデータ送信は、ほとんどの場合、複雑ではありません。 HAL_I2C_Master_Transmit()またはHAL_I2C_Mem_Write()関数のいずれかを使用することができます。どちらを選択するかは、メッセージの構造によって、あるいは単に個人の好みによって決まります。

HAL_I2C_Master_Transmit()

このAPI関数の関数プロトタイプを以下に示します。最初のパラメータは単純にコンフィギュレーション構造体です。この構造体の作成方法については、getting started tutorial(入門チュートリアル)に詳しく説明されています。第2のパラメータは、スレーブデバイスのアドレス(1つ左にシフトする必要があります)です。第3、第4のパラメータはそれぞれ、データバッファへのポインタと、バッファからスレーブ機器に送信すべきデータ量です。最後のパラメータは、ミリ秒単位のタイムアウト時間です。ユーザーは引数としてHAL_MAX_DELAYを与えることで、タイムアウトを無効にし、関数が戻るまで無限にブロックできることに注意してください。

HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, 
										  uint16_t DevAddress, 
										  uint8_t *pData, 
										  uint16_t Size, 
										  uint32_t Timeout);

この関数を呼び出した結果のI2Cシーケンスを以下に示します。網掛け部分は、スレーブデバイスによって信号が駆動される場所を示しています。この場合、スレーブは自分のアドレス(と書き込みビット)およびそれに続くデータバイトのみを認識します。送信されるデータバイト数は、関数呼び出しに提供されるSize(サイズ)引数によって決定されることを思い出してください。

この関数の使用例として、バッファの内容をアドレス0x40のスレーブデバイスに送信するコードを考えてみましょう。 I2C通信をロジックアナライザでキャプチャし、その結果の波形も下記に示します。開始条件はありますが、デコードされたプロトコルのグラフィックにはラベルを貼るスペースがないことに注意してください。

uint8_t dataBuffer[10] = {0x03, 0x01};
HAL_I2C_Master_Transmit(&hi2c1, (0x40 << 1), dataBuffer, 2, HAL_MAX_DELAY);

HAL_I2C_Mem_Write()

この機能は、マスターデバイスがスレーブデバイスの特定のメモリロケーションに書き込みを行いたい場合の一般的なシナリオを対象としています。例えば、ほとんどのI2Cセンサは、設定の変更や測定の開始に使用するコンフィギュレーションレジスタとコマンドレジスタを含んでいます。本機能のプロトタイプは以下の通りです。

HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, 
									uint16_t DevAddress, 
									uint16_t MemAddress, 
									uint16_t MemAddSize, 
									uint8_t *pData, 
									uint16_t Size, 
									uint32_t Timeout);

明らかに、HAL_I2C_Master_Transmit()関数と同じパラメータをすべて含み、さらに2つのパラメータを追加しています。最初のMemAddressは、バッファの内容が書き込まれるスレーブデバイスの開始メモリアドレスです。2番目のMemAddSizeは、単純に内部メモリアドレスのサイズ(I2C_MEMADD_SIZE_8BITまたはI2C_MEMADD_SIZE_16BIT)です。これでI2Cのシーケンスが以下のように表示されます。

HAL_I2C_Master_Transmit()で生成されるシーケンスと同じですが、MemAddress引数がスレーブアドレスの後、データバッファの最初のバイトの前に送信される点が異なります。以下の例では、HAL_I2C_Mem_Write()関数を使用して、スレーブデバイスのメモリアドレス0x03に位置するレジスタに値0x01を書き込んでいます。ロジックアナライザが捉えた I2C動作は、上記のHAL_I2C_Master_Transmit()関数の例で示したものと全く同じであることに注意してください。

uint8_t dataBuffer[10] = {0x01};
HAL_I2C_Mem_Write(&hi2c1, (0x40 << 1), 0x03, I2C_MEMADD_SIZE_8BIT, dataBuffer, 1, HAL_MAX_DELAY);

データ受信

マスターからスレーブへのデータ送信とは異なり、スレーブからマスターへのデータ受信に利用できる2つの機能は互換性がありません。一方はデータの受信のみで、もう一方は最初にデータを受信するアドレスを指定します。

HAL_I2C_Master_Receive()

本API関数は、スレーブ機器に単純にデータを要求するために使用されます。以下のプロトタイプでは、パラメータがHAL_I2C_Master_Transmit()のものと同じであることに注目してください。しかし、この場合、受信データを格納するためにデータバッファが使用され、Sizeパラメータは、Nackを送信する前に何バイトのデータを受信するかを指定します。

HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, 
										 uint16_t DevAddress, 
										 uint8_t *pData, 
										 uint16_t Size, 
										 uint32_t Timeout);

本機能の動作を説明するシーケンス図を以下に示します。前述したいずれのデータ送信機能とも根本的に異なることに注目ください。まず、アドレスバイトの方向ビットは、書き込みではなく、読み出しに設定されています。次に、スレーブが自分のアドレスを確認した後、マスターにデータバイトを送信し始めます(網掛けのフィールドは、スレーブが信号を駆動していることを示すことを思い出してください)。そして今度はマスターが、受信する予定のバイトまで、受信したバイトを確認(Ack)しなければなりません。Nackの後にStop条件を送信することで、スレーブにデータ送信の停止を指示します。

以下のコード例では、マスターがアドレス0x40のスレーブデバイスに3バイトのデータを要求しています。ロジックアナライザのキャプチャを観察すると、動作完了後のデータバッファには{0x00, 0x68, 0xF0}の値が格納されていることがわかります。

HAL_I2C_Master_Receive(&hi2c1, (0x40 << 1), dataBuffer, 3, HAL_MAX_DELAY);

HAL_I2C_Mem_Read()

本API関数は、スレーブデバイスに特定のメモリアドレスからデータを要求するために使用されます。ここでは、測定値がスレーブ上の特定のレジスタに格納されており、マスターがそのレジスタからデータを読み取る必要があるI2Cセンサを考えてみましょう。下図のように、関数プロトタイプにはHAL_I2C_Mem_Write()関数と同じ引数が含まれています。しかし、上記のケースと同様に、データバッファは送信用としてではなく、受信データを保存するために使用されます。

HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, 
								   uint16_t DevAddress, 
								   uint16_t MemAddress, 
								   uint16_t MemAddSize, 
								   uint8_t *pData, 
								   uint16_t Size, 
								   uint32_t Timeout);

この機能のユニークな点は、まずI2Cの送信操作を行い、スレーブデバイスにどのメモリアドレスからデータを送信するかを伝える点にあります。この後、スタート条件が繰り返され、受信操作が開始されます。I2Cの全シーケンスを以下に示します。HAL_I2C_Mem_Read()は、ブロッキングモードで繰り返し開始条件を生成することができる唯一の関数であることに注意してください。繰り返し起動が必要な場合、HAL_I2C_Master_Transmit()を呼び出した直後にHAL_I2C_Master_Receive()を呼び出すだけでは不十分です。

次のコード例では、アドレス0x40のスレーブデバイスからメモリアドレス0x01から始まる2バイトのデータを受信します。ロジックアナライザのキャプチャから、動作完了時にバッファに{0x68, 0x90}が格納されていることに注意してください。

HAL_I2C_Mem_Read(&hi2c1, (0x40 << 1), 0x01, I2C_MEMADD_SIZE_8BIT, dataBuffer, 2, HAL_MAX_DELAY);

/* This is NOT guaranteed to work as a substitute for the above! */
// dataBuffer[0] = 0x01;
// HAL_I2C_Master_Transmit(&hi2c1, (0x40 << 1), dataBuffer, 1, HAL_MAX_DELAY);
// HAL_I2C_Master_Receive(&hi2c1, (0x40 << 1), dataBuffer, 2, HAL_MAX_DELAY);

要約

STMicroelectronicsが提供するHAL I2Cドライバは、マスターデバイスがスレーブデバイスとブロッキングモードまたはノンブロッキングモード(ブロッキングモードの方がより単純)で通信することができます。ブロッキングモードでスレーブデバイスにデータを送るには、HAL_I2C_Master_Transmit()関数とHAL_I2C_Mem_Write()関数のどちらかを使用することができます。この2つは互換性があります。唯一の違いは、スレーブ上のメモリアドレスがHAL_I2C_Mem_Write()において引数として明示的に指定されることです。用途に応じて最適なものをユーザーが決定する必要があります。スレーブデバイスからデータを受信するには、HAL_I2C_Master_Receive()関数または、HAL_I2C_Mem_Read()関数のいずれかを使用することができます。しかし、この2つの機能は互換性がありませんHAL_I2C_Master_Receive()は単にスレーブデバイスからデータを読み取るのに対し、HAL_I2C_Mem_Read()は最初にメモリアドレスをスレーブに送信し、次にスタート条件を繰り返し、続いて読み取り操作によりそのメモリアドレスにあるデータを取得します。どの機能を使用するかは、スレーブデバイスが期待するI2C シーケンスに依存します。




オリジナル・ソース(English)