STM32のSPI

出典: Wikimura

ここでは、これまで別ページに散らばっていたSTM32のSPI関係の資料をまとめます。

目次

SPI関数

関数名説明
SPI_BiDirectionalLineConfig両方向データモード(1信号線を使って送受信する)の信号方向を設定する。

両方向データモードの出力イネーブルBIDIOE(CR1)をセット・クリアする。
とり得る値は以下のどちらか。

  • SPI_Direction_Tx: 送信専用
  • SPI_Direction_Rx: 受信専用
CalculateCRC CRC自動計算機能の有効化・無効化。
→CRCENビット(CR1)のセット・クリア
SPI_Cmd指定したSPIを動作・停止させる。
SPI_DataSizeConfig指定したSPIのデータサイズを設定する。とり得る値は以下のどちらか。
  • SPI_DataSize_16b: 16bit
  • SPI_DataSize_8b: 8bit
SPI_GetCRC指定したSPIの送信または受信CRCを返す。
  • SPI_CRC_Tx: 送信CRCを選択
  • SPI_CRC_Rx: 受信CRCを選択
SPI_GetCRCPolynomial指定したSPIのCRC生成多項式を返す。
SPI_I2S_ClearFlag指定したSPIのフラグをクリアする。(CRCERRのみらしい)

他のフラグは別の手段でしかクリアできないので注意(フラグクリア方法参照)

  • SPI_FLAG_CRCERR: 唯一クリアできるフラグ。
SPI_I2S_ClearITPendingBit指定したSPIの割り込み保留ビットをクリアする。(CRCERRエラーのみ)

他のビットは別の手段でしかクリアできないので注意(割り込み保留ビットクリア方法参照)

  • SPI_FLAG_CRCERR: 唯一クリアできるフラグ。
SPI_I2S_DeInit指定したSPIのレジスタをリセット値に戻す。
同時にI2Sも影響を受ける。
SPI_I2S_DMACmdSPI(とI2S)のDMA要求を有効化・無効化する。
  • SPI_I2S_DMAReq_Tx: 送信のDMA要求
  • SPI_I2S_DMAReq_Rx: 受信のDMA要求
SPI_I2S_GetFlagStatus

SPI(I2S)のフラグがセットされているか調べる。
調べる対象は以下のいずれか。

  • SPI_I2S_FLAG_TXE: Transmit buffer empty flag.
  • SPI_I2S_FLAG_RXNE: Receive buffer not empty flag.
  • SPI_I2S_FLAG_BSY: Busy flag.
  • SPI_I2S_FLAG_OVR: Overrun flag.
  • SPI_FLAG_MODF: Mode Fault flag.
  • SPI_FLAG_CRCERR: CRC Error flag.
  • I2S_FLAG_UDR: Underrun Error flag.
  • I2S_FLAG_CHSIDE: Channel Side flag.

戻り値は以下のいずれか。

  • SET
  • RESET
SPI_I2S_GetITStatus割り込み保留ビットがセットされているか調べる。

調べる対象は以下のいずれか。

  • SPI_I2S_IT_TXE: Transmit buffer empty interrupt.
  • SPI_I2S_IT_RXNE: Receive buffer not empty interrupt.
  • SPI_I2S_IT_OVR: Overrun interrupt.
  • SPI_IT_MODF: Mode Fault interrupt.
  • SPI_IT_CRCERR: CRC Error interrupt.
  • I2S_IT_UDR: Underrun Error interrupt.

戻り値は以下のいずれか。

  • SET
  • RESET
SPI_I2S_ITConfig指定した割り込みを許可・禁止する。

設定できるのは以下の組み合わせ(ORをとる)。

  • SPI_I2S_IT_TXE: Tx buffer empty interrupt mask
  • SPI_I2S_IT_RXNE: Rx buffer not empty interrupt mask
  • SPI_I2S_IT_ERR: Error interrupt mask
SPI_I2S_ReceiveData最近の受信データを16ビットで返す。
8ビットモードの場合は下位8ビットのみ有効で、上位8ビットは0で埋まる。(RM0008参照)
SPI_I2S_SendData データを送信する。
SPI_Init SPI_InitStruct型のパラメータでSPIを初期化する。
SPI_NSSInternalSoftwareConfig (ソフトウェアスレーブセレクトモードの時)このビットがNSSI(CR1)をセット・クリアする。
SPI_SSOutputCmd
SPI_StructInit
SPI_TransmitCRC 送信CRCレジスタの値を送信する。

送信データをすべて送信し終わってから行うこと。
CRCNext(CR1)ビットをセットすると、送信後に自動でCRCが送られる。


初期化構造体の設定値について

CPOL

クロックパルスの極性を設定する。

  • SPI_CPOL_High(0x0002): 負パルス
  • SPI_CPOL_Low(0x0000): 正パルス

CPHA

ラッチとシフト、どちらが先行するかを設定する。

  • SPI_CPHA_1Edge(0x0000): ラッチ先行(1つ目のエッジでラッチ)
  • SPI_CPHA_2Edge(0x0001): シフト先行(2つ目のエッジでラッチ)

Direction

信号の本数と送受信の方向設定。 マスタとスレーブがMISOとMOSIの2本でつなぐものを1本でも使える。

1本の場合は、マスタ・スレーブ関係なくMISOを送受信に使う。 MISOを送受信のどちらに使うかは以下で選ぶ。

  • SPI_Direction_1Line_Rx(0x8000): 1信号線、受信専用
  • SPI_Direction_1Line_Tx(0xC000): 1信号線、送信専用

2本の場合は、「マスタからスレーブへ、スレーブからマスタへ」の全二重か、「スレーブからマスタへのみ」の2種類から選ぶ。

  • SPI_Direction_2Line_FullDuplex(0x0000): 2信号線、全二重
  • SPI_Direction_2Line_RxOnly(0x4000): 2信号線、受信専用

なぜこうなるかはSPIの構造から分かる。 SPIはマスタとスレーブのシフトレジスタがループを作っている。マスタからデータを送らず、スレーブからデータをもらうだけなら、受信専用になる。マスタからデータを送って、同時にスレーブからも受け取るなら、全二重になる。

FirstBit

最初に送信するビットを設定する。

  • SPI_FirstBit_LSB(0x0000): LSBから送信
  • SPI_FirstBit_MSB(0x0080): MSBから送信

NSS

オフィシャルの説明が良く分からないが、恐らくスレーブモードに関する設定。 SPIのNSS(Slave select)のソースをNSSピンにするか、レジスタにするかを選ぶ。

Hardの場合、NSSピンの信号が内部NSSにつながる。 Softの場合、SSIビット(CR1)が内部NSSにつながる。 内部NSSがアサートされるとSPIがスレーブとして動作する。

スレーブが常に選択されている場合、普通はNSSピンをLowに固定する必要がある。 これをソフトウェア的にやれば、そのピンが空き、別の用途に使えるようになる。

ピンの節約につながるようなもの。

  • SPI_NSS_Hard(0x0000): ハードウェア的(NSSピンを使う)
  • SPI_NSS_Soft(0x2000): ソフトウェア的(SSIビットを使う)

マスタ・スレーブについて

マイコン同士の全二重の場合、スレーブが先にデータレジスタに書き込んでおく。マスタが書き込むと同時にSPIがクロックを出し始める。スレーブのデータとマスタのデータが入れ替わる。

スレーブでNSSが来たからと言って割り込みが生じるわけではない。データレジスタの値が勝手に持っていかれるだけ?


CRCについて

CRCを有効化すると、データを送るたびにCRCが計算されます。

CRCの送信は、送信バッファに最終データを書き込むと同時にSPI CR1のCRCNextビットをセットすることで、最終データ送信後に自動で送信されます。 DMAモードであれば、最終データ送信後に自動的にCRCが送信されます。

CRC計算に用いる生成多項式は、データサイズが8ビットの時CR8(?)、16ビットの時CRC-16-CCITTが使われるそうです。これはデフォルト値であり、生成多項式は初期化構造体(SPI_InitTypeDef)のメンバから設定できます。

生成多項式レジスタの意味

STM32のCRCは、PIC24と違って生成多項式の長さを設定するレジスタがありません。 8bitか16bit長で固定されています。そのため、生成多項式は以下のようになります。 最高次の項は省略されており、それ以下の項の有無がビットの1/0に対応します。

データ長 8bitの時:


G(x) = x^8 + \sum_{i=0}^{i=7}a_i x^i

データ長 16bitの時:


G(x) = x^{16} + \sum_{i=0}^{i=15}a_i x^i

そのため、これではCRC-7になりません。SDカードのCRC-7はハードウェア実装できないので注意してください。

\times \ G(x) = x^8 + x^3 + 1
\circ \ G(x) =  x^7 + x^3 + 1

クロックモードについて

クロックのモードは4種類あります。SPIはデータをシフトレジスタで受け取るために、ラッチ・シフトのタイミングを、クロックの上がり下がりで交互に行うからだそうです。ラッチとシフトのどちらが先行するかで2通り、パルス極性(無信号時にHighかLowか)で2通り、合計4通りということです。無信号時にHighなのは負パルス、Lowなのは正パルスというそうです。

モードの表現は統一されていないようです。

  • Microchip: Mode0,0 / Mode1,1
  • STMicroelectronics: POL=0, PHA=1
    • PHA: ラッチ/シフトどちらが先行するか(1:シフト先行, 0:ラッチ先行)
    • POL: パルス極性(1:負パルス, 0:正パルス)


NSS信号の重要な留意事項

普通のSPIモジュールでは、スレーブを選択するSS信号(負論理なのでnが付く)は、無信号時にネゲートされていて、通信時はアサートされていると思います。 これらはハードウェア的に行われるものです。

しかし、STM32のSPIモジュールでは、マスタモードでNSS信号の出力を有効化すると、アサートされ続けてしまうという仕様になっています。 更に怖いことに、リファレンスマニュアルの図は「自動的にNSSが切り替わる」ような図になっています。これに気付かないと、DACが動かないなどのトラブルに見舞われます。

このことについてはSTMicroelectronicsのフォーラムで議論されています。 書き込みを読んだところ、既に改善の要望を伝えたらしいので、今後のリビジョンで改善されるかもしれません。

実際に遭遇したトラブル

ROMテーブルのデータをSPIへ、DMAを使って連続的に転送することで、外付けのDACからアナログ波形を出力しようとしたときに問題がおきました。データを連続して送っても、一向に波形がでこないのです。 オシロスコープで波形を観測したところ、NSSがずっとアサートされっぱなしだということに気付きました。

私の使用していたMicrochip社のMCP4921というDACは、NSSがアサートされている間にデータが転送されたのち、NSSのネゲートを検出すると変換が開始されます。従って、アサートされっぱなしでは波形が出ることはありません。

CPUの介在をなくして高速に転送するには一工夫いりそうです。

DMAをどうしても使いたい場合

DMA連続的にデータ転送する場合、通常SPIからDMA要求を出します。 しかしDACの事例のように、NSSがアサートされっぱなしで困ることがあります。

そこで、タイマ・Bit-banding・DMAのサイクリックモードを活用する方法を思いつきました。 タイマが持つ4チャネルのコンペア・キャプチャモジュールは、それぞれ独立して割り込み(DMA要求も)を出せるようになっています。この要求を使うことで、NSSのアサート、SPI転送開始、NSSのネゲートを時間差で行ってしまおうと考えました。

NSSのアサート、ネゲートはビットセット・クリアなので、ワード単位で転送されると困ってしまいます。 しかし、Cortex-M3コアには「Bit-banding」という便利な機能があります。これは、単一ビットをワードとしてアクセスできるというものです。これを使って、0x0(アサート)と0x1(ネゲート)をNSSビットに相当する部分へDMA転送すれば、CPUの介在なしにNSSが操作できるようになります。

これをDMAのサイクリックモードで行えば、延々と転送が繰り返されるため、CPUの介在は全く不要になります。

Image:Morita-090731-SPI-Idea.png

上図は動作イメージです。タイマカウンタが繰り返すカウントから、アウトプットコンペアによるDMA要求が3回生じます。1回目でNSSアサート、2回目でSPI転送開始、3回目でNSSネゲートとなっています。 DMAを3チャネルも使ってしまいますが、転送速度はタイマの繰返し周波数にすることができます。

これを実装してみようと考えています。

タイマを使ったSPIの連続転送

実装しました( 動作例)。今度別ページにまとめます。

NSSの挙動

実機で試した結果を書きます。

SSOEの動作中の切り替え

転送準備をして置き、SSOEをアサートすれば転送が開始するのでは...と期待したのです、そうはいかないようです。

マスタモード+ハードウェアNSSモード+NSS出力ピンで、SSOEをDisableにしてから動作開始してしまうと、動作中にSSOEをEnableできないようです。SSOEの切り替えは動作を停止してから行わなくてはなりませんでした。確かに途中で切り替えたら怖いので、こうあるべきでしょう。

つまり、SSOEだけではNSSのアサート・ネゲート切り替えができないということです。


マスタモードでNSSをGPIOから入力させる

NSSを入力モードにして、ハードウェアNSSモードにすると、NSSピンを監視するようになるようです。 これはスレーブモードでのみ有効なのか、マスタでも監視するのか確認しました。

試した結果

NSS入力+ハードウェアNSSモードとしました。

GPIOでIPU(input pull up)モードでは、NSSがHighのままで送信しました。 入力モードなので、NSSがLowになることはありませんでした。

GPIOでIPD(input pull down)モードとし、NSSをLowにしておくと、送信は開始されませんでした。 ちゃんと監視しているようです。

そのため、マスタ+ハードウェアNSSモードではNSSをLowにできないことが分かりました。


NSSを出力しつつマスタになる

SPIにNSSがフリーであると思わせながら、NSSを出力するには、ソフトウェアNSSモードで動作させるしかないのかもしれません。 ソフトウェアNSSモードとして内部NSSをネゲートさせながら、GPIOでNSSを出力すれば良さそうです。 というかこれしかなさそうです。

試した結果

NSSに関係なくクロック・データが出ました。

関連記事

  1. STM32F10x Standard Peripherals Firmware Library: Overview
  2. STM32F10x Standard Peripherals Firmware Libraryを使う
  3. Bit-bandについてのメモ

参考文献

  1. STM32関連ファイル
  2. STM32シリーズリファレンスマニュアル(RM0008)
  3. STM32 Standard Peripherals Firmware Library(ZIPファイル)
  4. ELM - SPIについて
個人用ツール