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 사용
- 헤더 포함 및 객체 선언
#include "IntrUART.h" ... IntrUART Uart1; ...
- 객체 초기화 함수
void InitUarts(void); ... /* * */ void InitUarts(void) { Uart1 = IntrUART(USART1, &CTask); Uart1.InitInterrupt(); }
- main.c에서 초기화 호출
... InitUarts(); ...
- 인터럽트 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 사용
- 헤더 포함 및 객체 선언
#include "DmaUART.h" ... DmaUART Uart1; ...
- 객체 초기화 함수
void InitUarts(void); ... /* * */ void InitUarts(void) { Uart1 = DmaUART(USART1, DMA2, LL_DMA_STREAM_7, DMA2, LL_DMA_STREAM_2, &CTask); Uart1.DMARxEnable(); }
- main.c에서 초기화 호출
... InitUarts(); ... while(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 */ }
트러블슈팅 & 팁
- 데이터 수신 안 됨 → RXNEIE, TXEIE 인터럽트 활성화 여부 확인
- 깨진 데이터 수신 → Baud rate, 패리티, 데이터 길이 설정 일치 여부 확인
- ISR이 호출되지 않음 → NVIC 설정 및 벡터 테이블 매핑 확인
- 처리 속도가 너무 느림 → IntrUART 대신 DmaUART 사용 고려
마치며
이번 단계에서는 UART를 클래스 계층 구조로 설계하여, 아래와 같이 나누어 살펴보았습니다.
- BaseUART: 공통 기반 제공
- IntrUART: 인터럽트 기반 처리
- DmaUART: DMA 기반 고속 처리
이와 같은 계층적 구조를 활용하면, 애플리케이션 레벨에서는 코드 변경 없이 손쉽게 통신 방식을 교체할 수 있습니다.
0개의 댓글