打印
[应用相关]

STM32单片机极简方法 使用宏定义 代替复杂的重定向printf()...

[复制链接]
702|3
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
wiba|  楼主 | 2021-9-4 11:46 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
对于 printf() 函数我们并不陌生,初学C语言使用的第一个函数,其作用是在终端打印显示格式化字符串。

但是如果我们使用的是单片机运行C语言代码,如果不经任何修改直接使用 printf() 函数,结果是什么现象都没有。要想使用这个函数,常规方法是重定向 printf() 函数,结合串口来打印到串口助手上位机。这里的重定向就非常复杂了,你需要懂 printf() 函数内部实现机制。。。。。(参考正点原子的例程,本人表示看不懂太多太复杂了)

接下来我介绍一种特别简单的方法,让你不需要重定向 printf() 函数也可以实现相似的功能,就是那种%d,%f,%c。。。各种格式控制符的功能,我们想用这个函数大半原因就是为了这些功能。只要在合适的地方添加以下语句即可:

#define _DEBUG_  1         //串口打印宏函数开关,1是开,0是关,调试的时候开,调式完毕就可以一键关闭
uint8_t  USART_TX_BUF[200]; //发送缓冲,最大200字节,不能太小,如果你的内容太长会访问非法内存

#if _DEBUG_
#define ps(...)  HAL_UART_Transmit(&huart1,USART_TX_BUF,sprintf((char *)USART_TX_BUF,__VA_ARGS__),1000)//可修改到其他串口                                                                                               
#else
#define ps(...)
#endif
以上代码表示用 ps() 这个宏函数代替HAL库的串口发送函数,并且可以使用格式控制符%c,%d,%f,\r,\n等可变参数控制符。

ps()宏函数使用方法如下:

  while (1)
  {
        num++;
        ps("串口打印宏函数 \r\n");//无参数打印,\r\n表示换行
        ps("num = %d \r\n",num);//含参数%d
        HAL_Delay(500);
  }
串口助手打印情况:




使用特权

评论回复
沙发
wiba|  楼主 | 2021-9-4 11:47 | 只看该作者
如果上面看不懂我再解释一下原理:
核心内容:变参宏   “__VA_ARGS__”  与  “...”

1.首先三个点 "..." 在C语言中代表“参数个数可变的参数”,我们可以看一下printf()函数的原型:

int printf(const char* format,...);//printf()函数声明原型


//使用printf函数的方法
int num1;
printf("num1 = %d \r\n",num1);//一个参数时,参数是整型

float num2;
printf("num2 = %f \r\n",num2);//参数是浮点型

printf("num1 = %d,num2 = %f \r\n",num1,num2);//两个不同类型的参数时

printf("Hello World \r\n");//无参数时
由上述代码可以知道,"fomat"表示只读类型的字符串,而三个点 “...” 表示个数未确定的参数,可以没有参数,也可以有多个。

2.__VA_ARGS__是三个点"..."的宏定义形式。也就是说宏定义中的__VA_ARGS__会被替换成"..."

#define ps(format,...)    printf(format,__VA_ARGS__)//第1种方法,有些C标准不支持要加##

#define ps(format,...)    printf(format,##__VA_ARGS__)//第2种方法,##可以防止无参数时编译出错

#define ps(...)           printf(format,__VA_ARGS__)//第3种方法

#define ps(...)           printf(format,##__VA_ARGS__)//第4种方法

#define ps(...)           printf(__VA_ARGS__)//第5种方法

#define ps(...)           printf(##__VA_ARGS__)//第6种方法
以上六种都可以用 ps() 代替 printf() 函数,功能以及用法一模一样。C标准不一样的时候可能会有差别,总之编译出错时在以上6种之中更换即可。我只用第1、2、5这三种方法。

3.其实在单片机之中如果不重定向 printf() 函数。我们只要使用 sprintf() 函数即可:

int printf(const char* format,...);//printf()函数声明原型

int sprintf(char *buffer,const char* format,...);//sprintf()函数声明原型
sprintf() 只是比 printf() 多一个参数,即第一个字符数组,他们两功能也相似,只是有以下区别:

sprintf()----------》把内容转成字符串,并输出到一个字符数组中,返回字符串的字符个数;

printf()------------》把内容转成字符串,并输出到显示终端,返回字符串的字符个数;

我们可以利用sprintf()的特点把要显示的内容转换成字符串,存到一个预先定义好的字符数组中,然后再用HAL库串口发送函数,把该字符数组内的信息发出去,发送的个数就是sprintf()的返回值:

uint8_t  USART_TX_BUF[200]; //发送缓冲数组,最大200字节

#define ps(...)  HAL_UART_Transmit(&huart1,USART_TX_BUF,sprintf((char *)USART_TX_BUF,__VA_ARGS__),1000)//可修改到其他串口



使用特权

评论回复
板凳
麻花油条| | 2021-9-4 19:37 | 只看该作者
不错,很棒的方法,收藏

使用特权

评论回复
地板
ufbycd| | 2021-9-4 21:05 | 只看该作者
老手不会用sprintf函数而用snprintf替代。
另外,C库里的printf系列函数一般会调用malloc/free函数来使用堆,所以老手一般不太喜欢C库里的printf实现而自己实现printf
比如我用的是ChibiOS里的开源代码:https://gitee.com/ufbycd/miniv2-board/blob/master/software/source/Core/Src/stream/chprintf.c
再自己来包装一下:https://gitee.com/ufbycd/miniv2-board/blob/master/software/source/Core/Src/mstdio.c

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

77

主题

3312

帖子

3

粉丝