串口通信:printf 重定向 (****)

要点:

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值