ComputerScience & Embedded/NUCLEO & CAN Tranceiver
STM32 HAL Peripheral data handling APIs
leecrossun
2025. 3. 1. 15:28
데이터 처리 API 분류
- Polling 방식: 주기적으로 상태를 확인하여 데이터를 처리. 간단하지만 비효율적이고 CPU 자원을 많이 소모, 프로세서가 항상 켜져 있어야 하므로 저전력 모드나 절전 모드가 많은 경우 불리함
- 인터럽트 방식: 데이터가 준비되면 인터럽트를 발생시켜 CPU가 처리하도록 유도. 효율적이고 빠르지만 구현이 복잡하고 과도한 인터럽트가 성능 저하를 유발할 수 있음, 대부분의 통신 페리페럴이 사용
- DMA 방식: CPU 개입 없이 직접 메모리와 주변 장치 간 데이터를 전송. 대량의 데이터 전송에 효율적이며 CPU 부하를 줄여줌
⇒ 세가지 모두 데이터 핸들링이라는 공통적인 목표, 내부 작동 방식에서 차이가 있음
UART Data TXing
Polling mode를 왜 blocking mode라고 부를까?
API 내부 구조를 보면 while 루프가 있는데, 실행 제어가 이 API에 도달하면 코드가 이 루프에서 blocking 됨
즉, 데이터의 양을 의미하는 TransferCount가 0이 될때까지 루프에 갇힘
각 문자가 UART 페리페럴에 전송되면 이 카운트가 1만큼 감소
// main_app.c
#include <string.h> // 문자열 관련 lib
UART_HandleTypeDef huart2;
char *user_data = "The application is running\r\n"; // 전송 데이터
int main(void)
{
HAL_Init();
SystemClockConfig();
UART2_Init();
uint32_t len_of_data = strlen(user_data); // 데이터 길이
// UART 데이터 전송 : 데이터 핸들링 structure 주소, 데이터 포인터, 길이, timeout 호출까지의 길이
HAL_UART_Transmit(&huart2, user_data, len_of_data, HAL_MAX_DELAY);
return 0;
}
- UART 초기화 문제 확인
- UART2_Init()에서 고수준 초기화(파라미터 설정)를 수행하지만, 저수준 초기화는 msp.c에서 진행
- USART2 주변장치의 클록은 활성화했지만, GPIO 포트 A의 클록을 활성화하지 않음 → GPIO 엔진이 동작하지 않음
- 해결 방법: __HAL_RCC_GPIOA_CLK_ENABLE(); 추가하여 GPIO 포트 A의 클록 활성화
하지만 위 경우처럼 문자열이 잘려서 나옴. 왜그럴까?
- 전송된 데이터가 부분적으로만 출력되는 문제 해결
- STM32 Cube 프레임워크에서는 SysTick 타이머가 1ms마다 인터럽트를 발생시킴.
- STM32 Cube 레이어의 시간 관련 기능이 SysTick 타이머와 글로벌 Tick 변수에 의존함.
- 하지만 현재 코드에서 SysTick 핸들러를 구현하지 않음 → 글로벌 Tick 변수가 증가하지 않아 문제가 발생.
- SysTick 핸들러 구현
- startup.c에서 SysTick_Handler 함수명을 확인 후, it.c에 정의.
- SysTick_Handler 내부에서 HAL_IncTick(); 호출하여 글로벌 Tick 변수 증가
- HAL_SYSTICK_IRQHandler(); 호출하여 SysTick 인터럽트 처리
- SysTick의 콜백 기능은 필요하지 않으므로 구현하지 않음
코드 수정 후 위와 같이 정상 출력됨을 알 수 있음
UART Data Rxing - Polling
uint8_t convert_to_capital(uint8_t data);
UART_HandleTypeDef huart2;
char *user_data = "The application is running\r\n";
int main(void)
{
HAL_Init();
SystemClockConfig();
UART2_Init();
uint16_t len_of_data = strlen(user_data);
if (HAL_UART_Transmit(&huart2, (uint8_t*)user_data, len_of_data, HAL_MAX_DELAY) != HAL_OK)
{
Error_handler();
}
uint8_t rcvd_data;
uint8_t data_buffer[100];
uint32_t count = 0;
while(1)
{
HAL_UART_Receive(&huart2, &rcvd_data, 1, HAL_MAX_DELAY); // 데이터 수
if(rcvd_data == '\r') break; // 줄바꿈 시 루프 종료
else data_buffer[count++] = rcvd_data; // 버퍼에 데이터 저장
}
data_buffer[count++] = '\r'; // 수신 데이터 출력 후 줄바꿈 처리
HAL_UART_Transmit(&huart2, data_buffer, count, HAL_MAX_DELAY); // 입력 데이터 Echo
while(1);
return 0;
}
uint8_t convert_to_capital(uint8_t data)
{
if(data >= 'a' && data <= 'z')
{
data = data - ('a'-'A');
}
return data;
}
- 사용자의 데이터 입력 및 처리
- 사용자가 데이터를 입력하고 ENTER(캐리지 리턴, \\r) 키를 누르면 데이터 전송 완료로 간주.
- 몇 바이트를 받을지 모르므로, 1바이트씩 읽기로 결정.
- UART 데이터 수신 (HAL_UART_Receive())
- HAL_UART_Receive() API 사용.
- 첫 번째 인자로 핸들러, 두 번째 인자로 데이터를 저장할 변수 주소, 세 번째 인자로 수신할 바이트 수, 마지막으로 타임아웃 설정.
- uint8_t rcvd_data 변수를 선언해 1바이트씩 저장.
- 수신 데이터 버퍼에 저장
- data_buffer[100] 선언 후 rcvd_data를 순차적으로 저장.
- 데이터가 \\r이면 루프 종료, 아니라면 계속 저장.
- 입력 데이터 출력 (Echo 기능)
- HAL_UART_Transmit()을 사용해 입력받은 데이터를 그대로 출력.
- 소문자를 대문자로 변환
UART Data Rxing - Interrupt
폴링 기반 UART 애플리케이션
- 기존 애플리케이션은 폴링 방식으로 동작함
- 코드가 사용자가 UART를 통해 입력할 때까지 대기(블로킹) 함
- 즉, while 루프를 돌면서 데이터가 수신되었는지 계속 확인함
인터럽트 기반 UART 애플리케이션으로 변환
- 비동기(non-blocking) API를 사용하여 UART 데이터 수신을 처리
- HAL_UART_Receive_IT() API를 호출하면, 프로세서가 데이터 입력을 기다리지 않고 다른 작업을 수행할 수 있음
- 데이터가 수신되면 UART 인터럽트 핸들러(IRQ Handler) 가 실행됨
인터럽트 핸들러 구현
- USART2_IRQHandler를 찾아서 정의. ← startup 파일
- STM32 Cube Framework는 모든 페리페럴에 대한 IRQ processing API를 제공 ( ex. HAL_UART_IRQHandler() )
- 인터럽트가 발생하면 이벤트가 발생한 이유(전송 완료, 수신 완료, 에러 등)를 확인해야 함
- 이를 위해 HAL_UART_IRQHandler() API를 호출하여 인터럽트를 처리
코드 수정 및 빌드
- HAL_UART_IRQHandler()를 호출하기 위해, UART 핸들러 변수를 extern 키워드로 참조
- 빌드 시 오류 발생 → stm32f4xx_hal.h 헤더 파일이 누락됨
- 해당 헤더 파일을 추가하여 오류 해결
- 기존 폴링 기반 코드에서는 CPU가 데이터 입력을 기다리는 동안 다른 작업을 수행할 수 없음
- 인터럽트 기반 코드로 변환하면, CPU가 유휴 상태가 되지 않고 다른 작업을 수행할 수 있음
- STM32Cube 라이브러리에서 제공하는 HAL API를 사용하면 인터럽트 처리를 쉽게 구현 가능
데이터 수신 처리 과정
- UART에서 데이터가 수신되면 인터럽트가 발생
- 인터럽트 핸들러(USART2_IRQHandler)가 호출
- 인터럽트 처리 API가 실행되며, 적절한 콜백 함수가 호출
- 에러 발생 시 → 에러 콜백 호출
- 데이터 수신 완료 시 → 수신 완료 콜백 호출
- 전송 완료 시 → 전송 완료 콜백 호출
콜백 함수 구현
- STM32Cube 프레임워크에서는 여러 주변장치(ADC, DAC, Timer, SPI, I2C, CAN 등)에 대한 콜백을 제공
- UART 데이터 수신 시 HAL_UART_RxCpltCallback()이 호출
- HAL_UART_RxCpltCallback()은 기본적으로 weak 속성을 가지므로, 사용자가 직접 구현
데이터 수신 및 처리 로직
- 전역 변수로 데이터 버퍼(char data_buffer[100])와 카운터 변수(uint32_t count = 0)를 선언
- HAL_UART_RxCpltCallback()에서 수신된 데이터를 data_buffer에 저장한다.
- 수신된 데이터가 '\\r'(캐리지 리턴)이라면 입력이 완료된 것으로 간주하고, reception_complete 플래그를 TRUE로 설정한다.
- 모든 데이터 수신이 완료되면, data_buffer를 다시 사용자에게 전송한다.
- 전송 시 마지막 바이트를 '\\r'로 설정하고 HAL_MAX_DELAY를 사용하여 전송한다.
- while(reception_complete != TRUE) 루프를 사용하여 데이터가 수신될 때까지 계속 대기한다.
STM32 Cube API를 사용하고 싶을 때마다 드라이버 파일의 API들을 탐색하자
그다음 API 정의한 곳으로 이동하여 description과 parameter에 대해 읽고 해당 함수의 역할과 인자가 정확히 무엇을 받는 것인지 이해하자
반응형