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

임베디드 시스템과 인터페이스 하는 많은 접근법이 있지만, 가장 단순하면서 다용도인 것 중 하나는 직렬 콘솔로부터 사용자 입력을 받는 것입니다. 입력되는 ASCII 문자를 수동으로 파싱하고 적절한 데이터 형식으로 변환하는 대신, 자동으로 할 수 있도록 scanf() 함수가 사용되는 경우가 많습니다. 그러나 printf() 함수와 마찬가지로, 이 stdio 라이브러리 함수를 활용하기 위해서는 추가 코드가 필요합니다. STM32에서 printf 간편하게 사용하기에 동반 게시글로 기획되었으며, 이 게시글은 UART 주변 장치를 scanf() 함수에도 매핑함으로써 한 단계 더 나아갑니다.

절차

다음 절차는 모든 STM32CubeIDE(게시글 작성 시점의 버전은 1.9.0) 프로젝트에 그대로 적용할 수 있습니다. 다른 개발 환경을 선호하는 경우, 차이점을 수용하기 위해 해당 절차를 약간 수정해야 할 수 있습니다.

0. UART 인스턴스 설정

이 단계는 예비 단계로, UART(또는 USART) 주변 장치가 가상 COM 포트와 데이터를 주고 받을 수 있도록 올바르게 설정되었는지 확인합니다. 이는 ST 개발 기판에서 RX 및 TX 라인이 ST-LINK 프로그래머/디버거에 연결된 UART 주변 장치를 선택하는 것을 의미합니다. 자세한 내용은 STM32에서 printf 간편하게 사용하기의 해당 단계를 확인해 보시기 바랍니다.

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

a. 그림 1과 같이, main.c 파일의 Private Includes 부분에 stdio 라이브러리의 include 지시문을 추가합니다.

Include the stdio library
그림 1: stdio 라이브러리 포함

b. scanf() 함수만 리다이렉트하려면 목록 1에 제공된 코드를 복사하여 main.c의 Private function prototypes 부분에 붙여넣습니다. (대부분의 애플리케이션에서 요구하는 것처럼) scanf()printf() 함수를 모두 리다이렉트하려면 목록 2에 제공된 코드를 복사하여 붙여넣습니다.

목록 1: scanf()를 리다이렉트하기 위한 간략한 코드

#ifdef __GNUC__
#define GETCHAR_PROTOTYPE int __io_getchar(void)
#else
#define GETCHAR_PROTOTYPE int fgetc(FILE *f)
#endif

GETCHAR_PROTOTYPE
{
  uint8_t ch = 0;

  /* Clear the Overrun flag just before receiving the first character */
  __HAL_UART_CLEAR_OREFLAG(&huart?);

  /* Wait for reception of a character on the USART RX line and echo this
   * character on console */
  HAL_UART_Receive(&huart?, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
  HAL_UART_Transmit(&huart?, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
  return ch;
}

목록 2: printf()scanf() 둘 다 리다이렉트하기 위한 간략한 코드

#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#define GETCHAR_PROTOTYPE int __io_getchar(void)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#define GETCHAR_PROTOTYPE int fgetc(FILE *f)
#endif

PUTCHAR_PROTOTYPE
{
  HAL_UART_Transmit(&huart?, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
  return ch;
}

GETCHAR_PROTOTYPE
{
  uint8_t ch = 0;

  /* Clear the Overrun flag just before receiving the first character */
  __HAL_UART_CLEAR_OREFLAG(&huart?);

  /* Wait for reception of a character on the USART RX line and echo this
   * character on console */
  HAL_UART_Receive(&huart?, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
  HAL_UART_Transmit(&huart?, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
  return ch;
}

위 목록에 사용된 더미 UART 핸들을 원하는 UART 주변 장치로 변경해야 합니다. 예를 들어, 그림 2는 huart2가 대상 UART 인스턴스로 사용되고 있는 것을 보여줍니다.


그림 2: main.c 파일에 리타겟팅 코드 추가

c. 안타깝게도 STM32CubeIDE에서 자동으로 생성된 기본 syscalls.c 파일은 입력 스트림의 내부 버퍼링이 활성화된 경우 예기치 못한 동작을 야기합니다. 이 문제에 대한 가장 간단한 해결책은 scanf()에 대한 호출이 발생하기 전에 버퍼링을 비활성화하는 것입니다. 목록 3에 제공된 코드를 복사하여 main() 함수의 초기화 부분에 붙여넣습니다.

목록 3: 입력 스트림에 대한 내부 버퍼링 비활성화

setvbuf(stdin, NULL, _IONBF, 0);

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

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

printf() 함수와 마찬가지로, 애플리케이션에서 직렬 입력으로 부동 소수점을 읽을 필요가 있다면 scanf()에 대한 부동 소수점 지원은 명백하게 활성화되어야 합니다. 그렇지 않으면, 형식 문자열에 부동 소수점 지정자가 있는 모든 scanf()에 대한 호출이 예상과 다르게 동작할 것입니다.

a. Project Explorer에서 프로젝트 이름을 마우스 오른쪽 클릭하고 Properties를 선택합니다. “C/C++ Build” 카테고리에서 Settings를 선택하고 Tool Settings 탭에서는 MCU Settings을 선택합니다. 아래 그림 3과 같이 “Use float with scanf from newlib-nano” 앞의 상자를 체크합니다. Apply and Close를 클릭합니다.


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

결과

scanf 레퍼런스 페이지의 예제 코드를 사용하면 사용자 입력을 올바르게 읽고 표시하는 것을 그림 4에서 알 수 있습니다.

목록 4: 테스트용 예제 코드

char str[80];
int i;

printf("Enter your family name: ");
scanf("%79s", str);
printf("Enter your age: ");
scanf("%d", &i);
printf("Mr. %s, %d years old.\n", str, i);
printf("Enter a hexadecimal number: ");
scanf("%x", &i);
printf("You have entered %#x (%d).\n", i, i);

example code output
그림 4: 예제 코드 출력



영문 원본: Easily Use scanf on STM32