【嵌入式学习-STM32CubeMX篇】配置HAL库串口并重定向printf和getchar函数 (***) https://blog.csdn.net/ken2232/article/details/135383655 (本文)
STM32-HAL库-printf函数重定向(USART应用实例) https://blog.csdn.net/qq_45772333/article/details/113530716
STM32-HAL-串口的printf重定向 https://blog.csdn.net/sinat_41690014/article/details/130273292
STM32-HAL库-printf函数重定向(简单易懂亲测有效) https://blog.csdn.net/weixin_55686654/article/details/132745884
** printf 函数:典型源码编写样例(摘录自 debian os 源码库) https://blog.csdn.net/ken2232/article/details/135715412
第43讲 基础篇-printf函数输出流程及使用
https://www.bilibili.com/video/BV1bv4y1R7dp?p=43
P44第44讲 基础篇-printf函数支持
https://www.bilibili.com/video/BV1bv4y1R7dp?p=44&spm_id_from=pageDriver
避免半主机模式:微库法,代码法
串口模式的连接要求:避开使用半主机模式
视频开始时间:02:40
----------------------------------------------------------------------
关联参考:
串口通信:printf 重定向 (****) https://blog.csdn.net/ken2232/article/details/135347248
要点:
避免半主机模式:微库法,代码法
对正点原子的"微库法"与"代码法"的概念理解
注意:以下,可能是错误的理解?
1. 所谓的:"微库法"与"代码法",这两个的概念创造,模糊不清,不容易理解。
本质上,printf 包含在 stdio.h 标准库中,采用什么样的方法与方式,来将 stdio.h 引入到自己的工程中,这才是关键。
1.1. 所谓的:"微库法",其实是通过对 IDE 选项的勾选,来将 stdio.h 引入到自己的工程中。
这种做法的好处是,用户自己不需要关心如何将stdio.h 引入到自己工程中的方法和步骤; IDE 已经帮助实现了。
此时,通过 keil IDE 引入的 stdio.h 函数库,是由 keil 精简之后的函数库,也就是所谓的“微库”。
可以通过编写代码的方式("代码法")来引入微库吗?可能不行吧?毕竟 keil是卖钱的,微库也不是开源的。微库可能是与 Keil IDE 捆绑在一起的吧?
1.2. 所谓的:"代码法",则不是通过对 IDE 选项的勾选,而是用户自己通过代码的方式,来将 stdio.h 引入到自己的工程中。
此时,所引入的 stdio.h 函数库,是由 Arm公司提供的标准库,功能应该更加全面。
1.3. 概念定义,模糊不清,导致在理解上产生问题?
本质上,所谓:
"微库法":通过对 keil IDE 选项的勾选方式,来引入 stdio.h 函数库;从而、以及实现 printf 重定向的步骤和方法。
-
"代码法":用户自己通过编写代码的方式,来引入 stdio.h 函数库;从而、以及实现 printf 重定向的步骤和方法。
也许,需要重新创造两个更加准确的概念,来取代模糊不清的所谓 "微库法"与"代码法"这样的概念定义吧?这样才有可能让概念更直观,理解更简单、容易?
1.4. keil IDE 自带的 stdio.h 函数库,也就是所谓的“微库”;与 Arm 编译器所带的 stdio.h 函数库;这是两个不同的库,虽然名称都是 stdio.h 函数库。就像“洗衣机”这个名字一样,海尔的和小天鹅的,它们的结构构造,未必相同。
在 Arm没有收购 Keil之前,keil 微库中的 stdio.h和 Arm的 stdio.h,应该是不同的。至于在收购之后,有多大的差别,这个就没有、或不能(若是闭源时,比较就是一件麻烦事了。)深入去比较了。
使用仿真器进行调试,相比于串口调试,这是更好的调试方法。问题是:仿真器,太贵了?一般用途,没不要去花那钱?
要不要“重复造轮子”的问题
"代码法":可以类比于是:命令行编程。
"微库法":可以类比于是:IDE 编程。需要的配置,交给了 IDE 第三方。此时,是否还偏执地去使用“命令行编程”呢?到底需不需要“重复造轮子”呢?这就要:具体场景,具体分析了。
在 stdio.h 函数库中,std 在拉丁语中,不只是表示“标准”这一个意思。
std 在这里,似乎应该翻译成 “产品预设规格”之类的,更恰当?而不是所谓的“标准”。
对于不同制造者的 stfio.h 来说,它的使用效果有两种:
- 符合 stdio.h 函数库使用的默认场景,则不需要使用者做任何修改,就可以直接使用了。
- 不符合 stdio.h 函数库使用的默认场景,则很可能需要进行额外的设置与配置,才能够使用了。
比如:假如采用了 Arm 专门的调试器,那么,直接 #include < stdio.h >之后,就可以直接使用 printf 函数了。如果没有采用 Arm 专门的调试器,而是使用串口的方式,则需要自己额外进行一些设置与配置。
----------------------------------------------------------------------
摘要
//=// 不同的编译器,字符输出函数的名称、输入参数,是不同的。
// 可能的原因:?之所以 GNUC 没有 FILE *f 参数,是因为 GNU的 printf 将它单独提取出来,用“重定向运算符”来取代它?或者说是嵌入式为了压缩,裁剪掉了 GNU的重定向运算符的功能?<< 可能吧?
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int _io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__*/原文链接:https://blog.csdn.net/qq_45772333/article/details/113530716
/* 加入以下代码, 支持printf函数, 而不需要选择use MicroLIB */
#if 1
//=// AC5 和 AC6 的源码,存在不兼容。在 AC5 下的源码,使用 AC6 来编译,会发生错误。
#if (__ARMCC_VERSION >= 6010050) /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t"); /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t"); /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */#else
//=// 在半主机模式下,ARM MCU 的 printf 默认的输出设备,为:arm 专用的调试器。(然后,通过调试器,再输入到电脑上位机。<注意:未验证,注者没有arm 专用的调试器>)
// 如果是这样,那么,就意味着:在使用 Arm MCU的场景,直接使用 arm 专用的调试器,就不需要用户自己去修改 printf 的重定向代码了?
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)
struct __FILE
{
int handle;
/* Whatever you require here. If the only file you are using is */
/* standard output using printf() for debugging, no file handling */
/* is required. */
};#endif
/* 不使用半主机模式,至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
ch = ch;
return ch;
}/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
x = x;
}char *_sys_command_string(char *cmd, int len)
{
return NULL;
}/* FILE 在 stdio.h里面定义. */
FILE __stdout;/* 重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
while ((USART1->SR & 0X40) == 0); /* 等待上一个字符发送完成 */
USART1->DR = (uint8_t)ch; /* 将要发送的字符 ch 写入到DR寄存器 */
return ch;
}
#endifhttps://blog.csdn.net/sinat_41690014/article/details/130273292
========================================
【嵌入式学习-STM32CubeMX篇】配置HAL库串口并重定向printf和getchar函数(***)
https://blog.csdn.net/zengchenAAA/article/details/134041179
为什么需要重定向printf和getchar函数
重定向printf和getchar函数可以将它们的输入/输出流重定向到其他设备或接口,最常见的情况是将它们重定向到串口。
这样做有以下几个主要原因:
- 调试和日志输出:通过重定向printf函数,可以将调试信息、状态和其他输出直接发送到串口。这对于调试嵌入式系统特别有用,因为通常无法直接在设备上查看输出。
通过使用串口进行调试输出,可以实时监视系统状态、变量值和错误信息,从而更方便地进行调试和故障排查。 - 人机交互:通过重定向getchar函数,可以从串口接收用户输入,实现与嵌入式系统的交互。
这使得用户可以通过串口终端或其他串口工具向系统发送命令、配置参数或提供输入数据。这对于需要与嵌入式系统进行交互的应用程序非常有用,例如控制台应用程序或配置工具。 - 引入标准库功能:重定向printf和getchar函数还可以在嵌入式系统中引入标准库(如C库)功能。
标准库提供了许多有用的函数和工具,(如 scanf 等等)可以简化开发过程和代码编写。
通过重定向这些函数,可以在嵌入式系统中使用标准库函数,如字符串处理、数学计算等,从而提高开发效率。
创建项目
选好你要的芯片型号
配置RCC
配置时钟树,配置时钟树,在Clock configuration中将HCLK频率设置为最高(看使用需求),等待软件自动改变时钟树的配置。
Debug选择 Serial Wire,右边的视图中出现了SWDIO和SWCLK两个引脚,这是用于SWD协议的仿真器和下载器的信号线。
在Connectivity 中选择 USART1 并在Mode中选择 asynchronous(异步),在最右边我们发现引脚A9、A10变成了USART1_TX(传输)和USART1_RX(接收)。
在NVIC Settings中有个USART1 global interrupt选项,你可以选择enabled也可以不选择
在CubeMX中,USART1 global interrupt(全局中断)是指针对USART1串口模块的中断功能。中断是一种处理器与外部设备之间的通信方式,它允许设备在特定事件发生时中断处理器的正常执行流程,从而提高系统的响应性和效率。
USART1是一种通用同步/异步收发器,用于串行通信。通过启用USART1 global interrupt,您可以在USART1接收到数据、发送完成或发生错误时,自动触发一个中断服务程序(ISR)。这意味着当某个事件发生时,处理器会立即停止当前的执行任务,转而执行中断服务程序来处理该事件。
在使用USART1进行串口通信时,启用USART1 global interrupt可以提供以下几个优势:
- 实时数据处理:当有数据到达USART1时,中断可以立即将控制权转移到中断服务程序,您可以在其中处理接收到的数据,进行实时的数据处理和响应。
- 节省处理器资源:使用中断处理数据和事件可以减轻处理器的负担。相比轮询方式,中断允许处理器在等待数据时进入低功耗模式或执行其他任务,而不需要不断检查串口接收状态。
- 提高系统响应速度:中断可以快速响应外部事件,使系统能够及时处理紧急情况或高优先级任务。这对于实时应用程序或需要快速响应外部设备的应用非常有用。
在CubeMX中启用USART1 global interrupt的具体步骤可能会因版本和配置而有所不同,但通常涉及到选择USART1外设,然后在中断配置选项中启用相关的中断标志。请注意,您还需要编写适当的中断服务程序来处理中断事件。
需要注意的是,启用中断会增加代码复杂性和处理器负担,因此在使用中断之前,您需要仔细考虑系统的需求和性能要求,确保正确配置和处理中断,以充分利用其优势。
在 Project Manager中更改IDE为MDK-ARM
勾选下图选项,方便程序解耦 (??)
点击generate code生成代码
在项目工程里面找到uart.c文件
添加代码块,重定向为串口一
// 重定向printf
int fputc(int ch, FILE *f){
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
return ch;
}// 重定向getchar
int fgetc(FILE *f)
{
int ch;
while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) == RESET);
HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
return (ch);
}
在main.c里面写入测试代码
肯定有人会好奇,为什么重定向printf和getchar函数却是重写fputc和fgetc函数?
这是因为在C语言标准库中,printf和getchar等输入/输出函数实际上是依赖于更底层的字符输出函数fputc和字符输入函数fgetc来完成的。因此,通过重写fputc和fgetc函数,可以实现对printf和getchar函数的重定向。
- 统一接口:重写fputc和fgetc函数可以提供一个统一的接口,用于处理字符的输出和输入,而不仅仅局限于printf和getchar函数。这样,可以通过重定向这两个函数,同时重定向其他使用fputc和fgetc的函数,如puts、fgets等,实现整个输入/输出流的重定向。
- 更底层的操作:fputc和fgetc函数提供了对字符的更底层操作,可以直接与底层设备进行通信,如串口、文件系统等。重写这两个函数可以让我们将字符输出和输入重定向到所需的设备或接口,例如串口,从而实现定制的输入/输出流。
- 与C标准库兼容性:通过重写 fputc 和 fgetc 函数,可以保持与C标准库的兼容性。
这意味着在重定向后,可以继续使用其他标准库函数,如sprintf、fprintf等,它们依赖于printf函数的输出。
同样,可以使用其他标准库函数,如 fgets、fscanf等 它们依赖于 getchar函数的输入。
重写fputc和fgetc函数可以提供一个通用的底层接口,用于处理字符的输出和输入,并且与C标准库函数保持兼容。通过重定向这两个函数,可以实现对printf和getchar等高级函数的重定向,并且可以在更底层的级别上控制字符的输入和输出。这提供了更大的灵活性和定制化的能力,以满足特定嵌入式系统的需求。
build一下项目并download一下
通过串口助手观察结果
输入1,返回也是1 ,配置成功!
————————————————
版权声明:本文为CSDN博主「zengchenAAA」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zengchenAAA/article/details/134041179