シリアル通信SPI【STM32のSPI詳細】

STM32に内蔵のペリフェラルSPIの使い方を解説しています。この章ではSPI仕様のEEPROMへのデータの読み込および書き込を例に解説しています。

シリアル通信SPIとは

めかのとろ

シリアル通信SPI同期型シリアル通信の一つです。クロック(SCLK)入力MOSI出力MISOチップセレクト(CS)の4本の信号線で通信を行います。

めかのとろ

SPIに接続されるデバイスにはマスタスレーブがあります。1つのマスタデバイスに共通のバスで複数のスレーブデバイスを接続でき、通信時にチップセレクト(CS)で通信対象のデバイスを選択します。

シリアルSPI通信
めかのとろ

SPI通信はデバイスにより通信フォーマットは様々ですが、チップセレクト(CS)信号で通信対象のデバイスを選択し、通常は8ビット(1バイト)データを一単位としてコマンドやアドレスなどを送信(書き込み)し、デバイスレジスタからの応答データを受信(読み込み)します。I2C通信と比べると配線は4本で多いですが、通信の仕組みがシンプルで高速なのが特徴です。

めかのとろ

STM32のSPIコントローラはマスタおよびスレーブのどちらも対応できますが、ここではEEPROM(Rohm社製BR25G640-3)を使用したSPIマスタの解説をします。SPIはマスタが主導で、命令を実行するたびにクロックを発生させて8ビットデータをやりとりします。

SPI型EEPROM接続回路
めかのとろ

EEPROMの命令モードは下記6種類です。このうちステータスレジスタの読み出し命令を与えた時のタイムチャートをみてみます。このEEPROMでは書き込み(MISO)はクロック(SCLK)の立ち上がりエッジに同期してデバイス内部に取り込まれ、読み込み(MOSI)はクロック(SCLK)の立下りエッジに同期してデバイス内部に取り込まれます。1バイト(8ビット)単位のリードステータスレジスタ(RDSR)コマンドコードを送信すると次の1バイト(8ビット)単位でステータスデータが返ってきます

めかのとろ

メモリデータの読み書きの場合にはリード(READ)やライト(WRITE)コマンドコードをデータ格納用メモリのアドレス(2バイト)を上位と下位の1バイトずつ分けたものとセットで送受信します。この例のEEPROMのアドレスは2バイトですが、デバイスにより異なる場合があります。

ステータスレジスタ読み出し命令

SPI通信初期化手順

めかのとろ

まず、他のペリフェラルと同様に初期化を解説します。

目的:使用するSPIはSPI1でPB3-5を使用
■ チップセレクトはPE0

 ① GPIOおよびSPI1にクロック供給 

めかのとろ

使用するGPIOおよびSPI1にクロックを供給します。APB2バスに接続していますのでAPB2に供給します。

 ② GPIOの設定 

めかのとろ

SPI1に使用するポートはSCK,MISO,MOSIでPB3-5をオルタネートプッシュプル出力に指定します。ここではGPIO_PinRemapConfig(GPIO_Remap_SPI1, ENABLE)を実行してリマップしてPB3-5およびPA15をSPI1に設定します。

めかのとろ

このうちPA15はSTM32のSPI_NSSでマスタとするときは使用しません。さらに、PB3とPB4に関してはデフォルトではデバッグ用ポートの機能が割り当てられていますのでそのままでは使用できません。そこで、通常のGPIOポートに開放するためGPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE)を実行しています。リマップでなくデフォルトのSPIを使用する場合はこの処理は不要です。

めかのとろ

チップセレクトCS用にはPE0を通常のプッシュプル出力に設定しています。

 ③ SPIの初期設定 

めかのとろ

SPI通信の初期設定を実行します。まず初期化のはじめに対象のチップセレクトCS(PE0)をHレベルにしてSPI通信を受け付けないようにしておきます。

SPI初期化関数実行例: SPI_Init(SPI1, &SPI_InitStructure);

めかのとろ

関数の第1引数は設定対象のSPI(SPI1-3:マイコンによります)、第2引数は構造体メンバになっていて以下に示します。

めかのとろ

SPI_Directionメンバにはデータの方向性を指定します。通常は2線・全二重を指定します。

めかのとろ

SPI_ModeメンバにはSPIのモードを指定します。ここではマスタモードを指定します。

めかのとろ

SPI_DataSizeメンバにはデータサイズを指定します。ここでは8ビットデータを扱います。

めかのとろ

SPI_CPOLメンバには待機中のSCLKの信号レベルを指定します。SPI_CPHAメンバにはデータを読み込むクロックのタイミングを指定します。クロックの立ち上がりエッジでデータを読み込むためには待機中のSCLKの信号レベルはLとしていて最初のエッジタイミングで読み込むように指定します。

めかのとろ

SPI_CPOLメンバはクロックの極性、SPI_CPHAメンバはクロックの位相を意味し、これらの組み合わせでSPIモードと呼ばれるSPI通信モードを設定します。スレーブ側のデバイスによっては使用できるモードが限られていますので適切に設定します。

SPI通信モード
SPIタイムチャート(レファレンスマニュアルより)
めかのとろ

SPI_BaudRatePrescalerメンバには通信速度を設定するためのプリスケーラの分周比を指定します。例えば16分周を指定するとPCLK2が72MHzの場合はSPIに供給されるクロックは4MHzになります。EEPROMの仕様によりクロックの最高速度が定められていますので超えないものを指定します。

めかのとろ

SPI_FirstBitメンバにはデータ送信をMSBから行うMSBファーストかLSBから行うLSBファーストを指定します。通常はMSBファーストを指定します。

 ④ SPI1の有効化 

めかのとろ

これまででSPI通信の初期化ができましたのでSPI_Cmd関数を実行してSPI通信をを有効にします。

SPI初期化関数実行例: SPI_Cmd(SPI1, ENABLE);

めかのとろ

関数の第1引数には設定対象のSPI(SPI1-3:マイコンによります) を指定し、第2引数はENABLEで有効DISABLEで無効となります。

SPI通信のバイト送受信

めかのとろ

SPIコントローラの初期化が完了すると、アプリケーションプログラム内で任意に通信を開始することができます。共通のSPIバスに複数のデバイスを接続している場合は通信毎に対象のデバイスをCS信号で選択し、通信を実行して終了時には選択解除します。

めかのとろ

SPI通信において最も基本的な1バイトデータを送受信する流れをみていきましょう。

SPI通信のバイト送受信
SPI通信流れ

 ① SPIデバイス選択(チップセレクト)

めかのとろ

通信対象のSPIデバイスのCS信号をLレベルにして選択します。

 ② SPI送受信関数 

めかのとろ

1バイトデータを送受信するための手順をSPI_Send_Receive関数にまとめました。データの送信を開始する前に、前回の送信が完了しているかどうかをSPI_I2S_GetFlagStatus関数の送信ステータスフラグ(SPI_I2S_FLAG_TXE)で確認します。フラグがSETになった段階で送信レジスタが空になったことになります。

めかのとろ

送信準備完了しだいSPI_I2S_SendData関数で1バイトデータを送信します。関数の第1引数は設定対象のSPI(SPI1-3:マイコンによります)を指定し、第2引数は送信する1バイトデータを指定します。

めかのとろ

データ送信すると同時に相手からデータが返ってきて受信レジスタに入るのですが入っているかどうかをSPI_I2S_GetFlagStatus関数の受信ステータスフラグ(SPI_I2S_FLAG_RXNE)で確認します。フラグがSETになった段階で受信レジスタにデータがあることになります。

めかのとろ

受信準備ができたところでSPI_I2S_ReceiveData関数を実行して受信データを取得します。この関数はSPIが最後に受信したデータを返します。関数の引数は 設定対象のSPI(SPI1-3:マイコンによります) を指定します。データを読み込むと受信フラグは自動的にクリアされます。

めかのとろ

SPI通信はマスタ側とスレーブ側は発生したクロックに合わせてデータを同時通信します。マスタが1バイトコマンドを送信中すると同時にスレーブ側からも0x00や0xFFの1バイトデータが返されて受信レジスタに格納されます。そのため、SPI_Send_Receive関数では受信フラグをクリアして、受信データを取得する処理も含めています。送信のときは受信データの返り値は使用しません。

 ③ SPIデバイス選択解除 

めかのとろ

通信完了後は通信対象のSPIデバイスのCS信号をHレベルにして選択解除します。その前に、通信が完了してビジー状態でないことをSPI_I2S_GetFlagStatus関数のビジー状態フラグ(SPI_I2S_FLAG_BSY)で確認しておきます。フラグがSETである段階ではビジー状態ですので待機します。

EEPROM書き込み

めかのとろ

これまでは1バイトデータ送受信の流れを見てきました。実際の送受信で例えばEEPROMの書き込みと読み込みでは作成した送受信関数SPI_Send_Receive関数を組み合わせてコマンド、アドレスを順次送り、その後データを送信したり、受信したりします。

EEPROM書き込み
めかのとろ

EEPROM書き込み専用にSPI_EEPROM_writebyte関数を作成してみましょう。

SPI_EEPROMバイトデータ書き込み関数実行例: SPI_EEPROM_writebyte(Address, Data);

めかのとろ

このEEPROMはアドレスが16ビットですので、関数の第1引数は16ビットアドレスを指定し、第2引数は格納したい1バイト(8ビット)データを指定する関数を定義します。書き込みコマンドバイト、アドレスバイト、データバイトなど複数のバイトデータを送信する一連の手順を関数内にまとめます。

 ① デバイスアドレス 

めかのとろ

SPIで通信するデータは1バイト単位ですので、指定するアドレスを関数内で1バイト分の上位アドレスと下位アドレスに分離しておきます。

 ② 書き込み許可 

めかのとろ

書き込みの場合は書き込み許可に設定しておく必要があります。EEPROM_write_enable関数にまとめましたので書き込み前に実行しておきます。わざわざ関数にまとめなくてもEEPROMのイネーブルコマンドバイトWRENをSPI_EEPROM_writebyte関数内の書き込みコマンドバイトWRITEの前に送信してもかまいません。

 ③ 書き込みコマンドバイト送信 

めかのとろ

書き込みコマンドバイトWRITE、上位アドレス、下位アドレス、そして格納したいバイトデータの順に送信します。

 ④ ステータスレジスタによる待機 

めかのとろ

書き込みにはある程度時間がかかりますので、ステータスレジスタ読み込みコマンドバイトRDSRを送信して得られたステータスレジスタのビット0の状態がビジーでなくなるまで待機するようにします。Write_wait関数にまとめています。

EEPROM読み込み

めかのとろ

EEPROMの読み込みの場合は書き込みの手順とほぼ同じですが書き込み許可のコマンド送信は必要ありません。書き込みのときと同様にアドレスを上位と下位に分離しておきます。読み込みの場合はレジスタからデータを一時メモリに取り込み関数の返り値として渡します。

EEPROM読み込み
めかのとろ

EEPROM読み込み専用にSPI_EEPROM_readbyte関数を作成してみましょう。

SPI_EEPROMバイトデータ読み込み関数実行例: Data = SPI_EEPROM_readbyte(Address);

めかのとろ

関数の第1引数は読み込みたいデータを格納している16ビットアドレスを指定します。

 ① 読み込みコマンドバイト送信 

めかのとろ

読み込みコマンドバイトREAD、上位アドレス、下位アドレスのバイトデータの順に送信します。

 ② ダミーバイト送信 

めかのとろ

データを取り出すために、なんらかのバイトデータを送信するとSPI_Send_Receiver関数が返り値として受信データが取得できますので、一時メモリretに取り組みます。

 ③ 受信データ取得 

めかのとろ

SPIデバイス選択解除をしてから一時メモリretをSPI_EEPROM_readbyte関数の返り値とすると読み込みのたびにこの関数を実行すればバイトデータが取得できます。