在STM32上轻松使用scanf

虽然与嵌入式系统进行交互的方法多种多样,但其中最简单、最通用的一种方法是使用来自串行控制台的用户输入。相较于手动解析传入的ASCII字符并将它们转换为适当的数据格式的做法,我们可以使用scanf() 函数自动完成此操作。然而,与printf() 函数一样,我们必须提供一些额外的代码才能利用stdio 库函数。作为在STM32上轻松使用printf 一文的附属说明,本文将深入介绍将UART外设映射到scanf() 函数的操作。

步骤

以下步骤可直接应用于任何STM32CubeIDE项目(在撰写本文时,其版本为1.9.0)。如果你偏好其他开发环境,可能需要稍作修改以适应存在的差异。

0.建立UART实例

作为预备步骤,请确保已将UART(或USART)外设配置为与虚拟COM端口相互收发数据。在ST开发板上,这通常意味着选择连接到ST-LINK编程器/调试器的UART RX和TX线路。如需查看完整详情,请参阅在STM32上轻松使用printf 一文中的相应步骤。

1.重定向scanf()至UART实例

a. 在main.c 文件的“私有包含”(Private Includes)部分中,为stdio 库添加包含指令,如图1所示。

01_00
1 纳入stdio

b. 要仅重定向scanf() 函数,请将列表1中提供的代码复制并粘贴到main.c 的“私有函数原型”部分(Private Function Prototypes section)。要重定向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) ,并在工具设置选项卡下选择MCU 设置( MCU Settings 。勾选“从newlib-nano使用scanf的浮点数”( “Use float with scanf from newlib-nano”)旁边的复选框,如下图所示。点击应用并关闭


3 启用浮点格式化支持

结论

使用scanf()参考页面中的示例代码(参见列表4)后,我们可以看到在图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);

04_00
4 示例代码输出