历史上的今天
返回首页

历史上的今天

今天是:2025年01月20日(星期一)

正在发生

2020年01月20日 | STM32高级开发(12)-在GCC中使用printf打印串口数据

2020-01-20 来源:eefocus

在大家使用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   

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

 

/*

 * 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, '10');

    usart_send_blocking(USART1, ' ');

    usart_send_blocking(USART1, '10');

}

 

/*

 * 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] = '';

            usart_send_blocking(USART1, 'r');

            usart_send_blocking(USART1, 'n');

            return;

        }

 

        /* or DEL erase a character */

        if ((c == '10') || (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

#include

 

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 > 0n");


总结

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

推荐阅读

史海拾趣

對餘科技(DIOFIT)公司的发展小趣事

随着科技的不断发展,DIOFIT公司始终将技术创新作为发展的核心驱动力。他们不断投入研发资金,引进先进技术和人才,致力于研发具有自主知识产权的电子产品。在物联网、人工智能等前沿领域,DIOFIT公司取得了一系列突破性的成果,推出了一系列具有竞争力的产品,成为行业内的技术创新引领者。

Directed Energy Inc公司的发展小趣事

Directed Energy Inc公司自创立之初,就致力于在电子行业中推动技术创新。公司投入大量研发资源,开发出一系列高性能的脉冲式激光二极管驱动器和高压脉冲模块,这些产品凭借其卓越的性能和稳定性,迅速在市场上获得了认可。随着技术的不断迭代和升级,Directed Energy Inc逐渐在电子行业中树立了技术领先的形象,吸引了众多行业内的设计师和工程师的关注。

EHC(ELECTRONICHARDWARE)公司的发展小趣事

EHC公司自创立之初就专注于电子硬件的技术创新。在竞争激烈的电子市场中,EHC公司凭借其独特的设计理念和先进的生产工艺,成功推出了一系列高性能、高可靠性的电子产品。这些产品不仅满足了消费者对高品质电子产品的需求,也为EHC公司赢得了良好的市场口碑。随着技术的不断进步,EHC公司不断推出创新产品,逐步巩固了其在行业中的领先地位。

Black Box Corporation公司的发展小趣事

为了进一步扩大市场份额,Black Box积极寻求与全球各地的企业合作。通过与跨国公司的战略合作,Black Box不仅获得了先进的技术和管理经验,还成功打开了多个国际市场。同时,公司也在全球范围内设立了多个分公司和办事处,以便更好地服务当地客户。这一系列的全球扩张行动使Black Box成为了真正意义上的国际企业。

Emerson Embedded Power公司的发展小趣事

在电子行业的早期,Emerson Embedded Power就开始注重技术创新。该公司不断投入研发资源,开发高效、可靠的电源解决方案,以满足不断增长的市场需求。其创新的电源管理技术不仅提高了设备的性能,还降低了能源消耗,赢得了客户的广泛认可。

Cooper Industries公司的发展小趣事

早在2007年,Cooper Industries就展现出其全球扩张的雄心。同年10月8日,该公司与宁波知名企业耐吉科技股份有限公司共同注资3000万美元,成立了库柏耐吉(宁波)电气有限公司。这一合资公司的成立,不仅加强了Cooper在中国市场的地位,也为其全球业务布局增添了重要一环。库柏耐吉(宁波)电气有限公司地处浙江省慈溪市,工业园占地400余亩,位于世界最长跨海大桥——杭州湾跨海大桥的南岸桥头堡区域,其优越的地理位置为公司的发展提供了有力支持。

问答坊 | AI 解惑

wince应用程序问题

用VS2005调试的wince应用程序,在开发板上运行的时候出现如下错误: 致命的应用程序错误 应用程序test.exe执行了一个非法操作,将被关闭。若问题持续出现,请与供应商联系。 程序:Test.exe 异常:0xc00000FD 地址:00011860 问题出现在哪呢 ...…

查看全部问答>

月薪3万+招人

月薪3万+招人   有非常强的调试技术.熟悉C/c++,汇编.能从内存中获取到我们需要的信息..最好是全职,工作量可能有点 大.. 我们这提供一切便利条件..    只要能胜任绝对待遇能让你满意..  报酬不会低于3万/月 急需& ...…

查看全部问答>

求助:关于调用函数产生歧义的错误?

大家好:    请大家帮帮我,我把2003的程序移到VS2005上,编译报一个错误(函数调用产生歧义的错误)如下: error C2668: \'ATL::TrackPopupMenu\' : ambiguous call to overloaded function         C:\\Pro ...…

查看全部问答>

lm306的下冲问题

本帖最后由 dontium 于 2015-1-23 13:32 编辑 今天,做了一个lm306的滞回比较电路,但是出波后每个方波下有一个下冲,请问是什么原因造成的 [ 本帖最后由 markzhao 于 2011-7-3 16:20 编辑 ] …

查看全部问答>

TI M3 DAY研讨会发的两个光盘分别是干什么用的

TI M3 DAY研讨会发了两个光盘,一个包含了keil,驱动等,另外一个是干嘛的?请指教…

查看全部问答>

【MSP430共享】基于以太网的智能设备控制器的设计与实现

嵌入式系统的发展促进 了智能设备的网络化, 针对家庭智能化 问题 , 提出了智能家居系统的应用模型, 设计了一种基于MS P 4 3 0 F 1 4 9单片机的具有网络接口的智能设备控制器, 分析其硬件接 口电路 、 软件层次结构和应用软件开发方法, 实现了 ...…

查看全部问答>

求助

I/O模拟I2C,主机读操作(从机向主机发送数据)时,SDA和SCL应该设置成什么状态?…

查看全部问答>

富士通项目心得小结

富士通DIY活动可以说已经做完了,总体来说在这次DIY活动中还是学到了一些东西,基本上也知道了如何配置相关的寄存器和编写一些相应的驱动。不过感觉还是没达到预期的效果,我一开始的想法是大伙一起相互讨论,进行技术切磋,但是好多时候大伙都不在 ...…

查看全部问答>

MSP430FF552X的数据手册

MSP430FF551X,MSP430FF552X的数据手册…

查看全部问答>