지난 포스트에서 좀 더 자세히 CubeMX에서 설정 부터 UART(USART) Printf - HAL & Polling을 설명해보도록 하겠습니다.
실제로 어플리케이션을 개발할 때 내부 동작 순서나 Debugging을 위해 자주 사용하는 printf() Log 출력하는 방법을 STM32G431RB Nucleo board를 사용해서 UART를 통해 구현해보겠습니다.
CubeMX(혹은 CubeIDE)핀 설정은 아래와 같이 해줍니다.

LPUART : PA2, PA3
BUTTON : PC13
STM32G4 Nucleo board에는 LPUART PA2,PA3을 통해서 UART VCP로 사용할 수 있습니다. 내부 회로도를 확인해 보면 PA2, PA3 핀은 ST-Link와 연결되어 최종적으로 PC에 Virtual COM Port로 사용할 수 있습니다.
물론 다른 보드에서는 다른 핀을 사용할 수 있으니 board 메뉴얼에서 확인하시면 됩니다.
PC13은 Nucleo 보드의 파란색 버튼으로 할당되어 있습니다. 이것은 모든 Nucleo board에서 공통입니다. 파란색 버튼을 누르면 Log를 출력하게 하기 위해서 EXTI로 설정하였습니다.

물론 NVIC에서 Interrupt도 enable해주어야 합니다.

CubeMX에서 모든 설정은 마쳤습니다. Code generation 하고 CubeIDE에서 Import하여 추가코드를 작성하도록 하겠습니다.
첫번째로 할 것은 main.c에 아래와 같이 stdio.h를 추가하는 것입니다.
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */
두번째는 main.c에 printf의 실제 출력되는 prototype 을 정의하는 하는 코드를 추가해 줍니다.
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&hlpuart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
CubeIDE는 __GNUC__ 로 define되어 있기 때문에 int __io_putchar(int ch)를 사용하게 됩니다. 이제 Log를 출력할 부분에 printf 구문을 추가해 주면 됩니다.
int main(void)
{
/* USER CODE BEGIN 1 */
/* 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_LPUART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("Hello world!\r\n");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
저는 위의 코드처럼 Hello world!를 출력하도록 코드를 작성했습니다. 문장 마지막에 \r\n을 써주지 않으며 문자가 출력되지 않으니 이점을 꼭 유의해야 하겠습니다.
다음은 버튼을 클릭했을 Log가 출력되도록 추가하도록 하겠습니다. PC13에 연결된 EXTI interrupt handler callback 함수를 override 해주면 됩니다.
/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
printf("user button pushed!\r\n");
}
/* USER CODE END 4 */
HAL_GPIO_EXTI_Callback 함수가 EXTI interrupt handler callback 함수이며 main.c에서 두어도 되고 원하는 위치에 두어도 됩니다.
위와 같이 코드를 추가한 후에 보드에 올리고 micro USB로 PC와 연결 후 serial 통신 프로그램(Tera Term 등)으로 연결하시면 아래와 같이 로그를 확인 할 수가 있습니다.

한가지 더 팁을 드리자면 현재 __io_putchar(int ch) 함수는 한 글자씩 UART로 문자를 보내기 때문에 상당히 MCU로드를 많이 소비하는 방법으로 되어 있습니다. 아래와 같이 __io_putchar(int ch) 함수를 _write 함수로 대체하면 MCU 로드를 많이 줄일 수 있습니다.
int _write(int file, char *ptr, int len)
{
HAL_UART_Transmit(&hlpuart1, (uint8_t *)ptr, len, HAL_MAX_DELAY);
return len;
}
어플리케이션이 MCU 로드에 민감하다면 위의 방법을 사용하는 것이 훨씬 더 좋은 방법이며 더 로드를 줄이고자 한다면 DMA 방식으로 바꾸는 것이 가장 좋습니다.
* printf에 float 형을 사용해야 한다면 아래 블로그를 참조해주세요.
https://forum.digikey.com/t/easily-use-printf-on-stm32/20157

이상입니다.
'▶ STM32 > Peripheral' 카테고리의 다른 글
STM32G4 최대 시스템 클럭 170MHz 설정 방법 (0) | 2024.03.30 |
---|---|
STM32 Flash Memory에 대한 이야기 (0) | 2024.03.25 |
STM32의 자체 ADC 에러 (0) | 2024.03.09 |
Dual Core MCU 에서 RCC, RCC_C1, RCC_C2의 차이점 (0) | 2024.03.02 |
STM32에서 CRC 사용하기 (0) | 2024.02.16 |