STM32에서 printf 간편하게 사용하기

임베디드 프로젝트를 디버깅할 때, LED를 깜박거리는 것 외에도 직렬 콘솔에 유용한 메시지를 출력하는 것은 가장 단순하고, 가장 간단하며 가장 일반적인 기법입니다. 대부분의 플랫폼이 UART 버스를 통해 데이터를 전송할 수 있는 자체 API를 가지고 있지만, 모두 printf() 함수와 같은 능력은 없으며 대중적이지도 않습니다. 불행히도, 임베디드 C 애플리케이션에서 이 기능을 활용하기 위해 단순히 stdio 라이브러리만 추가하는 것은 일반적으로 충분하지 않습니다. STM32 프로젝트의 경우 몇 줄의 코드가 추가로 필요합니다.

UART 버스를 통해 형식이 지정된 데이터를 보내고 받길 원하는 분들은 동반 게시글인 STM32에서 scanf 간편하게 사용하기도 확인해 보시기 바랍니다!

절차

절차는 적절한 UART 주변 장치를 설정하고 활성화하는 예비 단계, UART 인스턴스를 printf() 함수에 매핑하는 1차 단계 그리고 부동 소수점을 출력하길 원하는 사용자를 위한 선택 가능한 2차 단계에 대해 다룹니다. STM32CubeIDE (버전 1.11.2)로 개발된 프로젝트용으로 작성되었지만 다른 환경으로도 확장 가능할 것입니다.

0. UART 인스턴스 설정

다음 단계에서 설명하는 대로 stdio 라이브러리로부터 printf() 함수를 올바르게 포함시키기 위해, UART (또는 USART) 주변 장치는 형식이 지정된 문자열(formatted string)을 전송하도록 설정되어 있어야 합니다. 일반적으로 ST 개발 기판으로 작업하면, 이런 목적으로 RX 및 TX 라인이 ST-LINK 프로그래머/디버거에 연결된 UART 주변 장치를 선택합니다. 이러면 ST-LINK의 USB 가상 COM 포트 브리지 덕분에 문자열을 직렬 콘솔로 보낼 수 있습니다. 다행히도 STM32CubeIDE 또는 STM32CubeMX에서 새로운 프로젝트를 시작하면, 이 UART 인스턴스는 기본적으로 설정됩니다. 프로그래머라면 어느 UART가 설정되었는지 기록만 하고 다음 단계로 넘어가면 됩니다.

UART가 설정되어 있지 않은 기존 프로젝트의 경우, 프로젝트의 .ioc 파일을 열고 그림 1에 요약된 수정을 하기만 하면 됩니다. 구체적으로는,

  • 적절한 주변 장치 인스턴스를 선택합니다.
  • Mode와 Configuration 파라미터를 설정합니다(그림 1에 나와 있는 설정이 가장 일반적으로 사용됩니다).
  • 적절한 GPIO 핀이 UART RX 및 TX로 설정되었는 지 확인합니다.

마무리로, .ioc 파일을 저장하여 프로젝트에 대한 코드를 생성합니다.


그림 1: U(S)ART 주변 장치의 활성화 및 설정 예시

1. printf()를 UART 인스턴스로 리다이렉트

UART가 준비되면, printf()를 추가하는 것은 단지 코드 몇 줄만 추가하면 될 일입니다.

a. main.c 파일의 맨 위에 있는 Private includes 부분에 #include <stdio.h>를 추가합니다.(그림 2)

include
그림 2: stdio 라이브러리 추가

b. 다음 코드를 복사하여 main.c 파일의 UART 핸들(handle) 선언과 main() 함수 사이에 붙여넣습니다. 아래 그림 3과 같이 Private function prototypes 부분에 실용적인 옵션이 있습니다. UART 핸들을 &huart?에서 원하는 UART 주변 장치의 핸들(예를 들어 &huart2)로 변경해야만 합니다.

#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(&huart?, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
  return ch;
}

putchar_prototype
Figure 3: main.c 파일에 리타겟팅 코드 추가

printf() 함수는 이제 부동 소수점 형식 지정자를 제외한 모두에 대해 예상대로 동작해야 합니다. 부동 소수점 형식을 사용하기 위해서는, 계속해서 다음 단계를 진행하십시오.

2. 부동 소수점 지원 활성화(옵션)

부동 소수점 형식 지정의 문제를 설명하기 위해, printf 레퍼런스 페이지에 제공된 예제 코드를 사용해 볼 수 있습니다.

printf("Characters: %c %c\n", 'a', 65);
printf("Decimals: %d %ld\n", 1977, 650000L);
printf("Preceding with blanks: %10d\n", 1977);
printf("Preceding with zeros: %010d\n", 1977);
printf("Some different radices: %d %x %o %#x %#o\n", 100, 100, 100, 100, 100);
printf("floats: %4.2f %+.0e %E\n", 3.1416, 3.1416, 3.1416);
printf("Width trick: %*d\n", 5, 10);
printf("%s\n", "A string");

프로젝트에 이 코드를 추가하면, "the float formatting support is not enabled(부동 소수점 형식 지원이 활성화되지 않음)"을 설명하는 경고가 나타납니다. 계속해서 빌드해 프로젝트를 실행하면, 부동 소수점이 출력되지 않는 것을 직렬 콘솔로 확인할 수 있습니다(그림 4).

ex_output_noFloat
그림 4: 부동 소수점 형식 지원이 활성화되지 않은 출력 예시

애플리케이션에서 부동 소수점이 사용되지 않는다면 문제되지 않을 수 있습니다. 그러나 부동 소수점이 있고 활성화하고 싶다면, Project Explorer에서 프로젝트 이름을 마우스 오른쪽 클릭하고 Properties를 선택합니다. “C/C++ Build” 카테고리에서 Settings를 선택하고 Tool Settings 탭에서는 MCU Settings을 선택합니다. 아래 그림 5와 같이 “Use float with printf from newlib-nano” 앞의 상자를 체크합니다. Apply and Close를 클릭합니다.

printf()에 대한 부동 소수점 형식 지원을 활성화하면 상당한 양의 추가 메모리를 소비합니다. 구체적으로는, 부동 소수점 지원 없이 printf() 함수가 소비하는 것 외에도 0.35KB의 RAM과 10.30KB의 플래시를 소비합니다. 이는 보다 저가형인 장치에서는 문제가 될 수 있습니다.


그림 5: 부동 소수점 형식 지원 활성화

이제 프로젝트를 리빌딩하고 코드를 실행하면, 그림 6에서 보여주듯이 부동 소수점 값이 올바른 형식으로 표시됩니다.

ex_output_float
그림 6: 부동 소수점 형식 지원이 활성화된 출력 예시



영문 원본: Easily Use printf on STM32