2019年3月22日金曜日

STM32: SPIをPollingとDMAで使う(Nucleo-F446RE)

SPI通信を、ポーリングとDMA経由でテストしました。

参考にした記事

STM32CubeのExample「SPI_FullDuplex_ComPolling」 <STM32Cube>\Repository\STM32Cube_FW_F4_V1.24.0\Projects\STM32F413ZH-Nucleo\Examples\SPI\SPI_FullDuplex_ComPolling

STM32CubeのExample「SPI_FullDuplex_ComDMA」 <STM32Cube>\Repository\STM32Cube_FW_F4_V1.24.0\Projects\STM32F413ZH-Nucleo\Examples\SPI\SPI_FullDuplex_ComDMA

実行環境

  • Nucleo-F446RE
  • STM32CubeMX Version 5.1.0
  • System Workbench for STM32 - C/C++ Embedded Development Tools for MCU Version: 2.8.1.201903050911

SPI通信(ポーリング)


STM32CubeのExampleはボードを2枚使って互いに通信させていますが、SPI送信波形を見るだけにして一番シンプルな形でテストしました。

プロジェクト:
https://github.com/ryood/STM32Cube_Test/tree/master/SW4STM32/Nucleo-F446_SPI_SoftNSS_Test1

STM32CubeMXの設定


ピンのアサイン


[SPI1]-[Mode]
  Mode: Transmit Only Master
  Hardware NSS Signal: Disable

[Parameter Settings]
Clock Parameters
  Prescaler (for Baud Rate): 64
  Baud Rate: 1000.0KBits/s

SPI1はデフォルトでは、PB3がSYS_JTDO-SWOに割り当てられていて有効化できませんが、Pinout viewでPB3にSPI1_SCKを割り当ててやれば、有効化できます。

モードは送信のみの「Transmit Only Master」にしました。MISOが割り当てられず、SCKとMOSIの2線です。

CSはソフトウェア制御にしてPinout viewでPB4をGPIO_Outputに指定しました。

SPIの信号線 STM32のピン名 Arduinoヘッダのピン名
SCK PB3 D3
MOSI PB5 D4
CS PB4 D5

クロック設定


SPIクロックをちょうど1MHzにしたかったので、HCLKを2のN乗の128MHzにしました。またクロック源(PLL Source Mux)を内蔵RCのHSIではなく、NucleoボードのST-Link側に乗っている8MHzの水晶から取るために、HSEに切り替えています。

SW4STM32でコードを追加(一部抜粋)

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
 uint8_t data[2] = { 0x55, 0xAA };
 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
 if (HAL_SPI_Transmit(&hspi1, data, 2, 100) != HAL_OK) {
  Error_Handler();
 }
 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);
  }
  /* USER CODE END 3 */

0x55, 0xAAを送信しています。ビット列にすると「01010101:10101010」になります。

SPI通信のようす

ch1:MOSI(D4) ch2:SCK(D3)

SPI通信(DMA)


こちらも送信のみですが、設定は全二重(Full-Duplex Master)にしました。

プロジェクト:
https://github.com/ryood/STM32Cube_Test/tree/master/SW4STM32/Nucleo-F446_SPI_DMA_Test1

STM32CubeMXの設定


ピンのアサイン


[SPI1]-[Mode]
  Mode: Full-Duplex Master
  Hardware NSS Signal: Disable

[Parameter Settings]
Clock Parameters
  Prescaler (for Baud Rate): 64
  Baud Rate: 1000.0KBits/s

Transmit Only Masterの場合より1ピン増えて、MISOが追加されます。CSはPinout viewでPB10をGPIO_Outputに指定しました。

SPIの信号線 STM32のピン名 Arduinoヘッダのピン名
SCK PB3 D3
MISO PB4 D5
MOSI PB5 D4
CS PB10 D6

DMA Settings


SPIとDMAを紐付ける設定です。

[Add]ボタンを押して、[DMA Request]で[SPI1_TX][SPI1_RX]を追加します。

DMA Request Settings
  Mode: Normal

クロックの設定はポーリングの場合と同じです。

SW4STM32でコードを追加(一部抜粋)

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define BUFFERSIZE (2)
enum {
 TRANSFER_WAIT,
 TRANSFER_COMPLETE,
 TRANSFER_ERROR
};
/* USER CODE END PD */

/* USER CODE BEGIN PV */
/* transfer state */
__IO uint32_t wTransferState = TRANSFER_WAIT;
/* USER CODE END PV */

DMA転送の状態を示すフラグです。

/* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    uint8_t aTxBuffer[BUFFERSIZE] = { 0x55, 0xAA };
 uint8_t aRxBuffer[BUFFERSIZE];
 wTransferState = TRANSFER_WAIT;

    /*##-2- Start the Full Duplex Communication process ########################*/
    /* While the SPI in TransmitReceive process, user can transmit data through
    "aTxBuffer" buffer & receive data through "aRxBuffer" */
 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_RESET);
    if(HAL_SPI_TransmitReceive_DMA(&hspi1, (uint8_t*)aTxBuffer, (uint8_t *)aRxBuffer, BUFFERSIZE) != HAL_OK)
    {
   /* Transfer error in transmission process */
   Error_Handler();
    }
 HAL_GPIO_WritePin(SPI1_CS_GPIO_Port, SPI1_CS_Pin, GPIO_PIN_SET);

    /*##-3- Wait for the end of the transfer ###################################*/
    /*  Before starting a new communication transfer, you must wait the callback call
     to get the transfer complete confirmation or an error detection.
     For simplicity reasons, this example is just waiting till the end of the
     transfer, but application may perform other tasks while transfer operation
     is ongoing. */
    while (wTransferState == TRANSFER_WAIT)
    {
    }

    printf("SPI RX: %d\t%d\r\n", aRxBuffer[0], aRxBuffer[1]);
  }
  /* USER CODE END 3 */

こちらも0x55、0xAAを送信しています。

ソースのコメントにもありますが、DMA転送の完了をWhileループでフラグを見て待機していますが、本来はうまいことやってDMA転送完了待ち中は他の処理をするべきです。

/* USER CODE BEGIN 4 */
/**
  * @brief  TxRx Transfer completed callback.
  * @param  hspi: SPI handle
  * @note   This example shows a simple way to report end of DMA TxRx transfer, and
  *         you can add your own implementation.
  * @retval None
  */
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
  wTransferState = TRANSFER_COMPLETE;
}

/**
  * @brief  SPI error callbacks.
  * @param  hspi: SPI handle
  * @note   This example shows a simple way to report transfer error, and you can
  *         add your own implementation.
  * @retval None
  */
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
  wTransferState = TRANSFER_ERROR;
}
/* USER CODE END 4 */

DMA転送完了の割り込みハンドラです。wTransferStateに状態を代入しているだけです。

SPI通信のようす

ch1:MOSI(D4) ch2:SCK(D3)


ch1:MOSI(D4) ch2:SCK(D3)

printf()でUART通信しているので(ブロッキングされる)、SPI通信ごとの間隔が広がっています。

Puttyで受信しているようす

スレーブがつながっていないので受信データは0:0です。

メモ:


NSS(CS)をハードウェア制御できるようですが、マスター側ではソフトウェアでGPIO Writeしてやれば用は済みます。NSSのハードウェア制御の使い方はしばし保留。

0 件のコメント:

コメントを投稿