본문 바로가기

SW 개발 공부/STM32

[STM32] UART DMA + Ring Buffer

개요

통신 구현시 프로토콜의 패킷 구성이 명령어 별로 사이즈가 다른 경우, 정상적으로 데이터를 수신받기 위해서는 추가적인 처리가 필요하다.

 

HAL Library에서 기본적으로 제공하는 HAL_USART_Receive_IT or DMA() 메서드의 동작은 설정한 버퍼 사이즈만큼 데이터가 수신되면 인터럽트가 발생하는 방식이다.

따라서 해당 메서드를 사용하여 가변길이의 데이터를 파싱하기 위해선 반드시 1byte씩 데이터를 수신받아 내부 추가 처리를해야한다.

 

HAL_UARTEX_ReceivetoIdle_IT or DMA() 메서드를 사용하면 데이터를 수신받다가 IDLE 상태가 되면 인터럽트가 발생하는 방식이다.

 

따라서 해당 메서드와 링버퍼를 사용하면 가변 패킷을 수신받아도 추가 파싱 처리 없이 데이터를 수신받을 수 있다.

아래는 위의 내용에 대한 설명이다.


 기존 HAL_UART_Receive_DMA() 방식

 
HAL_UART_Receive_DMA(&huart1, RxBuf, RxBuf_SIZE);
  • 이 방식에서는 RxBuf_SIZE만큼 데이터가 채워져야 인터럽트가 발생합니다.
  • 만약 데이터가 1~2바이트씩만 들어오면, RxBuf_SIZE가 다 차기 전까지는 인터럽트가 발생하지 않음.
  • 즉, **"버퍼가 다 찼을 때만 인터럽트 발생"**하는 방식.

 

 HAL_UARTEx_ReceiveToIdle_DMA() 방식

 
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, RxBuf, RxBuf_SIZE);
  • 이 방식에서는 데이터가 수신되다가 "Idle 상태"가 감지되면 즉시 인터럽트가 발생함.
  • 즉, RxBuf_SIZE만큼 채워지지 않아도 연속된 데이터 수신이 멈추면(Idle 상태 감지) 바로 인터럽트 호출.
  • **"데이터가 들어오다가 멈추면 인터럽트 발생"**하는 방식.

Idle 상태(Idle Line Detection)란?

UART에서 Idle 상태연속된 데이터 수신이 멈춘 경우를 의미해요.

  • MCU는 UART 라인에서 일정 시간 동안 추가 데이터가 들어오지 않으면 "Idle"로 판단.
  • **즉, "더 이상 데이터가 안 들어온다!"**라고 감지되면 HAL_UARTEx_RxEventCallback()이 호출됨.

 

언제 HAL_UARTEx_ReceiveToIdle_DMA()를 사용하면 좋을까?

이 방식을 사용하면 가변 길이 패킷을 처리하는 경우에 매우 유용합니다.

  • 예를 들어, 패킷이 길이가 일정하지 않고, 데이터가 중간중간 멈추면서 들어올 때
  • HAL_UART_Receive_DMA()는 고정 크기의 데이터만 받을 수 있기 때문에 비효율적
  • 반면, HAL_UARTEx_ReceiveToIdle_DMA()는 데이터가 끊기면 자동으로 처리할 수 있어 더 유연함.

 

HAL_UARTEx_ReceiveToIdle_DMA()의 동작 흐름

  1. DMA를 통해 UART 데이터를 수신하다가...
  2. 데이터가 연속적으로 들어오다가 멈춘 순간(Idle 상태 감지)
  3. 즉시 HAL_UARTEx_RxEventCallback() 인터럽트 발생
  4. 콜백 내에서 현재까지 수신된 데이터 크기(Size)를 기반으로 데이터 처리
  5. 다시 HAL_UARTEx_ReceiveToIdle_DMA()를 호출하여 다음 데이터를 받을 준비

 

기존 방식과 Idle 방식 비교

방식동작 방식언제 인터럽트 발생?장점단점

 

방식 동작 방식 인터럽트 발생 조건 장점 단점
HAL_UART_Receive_DMA() 고정된 크기만큼 수신 RxBuf_SIZE만큼 다 채워졌을 때 일정한 데이터 블록 처리에 적합 가변 길이 데이터 처리 어려움
HAL_UARTEx_ReceiveToIdle_DMA() Idle 감지 시 즉시 인터럽트 데이터가 끊겼을 때(Idle) 가변 길이 데이터 처리 가능 너무 짧은 데이터 수신 시 자주 인터럽트 발생 가능

 

사용 예제 코드

#define RxBuf_SIZE 10
#define MainBuf_SIZE 20

uint8_t RxBuf[RxBuf_SIZE];
uint8_t MainBuf[MainBuf_SIZE];


int main()
{
....
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, RxBuf, RxBuf_SIZE);

}
uint16_t curPos=0;

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
	if( huart->Instance==USART1)
	{
		if(curPos+Size>MainBuf_SIZE)
		{
			int numofdata = MainBuf_SIZE - curPos;
			memcpy((uint8_t *)MainBuf+curPos,RxBuf,numofdata);

			curPos =0;
			memcpy((uint8_t *)MainBuf,(uint8_t *)RxBuf + numofdata,(Size-numofdata));

			curPos = Size - numofdata;
		}
		else
		{
			memcpy((uint8_t *)MainBuf+curPos,RxBuf,Size);
			curPos=(curPos+Size)%MainBuf_SIZE;
		}

		HAL_UARTEx_ReceiveToIdle_DMA(&huart1, RxBuf, RxBuf_SIZE);
	}
}

 


 

결론

 

UART에서 가변 길이 패킷을 처리해야 한다면 HAL_UARTEx_ReceiveToIdle_DMA()가 훨씬 효율적
✔ 데이터가 수신되다가 멈추면 자동으로 인터럽트가 발생하여 즉시 처리 가능
✔ 기존 HAL_UART_Receive_DMA()는 고정 크기 데이터 수신에는 좋지만, 가변 패킷 처리에는 비효율적