嵌入式系统简易版本的printf满足自己的需要

嵌入式中,调试手段通常有两种,一是远程gdb,一是直接printf。如果是调试自己玩的小板子,用gdb有点大张旗鼓了,大多数情况下printf就可以搞定。不过printf的问题是stdiolib的size太大,稍微有点程序,加上几个常用的库,比如stdio和string,超过16k甚至32k(已经大于一些低端芯片的flash容量了)是很正常的事情,而且通常比较慢,程序越多,越麻烦。道理很简单,标准c语言库的规范中,printf()必须处理大量的数据格式,包括字符串、字符、(各种长度的有符号和无符号)数字,以及浮点值。而且格式字符串还要包括用于更改文本对齐、基数、间距、字段宽度和精度的调节器和指示器。符合这个规范的代码必然会是冗长和繁重的。一些嵌入式系统库倒是提供了一些之针对整数的printf,但还是有问题,首先是还是太大,其次是你没有自己的调整权限。
其实printf也就是io的调用包装而已,我们完全可以自己写一个简易版本的printf满足自己的需要,并随时根据需要裁剪。具体来说,printf在这里要起的作用就是将调试字符串从嵌入式目标空闲的串口压出,并在运行于宿主工作站的终端模拟器上显示结果。下面就简单介绍一下,如何来自己写一个简易printf函数。
要写printf,首先要知道什么是可变参数传递,我们来看看标准库里面,是如何定义可变参数实现的:
#define _aupbnd (sizeof (acpi_native_int) - 1)
#define _adnbnd (sizeof (acpi_native_int) - 1)
#define _bnd(x, bnd) (((sizeof (x)) + (bnd)) & (~(bnd)))
#define va_arg(ap, t) (*(t *)(((ap) += (_bnd (t,_aupbnd))) - (_bnd (t,_adnbnd))))
#define va_end(ap) (void) 0
#define va_start(ap, a) (void) ((ap) = (((char *) &(a)) +(_bnd(a,_aupbnd))))
关于可变参数的原理,网上有一些文章,总结来说,就是我们可以通过intel80×86机器的对齐特性来获得所有的参数,因为在intel80×86机器上,每个变量的地址都要是sizeof(int)的倍数,这样能提升cpu运行的效率。也就是说,所有参数的首地址都要是4的倍数,就算你是char型的,那浪费3个byte也要安排你占第四个坑。
好,由于c语言传递参数时是用push指令从右到左将参数逐个压栈,因此我们通过栈指针跳4n格来访问第n个参数,不要忘了,参数的地址都是字对齐的。这里,我们用#define _bnd(x, bnd) (((sizeof (x)) + (bnd)) &(~(bnd)))来计算类型为x的参数在栈中占据的字对齐后的字节数。bnd是sizeof (acpi_native_int) –1,acpi_native_unit在32位机的定义是:
typedef u32 acpi_native_uint;
所以( ~(bnd))就是0xfffffffc 。 因此,_bnd(x,bnd) 宏在32位机下就是
( (sizeof(x) + 3)&0xfffffffc )
很明显,其作用是–倘若sizeof(x)不是4的整数倍,将其变为4的整数倍。
va_start(ap,a) 负责初始化参数指针ap,将函数参数a右边第一个参数的地址赋给ap,这个第一个参数通常就是printf里面的”%x%d%f%d”。
va_arg(ap,t) 可以获得ap指向参数的值,并使ap指向下一个参数,t用来指明当前参数类型。
在这里,上述代码还是麻烦,而且sizeof我们也不能直接用,所以我们不如干脆直接写一个不那么麻烦而有针对性的可变参数操作定义:
有了这几个定义,print函数就好写了,为了节省空间,这个简单的print()只支持“%s”,“%d”和”%c”格式的分类符,暂时不需要其他功能,比如格式对齐之类的,当然,可以根据自己的需要扩展这个函数。
int print( const char *fmt, 。.. )
{
const char *s;
char c;
int d;
va_list ap;
va_start(ap, fmt);
while( *fmt != ‘\0’ )
{
if( *fmt != ‘%’ )
{
uart_putc(*fmt++);
continue;
}
switch(*++fmt)
{
case ‘s’:
{
s = va_arg(ap, const char *);
uart_puts(s);
break;
}
case ‘d’:
{
d = va_arg(ap, int);
uart_putints(d, 10);
break;
}
case ‘c’:
{
c = va_arg(ap, char);
uart_putc(c);
break;
}
default:
uart_putc(*fmt);
}
fmt++;
}
va_end(ap);
return 1;
}
这里面有一些函数,uart_putc是串口驱动程序,给串口送东西的,uart_puts是简单的多重putc包装。uart_putints则需要做一些atoi的转换,一个比较简单但是有效的atoi程序宏定义如下:
#define atoi(x, result) \
do{ \
char *lptr = x; \
result = 0; \
while (1) \
{ \
if ((*lptr 》= ‘0’) && (*lptr 《= ‘9’)) \
{ \
result *= 10; \
result += *lptr - ‘0’; \
lptr++; \
} \
else \
{ \
break; \
} \
} \
}while(0)


联想可能正在对新笔记本电脑的显示屏进行更改
NVIDIA在着手于把老游戏用RTX科技进行包装,使其焕发新生
助力数据采集 推动信息化系统云端发展
Littelfuse产品荣获智能化行业优秀解决方案奖
智能可穿戴设备处于逆境,不如看看苹果怎么说?
嵌入式系统简易版本的printf满足自己的需要
4种线性稳压驱动器电路
PACK市场发展4大趋势,将是未来重点攻克的市场
DXO排名第一 荣耀Magic4系列上探影像天花板
统信UOS进军国际市场 已与六家Linux发行版团队开始对接
旋转编码器的零位
科学家通过脑机接口可预测癫痫的发作日期
新能源汽车的动力来源发展历程回顾
为何起步最晚的华为云突飞猛进?
蒲公英P5千兆旁路组网盒子新升级,支持PoE供电,机体优化更精巧!
2018年第四季度各类元器件交期及价格走势
分享一些从单片机向Linux进阶需要掌握哪些基础知识
小型气象站是什么?一文浅谈
深度卷积网络的发展给人脸识别带来突破
背靠富士康找回自信 夏普计划重返PC市场