在嵌入式里面实现printf()类似的功能

2023-05-16

学习C语言大多数都是从printf("hello world")开始的,对于printf的熟悉程度最高,在嵌入式编程中,实现printf函数有一种很标准的办法就是实现putch,绑定对应的串口输出,设置好波特率,使能串口就可以了,使用mircolib效果更加,但是随着工程的实践中,有着另外的使用需求。

嵌入式接口资源比较紧张,一般的cpu也就自带四个串口,往往外设很多,如果独立使用一个串口用来调试,这样的IO资源浪费和成本是不能忍受的,所以只能复用。一般的串口数据传输函数接口为usartSend(char buf[],size_t len);// 表示要传输的数据和长度,这个可以很好的满足跟外设通信的接口要求,但是调试的时候很不方便。如果我要看几个float变量的值,那就无法直接输出,之前采用的办法是sprintf()格式转换,再次输出,这样用到调试的地方至少要写3行代码,如果加上调试宏和必要的延时等待发送完成,那就需要5-6行代码。如果不嫌弃的话,也可以这样做,我这样实现了一年之后,决定换一个方法来减轻调试时候的代码量。

方法是这样的(需要GNU编译器支持,keil中已经集成了GNU编译器,用起来特别好用):

 (1)使用__attribute__扩展format属性,关于扩展语法可以看这篇文章(GNU C扩展语法_风一样的航哥的博客-CSDN博客)

先给一个例子:void LOG(const char * fmt,...) __attribute__((format(printf(1,2)));

这个属性告诉编译器,请按照printf函数的参数格式对LOG函数进行参数检查。...就表示可变参数了,那么如果读取可变参数和使用呢?继续往下看。

(2)函数实现,使用封装好的宏即可获取参数列表,在头文件<stdarg.h>中提供了4个很有用的宏。分别是va_list、va_start、va_arg、va_end。

va_list:变量类型,用于创建一个 va_list 类型变量解析可变参数.va_list args;
va_start(args,fmt):根据参数fmt的地址,获取fmt后面参数的地址,并保存在args指针变量中。

C 库宏 void va_start(va_list ap, last_arg) 初始化 ap 变量,它与 va_arg 和 va_end 宏是一起使用的。last_arg 是最后一个传递给函数的已知的固定参数,即省略号之前的参数。

这个宏必须在使用 va_arg 和 va_end 之前被调用。

va_arg(args,int):使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项,int表示自动增加sizeof(int)的长度,参考其他文献好像只能支持int和double两种类型,就是整型都是int,不管是char还是short,浮点型都是double,使用float会得不到想要的结果。
va_end(args):使用宏 va_end 来清理赋予 va_list 变量的内存,并指向NULL。

下面给一个例子,遍历double类型的可变参数,实现返回所有值的sum操作。(int类型的例子其他帖子写的不错)。

void *fun01(double num, ...)
{
	int i;
	double res = 0;
	va_list v1;				//v1实际是一个字符指针,从头文件里可以找到 
	
	va_start(v1, num);		//使v1指向可变列表中第一个值,即num后的第一个参数 
	
	printf("*v = %lf\n",(double)*v1);
	
	for(i = 0; i < (int)num; i++)	//num 是为了防止下标超限 
	{
		res += va_arg(v1, typeof(num));		//该函数返回v1指向的值,并是v1向下移动一个int的距离,使其指向下一个int 
		printf("res = %lf, v1 = %p\n",res, v1); 
	} 
	va_end(v1);				//关闭v1指针,使其指向null
	return &res;
}

(3)实现格式化输出,知道了参数如果处理之后,就可以格式化输出了,本来我使用的是sprintf函数来处理后面的参数,结果一直不对。经过查询和反思,最终明白了库里面提供了专门的函数来处理va_list的变量,是vprintf系列。

C语言printf家族函数的成员:

#include <stdio.h>

int printf(const char *format, ...); //输出到标准输出
int fprintf(FILE *stream, const char *format, ...); //输出到文件
int sprintf(char *str, const char *format, ...); //输出到字符串str中
int snprintf(char *str, size_t size, const char *format, ...);
                                     //按size大小输出到字符串str中
  
以下函数功能与上面的一一对应相同,只是在函数调用时,把上面的...对应的一个个变量用va_list调用所替代。在函数调用前ap要通过va_start()宏来动态获取。

#include <stdarg.h>

int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);     int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);

于是就有了这样的版本:

void __attribute__((format(printf(1,2)))) my_printf(char *fmt, ...)
{
    va_list args;
    va_start(args,fmt);
    vprintf(fmt,args);
    va_end(args);
}

(4)函数嵌入式移植,上述版本已经差不多可以用了,只要将vprintf换成vsnprintf,再调用嵌入式的串口发送函数即可。

int vsnprintf (char * sbuf, size_t n, const char * format, va_list arg );

参数sbuf:用于缓存格式化字符串结果的字符数组

参数n:限定最多打印到缓冲区sbuf的字符的个数为n-1个,因为vsnprintf还要在结果的末尾追加\0。如果格式化字符串长度大于n-1,则多出的部分被丢弃。如果格式化字符串长度小于等于n-1,则可以格式化的字符串完整打印到缓冲区sbuf。一般这里传递的值就是sbuf缓冲区的长度。

参数format:格式化限定字符串

参数arg:可变长度参数列表

返回:成功打印到sbuf中的字符的个数,不包括末尾追加的\0。如果格式化解析失败,则返回负数。

于是产生了下面的版本。

#include "stdio.h"
#include "stdarg.h"
#include "string.h"

void __attribute__((format(printf(1,2)))) my_printf(char *fmt, ...)
{
#ifdef    __DEBUG
    char sendbuf[512]={0};
    va_list args;
    va_start(args,fmt);
    vsnprintf(sendbuf,sizeof(sendbuf),fmt,args);
    va_end(args);
    Usart(sendbuf,strlen(sendbuf));    // 调用串口发送函数,实际情况改动
    delayms(strlen(sendbuf));           // 延时确保发送结束,以9600波特率为参考
#endif
}

上述代码中,__DEBUG表示调试宏,发布程序的时候关闭这个宏就可以了。一般的全局的调试宏在下图所示的地方定义。

 总结:通过可变参数函数,就实现了在嵌入式上熟悉的printf函数,还与正式发布的串口传输函数不冲突,带来的代价就是占用了更多的内存,发布的时候取消宏就OK啦。

在学习过程中还看到了可变参数宏,大概是这样的。

 只要懂得##是连接符,就明白什么意思了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在嵌入式里面实现printf()类似的功能 的相关文章

  • 使用巨控GRM530模块远程通信连接TIA WINCC(异地组网)SIMATIC NET与S7-200 SMART 集成以太网口OPC 通信

    使用巨控GRM530模块远程通信连接TIA WINCC xff08 异地组网 xff09 SIMATIC NET与S7 200 SMART 集成以太网口OPC 通信 硬件 xff1a S7 200 SMART SR40PC 机 集成以太网卡
  • 01_使用cMake编译目标文件与库文件

    1 将单个源文件构建为可执行文件 1 1 准备工作 假设有以下源代码 xff0c 希望使用cMake将其构建为一个可执行文件 示例源码如下 span class token macro property span class token d
  • ROS image_transport及cv_bridge

    ROS image transport 压缩 此外上面提到过FPS这个参数 xff0c 自然少不了介绍图像的压缩 一般使用的时候直接会使用没有压缩的图像 xff0c 但是在需要传输的时候 xff0c 这一些大图像非常占用带宽 xff0c 所
  • NEMA格式

    NMEA標準格式 大部份的GPS receiver都具被有美國國家海洋電子學會 National Marine Electronics Association xff0c NMEA 所制定的標準規格 xff0c 其制定了所有航海電子儀器間的
  • 在 JETSON 上配置 CAN 总线收发并基于Node.js编写应用DEMO

    概述 CAN 总线 是汽车电子行业常用的通信协议 Nvidia 推出的边缘 AI 推理设备 JETSON TX2 AGX Xavier 两款开发板支持 CAN 总线通信 这里以 AGX Xavier 开发者套件 为例 介绍如何配置实现基本的
  • C++开发UDP通信:使用socket创建UDP服务器端和客户端

    我的主页视频讲解 博客不经常在线 xff0c 私信请到西瓜视频搜索 智能之心 xff0c 以智能之心赴智能之梦 UDP通信步骤 引用UDP通信步骤 一 UDP套接字服务端架构步骤 1 xff0e 初始化套接字环境WSAStartup 2 x
  • D6 PRO充电器使用方法

    充电方式两种 xff1a 接220V供电 xff08 推荐 xff09 接XT60电池供电 接线 xff1a 大头XT60接口平衡头6S 4S 3S 2S接口 xff0c 123456 黑线从右侧减号一侧开始接 xff0c 红线在左侧数字一
  • uORB和MAVLink通讯例程

    uORB uORB 是一种异步 publish subscribe 的消息传递 API xff0c 用于进程或者线程间通信 IPC 添加新的Topic xff08 主题 xff09 在msg 目录下创建一个新的 msg文件 xff0c 并将
  • 往android studio原生java工程中添加jni的过程

    一 配置步骤 xff1a 第一步是在 src main 中建立一个文件夹cpp 第二步是在文件夹中建立两个文件一个是native lib cpp和CMakeLists txt 第三步在上述两文件中添加如下内容 xff1a span clas
  • C++迭代器

    一 什么是迭代器 xff1a C 43 43 中 xff0c 迭代器就是一个类似于指针的对象 xff0c 它能够用来遍历C 43 43 标准模板库容器中的部分或全部元素 xff0c 每个迭代器对象代表容器中的确定的地址 以下面的string
  • webpack vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin

    场景 webpack2 4 集成vue loader 64 15 2 4报错 vue loader was used without the corresponding plugin Make sure to include VueLoad
  • slamugv使用说明--5.电机编码测速

    电机编码器测速 编码器 xff08 encoder xff09 是将信号 xff08 如比特流 xff09 或数据进行编制 转换为可用以通讯 传输和存储的信号形式的设备 编码器把角位移或直线位移转换成电信号 编码器分类及原理 xff1a 按
  • STM32相关名词解释

    L N NO NC COM名词解释 L是line的意思 xff0c 相线 xff0c 俗称火线 xff1b N是neutral xff0c 中性线的意思 xff0c 在两相线中也称为零线 NO xff1a 也就是常开 xff0c 即在未通电
  • 三轴加速度计、三轴陀螺仪、三轴磁力计

    1 今天要做的事情 列出 xff1a 可能影响因素 xff0c 对影响因素做实验 如何设置对比实验 xff1f 如何设置对比实验 xff1f 三轴加速度计 1 加速度计的原理 三轴加速度 xff1a 输出的速度是去掉重力后的整体加速度 xf
  • zed相机使用

    zed立体相机 43 pyhon 43 opencv zed相机网上资料都是关于安装的教程 xff0c 后续的一些使用介绍的不是很多 xff0c 因为官方文档已经介绍的很详细 xff0c 这里介绍一下自己的学习过程 xff0c 以备查阅 x
  • 思岚A1激光雷达hector_mapping建图与定位

    ROS中最常见的定位包还是gmapping xff0c gmapping是需要里程计的数据也就是IMU或者视觉里程计的数据 xff0c 但是目前还是仅仅在电脑中测试 xff0c 并未实现树莓派与飞控通信 xff0c 因此使用不用里程计数据的
  • 科技概念/名词解释

    文章目录 自然科学算法类无人机互联网软件类硬件类计算机架构类人工智能 自然科学 量子 百度百科 量子 xff08 quantum xff09 是现代物理的重要概念 即一个物理量如果存在最小的不可分割的基本单位 xff0c 则这个物理量是量子
  • 自动驾驶基础知识(一)——英文缩写(持续更新...)

    这一篇就先整理一下接触到的英文缩写 xff0c 相当于给自己补补课 xff0c 以后遇到新的 xff0c 会持续更新上来 xff01 英文缩写中文全称英文全称ABS防抱死制动系统Anti lock Braking SystemACCS自适应
  • 自动驾驶基础知识(二)——术语中英文对照

    文章目录 深度学习自动驾驶深度增强学习计算机视觉其他 深度学习 中文名称英文名称人工智能系统Artificial Intelligence System认知负荷Cognitive Load深度学习Deep Learning3D卷积神经网络3
  • 使用Typora绘制流程图

    Typora可以直接在markdown中画流程图 xff0c 而且语法简洁易懂 xff0c 这是个让我异常惊喜的功能 Typora使用flowchart js来生成简单的SVG流程图 xff0c 此外它还支持mermaid engine 要

随机推荐