코드 출처 : https://controllerstech.com/uart-dma-with-idle-line-detection/
이전에 UART로 원하는 형식의 데이터를 수신할 때는 패킷의 종료를 뜻하는 특정 문자를 끝에 넣거나 개행 문자를 넣어서 인터럽트 안에서 버퍼에 복사해 처리해야했는데 이 때는 수신될 데이터의 길이 또는 범위를 미리 알고 있어야 했다.
다음은 UART가 IDLE Line을 감지하고 발생시키는 Interrupt를 활용하여 사전에 길이를 알 수 없는 데이터를 효과적으로 수신하는 방법이다.
CubeMX 설정
파라미터 세팅은 그대로 두고 DMA 세팅으로 가서 RX Request를 추가한다.
Mode는 Normal으로 한다.
Direction은 Peripheral To Memory 이고 Byte 단위로 수신해서 버퍼인 배열에 Memory Increment 하며 차곡차곡 쌓이게 될거다.
NVIC 세팅에서 global interrupt를 켠다.
IDLE line 이란 :
말 그대로 line이 유휴 상태인 것으로 UART RX line이 한 프레임 시간 동안 비활성화 되었을 때 IDLE line event 가 트리거 된다. 그리고 이 frame time은 baudrate에 의해 정해진다. baudrate가 높을 수록 한 frame의 시간이 적다.
소스코드
/* USER CODE BEGIN Includes */
#include <string.h>
/* USER CODE END Includes */
// ...
/* USER CODE BEGIN PD */
#define RxBuf_SIZE 10
#define MainBuf_SIZE 20
/* USER CODE END PD */
//...
/* USER CODE BEGIN PV */
extern DMA_HandleTypeDef hdma_usart3_rx;
uint8_t RxBuf[RxBuf_SIZE];
uint8_t MainBuf[MainBuf_SIZE];
/* USER CODE END PV */
//...
usart.c파일에 정의된 hdma_usart3_rx 핸들러를 extern 해서 가져오고 DMA방식으로 데이터를 수신할 RxBuf 와 이걸 링버퍼 타입으로 복사해둘 MainBuf를 선언한다.
/* USER CODE BEGIN 2 */
HAL_UARTEx_ReceiveToIdle_DMA(&huart3, RxBuf, RxBuf_SIZE);
__HAL_DMA_DISABLE_IT(&hdma_usart3_rx, DMA_IT_HT);
/* USER CODE END 2 */
CODE BEGIN2 영역에 HAL_UARTEx_ReceiveToIdle_DMA() 함수를 호출한다. Ctrl + 클릭 또는 F3을 눌러 따라 들어가면 아래와 같이 정의와 설명을 볼 수 있다.
그리고 RxHalfCpltCallback 은 안 쓸거니까 __HAL_DMA_DISABLE_IT(&hdma_usart3_rx, DMA_IT_HT); 한다.
다음 HAL_UARTEx_RxEventCallback() 함수를 가져다 재정의한다.
이 함수의 두번째 매개변수인 Size로는 수신된 데이터의 수가 들어온다.
/* USER CODE BEGIN 0 */
uint16_t oldPos = 0;
uint16_t newPos = 0;
int isOK = 0;
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if (huart->Instance == USART3)
{
oldPos = newPos; // Update the last position before copying new data
/* If the data in large and it is about to exceed the buffer size, we have to route it to the start of the buffer
* This is to maintain the circular buffer
* The old data in the main buffer will be overlapped
*/
if (oldPos+Size > MainBuf_SIZE) // If the current position + new data size is greater than the main buffer
{
uint16_t datatocopy = MainBuf_SIZE-oldPos; // find out how much space is left in the main buffer
memcpy ((uint8_t *)MainBuf+oldPos, RxBuf, datatocopy); // copy data in that remaining space
oldPos = 0; // point to the start of the buffer
memcpy ((uint8_t *)MainBuf, (uint8_t *)RxBuf+datatocopy, (Size-datatocopy)); // copy the remaining data
newPos = (Size-datatocopy); // update the position
}
/* if the current position + new data size is less than the main buffer
* we will simply copy the data into the buffer and update the position
*/
else
{
memcpy ((uint8_t *)MainBuf+oldPos, RxBuf, Size);
newPos = Size+oldPos;
}
/* start the DMA again */
HAL_UARTEx_ReceiveToIdle_DMA(&huart3, (uint8_t *) RxBuf, RxBuf_SIZE);
__HAL_DMA_DISABLE_IT(&hdma_usart3_rx, DMA_IT_HT);
}
// 테스트용
for(int i=0; i<Size; i++){
if((RxBuf[i]=='O') && (RxBuf[i+1])=='K'){
isOK=1;
}
}
}
/* USER CODE END 0 */
RxBuf에서 MainBuf 로 memcpy하고 만약 MainBuf 의 사이즈가 넘었으면 다시 인덱스 0번부터 memcpy한다.
그 아래에는 OK라는 문자열이 수신되었는지 체크하는 테스트용 코드이다. 이것보다 더 복잡한 데이터의 처리는 인터럽트 바깥에서 수행하게끔 하는게 좋다.
잘 수신되는 것을 확인할 수 있다.
다음 send file 테스트이다. 먼저 버퍼의 사이즈를 키우고 다시 디버그 한다.
/* USER CODE BEGIN PD */
#define RxBuf_SIZE 512
#define MainBuf_SIZE 2048
/* USER CODE END PD */
보낼 파일의 크기는 1382 byte이다.
아래와 같이 파일을 통째로 보냈을 때
텍스트 파일의 크기 만큼 시작부터 EOF 까지 MainBuf에 잘 복사된 것을 확인할 수 있다.
참고 영상 : https://www.youtube.com/watch?v=Bo6MC5A8uTE
깃허브 라이브러리 : https://github.com/controllerstech/STM32/tree/master/UART%20CIRCULAR%20BUFFER
위 깃허브에 가면 링버퍼의 head와 tail까지 식별할 수 있는 라이브러리가 있다.
추가글 :
2022.12.30 - [DSP, MCU/TMS320F2838x (C28x)] - 링 버퍼 Circular Buffer
'임베디드 개발 > STM32 (ARM Cortex-M)' 카테고리의 다른 글
STM32 ] USB CDC (Virtual Port Com) 사용하기 (0) | 2022.08.26 |
---|---|
STM32 ] 역기구학(Inverse kinematics)을 통한 델타로봇 제어 (3) (0) | 2022.08.22 |
STM32 ] Dynamixel AX-12 사용 - 델타로봇 제어하기 (2) (0) | 2022.08.11 |
STM32 ] Dynamixel AX-12A 모터 제어하기 (1) (7) | 2022.08.11 |
STM32 ] FreeRTOS + Semaphore ISR 사용 예제 (0) | 2022.08.08 |