基于串口通信做my_printf时遇到的坑儿
首先,完成了串口向终端putty的打印函数ConsolePrint(),但该函数只能打印字符串,无法像stdio库中的printf函数一样打印整数和浮点数等。
因此,我先是使用了标准库stdio中的sprintf函数。该函数可以将所要打印的数字格式化成对应的字符串并存储到字符串数组中,如sprintf( str_buffer, "num : %.2f", 12.34)将字符串“num : ”及浮点数12.34转换成对应的字符串“num : 12.34”存储在str_buffer数组中,接下来就可以用串口打印函数ConsolePrint(str_buffer)将其打印到终端putty。具体的使用方法如下:
char str_buffer[ ];
sprintf( str_buffer, "num : %d", 12.34 );
ConsolePrint( str_buffer );
但是这样使用很不方便,每次使用都要先声明一个数组,调sprintf进行格式化操作,最后再打印。那么能否将这些代码封装成一个函数呢?
答案是:很难!
首先我们需要抽象一个用户函数,使其能够像printf一样使用,既能够只打印字符串,也能够打印数字,而且可以一次打印多组数字。抽象得到的函数原型为:
void my_printf( const char *format, ... )
该函数是一个具有可变参数的函数,这样才能满足打印多组数字的要求。
我们想要在my_printf中调用sprintf函数,其函数原型为:
int _EXFUN(sprintf, (char *, const char *, ...)
这时就遇到了一个较为复杂的问题:一个具有可变参数的函数其子函数也具有可变参数。此时就需要在my_printf根据打印格式对可变参数进行解析(变参数的解析使用va_arg( ap, <type>,其中type是数据类型,必须根据打印格式知道其类型后才能解析出正确的数),然后再将解析后的数传给sprintf,在传的过程中还要看情况选择是否使用sprintf的变参数部分,整体来说非常麻烦。
--------------------------------------------------------------------------------------------分割线-----------------------------------------------------------------------------------------
要解决这个问题可以使用vsprintf函数,其函数原型为
int _EXFUN(vsprintf, (char *, const char *, __VALIST)
vsprintf接受一个va_list类型的形参用于接受可变参数的栈指针,这样只要将my_printf中可变参数的栈指针传递给它即可,vsprintf内部有对可变参数进行解析的程序从而将数字按照指定格式转换成对应的字符串(即使调用my_Printf时没有使用可变参数,vsprintf也能够进行处理),这样my_printf函数只要将变参的栈指针传递给它进行处理即可,具体实现如下:
void my_printf( char const *format, ... )
{
char buffer[ BUFFER_SIZE ];
va_list ap; va_start( ap, format ); //将ap初始化为变参数栈的栈顶指针
vsprintf( buffer, format , ap ); //将指针ap传递给vsprintf做格式化处理
va_end( ap ); Console_Print( buffer );
}
vsprintf函数的内部实现大致如下(并非标准库的实现):
int usr_vsprintf(char *dest, const char *fmt, va_list ap)
{
char c, sign, *cp, *dp = dest;
int left_prec, right_prec, zero_fill, length, pad, pad_on_right;
char buf[];
long val; while ((c = *fmt++) != )
{
cp = buf;
length = ;
if (c == '%')
{
c = *fmt++;
left_prec = right_prec = pad_on_right = ;
if (c == '-')
{
c = *fmt++;
pad_on_right++;
}
if (c == '')
{
zero_fill = TRUE;
c = *fmt++;
}
else
{
zero_fill = FALSE;
}
while (is_digit(c))
{
left_prec = (left_prec * ) + (c - '');
c = *fmt++;
}
if (c == '.')
{
c = *fmt++;
zero_fill++;
while (is_digit(c))
{
right_prec = (right_prec * ) + (c - '');
c = *fmt++;
}
}
else
{
right_prec = left_prec;
}
sign = '\0';
/* handle type modifier */
if (c == 'l' || c == 'h')
{
c = *fmt++;
}
switch (c)
{
case 'd' :
case 'u' :
case 'x' :
case 'X' :
val = va_arg(ap, long);
switch (c)
{
case 'd' :
if (val < )
{
sign = '-';
val = -val;
}
/* fall through */
case 'u' :
length = _cvt(val, buf, , "");
break;
case 'x' :
length = _cvt(val, buf, , "0123456789abcdef");
break;
case 'X' :
length = _cvt(val, buf, , "0123456789ABCDEF");
break;
}
break;
case 's' :
cp = va_arg(ap, char *);
length = strlen(cp);
break;
case 'c' :
c = (char)va_arg(ap, long);
*dp++ = c;
continue;
case '%' : /* '%%' ==> output '%' */
*dp++ = c;
break;
default:
*dp++ = '?';
}
pad = left_prec - length;
if (sign != '\0')
{
pad--;
}
if (zero_fill)
{
c = '';
if (sign != '\0')
{
*dp++ = sign;
sign = '\0';
}
}
else
{
c = ' ';
}
if (!pad_on_right)
{
while (pad-- > )
{
*dp++ = c;
}
}
if (sign != '\0')
{
*dp++ = sign;
}
while (length-- > )
{
c = *cp++;
if (c == '\n')
{
*dp++ = '\r';
}
*dp++ = c;
}
if (pad_on_right)
{
while (pad-- > )
{
*dp++= ' ';
}
}
}
else
{
if (c == '\n')
{
*dp++= '\r';
}
*dp++ = c;
}
} *dp = '\0'; return ((int)dp - (int)dest);
}
据说,sprintf就是用vsprintf来实现的,其大致实现如下(非标准库代码):
int usr_sprintf(char *buf, char const *fmt, ...)
{
int ret;
va_list ap; va_start(ap, fmt);
ret = usr_vsprintf(buf, fmt, ap);
va_end(ap); return ret;
}
总结一下:
为什么不用sprintf而是vsprintf?引用https://blog.csdn.net/heybeaman/article/details/80495846#commentBox博主的话来说,就是因为 vsprintf() 比 sprintf() 更加接近底层(栈)。
为什么void my_printf( const char *format, ... )调用int _EXFUN(sprintf, (char *, const char *, ...)会难以处理?使用 sprintf() 只能原始的为它输入所有的参数而不能以传参的方式给它。
--------------------------------------------------------------------------------------------分割线-----------------------------------------------------------------------------------------
最后说说所谓的“坑儿”。my_printf函数定义在console_print.c文件中,在main函数调用,此时如果不在main中声明my_printf函数或者在consol_print.h中声明,而在main函数中include(声明如下),会发生变参数部分解析错误的情况,一般是把变参数解析为0。
extern void my_printf( const char *format, ... );
基于串口通信做my_printf时遇到的坑儿的更多相关文章
- [python] 3 、基于串口通信的嵌入式设备上位机自动测试程序框架(简陋框架)
星期一, 20. 八月 2018 01:53上午 - beautifulzzzz 1.前言 做类似zigbee.ble mesh...无线网络节点性能测试的时候,手动操作然后看表象往往很难找出真正的原 ...
- VS2008基于对话框的MFC上位机串口通信(C++实现)简单例程
首先,在 vs2008 环境下创建 MFC 运用程序 设置项目名称为 ComTest(这个地方随意命名,根据个人习惯),点击确定后,点击下一步 出现如下界面 选择"基于对话框"模式 ...
- 基于FPGA的红外遥控解码与PC串口通信
基于FPGA的红外遥控解码与PC串口通信 zouxy09@qq.com http://blog.csdn.net/zouxy09 这是我的<电子设计EDA>的课程设计作业(呵呵,这个月都拿 ...
- C#中缓存的使用 ajax请求基于restFul的WebApi(post、get、delete、put) 让 .NET 更方便的导入导出 Excel .net core api +swagger(一个简单的入门demo 使用codefirst+mysql) C# 位运算详解 c# 交错数组 c# 数组协变 C# 添加Excel表单控件(Form Controls) C#串口通信程序
C#中缓存的使用 缓存的概念及优缺点在这里就不多做介绍,主要介绍一下使用的方法. 1.在ASP.NET中页面缓存的使用方法简单,只需要在aspx页的顶部加上一句声明即可: <%@ Outp ...
- [stm32][ucos] 1、基于ucos操作系统的LED闪烁、串口通信简单例程
* 内容简述: 本例程操作系统采用ucos2.86a版本, 建立了5个任务 任务名 优先级 ...
- 【Delphi】基于状态机的串口通信
通信协议 串行通信接口(如RS232.RS485等)作为计算机与单片机交互数据的主要接口,广泛用于各类仪器仪表.工业监测及自动控制领域中. 通信协议是需要通信的双方所达成的一种约定,它对包括数据格式. ...
- C#做一个简单的进行串口通信的上位机
C#做一个简单的进行串口通信的上位机 1.上位机与下位机 上位机相当于一个软件系统,可以用于接收数据.控制数据.即可以对接收到的数据直接发送操控命令来操作数据.上位机可以接收下位机的信号.下位机是 ...
- (转载)用vs2010开发基于VC++的MFC 串口通信一*****两台电脑同一个串口号之间的通信
此文章以visual C++数据採集与串口通信測控应用实战为參考教程 此文章适合VC++串口通信入门 一.页面布局及加入控件 1, 安装好vs2010如图 2, 新建一个基于VC++的MFC项目com ...
- 基于Arduino和python的串口通信和上位机控制
引言 经常的时候我们要实现两个代码之间的通信,比如说两个不同不同人写的代码要对接,例如将python指令控制Arduino控件的开关,此处使用串口通信是非常方便的,下面笔者将结合自己踩过的坑来讲述下自 ...
随机推荐
- AndroidOS体系结构
首先上图一张 对照着图,我们再来看Android 系统的体系结构就爽多了.我们从底层向上进行分析. 一.Linux 内核层 Linux Kernel 基于linux2.6.其核心系统服务如安全性.内存 ...
- 如何用CSS实现中间自适应,两边定宽三栏布局
1.前言 用css实现“两边定宽,中间自适应的三栏布局”这个问题应该是在前端面试中被面试官提问到的高频问题了,一般当面试者写出一种实现方法之后,面试官还会问你还有没有别的方法,尽量多的写出几种实现方法 ...
- Docker变量的相关总结
一.AVG与ENV 1.在Dockerfile中,使用ARG与ENV的区别 ARG:ARG定义的变量用于构建Docker镜像,在通过build把Dockerfile构建成镜像后,ARG定义的变量便不在 ...
- NOIP 模拟17
最近状态有些不对劲,总是出现各种各样的小错误...... 这次可以说是很水的一套题(T3神仙题除外),T1就是一个优化的暴力,考场上打了一个n的四次方的程序,在距考试结束还有5分钟的时候猜想出来正解, ...
- Project Euler 59: XOR decryption
计算机上的每个字母都对应一个独特的编号,普遍接受的标准是ASCII(美国信息交换标准代码).例如,大写字母的A的ASCII码是65,星号(*)的ASCII码是42,而小写字母k的代码是107. 一种现 ...
- 一种logging封装方法,不会产生重复log
在调试logging的封装的时候,发现已经调用了logging封装的函数,在被其它函数再调用时,会出现重复的logging.原因是不同的地方创建了不同的handler,所以会重复,可以使用暴力方法解决 ...
- PHP获取PHP执行的时间
php获取PHP执行的时间 <pre> //程序运行时间 $starttime = explode(' ',microtime()); //代码区域 //程序运行时间 $endtime = ...
- Python 基础 面向对象之二 三大特性
Python 基础 面向对象之二 三大特性 上一篇主要介绍了Python中,面向对象的类和对象的定义及实例的简单应用,本篇继续接着上篇来谈,在这一篇中我们重点要谈及的内容有:Python 类的成员.成 ...
- jdk 错误1316 指定账户已存在 与 jdk1.7安装和配置环境变量 与 jdk1.8与1.7版本的切换使用
问题: 安装JDK,提示错误信息:,指定的账号已存在. 原因: 安装JDK,相当于安装了一个软件,要使用系统的软件卸载功能卸载,不能只删除安装目录文件夹下的文件,如果只 ...
- 编译spark支持thriftserver
cdh默认把spark的spark-sql以及hive-thriftserver给弃用掉了,想玩玩thriftserver,于是自己重新编译一个 官网参考: http://spark.apache.o ...