안녕하세요.
MCU에서 인터럽트 핸들러 안에서는 동작을 최대한 짧게 가져가야 합니다.
그래서 USART와 같은 통신 인터페이스를 사용할때에는 Queue Buffer 구조를 사용합니다.
큐(Queue)는 FIFO(First-In, First-Out)로 가장 먼저 들어온 데이터가 가장 먼저 나가는 자료 구조입니다.
그 중에서도 원형큐(Circular Queue) 또는 링 버퍼(Ring Buffer)는 다음과 같은 구조를 가지고 있습니다.
만약 원이 다 차서 새로운 데이터가 들어오게 된다면 어떻게 될까요?
-> Data1 자리에 덮어쓰게 됩니다. 그 다음 데이터는 Data2 자리에..
원으로 계속 반복하게 된다고 해서 원형 큐라고 합니다.
원형큐를 다음과 같이 구현할 수 있습니다.
/*
* Queue 구조 예시
* 만약 head가 tail을 역전할 경우 처리는..?
*/
#define MAX_QUEUE_SIZE (255)
uint8_t queue_buffer[MAX_QUEUE_SIZE] = {0,};
uint8_t head;
uint8_t tail;
// push: 새로운 데이터를 Queue buffer에 담는다.
void push(uint8_t new_data)
{
// 새로운 데이터를 queue에 넣는다.
queue_buffer[head] = new_data;
head++;
// head가 queue 마지막 위치에 도달했다면, 0으로 초기화
if (head >= MAX_QUEUE_SIZE) {
head = 0;
}
}
// pop: 가장 오래된 데이터를 가져온다.
uint8_t pop(void)
{
// tail의 위치에 데이터를 가져온다
uint8_t pop_data = queue_buffer[tail];
tail++;
// tail이 queue 마지막 위치에 도달했다면, 0으로 초기화
if (tail >= MAX_QUEUE_SIZE) {
tail = 0;
}
return pop_data;
}
uint8_t isEmpty(void)
{
// head와 tail의 위치가 같으면 queue가 비어있음
return head == tail;
}
위의 코드를 이용하여 USART 데이터를 처리하는 방식을 알아봅시다.
/* USER CODE BEGIN PFP */
uint8_t rx_data;
/*Interrupts RX Callback************************************************************/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1) {
push(rx_data);
HAL_UART_Receive_IT(&huart1, &rx_data, 1);
}
}
int main(void)
{
/* USER CODE BEGIN 1 */
uint8_t data;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, &rx_data, 1);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if (isEmpty() == 0) {
data = pop();
// 데이터 처리
// ...
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
상세히 살펴볼게요
HAL_UART_RxCpltCallback 함수는 Uart Rx 인터럽트가 발생하면 호출되는 함수입니다.
1. huart 인스턴스가 USART1로부터 Rx 인터럽트가 발생되었다면
2. rx_data를 queue buffer에 넣고
3. 1개 USART 데이터를 수신했다면 인터럽트를 발생시켜라
가 되겠습니다.
main을 분석해보면
1. 1개 데이터를 수신했다면 인터럽트를 발생시켜라
2. 만약 Queue Buffer에 데이터가 있다면
3. 데이터를 꺼내와라
4. - 데이터 처리 -
5. 2 반복
이 되겠습니다.
데이터 처리는 제품에 맞는 프로토콜에 따라 프로그램하면 되겠습니다.
이를 모듈화 하기 위해서 구조체를 사용하여 구조를 잡아주면 좋을거 같습니다.
// module_uart.h
#define MAX_BUFFER_SIZE (255)
typedef struct{
uint8_t head;
uint8_t tail;
uint8_t buffer[MAX_BUFFER_SIZE];
}uart_t;
void push(uart_t*, uint8_t);
uint8_t pop(uart_t*);
uint8_t isEmpty(uart_t*);
// module_uart.c
void init_uart(uart_t* u)
{
u->head = 0;
u->tail = 0;
memset(u->buffer, 0, sizeof(u->buffer));
}
void push(uart_t* u, uint8_t data)
{
u->buffer[u->head] = data;
u->head++;
if (u->head >= MAX_BUFFER_SIZE) {
u->head = 0;
}
}
uint8_t pop(uart_t* u)
{
uint8_t data = u->buffer[u->tail];
u->tail++;
if (u->tail >= MAX_BUFFER_SIZE) {
u->tail = 0;
}
return data;
}
uint8_t isEmpty(uart_t* u)
{
return u->head == u->tail;
}
// main.c
#include "module_uart.h"
uint8_t rx_data;
uart_t uart;
/*Interrupts RX Callback************************************************************/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1) {
push(&uart, rx_data);
HAL_UART_Receive_IT(&huart1, &rx_data, 1);
}
}
void main() {
uint8_t data;
// ..
init_uart(&uart);
HAL_UART_Receive_IT(&huart1, &rx_data, 1);
while(1) {
if (isEmpty(&uart) == 0) {
data = pop(&uart);
}
}
}
'MCU > STM32:HAL' 카테고리의 다른 글
[STM32CubeProgrammer]UART Bootloader로 펌웨어 다운로드 하기 (2) | 2023.06.21 |
---|---|
[STM32F][Teraterm]YMODEM 프로토콜로 헥사 전송시 버그 (0) | 2023.03.24 |
[STM32][DSP] DSP 라이브러리 설정 (2) | 2022.06.17 |
[STM32F][HAL] CAN (0) | 2021.05.20 |
[STM32F]FSMC와 LCD (0) | 2021.02.28 |
[STM32F]FSMC로 LCD(SSD1963) 제어하기 (2) | 2021.02.07 |
[STM32F][HAL] USART Interrupt (0) | 2020.12.23 |
[STM32F][HAL] DAC (Digital to Analog Converter) (0) | 2020.12.22 |