C++에서 UART Peripheral 사용하기

UART(Universal Asynchronous Receiver/Transmitter)는 임베디드 시스템에서 가장 널리 사용되는 통신 장치 중 하나입니다.

디버깅, 로그 출력, 외부 장치와의 데이터 교환 등 다양한 목적으로 활용되며, 단순하면서도 신뢰성 높은 통신 수단을 제공합니다.

이번 단계에서는 UART를 클래스 기반 구조로 구현하는 방법을 다룹니다.

기본 클래스인 BaseUART를 시작으로, 성능 요구에 따라 IntrUART(인터럽트 기반) 과 DmaUART(DMA 기반) 으로 확장하는 구조를 살펴보겠습니다.


BaseUART: 기본 클래스

BaseUART는 UART 통신을 위한 기본 기능을 제공합니다. 주요 기능은 다음과 같습니다.

  • 송수신 큐(Queue)
    • 전송 전/후 데이터를 임시 저장
  • 읽기/쓰기 함수
    • UART 송수신 제어 API 제공
  • 설정 속성(Properties)
    • Baud rate, Parity, Stop bit 등 통신 관련 속성 관리

이 클래스는 다른 UART 구현들의 공통 기반이 됩니다.


IntrUART: 인터럽트 기반 UART

IntrUART는 BaseUART를 상속받아 인터럽트 방식으로 동작합니다.

  • 소량의 데이터 전송/수신에 적합
  • 이벤트 기반 처리 가능 → CPU 유휴 시간 절약
  • ISR(Interrupt Service Routine) 내부에서 데이터 큐 처리

즉, 수신 이벤트가 발생할 때마다 인터럽트가 실행되고, 받은 데이터는 큐에 저장되어 애플리케이션에서 쉽게 가져올 수 있습니다.


DmaUART: DMA 기반 UART

DmaUART는 BaseUART를 상속받아 DMA를 이용한 고속 데이터 전송을 지원합니다.

  • CPU 개입을 최소화하여 성능 극대화
  • 대용량/연속 데이터 스트림 처리에 최적
  • 다른 태스크와 병렬로 안정적인 동작 가능

특히 로그 대량 출력, 대역폭이 큰 데이터 교환 등에서 강력한 성능을 발휘합니다.


코드 예제

예제 1 – IntrUART 사용

  1. 헤더 포함 및 객체 선언
#include "IntrUART.h"
...
IntrUART Uart1;
...
  1. 객체 초기화 함수
void InitUarts(void);
...
/*
 *
 */
void InitUarts(void)
{
	Uart1 = IntrUART(USART1, &CTask);
        Uart1.InitInterrupt();
}
  1. main.c에서 초기화 호출
...
  InitUarts();
...
  1. 인터럽트 ISR 처리
#include <IntrUART.h>

...
extern IntrUART Uart1;

/**
  * @brief This function handles USART2 global interrupt.
  */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
    Uart1.RxInterruptHandler();
    Uart1.TxInterruptHandler();
  /* USER CODE END USART1_IRQn 0 */
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

예제 2 – DmaUART 사용

  1. 헤더 포함 및 객체 선언
#include "DmaUART.h"
...
DmaUART Uart1;
...
  1. 객체 초기화 함수
void InitUarts(void);
...
/*
 *
 */
void InitUarts(void)
{
	Uart1 = DmaUART(USART1, DMA2, LL_DMA_STREAM_7, DMA2, LL_DMA_STREAM_2, &CTask);
        Uart1.DMARxEnable();
}
  1. main.c에서 초기화 호출
...
    InitUarts();
...
    while(1){
      ...
    }
  1. DMA ISR 처리
...
#include <ConsoleTask.h>

...
extern ConsoleTask CTask;

...
/**
  * @brief This function handles USART1 global interrupt.
  */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
	CTask.uart.IDLEInterruptHandler();
  /* USER CODE END USART1_IRQn 0 */
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}
...
/**
  * @brief This function handles DMA2 stream2 global interrupt.
  */
void DMA2_Stream2_IRQHandler(void)
{
  /* USER CODE BEGIN DMA2_Stream2_IRQn 0 */
	CTask.uart.RXDMAInterruptHandler();
  /* USER CODE END DMA2_Stream2_IRQn 0 */
  /* USER CODE BEGIN DMA2_Stream2_IRQn 1 */

  /* USER CODE END DMA2_Stream2_IRQn 1 */
}
...
/**
  * @brief This function handles DMA2 stream7 global interrupt.
  */
void DMA2_Stream7_IRQHandler(void)
{
  /* USER CODE BEGIN DMA2_Stream7_IRQn 0 */
	CTask.uart.TXDMAInterruptHandler();
  /* USER CODE END DMA2_Stream7_IRQn 0 */
  /* USER CODE BEGIN DMA2_Stream7_IRQn 1 */

  /* USER CODE END DMA2_Stream7_IRQn 1 */
}

트러블슈팅 & 팁

  1. 데이터 수신 안 됨 → RXNEIE, TXEIE 인터럽트 활성화 여부 확인
  2. 깨진 데이터 수신 → Baud rate, 패리티, 데이터 길이 설정 일치 여부 확인
  3. ISR이 호출되지 않음 → NVIC 설정 및 벡터 테이블 매핑 확인
  4. 처리 속도가 너무 느림 → IntrUART 대신 DmaUART 사용 고려

마치며

이번 단계에서는 UART를 클래스 계층 구조로 설계하여, 아래와 같이 나누어 살펴보았습니다.

  • BaseUART: 공통 기반 제공
  • IntrUART: 인터럽트 기반 처리
  • DmaUART: DMA 기반 고속 처리

이와 같은 계층적 구조를 활용하면, 애플리케이션 레벨에서는 코드 변경 없이 손쉽게 통신 방식을 교체할 수 있습니다.


0개의 댓글

답글 남기기

아바타 플레이스홀더

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

ko_KRKorean
Select your currency
KRW 한국 원화