STM32CubeIDE UART 사용해 printf 출력하기에서 설정 및 코드에 대해 분석해 보겠습니다.
목차
- 'PC13'을 'GPIO_EXTI13'으로 설정
- C printf 분석
- C Callback 함수 분석
'PC13'을 'GPIO_EXTI13'으로 설정
2. 핀 설정 및 소스코드 생성하기에서 'PC13' 핀을 'GPIO_EXTI13'으로 설정합니다.
그 이유는 Schematic 상 PC13과 Blue button이 연결되어 있기 때문입니다.
C printf 분석
UART를 통해 printf를 사용하기 위해서는 아래 코드가 "필수"입니다.
// main.c
int __io_putchar(int ch)
{
(void) HAL_UART_Transmit(&huart2, (uint8_t*) &ch, 1, 100);
return ch;
}
일반적인 환경(임베디드가 아닌)은 표준 출력이 있어 printf()를 사용하면 기본적으로 stdout이 터미널(콘솔)에 연결되어 있습니다.
그래서 자동으로 화면(콘솔)에 출력됩니다.
이는 운영체제가 있고, stdout은 운영체제가 관리하는 입출력 스트림을 통해 터미널과 연결되기 때문입니다.
하지만 STM32 같은 임베디드 환경에서는 운영체제가 없거나, 있더라도 기본적인 터미널이 없습니다.
그래서 표준 출력 장치가 없어 printf()의 출력 경로를 직접 지정해야 합니다.
출력 장치가 다양할 수 있어서 실제 출력 방식은 직접 구현할 수 있도록 합니다. USART, LCD, 블루투스 등 다양한 방식으로 출력 경로를 바꿀 수 있습니다.
printf가 newlib(경량 C 표준 라이브러리로 glibc을 임베디스 시스템(예: STM32)에서 사용하기 위해 설계됨)에서 _write()를 호출합니다.
// syscalls.c line 35~36
extern int __io_putchar(int ch) __attribute__((weak));
extern int __io_getchar(void) __attribute__((weak));
// syscalls.c line 80~
__attribute__((weak)) int _write(int file, char *ptr, int len)
{
(void)file;
int DataIdx;
for (DataIdx = 0; DataIdx < len; DataIdx++)
{
__io_putchar(*ptr++);
}
return len;
}
여기서 __io_putchar가 weak 속성으로 선언되어, 다른 파일에서 같은 이름의 함수를 만들면 새로운 정의가 우선됩니다.
underbar 1개(_) : 내부적으로 사용하거나 네이밍 충돌을 피하기 위한 접두사
underbar 2개(__) : 컴파일러, 라이브러리 내부 또는 시스템 예약용 심볼
그래서 저희가 정의한 __io_putchar가 실제로 사용됩니다.
C Callback 분석
1. GPIO
// main.c line 228 ~
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == BTN_Pin)
{
printf("Button Pressed\r\n");
}
}
버튼 인터럽트(HAL_GPIO_EXTI_Callback)은 GPIO 핀에서 신호가 변할 때 실행됩니다. ('PC13' 핀 'GPIO_EXTI13' 설정)
실행 흐름
버튼 눌림
→ EXTI 인터럽트 발생
→ STM32가 IRQ 핸들러(Interrupt Handler) 실행
→ HAL 라이브러리가 HAL_GPIO_EXTI_Callback(GPIO_Pin) 호출
→ 사용자가 정의한 HAL_GPIO_EXTI_Callback()이 실행
이는 stm32f1xx_it.c 파일에
line 218~ 에서 IRQ Handler를 실행하면서 HAL_GPIO_EXTI_Callback(GPIO_Pin)을 실행합니다.
이렇게 변경 가능한 이유는 위에서 언급한 weak 속성으로 선언됐기 때문입니다.
이는 stm32f1xx_hal_gpio.c에서 찾을 수 있습니다.
2. UART
// main.c line 236~
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART2) {
printf("Key Pressed: %c\r\n", rx_data);
HAL_UART_Receive_IT(&huart2, &rx_data, 1);
}
}
실행 흐름
UART가 데이터 수신 대기(HAL_UART_Receive_IT(&huart2, &rx_data, 1); 호출)
→ 데이터 입력으로 UART 인터럽트 발생
→ STM32가 IRQ 핸들러 실행
→ HAL 라이브러리가 HAL_UART_RxCpltCallback(huart)을 호출
→ 사용자가 정의한 HAL_UART_RxCpltCallback()이 실행
→ printf("Key Pressed: %c\r\n", rx_data); 실행
→ 다음 데이터를 받기 위해 HAL_UART_Receive_IT()를 다시 호출
이는 stm32f1xx_it.c 파일에서 USART2_IRQHandler를 실행시킵니다.
이는 stm32f1xx_hal_uart.c 파일에서 HAL_UART_IRQHandler에서 UART_Receive_IT를 실행시키며
최종적으로 저희가 정의한 HAL_UART_RxCpltCallback을 실행시킵니다.
weak 속성으로 선언됐기에 변경 가능하며 stm32f1xx_hal_uart.c에서 찾을 수 있습니다.
2025/02/20 ver 0
'Electrical Study' 카테고리의 다른 글
STM32CubeIDE에서 PWM 설정하고 출력하기 (0) | 2025.02.24 |
---|---|
STM32CubeIDE에서 RTC 설정하고 UART와 printrf로 출력하기 (2) | 2025.02.21 |
STM32CubeIDE UART 사용해 printf 출력하기 (0) | 2025.02.18 |
STM32CubeIDE에 NUCLEO-F103RB GPIO 출력 파형 찍어보기 (0) | 2025.02.18 |
STM Project 사용된 하드웨어 및 개발 환경 (0) | 2025.02.18 |