首先,完成了串口向终端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. 「Usaco2008 Jan」人工湖O(∩_∩)O 纯属的模拟+栈

    题目描述 夏日那让人喘不过气的酷热将奶牛们的烦躁情绪推到了最高点.最终,约翰决定建一个人工湖供奶牛消暑之用. 为了使湖看起来更加真实,约翰决定将湖的横截面建成N(1≤N≤105)个连续的平台高低错落的 ...

  2. day1 晚上 P4145 上帝造题的七分钟2 / 花神游历各国 线段树

    #include<iostream> #include<cstdio> #include<cmath> using namespace std; ; struct ...

  3. 我们碰到了大麻烦,一个新来的传教士惹恼了上帝,上帝很愤怒,要求我们把圣经(bbe.txt)背熟,直至他说哪个单词,我们就要飞快的回答出这个单词在第几行第几个单词位置。听说你是个优秀的程序员,那么髟助我们完成这个不可能的任务吧

    编程任务:1.我们碰到了大麻烦,一个新来的传教士惹恼了上帝,上帝很愤怒,要求我们把圣经(bbe.txt)背熟,直至他说哪个单词,我们就要飞快的回答出这个单词在第几行第几个单词位置.听说你是个优秀的程序 ...

  4. python文件的基本操作

    打开文件的三种方式: open(r'E:\学习日记\python\code\文件的简单操作.py') open('E:\\学习日记\\python\\code\\文件的简单操作.py') open(' ...

  5. Xshell选中的同时把内容复制到剪贴板

    1.设置对话框 工具 -> 选项 -> 键盘和鼠标 -> 将选定的文本自动复制到剪贴板 2.贴图如下 2.1.打开设置对话框 2.2.设置键盘鼠标,左键复制

  6. [LC]876题 Middle of the Linked List (链表的中间结点)(链表)

    ①中文题目 给定一个带有头结点 head 的非空单链表,返回链表的中间结点. 如果有两个中间结点,则返回第二个中间结点. 示例 1: 输入:[1,2,3,4,5]输出:此列表中的结点 3 (序列化形式 ...

  7. [LC]237题 Delete Node in a Linked List (删除链表中的节点)(链表)

    ①中文题目 请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点. 现有一个链表 -- head = [4,5,1,9],它可以表示为: 示例 1: 输入: hea ...

  8. [剑指offer]第1题,二维数组中的查找

    ①题目 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数. ...

  9. 关于css中的字体样式

    1.决定字体的属性 color:字体颜色  属性值:单词,十六进制表示,rgb 2.字体大小 font-size:12px:属性值是整数字,不要带小数,单位是px叫做像素单位:凡是由像素拼成的图片我们 ...

  10. React组件间的通讯

    组件化开发应该是React核心功能之一,组件之间的通讯也是我们做React开发必要掌握的技能.接下来我们将从组件之间的关系来分解组件间如何传递数据. 1.父组件向子组件传递数据 通讯是单向的,数据必须 ...