STM32のUSARTでDMA受信


はじめに

STM32のUSARTでHAL関数を使ってDMA受信する方法をまとめました。
単純にHALの受信関数のみ使うと受信するデータ列のサイズは固定長である必要が有ります。
ここでは可変長サイズでも対応可の方法を集めました。

環境

OS:Windows10 Pro 2004
IDE:STM32CubeIDE 1.3.0
デバイス:STM32F407 Discovery kit

循環バッファ方式

Topic: 'Best' way to load UART data to ring buffer with STM32/HAL (Read 25675 times)

DMA設定

USART受信のDMAをCircularモードに設定します。
これにより、バッファの末端を超えると先頭からデータを格納する循環バッファになります。

実装

データ受信をポーリング監視する場合の実装です。

#define USART_RX_BUFFSIZE 128

static UART_HandleTypeDef* pHuart;
static uint8_t RxBuff[USART_RX_BUFFSIZE];
static uint32_t rd_ptr;

#define DMA_WRITE_PTR ( (USART_RX_BUFFSIZE - pHuart->hdmarx->Instance->NDTR) % (USART_RX_BUFFSIZE) )

void USART2_Init(void)
{
    pHuart = &huart2;
    memset(RxBuff, 0, sizeof(RxBuff));
    rd_ptr = 0;

    __HAL_UART_DISABLE_IT(pHuart, UART_IT_PE);
    __HAL_UART_DISABLE_IT(pHuart, UART_IT_ERR);
    HAL_UART_Receive_DMA(pHuart, RxBuff, USART_RX_BUFFSIZE);
}

int USART2_RX_IsEmpty(void)
{
    return (rd_ptr == DMA_WRITE_PTR);
}

uint8_t USART2_RX_Read(void)
{
    uint8_t c = 0;
    if(rd_ptr != DMA_WRITE_PTR) {
        c = RxBuff[rd_ptr++];
        rd_ptr %= USART_RX_BUFFSIZE;
    }
    return c;
}

DMA_WRITE_PTRがDMAによる次書き込み位置を表し、rd_ptrが次読み取り位置を表しています。
なお、バッファサイズ指定を柔軟にする為に、参考記事からは終端越えの処理を変えています。

IDLEイベント方式

データ受信後、一定時間経過した時に発生するイベントです。
参考

受信データがパケット形式で間隔を空けて送られてくる場合などに使えます。

設定

実装

#define USART_RX_BUFFSIZE 128  // 受信パケットサイズより大きくします

static UART_HandleTypeDef* pHuart;
static uint8_t RxBuff[USART_RX_BUFFSIZE];
static uint32_t rd_ptr;

#define DMA_WRITE_PTR ( (USART_RX_BUFFSIZE - pHuart->hdmarx->Instance->NDTR) % (USART_RX_BUFFSIZE) )

void USART2_Init(void)
{
    pHuart = &huart2;
    memset(RxBuff, 0, sizeof(RxBuff));
    rd_ptr = 0;

    __HAL_UART_DISABLE_IT(pHuart, UART_IT_PE);
    __HAL_UART_DISABLE_IT(pHuart, UART_IT_ERR);
    __HAL_UART_ENABLE_IT(pHuart, UART_IT_IDLE);  // <= 追加
    HAL_UART_Receive_DMA(pHuart, RxBuff, USART_RX_BUFFSIZE);
}
・・・
void USART2_EventHandler(void)
{
    int rxlen = 0;

    if(__HAL_UART_GET_FLAG(pHuart, UART_FLAG_IDLE) != RESET)
    {
        __HAL_UART_CLEAR_IDLEFLAG(pHuart);
        HAL_UART_DMAStop(pHuart);

        // データ受信処理
        rxlen = DMA_WRITE_PTR;  // 受信データ長(受信データ長がバッファサイズと同じ場合は0に戻る為注意)

        HAL_UART_Receive_DMA(pHuart, RxBuff, USART_RX_BUFFSIZE);  // DMA受信再スタート
    }
}

stm32f4xx_it.c

void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */
  USART2_EventHandler();  // <= 追加
  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */

  /* USER CODE END USART2_IRQn 1 */
}