要点:
1. int fputc(int ch, FILE *f) :文件指针 f 所指向文件的当前写指针位置,简单理解就是把字符写入到特定文件中。参考:fputc 用法:Example
由于重定向到串口,因此,在这个函数体内,可以无视 “FILE *f” 这个参数,也就是不需要操作这个传入参数,这不会产生问题,也不会产生警告、或错误提示之类的。
2. keil 的微库法,只需要重写 fputc() 函数。
3. 正点原子还有一种:代码法,不需要使用 keil 的微库 “ Use MicroLIB”。
4. 简单的重写了 printf() 函数,而不只是重写 fputc() 函数。
STM32串口USART printf函数输出重定向及实现原理详解:printf 的实现
https://www.bilibili.com/video/BV1fW4y1m795/?spm_id_from=autoNext
5. 从 linux printf 的用法来看,这个函数的代码,还不是那么简单就完成了。
printf(1) — Linux manual page https://www.man7.org/linux/man-pages/man1/printf.1.html
6. 重定向函数的修改,不是一劳永逸的,而是要具体问题具体分析。
6.1 对于重定向函数的修改方法,不同的 MCU,需要修改的重定向函数的名称,也可能是不同的。
如:stm32 修改:fputc() 函数
stc 51 重定向,修改:putchar() 函数
6.2 同一款 MCU,在不同版本的开发工具上,即使是相同的重定向函数,也可能不可以直接运行。如:瑞萨RA FSP5.0.0裸机printf重定向
https://www.bilibili.com/video/BV1Q64y1p7Da/
6.3 说明:
6.3.1. 要掌握重定向的本质,以及实现的战略思想,这是关键。
至于具体的修改方法,则需要具体 MCU,具体开发工具,来具体分析了。
6.3.2. 也许有些 MCU原厂,就已经为用户做好了 printf 函数,无需用户自己编写。
毕竟这也是 MCU原厂之间的一种竞争力的表现。
就像标准库函数一样,MCU原厂、或 IDE原厂不主动给出,而是让用户自己去实现吗?
但是,由 MCU 或 IDE原厂来提供,也可能会有问题:比如:不知道与用户的实际串口应用,是否会产生冲突,因此需要设置宏开关;需要考虑不同版本的 IDE之间,printf 的兼容性等等。
这就要看实现 printf 函数,是否值得让MCU 或 IDE原厂去做了?
在半主机模式下,ARM MCU 的 printf 默认的输出设备,为:arm 专用的调试器。(然后,通过调试器,再输入到电脑上位机。<注意:未验证,注者没有arm 专用的调试器>)
// 如果是这样,那么,就意味着:在使用 Arm MCU的场景,直接使用 arm 专用的调试器,就不需要用户自己去修改 printf 的重定向代码了?
6.3.3. printf 函数,在大多的时候,只是一种临时调试的需要,不会包含在最终的产品里。因此,只要能满足自己的需求就好了。即使自己对 printf 的修改,可能存在着一些在特别场景下的输出错误,但又有什么关系呢?
思想:个人的能力与精力,都是有限的。在这方面追求完美了,就会难以顾及那方面。因此,事事都要追求完美的思想,其实,就是最大的不完美。
如何取舍?人生的过程,其实就是一个不断选择,不断取舍的过程。在这个过程中,可以收获到人生中的一切;生命的意义,就隐藏在人生不断的取舍过程之中。
思考:既然 printf 函数这么重要,嵌入式标准库的提供者,为什么不像 Linux OS标准库那样,直接为用户提供“重定向操作符”,从而简化 printf 重定向在通用串口上的使用呢?
可能原因:
1. 重定向的具体外设,无法预测。
许多的嵌入式产品,自带了显示屏。而显示接口协议,可能是串口、IIC、SPI、LCD专用驱动电路,以及其他等等。串口只是其中的一个最为普遍的外设而已;并且,同样是 LCD显示,细分又可以分成许多驱动函数有所区别的子类。
这是重定向需要程序员自己来实现的原因之一吗?
2. printf 的重定向,就是将 stdio.h 标准库默认的底层输入/输出函数,修改为用户的具体外设输入/输出函数。也就是将用户的具体 “外设的字符输入/输出函数”,替换掉 stdio.h 标准库默认的“ printf 的字符输入/输出函数”。
3. Linux OS中的 printf 重定向,直接使用"重定向运算符: < 、>"就可以了。而嵌入式的 printf 重定向应该相当于是其裁剪版本,printf 重定向的有些功能,需要程序员自己来实现。
4. printf 重定向的成功实现,需要(显示、通信、存储等的)“外设的字符输入/输出函数”的支持。
- 假如程序员已经掌握 “外设的字符输入/输出函数”的编写,那么,修改 printf 重定向就是一个简单的事情。
- 假如程序员不能够编写 “外设的字符输入/输出函数”的驱动,那么,就算掌握了 printf 重定向底层函数的修改,还是无法使得 printf 重定向能够正常使用。
- 具体的 “外设的字符输入/输出函数”,大的种类多,细分的种类也多。
- 基于上述等等的原因,这也许就是这么重要的 “ 嵌入式标准库中的 printf”的重定向,需要程序员自己来实现的原因吧?
软件仿真的困境
虽然市面上有 Proteus、qemu、keil 内置仿真器等等的软件仿真器。
但实际上,由于 MCU的种类繁多,在大多数的时候,软件仿真并不能够满足具体的 MCU要求。
因此,使用开发板方式来进行开发,就成为了最普遍的开发方式?
这使得通过将 printf 重定向,将用户希望的信息输入/输出到用户期望的设备上,成为了重要的调试手段之一。
猜想:stdio.h 库中的 printf 函数,默认的标准输出设备,是什么?
1. Linux OS中的 printf 默认输出到电脑屏幕上。
通过“重定向输出操作符:>” 可以输出到文件上,等等。
通过“重定向输入操作符:<” 可以从文件输入,等等。
2. arm 的 printf 默认输出到 arm专用的调试器上。<< 注意:未确认,可能有误 ?
2.1. 默认采用“半主机模式”,从 arm MCU输出到 arm专用的调试器;然后,再从 arm专用的调试器 输出到电脑的上位机。
2.2. 当不使用 arm专用的调试器,并且,也不使用 keil 的微库,而是使用“代码法”进行重定向到电脑的串口(或串口转 usb)上时,需要:
- 关闭“半主机模式”。
- 确定使用的 arm编译器版本 AC5,还是AC6。
- 如使用 GNUC 编译器时,重定向底层设备函数的名称,输入参数等,均与 AC5,AC6有所不同。
3. “stdio.h 标准库”,这家在叫,那家也在叫。
不同的标准库作者们,厂家们,大家都在叫 “stdio.h 标准库”,那么,大家的 “stdio.h 标准库”,是完全相同的东西吗?
在广东,这家媳妇的老公叫“张三”;在东北,那家媳妇的老公也叫“张三”;这家的“张三”和那家的“张三”,到底是同一个人呢?还是两个不同的人呢?有时,旁听者都晕了。
4. 所谓的“标准输入输出设备”,在不同的行业,是不一样的?
这个行业有这个行业的所谓标准,那个行业有那个行业的所谓标准。这个“标准”不是那个“标准”。
-----------------------------------------------------------------
参考:
STM32CubeMX:配置HAL库串口并重定向 printf 和 getchar函数 (**)https://blog.csdn.net/ken2232/article/details/135383655
STM32单片机printf重定向
https://www.bilibili.com/video/BV1GF411U7DZ/
STM32串口USART printf函数输出重定向及实现原理详解:printf 的实现
https://www.bilibili.com/video/BV1fW4y1m795/?spm_id_from=autoNext
基础篇 17 STM32 printf重定向
https://www.bilibili.com/video/BV1vb411g7aw/
** STM32 printf 方法重定向到串口UART https://www.cnblogs.com/milton/p/14711577.html?ivk_sa=1024320u
C 库函数 - fputc() https://www.json.cn/jiaocheng/co-3639.html
***【嵌入式开发笔记】简要谈一谈嵌入式开发中重写printf的几种方法 https://club.rt-thread.org/ask/article/e2dabba73ccb772f.html
(重复上链 http://www.hzhcontrols.com/new-1581745.html )
玩转单片机系列(七)掌握Printf重定向
https://www.bilibili.com/video/BV1yk4y1b7Ay/
重定向:fputc() 函数
vscode+platformio的printf串口输出重定向问题
https://www.bilibili.com/video/BV1w94y1o7kF/
**** stm32:用一个GPIO引脚实现STM32 printf调试打印
https://www.bilibili.com/video/BV18z4y1b7PJ/
《登顶嵌入式 c语言之巅》
**** stm32:灵活修改 fputc 函数,拓宽 printf 应用范围
https://www.bilibili.com/video/BV1Pu4y1e7fZ/
** https://www.bilibili.com/video/BV1J94y1z7xa/
51单片机printf重新定向串口
51单片机printf重新定向串口
https://www.bilibili.com/video/BV1FC4y1c79R/
重定向:putchar() 函数
STC15单片机串口printf函数重定向
https://www.bilibili.com/video/BV1kS4y1w7Qj/
重写 2个函数
stc 单片机串口printf
https://www.bilibili.com/video/BV1cw411C7Xe/
三、e2studio VS STM32CubeIDE之瑞萨RA FSP5.0.0裸机printf重定向
瑞萨RA FSP5.0.0裸机printf重定向
https://www.bilibili.com/video/BV1Q64y1p7Da/
linux os:printf
每天一个Linux命令-printf
输出重定向
https://www.bilibili.com/video/BV1PV411V7mP/
-----------------------------------------------------------------
一,printf重定向原理
1,自己重写C的库函数
链接器检查到用户编写了与C库函数相同名字的函数时,优先调用用户编写函数,这样就可实现重定向.
2,重定向printf()函数
需重写fputc()这个c标准库函数(printf()在c标准库函数实质是一个宏,实际是调用fputc()函数)
说明
fputc()默认是把字符输出到调试器控制窗口,要把数据通过USART输出到串口助手,需对基于fputc()的printf()系列函数的输出都重定向到USART端口上去,要想使用USART功能,需重定向fputc()函数
原文链接:https://blog.csdn.net/qq_37732417/article/details/108514238
====================================
重定向 printf 和 scanf 函数
综述:只是为了实现输入/输出的重定向而已,能用就行。
一般而言,使用自己熟悉的编写方法即可,不必太过纠结于到底是使用 HAL库呢?还是其他的编程方式。
当然,一些公司有硬性的规定除外。
思想:
任何一种编程语言,都只不过是某种工具而已。本质上,应该是程序员(人类)在主宰着工具,而不是工具在主宰着程序员。
需要特别注意的是:工具也有好与坏之分;因为所使用的工具,也会反过来影响着程序员的思维方式和思维习惯,从而导致所谓的“职业病”的产生。
使用 HAL库的配置,与使用寄存器、或标准库的配置方法,略有不同
参考: STM32CubeMX:配置HAL库串口并重定向 printf 和 getchar函数 (**)https://blog.csdn.net/ken2232/article/details/135383655
可能还会涉及:
1. AC5 与 AC6编译器版本的不同设置
使用 keil,并且,不使用 keil 的微库场景,才需要考虑:?
#if (__ARMCC_VERSION >= 6010050) /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t"); /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t"); /* AC6下需要声明main函数为无参数格式,否则部分例程可能出现半主机模式 */#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)
原文链接:https://blog.csdn.net/sinat_41690014/article/details/130273292
2. GNUC 编译器 与 keil 编译器的重定向函数的名称,以及输入参数的不同
使用到 GNUC,以及 arm编译器时,才需要这个选择项:?
#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
参考:
keil stm32 : extern _ARMABI https://blog.csdn.net/ken2232/article/details/135346057
ABI : application binary interface。_ARMABI 表示这是一个针对ARM的二进制接口;
---------------------------------------
使用 keil 开发时,推荐:如下
3. 如果使用 keil 微库,实现就简单了?
// 重定向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);
}
摘录自:stm32f4xx_hal_uart.h
/* IO operation functions *******************************************************/
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
/** @brief Checks whether the specified UART flag is set or not.
* @param __HANDLE__ specifies the UART Handle.
* UART Handle selects the USARTx or UARTy peripheral
* (USART,UART availability and x,y values depending on device).
* @param __FLAG__ specifies the flag to check.
* This parameter can be one of the following values:
* 。。。。。。
* @arg UART_FLAG_RXNE: Receive data register not empty flag
* 。。。。。。
* @retval The new state of __FLAG__ (TRUE or FALSE).
*/
#define __HAL_UART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->Instance->SR & (__FLAG__)) == (__FLAG__))
摘录自:stdio.h
extern _ARMABI int fgetc(FILE * /*stream*/) __attribute__((__nonnull__(1)));
/*
* obtains the next character (if present) as an unsigned char converted to
* an int, from the input stream pointed to by stream, and advances the
* associated file position indicator (if defined).
* Returns: the next character from the input stream pointed to by stream.
* If the stream is at end-of-file, the end-of-file indicator is
* set and fgetc returns EOF. If a read error occurs, the error
* indicator is set and fgetc returns EOF.
*/
extern _ARMABI int fputc(int /*c*/, FILE * /*stream*/) __attribute__((__nonnull__(2)));
/*
* writes the character specified by c (converted to an unsigned char) to
* the output stream pointed to by stream, at the position indicated by the
* asociated file position indicator (if defined), and advances the
* indicator appropriately. If the file position indicator is not defined,
* the character is appended to the output stream.
* Returns: the character written. If a write error occurs, the error
* indicator is set and fputc returns EOF.
*/
---------------------------------------
野火:代码清单 20-9 重定向输入输出函数 (非 HAL 版本?)
/// USARTx :x 指具体的串口编号
///重定向 c 库函数 printf 到串口,重定向后可使用 printf 函数
int fputc(int ch, FILE *f)
{
/* 发送一个字节数据到串口 */
USART_SendData(USARTx, (uint8_t) ch);
/* 等待发送完毕 */
while (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET);
return (ch);
}
///重定向 c 库函数 scanf 到串口,重写向后可使用 scanf、 getchar 等函数
int fgetc(FILE *f)
{
/* 等待串口输入数据 */
while (USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(USARTx);
}
在 C 语言标准库中, fputc 函数是 printf 函数内部的一个函数,功能是将字符 ch 写入到
文件指针 f 所指向文件的当前写指针位置,简单理解就是把字符写入到特定文件中。
我们使用 USART 函数重新修改 fputc 函数内容,达到类似“写入”的功能。
fgetc 函数与 fputc 函数非常相似,实现字符读取功能。在使用 scanf 函数时需要注意字
符输入格式。
还有一点需要注意的,使用 fput 和 fgetc 函数达到重定向 C 语言标准库输入输出函数
必须在 MDK 的工程选项把“ Use MicroLIB”勾选上, MicoroLIB 是缺省 C 库的备选库,它
对标准 C 库进行了高度优化使代码更少,占用更少资源。
为使用 printf、 scanf 函数需要在文件中包含 stdio.h 头文件。
摘录自: 零死角玩转 STM32 基于野火 F429[挑战者]开发板 第 187 页 共 1046
摘录自:正点原子 串口重定向(非 HAL 版本?)
// 注意:修改日期:2020年的新版,还增加了如 OS选择,arm 编译器 V6选择等等的功能。
// 以下是 2015版本
// 修改日期:2015/9/7
//V1.0修改说明
//
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
//#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
//使用 Keil 微库,则直接改下如下,即可。
//重定义fputc函数
int fputc(int ch, FILE *f)
{// SR&0X40:为该款 MCU 串口寄存器的发送完成标志位
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
#endif
======
stdio.h
// Line 108
typedef struct __FILE FILE;
/*
* FILE is an object capable of recording all information needed to control
* a stream, such as its file position indicator, a pointer to its
* associated buffer, an error indicator that records whether a read/write
* error has occurred and an end-of-file indicator that records whether the
* end-of-file has been reached.
* Its structure is not made known to library clients.
*/// Line 119
struct __FILE {
union {
long __FILE_alignment;
#ifdef __TARGET_ARCH_AARCH64
char __FILE_size[136];
#else /* __TARGET_ARCH_AARCH64 */
char __FILE_size[84];
#endif /* __TARGET_ARCH_AARCH64 */
} __FILE_opaque;
};
/*
* FILE must be an object type (C99 - 7.19.1) and an object type fully
* describes an object [including its static size] (C99 - 6.2.5).
* This definition is a placeholder which matches the struct __FILE in
* size and alignment as used internally by libc.
fputc 用法:Example
https://cplusplus.com/reference/cstdio/fputc/
/* fputc example: alphabet writer */
#include <stdio.h>int main ()
{
FILE * pFile;
char c;pFile = fopen ("alphabet.txt","w");
if (pFile!=NULL) {for (c = 'A' ; c <= 'Z' ; c++)
fputc ( c , pFile );fclose (pFile);
}
return 0;
}