소개
STM32F103RCT6 마이크로컨트롤러에서 printf 함수를 사용하려면 UART를 통해 출력을 리다이렉션해야 합니다. 이 가이드에서는 Standard Peripheral Library와 HAL 라이브러리 두 가지 방법을 모두 다룹니다.
필요한 하드웨어:
- STM32F103RCT6 개발보드
- USB-to-Serial 변환기 (FTDI, CH340 등)
- 점퍼 와이어
1. UART 초기화 및 printf 리다이렉션
Standard Peripheral Library를 사용한 UART1 초기화 및 printf 리다이렉션 구현입니다.
#include "stm32f10x.h"
#include <stdio.h>
// UART1 초기화 함수
void UART1_Init(void) {
// 클럭 활성화
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
// GPIO 설정 (PA9: TX, PA10: RX)
GPIO_InitTypeDef GPIO_InitStruct;
// TX 핀 설정 (PA9)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// RX 핀 설정 (PA10)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// UART 설정
USART_InitTypeDef USART_InitStruct;
USART_InitStruct.USART_BaudRate = 115200;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_Init(USART1, &USART_InitStruct);
USART_Cmd(USART1, ENABLE);
}
// printf 리다이렉션을 위한 함수
int fputc(int ch, FILE *f) {
// UART1로 한 문자 전송
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_SendData(USART1, (uint8_t)ch);
return ch;
}
// 대안: _write 함수 재정의 (GNU ARM 툴체인)
int _write(int file, char *ptr, int len) {
int i;
for(i = 0; i < len; i++) {
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_SendData(USART1, ptr[i]);
}
return len;
}
💡 팁:
fputc 함수는 Keil MDK-ARM에서 주로 사용되고, _write 함수는 GCC 기반 툴체인에서 사용됩니다.
2. 메인 함수 예제
printf를 사용한 간단한 예제 프로그램입니다.
int main(void) {
// 시스템 클럭 설정 (옵션)
SystemInit();
// UART1 초기화
UART1_Init();
// printf 사용 예제
printf("STM32F103RCT6 Printf Example\r\n");
printf("System Clock: %ld Hz\r\n", SystemCoreClock);
int counter = 0;
float voltage = 3.3;
while(1) {
// 다양한 형식의 printf 사용
printf("Counter: %d\r\n", counter);
printf("Voltage: %.2f V\r\n", voltage);
printf("Hex value: 0x%04X\r\n", counter);
counter++;
// 딜레이 (간단한 방법)
for(volatile int i = 0; i < 1000000; i++);
}
}
3. HAL 라이브러리 사용 시 (CubeMX)
STM32CubeMX와 HAL 라이브러리를 사용하는 경우의 구현 방법입니다.
#include "main.h"
#include <stdio.h>
UART_HandleTypeDef huart1;
// printf 리다이렉션 (HAL 버전)
int __io_putchar(int ch) {
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
// 또는 _write 함수 재정의
int _write(int file, char *ptr, int len) {
HAL_UART_Transmit(&huart1, (uint8_t *)ptr, len, 0xFFFF);
return len;
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
printf("STM32F103RCT6 HAL Printf Example\r\n");
while(1) {
printf("System Tick: %ld\r\n", HAL_GetTick());
HAL_Delay(1000);
}
}
📌 참고: CubeMX에서 USART1을 설정할 때 다음 설정을 사용하세요:
- Mode: Asynchronous
- Baud Rate: 115200
- Word Length: 8 Bits
- Parity: None
- Stop Bits: 1
4. 컴파일러 설정
Keil MDK-ARM
- Project → Options for Target 메뉴 선택
- Target 탭으로 이동
- "Use MicroLIB" 체크박스 선택
- OK 클릭하여 저장
STM32CubeIDE / GCC
다음 중 하나의 방법을 선택하세요:
- syscalls.c 파일에
_write함수 구현 - 프로젝트 속성 → C/C++ Build → Settings → Tool Settings → MCU GCC Linker → Miscellaneous에서 링커 플래그에
-specs=nano.specs추가
⚠️ 주의: printf는 상당한 메모리와 처리 시간을 사용합니다. 실제 제품에서는 간단한 UART 출력 함수를 직접 구현하는 것을 권장합니다.
5. 디버깅 팁
조건부 컴파일을 사용한 디버그 매크로 구현 예제입니다.
// 디버그 매크로 정의
#ifdef DEBUG
#define DEBUG_PRINT(fmt, ...) printf("[DEBUG] " fmt "\r\n", ##__VA_ARGS__)
#else
#define DEBUG_PRINT(fmt, ...)
#endif
// 사용 예
DEBUG_PRINT("ADC Value: %d", adc_value);
DEBUG_PRINT("Temperature: %.1f C", temperature);
하드웨어 연결
- STM32F103RCT6의 PA9 (UART1_TX)를 USB-Serial 변환기의 RX에 연결
- STM32F103RCT6의 PA10 (UART1_RX)를 USB-Serial 변환기의 TX에 연결 (옵션)
- GND를 서로 연결
- PC에서 터미널 프로그램(PuTTY, Tera Term 등)을 실행하고 115200 baud rate로 설정
💡 성능 최적화 팁:
- 실시간 시스템에서는 printf 대신 간단한 UART 출력 함수 사용 권장
- 인터럽트 기반 UART 전송으로 블로킹 방지
- DMA를 사용한 UART 전송으로 CPU 부하 감소
- 순환 버퍼를 사용한 비동기 로깅 시스템 구현 고려