×
单片机 > 单片机程序设计 > 详情

STM32高级开发-在GCC和GNU中使用printf打印串口数据

发布时间:2020-06-18 发布时间:
|

在大家使用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  打印串口数据

『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
8051单片机的函数发生器的设计