1. UART를 이용한 Circular Queue 구현
Circular Queue는 UART 통신에서 흔히 사용되는 큐 중 하나이다. 사용하는 이유는 높은 baud rate로 인해 데이터 송 수신시에 UART 통신 데이터가 loss되는 경우가 발생할 수 있다. 이를 방지하기 위해 스택에 한 비트씩 데이터를 저장하여 먼저 들어온 데이터를 꺼내서 사용하는 FIFO(First In First Out) 방식으로 데이터 통신의 안정성을 증가시킬 수 있다.
이 Circular Queue를 이용하여 Comport master로 PC와 통신을 해볼 것이다. 동작 과정은 PC에서 문자열 전송하고, 해당 문자열에 맞게 LED를 제어한다.
#include "uart3.h"
#include "main.h"
#include "string.h"
#define COMMAND_MAX 40 // 최대로 저장할 수 있는 command 수
#define COMMAND_LENGTH 40 // command당 길이 40byte
volatile int input_index2 = 0; // UART로부터 들어온 문자를 저장하는 2차원 index값
volatile int input_index1 = 0; // UART로부터 들어온 문자를 저장하는 1차원 index값
volatile unsigned char rx_buff[COMMAND_MAX][COMMAND_LENGTH]; // 문자열을 저장하는 배열
extern uint8_t rx_data; //main.c에서 선언
extern UART_HandleTypeDef huart3;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart == &huart3) //pc로부터 값이 들어오면
{
unsigned char data;
data = rx_data; //pc로부터 데이터가 들어오면 저장
printf("%c",data); //그리고 다시 컴포트 마스터로 출력
if(input_index2 % COMMAND_MAX == (input_index2+1) % COMMAND_MAX) //큐가 꽉 찼는지 아닌지 확인
{
printf("Queue Full !!!\n");
}
else //큐가 다 안찼다면?
{
if(data == '\n' || data == '\r') //큐가 안찼을때 맨 끝에 개행문자인지 파악하고 맞으면 NULL넣어줌 맨 끝에
{
rx_buff[input_index2][input_index1] = '\0'; //문장의 끝은 NULL
input_index1 = 0;
input_index2++;
input_index2 %= COMMAND_MAX;
}
else
{
rx_buff[input_index2][input_index1++] = data; //2차원 array
}
}
// 주의 : 반드시 HAL_UART_Receive_IT를 call해야만 다음 RX INT가 뜬다.
HAL_UART_Receive_IT(&huart3, &rx_data, 1);
}
}
STM32에서의 큐 구현은 간단하다. 먼저 UART3.C파일 내에서 기본적인 변수들을 선언한다.
HAL_UART_RxCpltCallback함수를 통해 UART 수신완료를 판단한다.
UART3을 사용하므로 UART3 데이터가 수신되었는지 파악한 후, 40개의 2차원 배열이 가득 찼는지 안찼는지 판단한다.
가득 찼다면 "Queue Full"문자열을 출력하고, 그렇지 않으면 데이터를 저장하도록 한다.
2차원 배열이 가득 차지 않았을 때는 1차원 배열에 값을 저장해야하므로 개행문자나 줄바꿈을 만날 때 까지 스택에 데이터를 하나씩 저장한다.
데이터가 저장되었다면(개행문자나 줄바꿈을 만났다면) 열의 인덱스를 초기화하고, 행을 1씩 증가시킨다.
volatile int output_index2 = 0; // while(1) loop에서 가져가는 index값
void pc_command_processing(void);
int led1toggle_time = 0;
extern volatile int TIM10_10ms_counter;
void pc_command_processing(void) //출력은 이곳에서
{
if(input_index2 != output_index2) //rx_buff에 data가 들어 있으면,
{
printf("rx_buff[%d] : %s \n", output_index2, rx_buff[output_index2]); //현재 그 buff의 값을 출력해 봄
if(strncmp((const char *)rx_buff[output_index2], "led1toggle" ,10) == NULL) //값이 맞으면 NULL을 반환
{
TIM10_10ms_counter = 0;
led1toggle_time = atoi(&rx_buff[output_index2][10]);
}
else if(strncmp((const char *)rx_buff[output_index2], "led1off" ,7) == NULL) //값이 맞으면 NULL을 반환
{
led1toggle_time = 0;
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, 0);
}
output_index2++;
output_index2 %= COMMAND_MAX;
}
}
먼저 Comport master와 통신을 위해 pc_command_processing 함수를 구현한다.
인덱스 값을 이용하여 데이터가 들어왔는지 판단한다.
그리고 데이터가 들어오면 데이터(문자 한 개)를 출력한다.
그 다음 strncmp 함수를 사용하여, 해당 문자열과 비교했을 때 같으면 원하는 동작을 수행한다.
이 때, pc에서 "led1toggle30", "led1off" 두 개 문자열을 보낼 것이고, "led1toggle30"의 마지막 숫자인 30을 토글 시간으로 저장할 것이다.
이를 위해 atoi함수로 문자열을 지정하여 토글 시간 값을 저장한다.
volatile int TIM10_10ms_counter = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim -> Instance == TIM10)
{
TIM10_10ms_counter++;
}
}
/* USER CODE BEGIN WHILE */
while (1)
{
pc_command_processing();
if(led1toggle_time && TIM10_10ms_counter >= led1toggle_time)
{
TIM10_10ms_counter = 0;
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
main.c로 넘어와서, 방금 전 구현했던 pc_command_processing함수를 호출한다.
위 함수를 호출하므로써 PC와 통신을 계속 진행시킨다. 아래 if문에서는 led1toggle_time 와 TIM10_10ms_counter를 and연산한다.
두 변수를 and연산하면 토글 시간 값이 지정된 수까지만 연산이 진행된다.
(만약 led1toggle_time이 30이라면 ,TIM10_10ms_counter가 증가되면서 연산 결과가 0부터 30까지 증가됨 )
이 연산 결과와 led1toggle_time 값이 매칭이 되면 다시 카운트 값을 초기화하고 LED를 반전시킨다.
'STM32' 카테고리의 다른 글
[STM32] RTOS (0) | 2023.08.13 |
---|---|
[STM32] STM32에서 스텝 모터 동작 시키기 (0) | 2023.08.13 |
[STM32] STM32F429ZI 사용하기 (0) | 2023.08.13 |
[STM32] STM32의 타이머 카운터 (0) | 2023.08.13 |
[STM32] STM32에서 LED와 버튼 제어 (0) | 2023.08.13 |