利用C标准库函数vsprintf()实现多路串口使用“printf()”输出

目录

1. 使用全局变量,利用条件分支,实现多串口打印

2. 自己实现printf()函数


 

在集成开发环境MDK下进行裸机开发,总避免不了使用printf()函数,通过串口打印数据,进行通讯或者进行程序的可视化调试(通过串口,将调试信息打印到电脑端,进行分析),通常,会对printf()函数进行输出重定向其中printf是c标准的库函数,包含在<stdio.h>里边,具体怎么实现我们不去深究,只要了解printf()主要是通过调用fputc()函数进行数据的输出,所以可以重写fputc()函数,实现重定向输出 ,例如,原子的例程如下:

int fputc(int ch, FILE* stream)
{
    //重定向输出,利用USART输出
    USART_SendChar(USART1, (uint8_t)ch);    
    return ch;
}

可知,原子教程中的printf()函数是通过USART1打印输出的,所以,在使用该函数之前,必须初始化USART1,当我们想使用其他串口做输出时,可以修改   USART_SendChar(USART1, (uint8_t)ch),使用其他的串口打印,可以实现任意串口打印。

使用这种方法实现printf()重定向,的确可以应付大部分的场合。直到前一段,一个朋友问我(他的工程需要),怎么实现多路串口同时打印输出到PC端呢?一下子把问住了!

一阵讨论,想到了两个办法:

  • 自己实现printf()函数
  • 使用全局变量,利用条件分支,实现多串口打印

1. 使用全局变量,利用条件分支,实现多串口打印

先说第二个,在实现的时候,我们可以定义一个全局变量,例如定义unsigned char 型全局变量printf_port,在重定向printf 和使用时,代码如下:

//定义全局变量,默认使用USART0
u8   printf_port = 0;


//重定向的实现代码
int fputc(int ch, FILE* stream)
{
    //重定向输出,利用USART输出
   
    if (printf_port == 0)
    { 
     USART_SendChar(USART0, (uint8_t)ch);    
    }
    else if (printf_port == 1)
    {
     USART_SendChar(USART1, (uint8_t)ch);    
    }
    else //(printf_port >= 2 )
    {
     USART_SendChar(USART2, (uint8_t)ch);    
    }
    return ch;
}

....


//在应用中调用时,代码可以这样调用
extern printf_port;
...
printf_port  = 1; //1对应串口1
printf("information from USART1");

但是使用全局变量的方法,的确可以实现多串口使用printf()方法打印信息,但是使用比较麻烦,为了避免串口混乱,每次使用之前都要增加一行代码赋值printf_port,当然,可以用宏的方法(使用到了变参数的宏的写法),如下

//宏定义
#define _MY_PRINTF(port, str, ...)   \
        printf_port  =  port;  \                 
        printf(str, ##__VA_ARGS__)           
    

//使用时
...
_MY_PRINTF(1 , "information from USART1")

但是这种办法使用的是全局变量去标识输出的串口,当出现中断和用户程序一起使用时,可能出现错误(其实在嵌入式开发中,全局变量的使用都要慎重,慎重考虑访问冲突的情况,这样才可以写出健壮的系统)。

注意:在MDK下下使用C库中的printf()时,注意正确配置,注意半主机模式。

 

2. 自己实现printf()函数

至于第一种,就是比较麻烦的了,不用C库提供的printf(),自己实现,这个有兴趣可以在网上找到相关的源码,就是实现起来比较麻烦,但是自己实现printf(),相比使用C库的教程来说,调用透明,移植方便。还有一个好处就是,可以只实现简单的功能,以减少代码量(例如只实现整数打印%d,和字符串打印%s,这样简易的printf() ,可以大大减少代码量,若只是需要到printf()函数的部分功能,在资源和速度有限的裸机上,极力推荐该种办法)。

 

最近,查资料发现C语言的标准库里边,除了提供printf()打印函数以外,还提供了一个字符串格式化函数vsprintf(), 定义如下:

int vsprintf(char *str, const char *format, va_list arg)

简单来说,这个vsprintf()函数用法和我们刚接触C语言时使用的sprintf()一样的,就是将字符串按format的格式格式化之后,写入到str指向的内存中。例如:

a= 10;
b =5;
str[100] = {0};
...

//调用vsprintf
vsprintf(&str, "this is  a test: %d + %d = %d ", a, b, a+b);

经过调用之后:  str = "this is a test: 10 + 5 = 15"

得到格式化的字符串后,就可以通过字符串打印函数( 要自己实现,主要是串口打印字符串,说白了就是连续打印字符,实现起来还是很简单的),通过串口打印字符串了。这样就可以快速实现自己的printf()函数,避免了调用C标准库时繁琐配置和定义,还可以为各个串口都有自己的"printf()”实现,例如usart0_printf()对应串口0,例如usart1_printf()对应串口1,以此类推。例如如下实现串口1的输出:

void  usar0_print(char* str , ...)
{
	
  char buffer[200] = {0};
	va_list aptr;
	va_start(aptr, str);
	if ( vsprintf(buffer,sizeof(buffer), str, aptr) > 0)
    //if ( _vsprintf(buffer,sizeof(buffer), str, aptr) > 0) // _vsprintf 是vsprintf简易形式,不支持浮点%f输出。
	{
		usart0_printstr(buffer)	;                         //串口0,发送字符串buffer 函数
	}
	va_end(aptr);
}

其中的va_start()和va_end()是C库提供的宏。在stdarg.h中定义。可以参考https://www.runoob.com/cprogramming/c-macro-va_start.html

注:使用vsprintf()函数,当格式化的字符串长度大于buffer的长度时时, 会导致错误修改内存,导致不可预测的后果。可以使用有限制的vsnprintf()代替。(这也是SEGGER Embedded Studio  推荐的printf()实现方案)

*****************************************************************************************************************************************************************************************************

但是在MDK 5下,工具链 对printf()函数做了大量的优化,甚至刷新了我的认知。有时间,后续再进行记录。

 

 

 

 

 

 

 

  • 4
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值