STM32 SPIの設定

2025年2月26日水曜日

【STM32】 LL

t f B! P L

SPI通信の設定

I2C通信と比べ高速、構造もシンプルですが配線数が多いのがSPIです。
通信データが大きくなりがちなグラフィックLCDでは一般的な通信方式ではないでしょうか。
本記事ではCubeIDEが生成したSPIのinit関数を元にSPI通信に必要なレジスタを確認していきます。

クロックの供給

RCC_APBENR2レジスタにアクセスし、SPI1へのクロックを有効化します。
SPI1の場合、PA1(SCK)とPA2(MOSI)が使われています。
通常のGPIO設定同様、それぞれをAlternateモードとして設定しています。
またSPIではI2Cと異なり、プッシュプルでの出力が可能です。

SPI_CR1の設定

MX_SPI1_Init()では各パラメータを構造体に格納し、LL_SPI_Initに渡しています。
LL_SPI_InitではまずSPIが無効かのチェックをしています。
SPIの設定レジスタ、SPI_CR1にはSPIが無効であるときに設定すべきビットが多くありますので、その為でしょう。

これまでもいくつかSTM32側が提供しているInit関数を見てきましたが、SPIのInitは少し変わった構造です。
MODIFY_REGで各パラメータを一気に格納しています。
SPIはリファレンスマニュアルにおいても、レジスタ構造においてもI2S通信と共用している箇所が多いです。
その為ほとんどの設定はCR1とCR2レジスタのセットで終わっています。
このシンプルさゆえに一気に設定しているのかもしれません。

CR1の設定項目について見ていきます。

通信方法の設定


BIDIMODE
通信方向についての設定です。
1がセットされると半二重通信になります。
半二重通信はI2Cと似た形で、データラインが1本になる反面送信・受信が同時には行えなくなります。

BIDIOE
BIDIMODEが半二重通信であるとき、通信方向を設定します。
0で受信、1で送信になります。
通信中にも切り替えることがありそうなビットだけに、BIDE関連のビットはSPI有効時に設定をすることを禁止しているような記述はありません。

LLライブラリにおいては次のようなマクロが用意されています。

#define LL_SPI_FULL_DUPLEX

0x00000000U

#define LL_SPI_SIMPLEX_RX

(SPI_CR1_RXONLY)

#define LL_SPI_HALF_DUPLEX_RX

(SPI_CR1_BIDIMODE)

#define LL_SPI_HALF_DUPLEX_TX

(SPI_CR1_BIDIMODE | SPI_CR1_BIDIOE)


今回のように、送信のみであれば一番上が該当していました。
RXONLYについては後述します。
残り2つの定義ですが、BIDIMODE=1、BIDIOE=0により半二重受信のみ。
BIDIMODE=1、BIDIOE=1により半二重送信のみ。となります。

CRC計算


CRCEN,CRCNEXT,CRCL
エラーチェックに用いるCRC計算に関する部分です。
CRC計算は送信・受信側双方が同じ式でデータを割り、その余りが一致するかをチェックします。
参考:CRC(Cyclic Redundancy Check:巡回冗長検査)って何?

送信側はCRC計算式で割った余りをデータに添付し、受信側は同じCRC計算式で添付された余りと一致するかをチェックします。
果たして世の中にあるSPIデバイスにどれほどCRCに対応したものがあるのか未知数な所ですが、今回は使いません。

RXONLY
名前の通り受信専用モードとして動作するものです。
RXONLYがセットされるとBIDIMODEがクリアされるようなのでその点は注意すべきかもしれません。

NSS


SSM、SSI(CR1)NSSP、SSOE(CR2)
チップセレクト(CS)に相当するNSSの制御に関するビットです。
NSSに関してはCR2のビットも関連してくるので、ここでまとめます。

・SSM
ソフトウェアスレーブ管理ビット。
0であるときハードウェアで、1であるときソフトウェアで管理します。

・SSI
SSMがセットされている時、このビットの値がNSSピンの出力になります。
SSMがセットされている時、このビットの値がNSSピンの入力になります。

SSMがセットされている時、NSSはプログラム上で操作できます。
しかしマスタモードにおいて、NSSがLowに固定されるとフォールトします。
なのでSSIをセットすることで内部的なNSSの値をHighに固定し、フォールトを防ぎます。

・NSSP
NSSパルス管理。
このビットがセットされると一回のデータ送信(1バイト)毎にNSSが切り替わります。
データ送信開始時はLow、送信完了後はHighになります。

・SSOE
1がセットされているとき、NSSピンの出力が有効になります。
マルチマスタモードでは他のマスタのNSSとの衝突を防ぐ為に0で使うことがありそうです。

NSSのまとめ

・SSMが0
→ハードウェア管理。SSIは無効(不問)。SSOEはセット。

・SSMが1
→ソフトウェア管理。SSIをセット。SSOEは無効(不問)

その他の設定


LSBFIRST
0のときMSB、1のときLSBとして通信が行われます。

SPE
0のときSPIは無効。1のとき有効です。

BR
ボーレート。SPIクロックの周波数を決めます。
ペリフェラルクロック(APB)をBRの値に応じて分周します。

MSTR
0のときスレーブ、1のときマスタです。

CPOL
通信アイドル時のクロック(SCK)の値を決めます。
0のときLow。1のときHighです。

CPHA
クロック位相。
0のときSCKの最初のエッジ(立ち下がりor立ち上がり)
1のとき2番目のエッジでデータの送受信を開始します。


リファレンスマニュアルRM0444より引用。

SPI_CR2の設定

LDMA_TX、RX
SPIでDMAを利用し、かつデータパッキングを利用する状態で設定するビットになっています。

FRXTH
受信準備完了を通知するRXNEのしきい値を設定します。
0の場合16ビット以上のデータがあるとき、1のときは8ビット以上のデータがあるときにRXNEイベントが生成されます。

DS
SPI通信データのサイズをビット単位で設定します。

TXEIE、RXNEIE、ERRIE
送信バッファが空になったとき、受信バッファが空ではなくなったとき、各種エラー(CRC計算エラー、オーバーランなど)に割り込みを許可するか否かの設定を行います。

FRF
SPIの通信フォーマットを設定します。

TXDMAEN、RXDMAEN
TXE、RXNEフラグのセット時にDMAリクエストを送るかどうかの設定です。

SPI通信の実装

I2Cのようなハンドシェイク方式では無い分、通信としてはシンプルですが動きの把握に苦戦しました。
I2Cスレーブの時と同様にマスタ、スレーブを1つのデバイスに実装するテストを行いましたが、結局マスタ側が1バイト通信する毎に一定のディレイを挟まないとうまく受信できませんでした。

スレーブ割り込みハンドラ


そもそもの問題ですが、SPIクロックはマスタしか送信できません。
ですので全二重通信を行う際には送信と受信は同時に実行します。
スレーブ側に指定したSPI2ではマスタからの送信(MOSI)を受信後にマスタへの送信(MISO)を実行します。

ただしこのテストではスレーブからの送信が正しく行われない状態になりました。
具体的にはスレーブ送信データの先頭が予期しない値(0x00)になり、本来の10バイト目のデータは次の通信時に送信されていました。

後述する別々のマイコン同士の通信では問題なく通信できていること、またリファレンスマニュアル(RM0444)に次のような記述があることからスレーブの準備不足が原因と考えています。
P1101、SPIを有効にする手順 より引用
マスタがクロックを送信する前に、SPI スレーブを有効にすることを推奨します。さもなければ、望ましくないデータ送信が発生することがあります。スレーブのデータレジスタは、マスタとの通信を開始する前に、送信データをすでに格納していなければなりません
スレーブの準備不足が原因と仮定すると、SPI_DRレジスタの初期値(0x0000)がそのまま送信されてしまっているのでは、と考えられます。
1つのマイコンで2つのSPIで通信をするなんてあまりにも無意味な話での問題ですが、そうでなくても割り込みを使う場合には同じような問題が発生するかもしれません。

ポーリング通信

マスタをSTM32G030、スレーブをG031として通信をテストしました。
CubeIDEでは1つのマイコンしかデバッグできない?のでかなり面倒です。もうやりたくない

マスタ側デバッグ


sendがスレーブへの送信データ(MOSI)、recvがスレーブからの受信データ(MISO)です。

スレーブ側デバッグ


スレーブ側recvの値がマスタ側sendの値と、スレーブ側sendがマスタ側recvの値と一致している状態です。
また上記の通信はロジックアナライザで監視していても問題なく送受信できていることがわかりました。

このプログラムではマスタ側ではディレイを置かずに送信することができています。
またスレーブ側プログラムは基本的にRXNEフラグ、マスタからの送信が来るまで待機します。
スレーブはそれよりも前に送信データをDRにセットできているので、先ほど問題としてあげた「スレーブの準備不足」が発生しないのではないか?と考えています。

コード全体

もうさんざんマスタ、スレーブって書いてますがGitHubではleaderとfollowerと言い換えたレポジトリにしています。

またマスタ側は規定のサイズ分送信を完了したらSPIを無効化していますが、SRレジスタのFIFO受信レベルを確認し、受信できていないデータが無いか確認する方がより確実だと思います。
加えて今回のコードではNSSをハードウェア管理としています。
この場合SPIが有効な間NSSはLowになりますので、通信毎に有効・無効を切り替えています。

追記

この後SPI通信を行うデバイスを使っていた中で諸々問題点があったのでSPI送受信のプログラムを修正しました(C++)
cpp
特に送信→受信の流れを踏む時に必要なフラグのチェック(BSY、TRLVL)の点を追加しています。

Translate

検索

QooQ