历史上的今天

今天是:2024年10月05日(星期六)

正在发生

2018年10月05日 | STM32高级开发-在GCC和GNU中使用printf打印串口数据

发布者:SereneWanderer 来源: eefocus关键字:STM32  高级开发  GCC  GNU  printf  打印串口数据 手机看文章 扫描二维码
随时随地手机看文章

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


keil中的重映射方式及原理



#include   

//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 

#include 

#include 



int _write(int fd, char *ptr, int len);

int _read(int fd, char *ptr, int len);

void get_buffered_line(void);



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



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');

}



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;

        }


        

        if ((c == '\010') || (c == '\177'))

        {

            if (buf_len == 0)

            {

                usart_send_blocking(USART1, '\a');

            }


            else

            {

                back_up();

            }


            

        }


        else if (c == 0x17)

        {

            while ((buf_len > 0) &&

                    (!(isspace((int) buf[end_ndx]))))

            {

                back_up();

            }


            

        }


        else if (c == 0x15)

        {

            while (buf_len > 0)

            {

                back_up();

            }


            

        }


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

            }

        }

    }

}



int _write(int fd, char *ptr, int len)

{

    int i = 0;


    

    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;

}



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; 

}


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


#include 

#include 


int _write(int fd, char *ptr, int len)

{

    int i = 0;


    

    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内容不显示且丢失。



printf("Enter the delay(ms) constant for blink : ");

fflush(stdout);



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


总结


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


关键字:STM32  高级开发  GCC  GNU  printf  打印串口数据 引用地址:STM32高级开发-在GCC和GNU中使用printf打印串口数据

上一篇:stm32串口加dma接收问题
下一篇:STM32CubeMX串口空闲中断加DMA实现不定长度收发数据

推荐阅读

目录一、电流检测AD采样值处理的高效方法二、AD转换N次采样去最大最小求平均算法 --------------------------------------------------一、电流检测AD采样值处理的高效方法在电机控制软件的编写过程中,经常要处理由AD采样回来的电流值。由于电流有正有负,电流传感器输出地电压也是以0为中点,而一般A...
中国储能网讯:国庆假期里,河北省张家口市崇礼区的红旗营220千伏输变电工程建设现场一派火热景象,国网冀北电力有限公司干部职工正加班加点为工程送电投运做准备。 为服务和保障好奥运盛会,国网冀北电力近几年对涉冬奥重点工作明确路线图、倒排时间表、细化任务书,强力推进了48大项、105小项冬奥重点工作任务,还启动冬奥电力保障攻坚年行动。 国...
谈到医疗机器人,许多人都会起到美国Intuitive surgical公司研发的达芬奇机器人,它是世界第一款手术机器人,代表着机器人在医疗行业应用的重要里程碑。不过,达芬奇仍然是由人类控制的机器系统。随着人工智能技术的进步发展,医疗行业又发生了巨大的变化。利用机器学习的方法,可以对不同病例进行学习,再结合所有医学知识库的知识,能够快速诊断出病情...

史海拾趣

问答坊 | AI 解惑

氢镍电池组均衡充电的一个新颖而实用的方法

氢镍电池组靠电压检测电流均衡会及其复杂,甚至可望不可及,其可靠性也会及其复杂。 而氢镍电池有一个其他电池不具备的特点,就是在充满电和接近充满电的时候,如果采用低于0.1C的电流充电,没有充满电的电池会继续充电,已经充满电的电池,会变成 ...…

查看全部问答∨

硬件工程师基础知识

硬件工程师基础知识 目的:基于实际经验与实际项目详细理解并掌握成为合格的硬件工程师的最基本知识。 1) 基本设计规范 2) CPU基本知识、架构、性能及选型指导 3) MOTOROLA公司的PowerPC系列基本知识、性能详解及选型指导 4) 网络处理器( ...…

查看全部问答∨

请问各位高手,psp光驱中带动激光头的电机的原理是怎样的?

我最近在做psp光驱中的激光头移植,可搞不清楚其中带动激光头的电机的原理!请各位高手指点一下!先谢过啦!…

查看全部问答∨

有没有作过取交流同步信号的朋友

本人现在需要做一个LED控制方案,需要采样交流信号做同步处理,不知道有没有哪位朋友能够提供具体的思路…

查看全部问答∨

请问  手机软件 BIN文件用什么工具打开   哪个高手知道

请问  手机软件 BIN文件用什么工具打开   哪个高手知道…

查看全部问答∨

段码式液晶屏的段码值怎么算的

我只知道液晶屏的COM口和SEG口的逻辑关系,可不知道怎么计算其段值?A,B,C,D,E,F,G可否举例说明?谢谢各位大侠!…

查看全部问答∨

查询器件的功能不能用了??

我今天在咱们网站上查询器件,出现以下错误,不能查询了。 “ Service Unavailable ”   也不知道在这里反应对不对,解决一下吧,哈哈…

查看全部问答∨

PXA270要配什么样的USB仿真器?

我发现用JLINK V7不能仿真,所以PXA270要用什么样的仿真器?最好是价格便宜点,淘宝有卖的!~…

查看全部问答∨

求教:atmegma48 如何访问外部寄存器

本帖最后由 paulhyde 于 2014-9-15 09:18 编辑 求教:atmegma48 如何访问外部寄存器?如Da0832采用双缓冲方式时该怎么办?  …

查看全部问答∨

packetsniffer抓包失败

我用仿真器和zigbee模块做抓包实验,上位机用的packetsniffer软件,怎么老是失败,求解 [ 本帖最后由 北极看月亮 于 2011-12-1 13:52 编辑 ]…

查看全部问答∨
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved