在大家使用keil或是iar开发stm32等arm芯片的时候,想来最不陌生的就是使用print通过串口输出一些数据,用来调试或是其他作用。但是要明确的是由于keil iar gcc 他们使用的标准C语言库虽然都遵循一个标准,但他们底层的函数实现方式都是不同的,那么在GCC中我们能否像在keil中一样重映射print的输出流到串口上呢?答案是肯定的。

keil中的重映射方式及原理
/* 
 * libc_printf.c 
 * 
 *  Created on: Dec 26, 2015 
 *      Author: Yang 
 * 
 *      使用标准C库时,重映射printf等输出函数的文件 
 *    添加在工程内即可生效(切勿选择semihost功能) 
 */

#include <stdio.h>  
//include "stm32f10x.h"

#pragma import(__use_no_semihosting)               
//标准库需要的支持函数                   
struct __FILE  
{  
    int handle;

};  
FILE __stdout;

//定义_sys_exit()以避免使用半主机模式      
_sys_exit(int x)  
{  
    x = x;  
}

//重映射fputc函数,此函数为多个输出函数的基础函数  
int fputc(int ch, FILE *f)  
{  
    while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);  
    USART_SendData(USART1, (uint8_t) ch);  
    return ch;  
}

在keil中的C库中,printf、scanf等输入输出流函数是通过fputc、fgetc来实现最底层操作的,所以我们只需要在我们的工程中重定义这两个函数的功能就可以实现printf、scanf等流函数的重映射。

GNU下的C流函数重映射方式
我们来看看前几篇中提供的样例工程中的usart_stdio例程中的代码片段:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

/*
 * To implement the STDIO functions you need to create
 * the _read and _write functions and hook them to the
 * USART you are using. This example also has a buffered
 * read function for basic line editing.
 */
int _write(int fd, char *ptr, int len);
int _read(int fd, char *ptr, int len);
void get_buffered_line(void);

/*
 * This is a pretty classic ring buffer for characters
 */
#define BUFLEN 127

static uint16_t start_ndx;
static uint16_t end_ndx;
static char buf[BUFLEN + 1];
#define buf_len ((end_ndx - start_ndx) % BUFLEN)
static inline int inc_ndx(int n) { return ((n + 1) % BUFLEN); }
static inline int dec_ndx(int n) { return (((n + BUFLEN) - 1) % BUFLEN); }

/* back up the cursor one space */
static inline void back_up(void)
{
    end_ndx = dec_ndx(end_ndx);
    usart_send_blocking(USART1, '\010');
    usart_send_blocking(USART1, ' ');
    usart_send_blocking(USART1, '\010');
}

/*
 * A buffered line editing function.
 */
void get_buffered_line(void)
{
    char c;

if (start_ndx != end_ndx)
    {
        return;
    }

while (1)
    {
        c = usart_recv_blocking(USART1);

if (c == '\r')
        {
            buf[end_ndx] = '\n';
            end_ndx = inc_ndx(end_ndx);
            buf[end_ndx] = '\0';
            usart_send_blocking(USART1, '\r');
            usart_send_blocking(USART1, '\n');
            return;
        }

/* or DEL erase a character */
        if ((c == '\010') || (c == '\177'))
        {
            if (buf_len == 0)
            {
                usart_send_blocking(USART1, '\a');
            }

else
            {
                back_up();
            }

/* erases a word */
        }

else if (c == 0x17)
        {
            while ((buf_len > 0) &&
                    (!(isspace((int) buf[end_ndx]))))
            {
                back_up();
            }

/* erases the line */
        }

else if (c == 0x15)
        {
            while (buf_len > 0)
            {
                back_up();
            }

/* Non-editing character so insert it */
        }

else
        {
            if (buf_len == (BUFLEN - 1))
            {
                usart_send_blocking(USART1, '\a');
            }

else
            {
                buf[end_ndx] = c;
                end_ndx = inc_ndx(end_ndx);
                usart_send_blocking(USART1, c);
            }
        }
    }
}

/*
 * Called by libc stdio fwrite functions
 */
int _write(int fd, char *ptr, int len)
{
    int i = 0;

/*
     * write "len" of char from "ptr" to file id "fd"
     * Return number of char written.
     *
    * Only work for STDOUT, STDIN, and STDERR
     */
    if (fd > 2)
    {
        return -1;
    }

while (*ptr && (i < len))
    {
        usart_send_blocking(USART1, *ptr);

if (*ptr == '\n')
        {
            usart_send_blocking(USART1, '\r');
        }

i++;
        ptr++;
    }

return i;
}

/*
 * Called by the libc stdio fread fucntions
 *
 * Implements a buffered read with line editing.
 */
int _read(int fd, char *ptr, int len)
{
    int my_len;

if (fd > 2)
    {
        return -1;
    }

get_buffered_line();
    my_len = 0;

while ((buf_len > 0) && (len > 0))
    {
        *ptr++ = buf[start_ndx];
        start_ndx = inc_ndx(start_ndx);
        my_len++;
        len--;
    }

return my_len; /* return the length we got */
}

这个文件因为实现了scanf的功能同时还带有在串口上终端回显并支持backspace键所以显得有些长,我们来将其中的实现printf重映射的片段取出:

#include <stdio.h>
#include <stdlib.h>

int _write(int fd, char *ptr, int len)
{
    int i = 0;

/*
     * write "len" of char from "ptr" to file id "fd"
     * Return number of char written.
     *
    * Only work for STDOUT, STDIN, and STDERR
     */
    if (fd > 2)
    {
        return -1;
    }

while (*ptr && (i < len))
    {
        usart_send_blocking(USART1, *ptr);

if (*ptr == '\n')
        {
            usart_send_blocking(USART1, '\r');
        }

i++;
        ptr++;
    }

return i;
}

与keil C库类似GNU C库下的流函数底层是通过_read、_write函数实现的,我们只要在工程中将他们重新定义就可以实现重映射的功能了。

补充
差点忘了最重要的。我们在使用GNU的printf时,一定要记住在发送的内容后添加 \n或者在printf后使用fflush(stdout),来立即刷新输出流。否则printf不会输出任何数据,而且会被后来的正确发送的printf数据覆盖。这是由于printf的数据流在扫描到 \n以前会被保存在缓存中,直到 \n出现或是fflush(stdout)强制刷新才会输出数据,如果我们在printf数据的末尾不加入\n或fflush(stdout),这个printf数据就不会被发送出去,而且在新的printf语句也会重写printf的缓存内容,使得新的printf语句不会附带之前的内容一起输出,从而造成上一条错误的printf内容不显示且丢失。

/*methord1*/
printf("Enter the delay(ms) constant for blink : ");
fflush(stdout);

/*methord2*/
printf("Error: expected a delay > 0\n");

总结
这里需要大家明白的是,GNU C 与 KEIL C 下的标准库函数实际上都是各个不同的机构组织编写的,虽然他们符合不同时期的C标准,如C89、C99等,那也只是用户层的API相同(同时要明白他们这些标准库是属于编译器的一部分的,就储存在编译器路径下的lib文件夹中)。虽然上层被调用的标准C函数相同,但是他们各有各的实现方式,他们在底层实现是可能完全不同的样子。所以在我们更换工具链后,一定要注意自己工程中的代码不一定会适应新的工具链开发环境。

补充
--------------------- 
作者:Foresights 
来源:CSDN 
原文:https://blog.csdn.net/zhengyangliu123/article/details/54966402 
版权声明:本文为博主原创文章,转载请附上博文链接!

STM32高级开发(12)-在GCC中使用printf打印串口数据相关推荐

  1. 【STM32 HAL库+STM32CUBEMX】使用usart1打印串口数据

    1.设置RRC外部时钟 设置高速外部时钟 2.设置DEBUG调试 debug设置成SW 3.设置usart 设置模式为异步通信 比特率115200 起始位8位 停止位1位 没有奇偶效验 4.设置时钟 ...

  2. STM32高级开发(8)-链接器与启动文件

    最近休息了一下,中间断断续续在虚拟机上靠着记忆恢复了原来崩溃的虚拟机上80%的工作成果,还算过得去吧,完全丢失的也就是些不大重要的资料.今天新买的机械键盘也到货了,不得不说顺丰的工作人员好评,给过年假 ...

  3. stm32中实现printf打印

    起因 最近在调试stm32片子,在调试的时候发现,原来的项目代码中没有实现printf函数,一直使用较为原始的打印方式:将需要打印的内容存在数组中(下称资源池),通知DMA进行搬运,最终通过usart ...

  4. STM32高级开发(15)-使用eclipse开发STM32

    在最起初的时候,我刚刚接触linux上单片机的开发,最喜欢的就是 eclipse + arm-plug-in + arm-none-eabi 的开发环境,因为这是在Linux上最接近于windows下 ...

  5. Android高级开发第二讲--Android中API翻译之Activity

    博客出自:刘兆贤的博客_CSDN博客-Java高级,Android旅行,Android基础领域博主,转载注明出处! All Rights Reserved ! Activity主要用来展示给用户,让用 ...

  6. 关于STM32中使用printf通过串口发送数据

    printf 是源文件 stdio.h 中的函数(同c语言) 使用 printf 必须先引用源文件:#include "stdio.h" printf 内部会调用 fputc 函数 ...

  7. android 串口开发_详细分析Esp8266上电信息打印的数据,如何做到串口通讯上电不乱码打印...

    01 写在前面: 上篇关于如何在内置仅1M的Esp8285做到 OTA 升级的同步到微信公众号,竟然被安信可的某些运维人员看到了,想要转载,我很欣慰,竟然自己的笔记可以被这么大型的公司员工认可! 我是 ...

  8. C语言中使用printf()打印漂亮的颜色字体

    1. 打印炫彩字体 :  "\033[字背景颜色;字体颜色m字符串\033[0m" 格式如:printf("\033[1;33m Hello World. \033[0m ...

  9. 如果成为一名高级安卓开发_什么是高级开发人员,我如何成为一名开发人员?

    如果成为一名高级安卓开发 Becoming a Senior Developer is something many of us strive for as we continue our code ...

最新文章

  1. tp5 sum某个字段相加得到总数
  2. java string 字符个数字_java从字符串中提取数字
  3. 容器化单页面应用中Nginx反向代理与Kubernetes部署
  4. 微信开发必看,使用.Net Core 开发微信跨平台应用
  5. Container(容器)与 Injector(注入)
  6. virtual 修饰符与继承对析构函数的影响(C++)
  7. Node.js HTTP
  8. spring教程笔记6
  9. 头像+壁纸微信小程序源码
  10. xilinx zynq zynqmp nvme SSD使用
  11. Linux环境变量PSI指什么,psi是什么单位?
  12. 计算机专业的浪漫情话,计算机科学与技术表白情话
  13. 微信小程序接收二进制流文件(图片预览,文件打开)
  14. Linux打tar包命令
  15. 想剑网三妹子最多服务器,玩家有多“疯狂”?为了新门派,提前一年为其准备108套外观...
  16. 二十四节气之大暑时节常识介绍
  17. 浙江计算机二级word试题,浙江省计算机办公二级新增试题(word、Excel)
  18. Oracle 12c数据库优化器统计信息收集的最佳实践
  19. 织梦调用banner图和栏目名称
  20. 深入浅出推荐系统(二):召回:内容为王

热门文章

  1. MIT 6.828 JOS学习笔记17. Lab 3.1 Part A User Environments
  2. 关于ActionBar的向下兼容
  3. 【leetcode】3Sum
  4. 用CMD开启Windows下的服务命令 转载
  5. 用分类行为解释为什么破碎的鸡蛋不能还原为一个完整的鸡蛋
  6. 8. An Introduction to MCMC for Machine Learning (5)
  7. mysql同步binlog_利用MySQL的Binlog实现数据同步与订阅(下)
  8. 移动端web设计尺寸_移动端页面设计规范尺寸大起底
  9. 【数理知识】《矩阵论》方保镕老师-第5章-矩阵微积分及其应用
  10. STM32 电机教程 17 - 基于ST MotorControl Workbench的电机调试