STM32 nucleoを使う (5) UART 受信


はじめに

送信を行うタイミングは自由に決めることができますが、受信のタイミングは相手任せのため何時始まるかわかりません。取りこぼしが起こらないようにしなければなりません。

ポーリングによる受信

ボーレートが遅ければポーリングで受信したかチェックできます。
9600bpsであれば、1byteの送信時間は(1⁺8⁺1)/9600 =1.04msですので、1ms周期でチェックすればポーリングでも受信できます。が遅いボーレートしか使えませんし、取りこぼしする可能性が高いのでわざわざポーリングで受信するのはお勧めできません。

割り込みによる受信

UARTが受信し受信割り込みが発生します。割り込み処理でレジスタからデータを読み込みます。
例としてターミナルから文字列を受信するプログラムを作成します。
文字列の終了はCRとします。

プロジェクトを作成しUARTの設定を行います。
通信速度等はデフォルトのまま、割り込みの設定を行います。
NVIC SettingタブでUSART2のEnabledにチェックを入れます。

受信割り込みには、HAL_UART_Receive_IT()を使います。

HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

pDataは受信したデータを格納するバッファのポインタ。Sizeは受信するデータ数です。
決まったサイズのデータを受信するのであれば、そのまま使えますがターミナルからの受信のようにサイズが決まっていない場合はちょっと使いずらいです。
Sizeを1byteととし1byte受信したら送信用バッファに格納し、CRを受信すると送信するようにします。

変数の定義と受信割り込みのcallback関数。

/* USER CODE BEGIN 0 */
#define BUFF_SIZE   (200)
#define CHAR_CR     (0x0d)
#define TRUE        (1)
#define FALSE       (0)


uint8_t flagRcved;              /* 受信完了フラグ */
uint16_t rcvLength;             /* 受信データ数 */
uint8_t rcvBuffer[BUFF_SIZE];   /* 受信バッファ */
uint8_t sndBuffer[BUFF_SIZE];   /* 送信バッファ */

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    flagRcved = TRUE;           /* 受信完了フラグ設定 */
}

/* USER CODE END 0 */

callback関数では受信完了フラグを設定させます。

main関数

  /* USER CODE BEGIN WHILE */

  while (1)
  {
    do
    {
      /* 受信割り込み開始 */
      HAL_UART_Receive_IT(&huart2, rcvBuffer, 1);

      /* 受信割り込み終了待ち */
      while (flagRcved == FALSE)
      {
          ;
      }

      sndBuffer[rcvLength] = rcvBuffer[0];
      rcvLength++;
      flagRcved = FALSE;
    } while ((rcvBuffer[0] != CHAR_CR) && (rcvLength < BUFF_SIZE));

    /* 受信した内容を送信 */
    HAL_UART_Transmit_IT(&huart2, sndBuffer, rcvLength);
    rcvLength = 0;
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

受信割り込みを開始し1byte受信するまで待ちます。次に受信した内容をバッファにコピーし、CRを受信するかバッファサイズまで受信すると、受信した内容を送信します。

DMAによる受信

受信からバッファの転送までをDMAで行います。
STM32のDMAにはサーキュラモード(Circular Mode)があります。これはバッファの最後に到達するとバッファの先頭から続けて転送を行うモードです。
このモードを使ってバッファへの転送を自動で行い、バッファにデータが書き込まれたのをポーリングでチェックするプログラムを作成します。

USART2のDMA Settingタブを編集します。
Addボタンを押しDMA RequestにUSART2_RXを追加します。
ModeをCirculaモード、Increment AddressをMemoryのみ、Data WidthはByteにします。

DMA書き込み位置

受信バッファサイズからDMA転送数(送信される残りのバイト数) を引くと、次にDMAが転送する位置がわかります。これを書き込みポインタとします。
DMA転送数はデータ数レジスタ(DMA_CNDTRx)を参照します。

UART_HandleTypeDef huart2;
であれば、CNDTRは
huart2->hdmarx->Instance->CNDTR
で参照できます。
受信バッファサイズをRCV_BUFF_SIZEとすると書き込みポインタ(wr_ptr)は

wr_ptr = RCV_BUFF_SIZE - huart2->hdmarx->Instance->CNDTR;

となりますが、サーキュラーモードの場合、最後に達すると先頭に戻るので次のようになります。

    wr_ptr = RCV_BUFF_SIZE - pUart2->hdmarx->Instance->CNDTR;
    if (wr_ptr == RCV_BUFF_SIZE) {
        wr_ptr = 0;
    }

書き込みポインタの位置と読み込みポインタの位置を比較し、違っていれば書き込まれたと判断できます。

    result = FALSE;
    if (Rd_ptr != getUartWrPtr()) {
        result = TRUE;                  /* 受信有り */
    }

割り込み受信と同様にターミナルから受信するプログラムを作成します。
マクロ定義と変数の定義。
サーキュラーモードの確認をするため受信バッファサイズは小さくしてます。

/* USER CODE BEGIN PD */
#define TRUE            (1)
#define FALSE           (0)
#define CHAR_CR         (0x0d)
#define RCV_BUFF_SIZE   (8) /* UART受信バッファサイズ */
#define SND_BUFF_SIZE   (512)   /* UART送信バッファサイズ */
/* USER CODE END PD */

/* USER CODE BEGIN PV */
static uint8_t RdBuff[RCV_BUFF_SIZE];   /* UART受信バッファ */
static uint8_t SdBuff[SND_BUFF_SIZE];   /* UART送信バッファ */
static uint16_t Rd_ptr;                 /* 受信ポインタ */
static UART_HandleTypeDef* pUart2;  
/* USER CODE END PV */

受信データ取得等の関数

/* 書き込みポインタ位置の取得*/
uint16_t getUartWrPtr(void)
{
    uint16_t wr_ptr;

    wr_ptr = RCV_BUFF_SIZE - pUart2->hdmarx->Instance->CNDTR;
    if (wr_ptr == RCV_BUFF_SIZE) {
        wr_ptr = 0;
    }

    return wr_ptr;
}

/* 受信確認*/
uint8_t isUartRcv(void)
{
    uint8_t result;

    result = FALSE;
    if (Rd_ptr != getUartWrPtr()) {
        result = TRUE;                  /* 受信有り */
    }
    return result;
}

/* 受信データの取得*/
uint8_t rdUart(uint8_t *rdData)
{
    uint8_t result;

    result = FALSE;
    if(isUartRcv() == TRUE) {
        *rdData = RdBuff[Rd_ptr++];
        if (Rd_ptr >= RCV_BUFF_SIZE) {
            Rd_ptr = 0;
        }
        result = TRUE;
    }
    return result;
}

メインループ

  /* USER CODE BEGIN 2 */
  pUart2 = &huart2;
  rcvLength = 0;

  HAL_UART_Receive_DMA(pUart2, RdBuff, RCV_BUFF_SIZE);  /* 受信開始 */
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    if (rdUart(&rdData) == TRUE) {
      SdBuff[rcvLength++] = rdData;
      if ((rdData == CHAR_CR) || (rcvLength >= SND_BUFF_SIZE)) {
        /* 受信した内容を送信 */
        HAL_UART_Transmit(pUart2, SdBuff, rcvLength, 0xFFFF);
        rcvLength = 0;
      }
    }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

実行しターミナルから”0123456789CR”を送信したときの受信バッファの内容です。

バッファの最後まで達した後に先頭から続けて書き込まれています。

UART受信はDMAを使うとかなりソフトの負担が下がるのでお勧めです。
ただこの例ではエラーに関して考慮していませんので注意願います。

次はタイマーの使い方を考えます。
STM32の使い方を学習した内容をまとめています。内容に不備等ありましたら連絡お願いします。