stm32 + LLでSPI通信(master/slave)を用いてマイコン同士で通信をしてみる(F303K8)

こんにちわ。そらです。
約1年ぶりにSTM32マイコンの周辺機能をいじっていきたいと思います。データシートを確認しながらやっていきたいと思います。それぞれ、Master/Slaveで8bit,16bitで通信するときのプログラムを書いていきたいと思います。
通信結果の確認はuartを用いてシリアル通信で取得を行いました。uartでprintfを使用する方法はこの記事では説明しません。こちらを見て頂ければと助かります。




SPI(master/slave)の実装

データシートを確認するとSPIの概要は以下のようになっていました。

RM0316より引用

データはFIFOに積まれていくみたいです。Slaveでは、NSSを使用しますがMasterでは使用しないで実装を行っていきます。

初期設定についてもMaster、Slaveでほとんど違いがないようなので、以下に紹介します。

RM0316より引用

STM32CubeMXを使用すると2.までは自動で設定してくれます。ただし、3.のa~fは設定がされていたりされていなかったりするため設定を確認する必要があります。RXFIFOの閾値は、初期では16bitに設定されています。

Master編

データシートからMasterで通信するときの流れを確認していきます。

RM0316より引用

フラグのTXEとRXNEを確認しながらデータ書き込みのタイミングとデータ読み込みのタイミングを待ちながら通信するプログラムを書いていけばいいということがわかりました。
8bitで通信を行う場合は、RXFIFOの閾値設定を8bitにする必要があります。これを踏まえてSTM32CubeMXの設定およびプログラムの実装をしていきます。CubeMXの設定は以下の通りです。

USARTを有効にして、printfを使用できるようにしておきます。
また、SPI通信を8bitで行う場合はBasicParameterのDataSizeを8bitに変更してください。

Project ManagerのCodeGeneratorで機能ごとにファイルが分割されるようにしました。

そして、大切なこと。Advanced Settingsで使用するライブラリをHALからLLに変更しましょう。

プログラムの実装

8bitおよび16bitでそれぞれ実装を行います。環境に合わせて8bitのものあるいは16bitのものを使用してください。

8bitのとき

void SPI1_Start8bit(void) {
  LL_GPIO_SetOutputPin(cs_GPIO_Port, cs_Pin);
  LL_SPI_SetRxFIFOThreshold(SPI1, LL_SPI_RX_FIFO_TH_QUARTER);
  LL_SPI_Enable(SPI1);
}
void SPI_Communication8bit(SPI_TypeDef *SPIx, uint8_t *tx_data,
  uint8_t *rx_data, int16_t length,
  GPIO_TypeDef *GPIOx, uint32_t CS_Pin) {
  int16_t count = length;
  LL_GPIO_ResetOutputPin(GPIOx, CS_Pin);
  if (length == 1) {
    LL_SPI_TransmitData8(SPIx, *tx_data);
    while (LL_SPI_IsActiveFlag_TXE(SPIx) == RESET);
    while (LL_SPI_IsActiveFlag_RXNE(SPIx) == RESET);
    *rx_data = LL_SPI_ReceiveData8(SPIx);
  } else {
    while (count > 0) {
      LL_SPI_TransmitData8(SPIx, *tx_data++);
      while (LL_SPI_IsActiveFlag_TXE(SPIx) == RESET);
      while (LL_SPI_IsActiveFlag_RXNE(SPIx) == RESET) ;
      *rx_data++ = LL_SPI_ReceiveData8(SPIx);
      count--;
    }
  }
  LL_GPIO_SetOutputPin(GPIOx, CS_Pin);
}

16bitのとき

void SPI1_Start16bit(void) {
  LL_GPIO_SetOutputPin(cs_GPIO_Port, cs_Pin);
  LL_SPI_Enable(SPI1);
}
void SPI_Communication16bit(SPI_TypeDef *SPIx, uint16_t *tx_data,
                            uint16_t *rx_data, int16_t length,
                            GPIO_TypeDef *GPIOx, uint32_t CS_Pin) {
  int16_t count = length;
  LL_GPIO_ResetOutputPin(GPIOx, CS_Pin);
  if (length == 1) {
    LL_SPI_TransmitData16(SPIx, *tx_data);
    while (LL_SPI_IsActiveFlag_TXE(SPIx) == RESET);
    while (LL_SPI_IsActiveFlag_RXNE(SPIx) == RESET);
    *rx_data = LL_SPI_ReceiveData16(SPIx);
  } else {
    while (count > 0) {
      LL_SPI_TransmitData16(SPIx, *tx_data++);
      while (LL_SPI_IsActiveFlag_TXE(SPIx) == RESET);
      while (LL_SPI_IsActiveFlag_RXNE(SPIx) == RESET);
      *rx_data++ = LL_SPI_ReceiveData16(SPIx);
      count--;
    }
  }
  LL_GPIO_SetOutputPin(GPIOx, CS_Pin);
}

Slave編

Slaveで通信を行うときの流れを確認します。

RM0316より引用

Masterのときと同じようにCubeMXの設定をしていきます。今回は8bitのときの設定を以下に示します。

CSピンはSTM32のSPI通信用の機能であるNSSを使用します。こうすることで、CSピンがOFFになったタイミングでSPI通信を始めるようにしてくれます。便利ですね。
Project Managerの設定は、Masterのときと同じようにすればいいと思います。

プログラムの実装

8bitおよび16bitでそれぞれ実装を行います。環境に合わせて8bitのものあるいは16bitのものを使用してください。

8bitのとき

void SPI1_Start8bit(void) {
  LL_SPI_SetRxFIFOThreshold(SPI1, LL_SPI_RX_FIFO_TH_QUARTER);
  LL_SPI_Enable(SPI1);
}
void SPI_Communication8bit(SPI_TypeDef *SPIx, uint8_t *tx_data,
                           uint8_t *rx_data, int16_t length) {
  int16_t count = length;
  if (length == 1) {
    while (LL_SPI_IsActiveFlag_TXE(SPIx) == RESET);
    LL_SPI_TransmitData8(SPIx, *tx_data);
    while (LL_SPI_IsActiveFlag_RXNE(SPIx) == RESET);
    *rx_data = LL_SPI_ReceiveData8(SPIx);
  } else {
    while (count > 0) {
      while (LL_SPI_IsActiveFlag_TXE(SPIx) == RESET);
      LL_SPI_TransmitData8(SPIx, *tx_data++);
      while (LL_SPI_IsActiveFlag_RXNE(SPIx) == RESET);
      *rx_data++ = LL_SPI_ReceiveData8(SPIx);
      count--;
    }
  }
}

16bitのとき

void SPI1_Start16bit(void) { LL_SPI_Enable(SPI1); }
void SPI_Communication16bit(SPI_TypeDef *SPIx, uint16_t *tx_data,
                            uint16_t *rx_data, int16_t length) {
  int16_t count = length;
  if (length == 1) {
    while (LL_SPI_IsActiveFlag_TXE(SPIx) == RESET);
    LL_SPI_TransmitData16(SPIx, *tx_data);
    while (LL_SPI_IsActiveFlag_RXNE(SPIx) == RESET);
    *rx_data = LL_SPI_ReceiveData16(SPIx);
  } else {
    while (count > 0) {
      while (LL_SPI_IsActiveFlag_TXE(SPIx) == RESET);
      LL_SPI_TransmitData16(SPIx, *tx_data++);
      while (LL_SPI_IsActiveFlag_RXNE(SPIx) == RESET);
      *rx_data++ = LL_SPI_ReceiveData16(SPIx);
      count--;
    }
  }
}

おわりに

同じSTM32ファミリでもシリーズや型番が違うと同じ機能でも少しずつ違うことがあるので、使用するマイコンのデータシートはしっかり確認しましょう(1敗)。FIFOのないマイコンについては、SPI_Start関数のRXFIFOの閾値設定がなくなるので、その行の削除をすれば動くと思われます。データシートを読めばわかりそうですね。

参考文献

RM0316 STM32F303K8 リファレンスマニュアル