STM32のSPI
出典: Wikimura
ここでは、これまで別ページに散らばっていたSTM32のSPI関係の資料をまとめます。
目次 |
SPI関数
| 関数名 | 説明 |
|---|---|
| SPI_BiDirectionalLineConfig | 両方向データモード(1信号線を使って送受信する)の信号方向を設定する。 両方向データモードの出力イネーブルBIDIOE(CR1)をセット・クリアする。
|
| CalculateCRC | CRC自動計算機能の有効化・無効化。
→CRCENビット(CR1)のセット・クリア |
| SPI_Cmd | 指定したSPIを動作・停止させる。 |
| SPI_DataSizeConfig | 指定したSPIのデータサイズを設定する。とり得る値は以下のどちらか。
|
| SPI_GetCRC | 指定したSPIの送信または受信CRCを返す。
|
| SPI_GetCRCPolynomial | 指定したSPIのCRC生成多項式を返す。 |
| SPI_I2S_ClearFlag | 指定したSPIのフラグをクリアする。(CRCERRのみらしい) 他のフラグは別の手段でしかクリアできないので注意(フラグクリア方法参照)
|
| SPI_I2S_ClearITPendingBit | 指定したSPIの割り込み保留ビットをクリアする。(CRCERRエラーのみ) 他のビットは別の手段でしかクリアできないので注意(割り込み保留ビットクリア方法参照)
|
| SPI_I2S_DeInit | 指定したSPIのレジスタをリセット値に戻す。 同時にI2Sも影響を受ける。 |
| SPI_I2S_DMACmd | SPI(とI2S)のDMA要求を有効化・無効化する。
|
| SPI_I2S_GetFlagStatus |
SPI(I2S)のフラグがセットされているか調べる。
戻り値は以下のいずれか。
|
| SPI_I2S_GetITStatus | 割り込み保留ビットがセットされているか調べる。 調べる対象は以下のいずれか。
戻り値は以下のいずれか。
|
| SPI_I2S_ITConfig | 指定した割り込みを許可・禁止する。 設定できるのは以下の組み合わせ(ORをとる)。
|
| 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レジスタの値を送信する。 送信データをすべて送信し終わってから行うこと。 |
初期化構造体の設定値について
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の時:
データ長 16bitの時:
そのため、これではCRC-7になりません。SDカードのCRC-7はハードウェア実装できないので注意してください。
クロックモードについて
クロックのモードは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の介在は全く不要になります。
上図は動作イメージです。タイマカウンタが繰り返すカウントから、アウトプットコンペアによる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に関係なくクロック・データが出ました。
関連記事
- STM32F10x Standard Peripherals Firmware Library: Overview
- STM32F10x Standard Peripherals Firmware Libraryを使う
- Bit-bandについてのメモ


