0

STM32L010 USARTの使い方(DMA)


USART2とPCで通信するプログラムを作成します。
送受信共にDMAを使用します。
受信に関しては、リングバッファを実装します。 PCから受信したデータをPCへ返送します。

1. デバイスの設定

1.1. USART2の設定

USART2 の設定に関しては USARTの使い方を参照してください。


1.2. DMAの設定

UART2のDMA Settingsを選択しUSART2_TXとUSART2_RXを追加します。
DMA ChannelはDMA1 Channel 4 と DMA1 Channel 5を設定してください。
DMA1 Channel 2/3も選択可能ですが、categoryの関係で使えないようです。
USART2_RXのIncrement Addressは、ここではチェックを外してください。



USART2_RXのIncrement Addressはチェックしてください。


2. 受信割込み処理のコーディング

2.1. ヘッダファイル

今回は main.cには記述せず、別ファイルを追加して記述します。
./Core/Inc にusart2.hを追加し、以下の様に記述します。
リングバッファのサイズはここでは8バイトにしておきます。
//
// USART2 DMA Ring Buffer 対応
//

#define		RINGBUFFSIZE	(8)

typedef struct {
	uint8_t				ubOneData;
	uint8_t				ubBuffer[RINGBUFFSIZE];
	uint32_t			uwInIndex;
	uint32_t			uwOutIndex;
} RINGBUFF, *PRINGBUFF;

extern		RINGBUFF	g_stRing;

void		USART_Receive_DMA_Start(UART_HandleTypeDef *huart, PRINGBUFF pRing);
uint32_t	GetReceiveBytes(UART_HandleTypeDef *huart);
uint16_t	GetData(UART_HandleTypeDef *huart);
void		WaitTransmitEnd(UART_HandleTypeDef *huart);
void		TransmitStrings(UART_HandleTypeDef *huart, char *pData);
void		TransmitOneData(UART_HandleTypeDef *huart, uint8_t ubData);
				


2.2. 受信DMA開始関数

次に ./Core/Src に usart2.cを追加します。
最初に受信DMA開始関数を記述します。
//
// 受信DMA開始
//
void	USART_Receive_DMA_Start(UART_HandleTypeDef *huart, PRINGBUFF pRing)
{
	(*pRing).uwInIndex	= 0;
	(*pRing).uwOutIndex	= 0;

	HAL_UART_Receive_DMA(huart, &(*pRing).ubOneData, sizeof((*pRing).ubOneData));
}
				
ここでRINGBUFF構造体の初期化を行い、HAL_UART_Receive_DMAで受信DMAを開始します。
RINGBUF構造体のメンバuwOneDataは、USART2で受信したデータをDMA転送で受け取るためのバッファです。

2.3. 受信DMA完了割込み処理

次に受信完了後の割込み処理を記述します。
//
// USART RX DMA Interrupt Callback
//
void	HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	PRINGBUFF	pRing = (PRINGBUFF)(*huart).pRxBuffPtr;

	if ((*pRing).uwInIndex - (*pRing).uwOutIndex < RINGBUFFSIZE) {
		(*pRing).ubBuffer[(*pRing).uwInIndex++ & (RINGBUFFSIZE - 1)] = (*pRing).ubOneData;
	}

	HAL_UART_Receive_DMA(huart, &(*pRing).ubOneData, sizeof((*pRing).ubOneData));
}
				
DMA転送が完了するとHAL_UART_RxCpltCallbackが呼び出されます。
RINGBUFFのメンバ uwOneDataに受信データが存在するので、リングバッファにストアします。
バッファフルの場合、今回は捨てます。

(*huart).pRxBuffPtrは、割込みやDMAで使用する受信バッファへのポインタ変数で、HAL_UART_Receive_DMAで設定されます。
HAL_UART_Receive_DMA呼び出しで uwOneDataのポインタを渡しているので、キャスティングすれば RINGBUFF構造体を示すことになります。
本来はUART_HandleTypeDefにRINGBUFF構造体を追加したいのですが、ここに変更を加えるのは抵抗があります。
オブジェクト化としたいための苦肉の策です。
賛否両論あるかと思いますが。。。


2.4. その他関数

GetReceiveBytes関数はリングバッファ内の有効データ数を返します。
GetData関数はリングバッファ内の有効データ1バイトを返します。
有効データがない場合は0xFFFFを返します。
//
// リングバッファ内受信データ数を返す
//
uint32_t	GetReceiveBytes(UART_HandleTypeDef *huart)
{
	PRINGBUFF	pRing = (PRINGBUFF)(*huart).pRxBuffPtr;

	return (*pRing).uwInIndex - (*pRing).uwOutIndex;
}

//
// リングバッファ内受信データを返す
//
uint16_t	GetData(UART_HandleTypeDef *huart)
{
	PRINGBUFF	pRing = (PRINGBUFF)(*huart).pRxBuffPtr;
	uint16_t	uhData = 0xFFFF;

	if ((*pRing).uwInIndex - (*pRing).uwOutIndex != 0) {
		uhData = (uint16_t)(*pRing).ubBuffer[(*pRing).uwOutIndex++ & (RINGBUFFSIZE - 1)];
	}
	return uhData;
}
				



2.5. 送信DMA関数

TransmitStrings関数は文字列送信、TransmitOneData関数は1バイト送信となります。
両関数ともHAL_UART_Transmit_DMAでDMA送信後、WaitTransmitEnd関数で送信完了まで待つようにしています。
//
// 送信DMA完了待ち
//
void	WaitTransmitEnd(UART_HandleTypeDef *huart)
{
	while ((*huart).gState != HAL_UART_STATE_READY);
}

//
// 文字列DMA送信
//
void	TransmitStrings(UART_HandleTypeDef *huart, char *pData)
{
	HAL_UART_Transmit_DMA(huart, (uint8_t *)pData, strlen(pData));
	WaitTransmitEnd(huart);
}

//
// バイナリデータDMA送信
//
void	TransmitOneData(UART_HandleTypeDef *huart, uint8_t ubData)
{
	HAL_UART_Transmit_DMA(huart, (uint8_t *)&ubData, sizeof(ubData));
	WaitTransmitEnd(huart);
}
				
gStateは、HAL_UART_Transmit_DMA内でHAL_UART_STATE_BUSYに設定され、送信DMA完了でHAL_UART_STATE_READYに設定されます。


2.6. main関数への処理追加

まずmain.husart2.hを追加します。
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "usart2.h"

/* USER CODE END Includes */
				
次に受信DMAを開始させるため、USART_Receive_DMA_Startを追加します。
  /* USER CODE BEGIN 2 */
  TransmitStrings(&huart2, "DMA start\r\n");
  USART_Receive_DMA_Start(&huart2, &g_stRing);

  /* USER CODE END 2 */
				
次に受信データをエコーバックする処理を追加します。
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	if ((uhData = GetData(&huart2)) != 0xFFFF) {
		TransmitOneData(&huart2, (uint8_t)uhData);
	}
  }
  /* USER CODE END 3 */