首先,完成了串口向终端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时遇到的坑儿的更多相关文章

  1. [python] 3 、基于串口通信的嵌入式设备上位机自动测试程序框架(简陋框架)

    星期一, 20. 八月 2018 01:53上午 - beautifulzzzz 1.前言 做类似zigbee.ble mesh...无线网络节点性能测试的时候,手动操作然后看表象往往很难找出真正的原 ...

  2. VS2008基于对话框的MFC上位机串口通信(C++实现)简单例程

    首先,在 vs2008 环境下创建 MFC 运用程序 设置项目名称为 ComTest(这个地方随意命名,根据个人习惯),点击确定后,点击下一步 出现如下界面 选择"基于对话框"模式 ...

  3. 基于FPGA的红外遥控解码与PC串口通信

    基于FPGA的红外遥控解码与PC串口通信 zouxy09@qq.com http://blog.csdn.net/zouxy09 这是我的<电子设计EDA>的课程设计作业(呵呵,这个月都拿 ...

  4. 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 ...

  5. [stm32][ucos] 1、基于ucos操作系统的LED闪烁、串口通信简单例程

    * 内容简述: 本例程操作系统采用ucos2.86a版本, 建立了5个任务            任务名                                             优先级 ...

  6. 【Delphi】基于状态机的串口通信

    通信协议 串行通信接口(如RS232.RS485等)作为计算机与单片机交互数据的主要接口,广泛用于各类仪器仪表.工业监测及自动控制领域中. 通信协议是需要通信的双方所达成的一种约定,它对包括数据格式. ...

  7. C#做一个简单的进行串口通信的上位机

    C#做一个简单的进行串口通信的上位机   1.上位机与下位机 上位机相当于一个软件系统,可以用于接收数据.控制数据.即可以对接收到的数据直接发送操控命令来操作数据.上位机可以接收下位机的信号.下位机是 ...

  8. (转载)用vs2010开发基于VC++的MFC 串口通信一*****两台电脑同一个串口号之间的通信

    此文章以visual C++数据採集与串口通信測控应用实战为參考教程 此文章适合VC++串口通信入门 一.页面布局及加入控件 1, 安装好vs2010如图 2, 新建一个基于VC++的MFC项目com ...

  9. 基于Arduino和python的串口通信和上位机控制

    引言 经常的时候我们要实现两个代码之间的通信,比如说两个不同不同人写的代码要对接,例如将python指令控制Arduino控件的开关,此处使用串口通信是非常方便的,下面笔者将结合自己踩过的坑来讲述下自 ...

随机推荐

  1. 手机信号G、E、O、3G代表什么意思?

    G指GPRS,是2.5G网络,属于GSM网络,也就是说这项技术位于第二代(2G)和第三代(3G)移动通讯技术之间,GPRS的传输速率可提升至56甚至114Kbps,已经将2017年确定为关闭GSM网络 ...

  2. PHP 精典面试题(附答案)

    1.输出Mozilla/4.0(compatible;MISIE5.01;Window NT 5.0)是,可能输出的语句是? A:$_SERVER['HTTP_USER_AGENT_TYPE']; B ...

  3. ElasticSearch(五):Mapping和常见字段类型

    ElasticSearch(五):Mapping和常见字段类型 学习课程链接<Elasticsearch核心技术与实战> 什么是Mapping Mapping类似数据库中的schema的定 ...

  4. 使用 element-ui 级联插件遇到的坑

    需求描述[省市区三级联动] 组件:Cascader 级联选择器 后端需要所选中的地区的名字,如:['北京市', '北京市', '东城区'] 获取后端省市区具体列表的接口返回数据: // 省 - 参数1 ...

  5. WordPress代码高亮插件SyntaxHighlighter终极使用详解

    子曰: 工欲善其事,必先利其器.作为码农一枚,再加上站长这个已经不再光鲜的称呼,岂能没有一款经济实用.操作简单.而且功能必须强大.样式也必须好看的Wordpress代码高亮插件?!作为一个视代码如生命 ...

  6. table的列固定

    <body onload="showFix(true,false,initTableId);"> <!doctype html> <html lang ...

  7. java线程池的介绍与使用(Executor框架)

    1. 先来看一下类构成 public interface Executor { //顶级接口Executor,定义了线程执行的方法 void execute(Runnable command); } ...

  8. LoadRunner具体流程

    创建负载测试场景场景目标:模拟10个用户同时登陆.搜索航班.购买机票.查看航班路线并退出打开Controller并创建一个新场景1.打开HP LoadRunner2.打开Controller在Load ...

  9. ReactJS的4行代码

    Angular 2一个显著的变动是,把Angular 1的Promise pattern改成了Observer pattern,并且使用了ReactJS.这里有一篇值得一读的文章 要搞懂ReactJS ...

  10. 使用 Casbin 作为 ThinkPHP 的权限控制中间件

    PHP-Casbin 是一个强大的.高效的开源访问控制框架,它支持基于各种访问控制模型的权限管理. Think-Casbin 是一个专为 ThinkPHP5.1 定制的 Casbin 的扩展包,使开发 ...