虽然与嵌入式系统进行交互的方法多种多样,但其中最简单、最通用的一种方法是使用来自串行控制台的用户输入。相较于手动解析传入的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所示。
图 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实例。
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”)旁边的复选框,如下图所示。点击应用并关闭。
结论
使用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);
图 4 : 示例代码输出