6 분 소요

4. USART

  • USART는 Microprocessor에서 가장 기본적이고 흔히 사용하는 Serial 통신 인터페이스이다.
    아무리 저사양의 MCU도 USART 기능을 하나 이상 포함하고 있을 것이다.
    USART는 synchronous와 asynchronous가 있는데,
    synchronous는 싱크를 맞추기 위한 Clock 신호를 같이 전달하는 방식이며,
    asynchronous는 받는 쪽에서 충분히 빠른 sampling을 통해서 데이터를 수신하는 방식이다.
    image

    (1) USART 설정 (CubeMX)

  • 설정 방법은 CubeMX를 통해서 간단히 할 수 있다.
    우리는 USART1 peripheral을 성정해 보자.
    image
    USART와 같이 통신 기능은 송신(TX)와 수신(RX)이 있는데,
    송신(TX)의 경우는 원하는 시점에 보내기만 하면 되기 때문에 간단하지만,
    수신(RX)의 경우는 조금 고민해야 하는 부분이 있다.
    1. 가장 쉽게 생각할 수 있는 방식은 항상 수신 버퍼를 확인하는 방식이다.
      이 방식은 통신을 포함하는 모든 코드를 Non-block 방식으로 구현해야하는 복잡함이 있다.
    2. 두 번째로 이용할 수 있는 방법은 수신 인터럽트 기능을 이용하는 것이다.
      이 방식은 해당 인터럽트 기능을 제공하는 MCU에서만 활용할 수 있는 기능으로,
      코드 구현에 있어서 Non-block일 필요가 없다는 장점이 있다.
      하지만, 추가적인 인터럽트가 많은 프로그램을 개발할 때에는 인터럽트 우선순위 및 점유율 등을 면밀한 설계하지 않으면,
      수신 데이터를 놓지면서 데이터가 깨질 수 있다.
    3. 마지막으로 우리가 설정해 볼 방식은 DMA 방식이다.
      DMA란 Direct Memory Access로, 데이터가 수신되면 프로세서의 처리과정 없이 곧바로 Memory에 저장되는 방식이다.
      쉽게 이해하자면 수신 버퍼의 데이터를 메모리 공간에 할당하는 프로그램이 FPGA 같은 하드웨어 프로그램으로 구현되어 있다고 생각하면 된다.
  • USART1의 DMA 설정은 다음과 같이 할 수 있다.
    imageimage
    RX DMA의 경우는 Mode를 Circular로 지정해 주자.
  • USART1의 NVIC 탭으로 가면 다음과 같이 USART1 과 DMA Stream2, Stream7의 인터럽트 Enable 체크박스가 있다.
    DMA를 사용하면, 실질적으로 인터럽트는 필요하지 않은데,
    CubeMX는 DMA 인터럽트를 해제하지 못하게 되어있다.
    image
    이것은 좌측의 NVIC 설정에서 “Force DMA channels Interrupts“의 체크를 해제하면 가능하다.
    image
    다시 USART1 설정의 NVIC로 돌아오면 다음과 같이 헤제가 가능하도록 활성화된다.
    image
    USART1 인터럽트는 활성화 하겠다.
    왜냐하며, RS485같은 Half-duplex 통신으로 구현할 때,
    TX Enable 신호를 만들거나 하는 용도로, TX Complete 시점에서 인터럽트가 걸리도록 구현해 볼 예정이다.

  • 마지막으로 Project Manager의 Advanced Settings에서 USART와 DMA의 라이브러리를 LL로 바꿔주고,
    코드를 생성해 주자.
    image

(2) USART1 API 함수 작성

usart.c/h 내용에 다음과 같이 API 함수들을 추가해 주자

usart.h

/**
  ******************************************************************************
  * @file    usart.h
  * @brief   This file contains all the function prototypes for
  *          the usart.c file
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __USART_H__
#define __USART_H__

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* USER CODE BEGIN Private defines */

/* USER CODE END Private defines */

void MX_USART1_UART_Init(void);

/* USER CODE BEGIN Prototypes */
extern void USART1_SetBaudrate(u32 baudrate);
extern u8 ucUSART1_ReadByte(u8 *pucByte);
extern u32 uiUSART1_ReadBuff(u8 *pucData, u32 uiSize);
extern void USART1_WriteBuff(u8 *pucData, u32 uiLength);
extern void USART1_Printf(const char *format, ...);
extern void USART1_WaitTXComplete(void);
extern void USART1_RxClear(void);
/* USER CODE END Prototypes */

#ifdef __cplusplus
}
#endif

#endif /* __USART_H__ */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

usart.c

/**
  ******************************************************************************
  * @file    usart.c
  * @brief   This file provides code for the configuration
  *          of the USART instances.
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "usart.h"

/* USER CODE BEGIN 0 */
#include <stdio.h>
#include <string.h>
#include <stdarg.h>

#define USART1_RX_BUFFER_SIZE	(0x800)

#define USART1_RX_BUFF_HEAD		(USART1_RX_BUFFER_SIZE - DMA2_Stream2->NDTR)
#define USART1_RX_SIZE			((USART1_RX_BUFF_HEAD + USART1_RX_BUFFER_SIZE - g_uiUSART1_RxBuffTail) & (USART1_RX_BUFFER_SIZE - 1))
#define USART1_RX_BUFF_CLEAR()	(g_uiUSART1_RxBuffTail = USART1_RX_BUFF_HEAD)

vu8 g_vucUSART1_TxComplete = 1;
u8 g_pucUSART1_RxBuffer[USART1_RX_BUFFER_SIZE] = {0,};
u32 g_uiUSART1_RxBuffTail = 0;
static char g_pcUART1_Printf_str[256];

LL_USART_InitTypeDef g_tUSART1_InitStruct = {0};
/* USER CODE END 0 */

/* USART1 init function */

void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  LL_USART_InitTypeDef USART_InitStruct = {0};

  LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* Peripheral clock enable */
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_USART1);

  LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOA);
  /**USART1 GPIO Configuration
  PA9   ------> USART1_TX
  PA10   ------> USART1_RX
  */
  GPIO_InitStruct.Pin = LL_GPIO_PIN_9|LL_GPIO_PIN_10;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  GPIO_InitStruct.Alternate = LL_GPIO_AF_7;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* USART1 DMA Init */

  /* USART1_RX Init */
  LL_DMA_SetChannelSelection(DMA2, LL_DMA_STREAM_2, LL_DMA_CHANNEL_4);

  LL_DMA_SetDataTransferDirection(DMA2, LL_DMA_STREAM_2, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);

  LL_DMA_SetStreamPriorityLevel(DMA2, LL_DMA_STREAM_2, LL_DMA_PRIORITY_LOW);

  LL_DMA_SetMode(DMA2, LL_DMA_STREAM_2, LL_DMA_MODE_CIRCULAR);

  LL_DMA_SetPeriphIncMode(DMA2, LL_DMA_STREAM_2, LL_DMA_PERIPH_NOINCREMENT);

  LL_DMA_SetMemoryIncMode(DMA2, LL_DMA_STREAM_2, LL_DMA_MEMORY_INCREMENT);

  LL_DMA_SetPeriphSize(DMA2, LL_DMA_STREAM_2, LL_DMA_PDATAALIGN_BYTE);

  LL_DMA_SetMemorySize(DMA2, LL_DMA_STREAM_2, LL_DMA_MDATAALIGN_BYTE);

  LL_DMA_DisableFifoMode(DMA2, LL_DMA_STREAM_2);

  /* USART1_TX Init */
  LL_DMA_SetChannelSelection(DMA2, LL_DMA_STREAM_7, LL_DMA_CHANNEL_4);

  LL_DMA_SetDataTransferDirection(DMA2, LL_DMA_STREAM_7, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);

  LL_DMA_SetStreamPriorityLevel(DMA2, LL_DMA_STREAM_7, LL_DMA_PRIORITY_LOW);

  LL_DMA_SetMode(DMA2, LL_DMA_STREAM_7, LL_DMA_MODE_NORMAL);

  LL_DMA_SetPeriphIncMode(DMA2, LL_DMA_STREAM_7, LL_DMA_PERIPH_NOINCREMENT);

  LL_DMA_SetMemoryIncMode(DMA2, LL_DMA_STREAM_7, LL_DMA_MEMORY_INCREMENT);

  LL_DMA_SetPeriphSize(DMA2, LL_DMA_STREAM_7, LL_DMA_PDATAALIGN_BYTE);

  LL_DMA_SetMemorySize(DMA2, LL_DMA_STREAM_7, LL_DMA_MDATAALIGN_BYTE);

  LL_DMA_DisableFifoMode(DMA2, LL_DMA_STREAM_7);

  /* USART1 interrupt Init */
  NVIC_SetPriority(USART1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
  NVIC_EnableIRQ(USART1_IRQn);

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  USART_InitStruct.BaudRate = 115200;
  USART_InitStruct.DataWidth = LL_USART_DATAWIDTH_8B;
  USART_InitStruct.StopBits = LL_USART_STOPBITS_1;
  USART_InitStruct.Parity = LL_USART_PARITY_NONE;
  USART_InitStruct.TransferDirection = LL_USART_DIRECTION_TX_RX;
  USART_InitStruct.HardwareFlowControl = LL_USART_HWCONTROL_NONE;
  USART_InitStruct.OverSampling = LL_USART_OVERSAMPLING_16;
  LL_USART_Init(USART1, &USART_InitStruct);
  LL_USART_ConfigAsyncMode(USART1);
  LL_USART_Enable(USART1);
  /* USER CODE BEGIN USART1_Init 2 */
  LL_USART_Disable(USART1);
  g_tUSART1_InitStruct = USART_InitStruct;
  LL_DMA_SetMemoryAddress(DMA2, LL_DMA_STREAM_2, (u32)g_pucUSART1_RxBuffer);
  LL_DMA_SetPeriphAddress(DMA2, LL_DMA_STREAM_2, (u32)(&USART1->DR));
  LL_DMA_SetDataLength(DMA2, LL_DMA_STREAM_2, USART1_RX_BUFFER_SIZE);
  LL_USART_EnableDMAReq_RX(USART1);
  LL_DMA_EnableStream(DMA2, LL_DMA_STREAM_2);

  LL_DMA_SetPeriphAddress(DMA2, LL_DMA_STREAM_7, (u32)(&USART1->DR));
  LL_USART_EnableIT_TC(USART1);

  USART1_RX_BUFF_CLEAR();
  LL_USART_Enable(USART1);
  /* USER CODE END USART1_Init 2 */

}

/* USER CODE BEGIN 1 */
void USART1_SetBaudrate(u32 baudrate)
{
	LL_USART_Disable(USART1);

	g_tUSART1_InitStruct.BaudRate = baudrate;
	LL_USART_Init(USART1, &g_tUSART1_InitStruct);
	LL_USART_ConfigAsyncMode(USART1);

	USART1_RX_BUFF_CLEAR();

	LL_USART_Enable(USART1);
}

u8 ucUSART1_ReadByte(u8 *pucByte)
{
	u8 ucRet = 0;
	if(0 < USART1_RX_SIZE) {
		*pucByte = g_pucUSART1_RxBuffer[g_uiUSART1_RxBuffTail];
		g_uiUSART1_RxBuffTail = (g_uiUSART1_RxBuffTail + 1) & (USART1_RX_BUFFER_SIZE - 1);
		ucRet = 1;
	}

	return ucRet;
}

u32 uiUSART1_ReadBuff(u8 *pucData, u32 uiSize)
{

	register u32 i = 0;
	u32 uiReadSize = 0;

	for(i = 0; i < uiSize; i++) {
		if (0 == USART1_RX_SIZE) break;

		pucData[i] = g_pucUSART1_RxBuffer[g_uiUSART1_RxBuffTail];
		g_uiUSART1_RxBuffTail = (g_uiUSART1_RxBuffTail + 1) & (USART1_RX_BUFFER_SIZE - 1);
		uiReadSize++;
	}

	return uiReadSize;
}

void USART1_WriteBuff(u8 *pucData, u32 uiLength)
{
	g_vucUSART1_TxComplete = 0;

	LL_DMA_SetMemoryAddress(DMA2, LL_DMA_STREAM_7, (u32)pucData);
	LL_DMA_SetDataLength(DMA2, LL_DMA_STREAM_7, uiLength);

	LL_USART_EnableDMAReq_TX(USART1);
	LL_DMA_EnableStream(DMA2, LL_DMA_STREAM_7);
	USART1_WaitTXComplete();
}

void USART1_Printf(const char *format, ...)
{
	va_list va;

	va_start(va, format);
	vsprintf(g_pcUART1_Printf_str, format, va);
	va_end(va);

	USART1_WriteBuff((u8 *)g_pcUART1_Printf_str, strlen(g_pcUART1_Printf_str));
	USART1_WaitTXComplete();
}

void USART1_WaitTXComplete(void)
{
	while(!g_vucUSART1_TxComplete);
}

void USART1_RxClear(void)
{
	USART1_RX_BUFF_CLEAR();
}
/* USER CODE END 1 */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

마지막으로, stm32f4xx_it.c 코드에서, TX Complete 시점에 해야하는 내용을 추가해 주자.

stm32f4xx_it.c

/* USER CODE BEGIN EV */
extern vu8 g_vucUSART1_TxComplete;
/* USER CODE END EV */

윗쪽에서 TX Complete 변수를 extern 해주고,
USART1_IRQHandler 함수 내용에 다음을 추가한다.

/**
  * @brief This function handles USART1 global interrupt.
  */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
  if(LL_USART_IsActiveFlag_TC(USART1)) {
    LL_USART_ClearFlag_TC(USART1);

    g_vucUSART1_TxComplete = 1;
    LL_DMA_ClearFlag_HT7(DMA2);
    LL_DMA_ClearFlag_TC7(DMA2);
    LL_DMA_DisableStream(DMA2,LL_DMA_STREAM_7);
    LL_USART_DisableDMAReq_TX(USART1);
  }
  /* USER CODE END USART1_IRQn 0 */
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

그리고, 테스트를 위해서 main.c에서, 이전 포스팅에서 구현한 VCP API를 같이 사용하여
USB to USART 통신 변환기가 되는 내용을 추가해 주자.

main.c

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USB_DEVICE_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  VCP_Init();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  u8 pucBuff[64];
  u32 uiSize;
  while (1)
  {
    uiSize = uiUSART1_ReadBuff(pucBuff, 64);
    if(0 < uiSize) {
      uiVCP_WriteBuff(pucBuff, uiSize);
    }
    uiSize = uiVCP_ReadBuff(pucBuff, 64);
    if(0 < uiSize) {
      USART1_WriteBuff(pucBuff, uiSize);
    }

    /* USER CODE END WHILE */

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

시중에 파는 USB to UART 통신 변환기와 다음과 같이 연결하고,
image
현재 USAT1의 Baudrate는 115,200 bps이므로,
Putty를 두 개 실행하여 각가의 COM Port를 115,200 bps로 Open 해준다.
image
각각의 터미널에서 글자를 타이핑하면 반대편 터미널로 전달되는 것을 확인할 수 있다.
image

카테고리:

업데이트:

댓글남기기