エビデイエビナイ電子工作

やったことの記録

STM32のレジスタを叩く。 #3 ~SPI編~

f:id:Rappy:20191113182922j:plain

お久しぶりです。約半年ぶりのまともな(?)更新です。


先日、部品箱を漁っていたらこんなものを見つけました。


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

f:id:Rappy:20200521221135p:plain
SPI_CR1

BR

まずはBRビットで、通信速度の設定を行います。

f:id:Rappy:20200521223443p:plain
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)か、を表します。

f:id:Rappy:20200522204336p:plain
CPOLとCPHA

モード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でコマンドを送信することで、ディスプレイを初期化することが出来ました。

f:id:Rappy:20200522001134j:plain
ディスプレイを初期化することができた


おわりに

SPIは設定項目が多く、リファレンスマニュアルを読むのに少し苦労しました。 リファレンスマニュアルを読む上でDeepL(https://www.deepl.com/ja/translator)が非常に役に立ったので、英語の資料等読む機会がある方にはオススメです。


twitter.com