printf的封装与实现

1 UART通信协议

1.1 UART通信的物理连接

    图1 UART的物理连接

 

1.2 逻辑电平

用电平表示逻辑1和逻辑0,逻辑1和逻辑0用来组织计算机层面的数据。

 

1.3 电平标准

根据通讯使用的电平标准不同,串口通讯可分为 TTL标准及 RS-232 标准。

 

1.4 协议解析

通讯双方需要约定波特率,并约定一致的数据包格式才能保证正常收发数据。

1.4.1 波特率(bps)

单位时间内,发送数据的位数。

 

1.4.2 数据格式

串口通信一般以起始位作为一帧数据传输的开始,以结束位表示一帧数据传输的结束。每一帧数据一般由起始位、数据位、停止位、校验位组成。

起始位:由1个逻辑0的数据位表示;

停止位:由 0.5、1、1.5或 2个逻辑 1的数据位表示;

校验位(奇校验/偶校验):当为奇校验时,数据位和校验位中,逻辑1的数据位的个数为奇数个;当为偶校验时,数据位和校验位中,逻辑1的数据位的个数为偶数个。

图2 115200,8n1; send 0b01000001

 

如图2所示,115200bps,则1/115200spb,即每传输一位需要1/115200秒,数据在(1/115200)/2处采样。

 

2 printf的实现

2.1 标准库中的printf

函数原型:

int printf(const char *format, ...)

返回值:

成功返回实际输出字符数,失败返回-1;

传入参数说明:

format:    固定参数

...:        可变参数;参数的个数不确定,类型不确定;

 

2.2 可变参数的实现原理

调用子函数,函数的参数最终会以被压入栈中,被函数使用;通过格式控制符,实现对栈中传入参数的读取和使用。

在标准库中的实现:

typedef char* va_list;

#define _INTSIZEOF(n)         ((sizeof(n)+sizeof(int)-1) & ~(sizeof(int)-1))        //保证4字节对齐

#define va_start(ap, v)     (ap = (va_list)&v + _INTSIZEOF(v))                 //获取第一个变参在栈中的地址

#define va_arg(ap, t)         (*(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))     //获取ap所指向的数据,并把ap偏移至下一个变参的地址

#define va_end(ap)     (ap = (va_list)0)                            //使ap指向空,避免野指针

    

 

 

 

printf(format, arg1, arg2, arg3);参数在栈中的存放

 

2.3.1 对_INTSIZEOF(n)分析

栈指针总是4字节对齐的,因此使用_INTSIZEOF(n),使变量的大小是4的倍数(实际变量在栈中占据的空间)。

#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1) & ~(sizeof(int)-1)),对这个宏定义有一个形象的比喻:

比方说有一个箱子可以装4个瓶子,

如果我有8个瓶子 ,那么我需要2个箱子;

如果我有10个瓶子呢,我不能说我需要10除4,需要2.5个箱子吧,实际上我需要3个箱子;

那怎么求我实际需要的箱子数呢?

用一个容易理解的公式来求上述问题:

设我的瓶子数为B,我需要的箱子数为C,一个箱子最多可以装A个瓶子。

公式:C =(B+A-1)/ A (舍去余数)

因此,((sizeof(n)+sizeof(int)-1) & ~(sizeof(int)-1))相当于C * A。

 

 

2.3.2 对va_arg(ap, t)分析

#define va_arg(ap, t)     (*(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))

这个宏定义实现了两个功能:

a、ap += _INTSIZEOF(t) —— 求下一个参数的指针

b、(*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t))) —— 取当前参数值,这个宏定义将返回的就是当前参数值

 

附录:源代码

uart.c —— 波特率:115200

 

debug_printf.c

 

 

猜你喜欢

转载自www.cnblogs.com/lilto/p/10920080.html