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

news2024/11/27 12:57:28

学习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啦。

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/49858.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

No module named ‘PyQt5.QtWebEngineWidgets‘kn-----已解决

1.情况说明 本人在学习PyQt5的时候遇到了 from PyQt5.QtWebEngineWidgets import * 报错的情况&#xff0c;原因就是ModuleNotFoundError: No module named PyQt5.QtWebEngineWidgets 没有PyQt5.QtWebEngineWidgets&#xff0c; 2.解决办法&#xff1a; 解决办法一&#xf…

最快最便捷的pytest使用allure测试报告

一、前言 最近通过群友了解到了allure这个报告&#xff0c;开始还不以为然&#xff0c;但还是逃不过真香定律。 经过试用之后&#xff0c;发现这个报告真的很好&#xff0c;很适合自动化测试结果的展示。下面说说我的探索历程吧。 选用的项目为Selenium自动化测试Pytest框架…

Day1使用Burpsuite抓包工具抓包,改变UA头使得手机和pc端界面互相转换

1.前期工作&#xff1a;安装Burpsuite工具这里网上有许多教程&#xff0c;大致步骤如下&#xff1a; 找到安装包然后解压缩&#xff0c;然后双击 安装jdk&#xff0c;然后就是配置环境变量&#xff0c;如果是默认jdk安装路径没有更改路径的话就是跟如下一样 配置如下&#xff…

跨平台开发方案的三个时代

跨平台开发从本质上讲是为了增加业务代码的复用率&#xff0c;减少因为要适配多个平台带来的工作量&#xff0c;从而降低开发成本。在提高业务专注度的同时&#xff0c;能够为用户提供一致的用户体验&#xff0c;实现“多快好省”的效果。 跨平台是跨哪些平台&#xff1f;怎么…

高视医疗在港交所招股:IPO募资要用于贷款,高铁塔为控股股东

11月30日&#xff0c;高视医疗&#xff08;HK:02407&#xff09;在港交所发布公告&#xff0c;拟全球发售1306.86万股股份&#xff0c;其中香港发售股份130.7万股&#xff0c;国际发售股份1176.16万股&#xff0c;另有15%超额配股权&#xff0c;于2022年11月30日至12月5日招股&…

Talk预告 | 亚马逊云科技上海人工智能研究院肖天骏:基于视频的自监督物体遮挡补全分割

本期为TechBeat人工智能社区第458期线上Talk&#xff01; 北京时间11月30日(周三)20:00&#xff0c;亚马逊云科技上海人工智能研究院资深应用科学家——肖天骏的Talk将准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “基于视频的自监督物体遮挡补全分割”…

请求和响应

目录1 请求对象1.1 请求对象介绍1.2 请求对象常用方法-获取各自路径1.3 请求对象常用方法-获取请求头信息1.4 请求对象常用方法-请求参数信息1.5 获取请求参数并封装对象1.5.1 手动封装方式1.5.2 反射封装方式1.5.3 工具类封装方式1.6 流对象获取请求信息1.7 中文乱码问题1.8 请…

可发生点击化学反应:1458576-00-5,Biotin-PEG4-alkyne,生物素-四聚乙二醇-炔

【中文名称】生物素-四聚乙二醇-炔&#xff0c;生物素-四聚乙二醇-丙炔基 【英文名称】 Biotin-PEG4-alkyne 【货号】Y-PE-2172 【CAS】1458576-00-5 【分子式】C21H35N3O6S 【分子量】457.58 【基团】alkyne 【纯度】95% 【规格】25mg&#xff0c;100mg&#xff0c;250mg 【是…

如何应对继承的双面性

如何应对继承的双面性 继承既强大又有破坏性&#xff0c;那怎么办呢&#xff1f; 1&#xff09;避免使用继承&#xff1b; 2&#xff09;正确使用继承。 我们先来看怎么避免继承&#xff0c;有三种方法&#xff1a; 使用final关键字&#xff1b; 优先使用组合而非继承&#…

11月30日:linux服务器安装以及部署项目

准备一个连接linux服务器的可视化工具&#xff0c;开始发车 推荐使用国产&#xff1a; finalshell 下载地址&#xff1a;FinalShell SSH工具,服务器管理,远程桌面加速软件,支持Windows,macOS,Linux,版本3.9.7,更新时间2022.10.26 - SSH工具 SSH客户端 xshell&#xff1a;安装…

Spring Cloud Gateway微服务网关快速入门

介绍 Spring Cloud Gateway 是 Spring 官方基于 Spring 5.0&#xff0c;Spring Boot 2.0 和 Project Reactor 等技术开发的网关&#xff0c;Spring Cloud Gateway 旨在为微服务架构提供一种简单而有效的统一的 API 路由管理方式。Spring Cloud Gateway 作为 Spring Cloud 生态…

Kamiya丨Kamiya艾美捷人乳铁蛋白ELISA说明书

Kamiya艾美捷人乳铁蛋白ELISA预期用途&#xff1a; 人乳铁蛋白ELISA是一种高度灵敏的双位点酶联免疫测定&#xff08;ELISA&#xff09;人类生物样品中乳铁蛋白的测定。仅供研究使用。不用于诊断程序。 引言 乳铁蛋白&#xff08;LF&#xff09;是一种具有抗菌活性的多功能铁…

运动“双十一”持续走热,缤跃酒店洞察市场需求,创新打造运动健康酒店!

2022年“双十一”购物促销活动刚刚结束&#xff0c;各大品牌陆续开始展示肌肉&#xff0c;在众多数据中运动健身领域相关数据不容忽视&#xff0c;居家健身器械等商品持续走热&#xff0c;户外运动设备销售量也保持热度。由此可见&#xff0c;在当下全民运动热潮下&#xff0c;…

uniapp开发微信小程序实现语音识别,使用微信同声传译插件,

第一步&#xff1a;在微信小程序管理后台&#xff1a;“设置”-》“第三方设置”-》“插件管理”中添加插件。 但是这个地方&#xff0c;没有搜索到插件&#xff0c;就到微信服务市场 搜索到以后添加到需要的小程序里面&#xff0c;然后返回管理中心查看&#xff0c;就可以看…

Css3 3D转换

特点&#xff1a; 近大远小物体后面遮挡不可见 三维坐标系&#xff1a; 三维坐标系其实就是指立体空间&#xff0c;立体空间是由3个轴共同组成的。 X轴&#xff1a;水平向右为正 Y轴&#xff1a;垂直向下为正 Z轴&#xff1a;垂直屏幕向外为正 3D位移和3D旋转&#xff1a…

曲柄压力机的离合器和制动系统设计

目 录 摘 要 I ABSTRACT II 第1章 绪论 1 1.1压力机发展的概况 1 1.2压力机工作原理 1 1.2.1压力机功能简介 1 1.2.2压力机的工作原理简介 3 1.3 压力机的分类 3 1.4 压力机的主要参数和型号 4 1.5本次设计压力机参数及内容 6 1.5.1主要技术参数 6 1.5.2设计内容 6 第二章 曲柄…

学妹居然叫我帮她P证件照自拍,结果发现.........

前因后果 事情是这样的 晚上我正在聚精会神写代码&#xff08;打游戏~&#xff09; 突然&#xff0c;收到学妹给我发来的消息 还有一张自拍照 而且是可以放在结婚证上的那种哦 原来是照片尺寸不合适 让我帮她修图。还要什么蓝底、红底各种背景的 效果 1、尺寸长宽调整为&…

有序数组转换为二叉查找树

问题描述 给定一个整数数组&#xff0c;其元素为先序排列&#xff0c;将其转换为高度平衡的二叉查找树。 示例 示例1 Input: nums [-10,-3,0,5,9] Output: [0,-3,9,-10,null,5] Explanation: [0,-10,5,null,-3,null,9] is also accepted: 示例2 Input: nums [1,3] Output: …

STM32实战总结:HAL之触摸屏

输入类设备简介 IO输入输出&#xff0c;是计算机系统中的一个概念。计算机的主要功能就是从外部获取数据然后进行计算加工得到目标数据并输出给外部&#xff08;计算机可以看成数据处理器&#xff09;。计算机和外部交互就是通过IO。每一台计算机都有个标准输入和标准输出。 常…

业务:财务会计业务知识

一、引言 会计是以货币为主要计量单位&#xff0c;对企业、事业、机关、团体及其他经济组织的经济活动进行记录、计算、控制、分析、报告&#xff0c;以提供财务和管理信息的工作。会计的职能主要是反映和控制经济活动过程&#xff0c;保证会计信息的合法、真实、准确和完整&a…