STM32のレジスタを叩く。 #3 ~SPI編~
お久しぶりです。約半年ぶりのまともな(?)更新です。
先日、部品箱を漁っていたらこんなものを見つけました。
1.3 インチ 240*240 IPS スクリーン ST7789 ドライブ IC SPI 通信 3.3 ボルト電圧 SPI インタフェースフルカラー液晶 OLED ディスプレイ
1.3インチと小型ながら、240*240の解像度を持つ、フルカラーのグラフィックディスプレイです。
ドライバICには、ST7789が採用されています。
このディスプレイはSPIで通信を行うので、今回はこれで遊びながら、STM32でSPIを扱うのに必要なレジスタの設定をまとめていこうと思います。
SPI
SPI(Serial Peripheral Interface)は、モトローラ(現 NXPセミコンダクターズ)が提唱したシリアルインターフェースです。 基本的には、SCK, MISO, MOSIの3線に、SSを加えた4種類の信号線で通信を行います。
SPIは設定項目が多く、使用するスレーブデバイスに合わせていくつかの項目を設定する必要があります。
- 通信速度
- SPIモード
- 通信方式(全二重/半二重/単方向)
- データ幅
- 先頭ビット
など
今回はこれらの項目を、ディスプレイに合わせて次のように設定することにします。
設定項目 | 設定値 |
---|---|
通信速度 | 1Mbps |
SPIモード | モード2 |
通信方式 | 単方向(送信のみ) |
データ幅 | 8bit |
先頭ビット | MSB |
レジスタの設定
リファレンスマニュアルの 26.3.7 SPI configuration に従って、レジスタの設定行います。
SPI_CR1
BR
まずはBRビットで、通信速度の設定を行います。 1Mbpsで通信を行うため、内蔵16MHzを16分周してSPIのクロックとします。
SPI1->CR1 |= SPI_CR1_BR_0 | SPI_CR1_BR_1; // fpclk/16
CPOL, CPHA
続いてSPIモードの設定を行います。 SPIでは、クロックの極性(CPOL)とクロックの位相(CPHA)によって、4つのモードが定義されています。
SPIモード | CPOL | CPHA |
---|---|---|
モード0 | 0 | 0 |
モード1 | 0 | 1 |
モード2 | 1 | 0 |
モード3 | 1 | 1 |
クロックの極性(CPOL)とは、通信を行っていない間、SCKがHIGH(CPOL=1)か、LOW(CPOL=0)か、を表します。
また、クロックの位相(CPHA)は、SCKの最初のエッジでサンプリングを行う(CPHA=0)か、二番目のエッジでサンプリングを行う(CPHA=1)か、を表します。
モード2で通信を行うため、CPOL=1, CPHA=0 に設定します。
SPI1->CR1 |= SPI_CR1_CPOL; // CPOL = 1 SPI1->CR1 &= ~SPI_CR1_CPHA; // CPHA = 0
RXONLY, BIDIMODE, BIDIOE
通信方式の設定には、RXONLY, BIDIMODE, BIDIOE の3つのビットを使用します。
送信のみの単方向通信は、全二重通信と同様の設定を行い、受信関係のビットを無視することで実現します。 従って、これらのビットはデフォルト(全二重通信)のままとします。
LSBFIRST
LSBFIRSTビットで、通信における先頭ビットを定義します。 LSBFIRST=0 とすることで、MSBを先頭ビットにします。
SPI1->CR1 &= ~SPI_CR1_LSBFIRST; // MSB first
SSM, SSI
SSM, SSIビットでSSピンの管理を行います。
通常、SPIマスターは、SS(Slave Select)信号を使って通信するスレーブを選択します。 しかし、今回使用するディスプレイにはSSピンがありません。
SSピンを使用しないときは、SSM=1とすることで、SSピンを他の用途に使用することが出来ます。 また、SSI=1 とすることで、MODFエラーを防ぎます。
SPI1->CR1 |= SPI_CR1_SSM; // SSM = 1 SPI1->CR1 |= SPI_CR1_SSI; // SSI = 1
MSTR
STM32をSPIマスターとして使用するため、MSTRビットを設定します。
SPI1->CR1 |= SPI_CR1_MSTR; // Master configuration
DFF
DFFビットでデータ幅を選択します。 DFF=0 で8bit、DFF=1 で16bitです。
SPI1->CR1 &= ~SPI_CR1_DFF; // 8-bit data frame format
SPE
以上の設定が終わったら、SPEビットを立ててSPIを有効にします。
SPI1->CR1 |= SPI_CR1_SPE; // SPI Enable
SPI_CR2
SSOE
今回は使用しませんが、SSピンを使う場合、SPI_CR2のSSOEビットを有効にする必要があります。
データの送信
ディスプレイに対してコマンドや画像データを送るために必要な、データの送信方法を示します。
これは、他のペリフェラルと同様、ステータスレジスタを監視してデータレジスタにデータを書き込むことで実現できます。 今回も簡単のため、ビジーループによってステータスレジスタを監視します。
void spi_send(uint8_t data){ while(!(SPI1->SR & SPI_SR_TXE)); SPI1->DR = data; }
送信のみの単方向通信を行う際の注意点
送信のみの単方向通信は、実際には全二重通信の受信側を無視しているだけです。 そのため、意図せずRXNEフラグやOVRフラグが立つことがありますが、これらは無視してもよいです。(よく見るとリファレンスマニュアルに書いてあるが、筆者はちゃんと読んでなかったせいで時間を溶かした。)
サンプルコード
以上を踏まえ、初期化とデータ送信の例を提示します。 事前にペリフェラル自体へのクロック供給を忘れずに行います。
/* SPI 初期化 */ void spi_init(void){ RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; spi_io_init(); SPI1->CR1 |= SPI_CR1_BR_0 | SPI_CR1_BR_1; // fpclk/16 SPI1->CR1 |= SPI_CR1_CPOL; // CPOL = 1 SPI1->CR1 &= ~SPI_CR1_CPHA; // CPHA = 0 SPI1->CR1 &= ~SPI_CR1_LSBFIRST; // MSB first SPI1->CR1 |= SPI_CR1_SSM; // SSM = 1 SPI1->CR1 |= SPI_CR1_SSI; // SSI = 1 SPI1->CR1 |= SPI_CR1_MSTR; // Master configuration SPI1->CR1 &= ~SPI_CR1_DFF; // 8-bit data frame format SPI1->CR1 |= SPI_CR1_SPE; // SPI Enable } /* SPI 初期化(IO)*/ void spi_io_init(void){ RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; /* PB3 SCK */ GPIOB->MODER |= (GPIO_MODE_AF_PP << GPIO_MODER_MODER3_Pos); GPIOB->AFR[0] |= (GPIO_AF5_SPI1 << GPIO_AFRL_AFSEL3_Pos); /* PB5 MOSI */ GPIOB->MODER |= (GPIO_MODE_AF_PP << GPIO_MODER_MODER5_Pos); GPIOB->AFR[0] |= (GPIO_AF5_SPI1 << GPIO_AFRL_AFSEL5_Pos); } /* SPI 送信 */ void spi_send(uint8_t data){ while(!(SPI1->SR & SPI_SR_TXE)); SPI1->DR = data; }
グラフィックディスプレイを動かす
ディスプレイの初期化シーケンスに従ってSPIでコマンドを送信することで、ディスプレイを初期化することが出来ました。
おわりに
SPIは設定項目が多く、リファレンスマニュアルを読むのに少し苦労しました。 リファレンスマニュアルを読む上でDeepL(https://www.deepl.com/ja/translator)が非常に役に立ったので、英語の資料等読む機会がある方にはオススメです。