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개의 댓글

답글 남기기

Avatar placeholder

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

ko_KRKorean