做嵌入式开发的朋友都知道,要获取正在调试的代码运行情况的相比PC开发要难得多.首要问题是数据显示的通路少,其次是嵌入式C开发经常发现很多基本的C运行库函数都没有在所使用的平台实现,即使有,也可能庞大得吓人。今天要DIY的就是PC平台使用最广泛的printf函数。参考了部分网上程序,从代码里有注明.
printf()函数在有的单片机开发环境下往串口发送数据,若你使用的环境恰好有这么个函数功能,那我很羡慕。没有也没关系,今天我们就来实现它这里要介绍实现的printf函数,也是往串口发送数据的,当然,要改变所发送的端口是轻而易举的,后面从代码里就很容易看出来了,实际上,我还在VS2008上模拟了这个函数,其中就将其改成发送到电脑屏幕上。区分标准器printf起见,我们将其命名为myprintf()。
下面先来看一下函数声明。
int myprintf(const char *fmt, ...);
这是一个可变参数,不明白的朋友可以Google一下,或在VS帮助文档里检索va_list,va_arg, va_end, va_start都行.
接下来贴出函数体
/********************************************
*输出到串口
* 只支持以下格式输出:
* s:字符串
* d:十进制
* x:十六进制
* b:二进制
* f:默认输出3位精度小数(没有做四舍五入)
*注意:不能转换太大的数/精度要求太高的数,以防溢出错误
* f:在vs2008中不能通过,
*作者:王桂杰,参考了一个网上程序
********************************************/
int myprintf(const char *fmt, ...)
{
#define DEFAULT_PRECI 3
const char *s;
int value;
int preci = DEFAULT_PRECI;
float fdata;
char buf[16];
va_list ap;
va_start(ap, fmt);
while (*fmt) {
if (*fmt != '%') {
term_putc(*fmt++);
continue;
}
switch (*++fmt) {
case 's':
s = va_arg(ap, const char *);
for ( ; *s != '/0'; s++) {
//putchar(*s);
term_putc(*s);
}
break;
case 'd': //Signed decimal integer
value = va_arg(ap, int); //不同编译器/CPU位数,类型可能不同,uint8_t,uint16_t,uint32_t等
itoa(value, buf, 10);
for (s = buf; *s != '/0'; s++) {
//putchar(*s);
term_putc(*s);
}
break;
case 'x': //Unsigned hexadecimal integer
value = va_arg(ap, int);
term_puts_P("0x");
term_puthex_byte(value);
break;
case 'b': //二进制打印
value = va_arg(ap,int);
term_putbin_byte(value);
term_putc('b');
break;
case 'f': //浮点数输出,默认精度为3位小数
//fdata = va_arg(ap, float);//MSP430F149(16位CPU),IAR平台可以,
fdata = va_arg(ap, double);//windows默认存储方式是double类型,使用float将获不到正确结果(vs2008,64位CPU)
ftoa(fdata, buf, DEFAULT_PRECI);
for (s = buf; *s != '/0'; s++) {
//putchar(*s);
term_putc(*s);
}
break;
case '.': //浮点数精度控制
if(isdigit(*++fmt)){
preci = *fmt - '0';
if(*++fmt == 'f'){
//fdata = va_arg(ap, float);
fdata = va_arg(ap, double);
ftoa(fdata, buf, preci);
for (s = buf; *s != '/0'; s++){
//putchar(*s);
term_putc(*s);
}
break;
}
preci = DEFAULT_PRECI;
}
term_putc(*fmt);
break;
/* 在下面增加其他格式 */
default:
//putchar(*fmt);
term_putc(*fmt);
#if 0
s = "UNSURPORT FORMAT!";
for( ; *s != '/0'; s++){
term_putc(*s);
}
va_end(ap);
return 0;
#endif
}
fmt++;
}
va_end(ap);
return 1; /* Dummy return value */
}
上面是函数是用于MSP430F149 IAR开发环境的,很用于看出,term_putc即是往单片机串口发送数据的。可想而知,在使用这个函数前应该先初始化串口,抽象出term_putc接口,本文就不涉及这个问题了。下面给出的就是通过#define term_puc putchar 偷梁换柱,在PC上模拟的程序.
/********************************************
* 只支持以下格式输出:
* s:字符串
* d:十进制
* x:十六进制
* b:二进制
* f:默认输出3位精度小数
*注意:不能转换太大的数/精度要求太高的数,以防溢出错误
*作者:王桂杰 参考了网上一个程序
********************************************/
#define WINDOWS_VS2008
int myprintf(const char *fmt, ...)
{
#define DEFAULT_PRECI 3
#ifdef WINDOWS_VS2008
#define term_putc putchar //用于vs2008测试
#endif
const char *s;
int value;
int preci = DEFAULT_PRECI;
float fdata;
char buf[32];//char buf[16];
va_list ap;
va_start(ap, fmt);
while (*fmt) {
if (*fmt != '%') {
//putchar(*fmt++);
term_putc(*fmt++);
continue;
}
switch (*++fmt) {
case 's':
s = va_arg(ap, const char *);
for ( ; *s != '/0'; s++) {
//putchar(*s);
term_putc(*s);
}
break;
case 'd': //Signed decimal integer
value = va_arg(ap, int);
itoa(value, buf, 10);
for (s = buf; *s != '/0'; s++) {
//putchar(*s);
term_putc(*s);
}
break;
#ifndef WINDOWS_VS2008
case 'x': //Unsigned hexadecimal integer
value = va_arg(ap, int);
term_puts_P("0x");
term_puthex_byte(value);
break;
case 'b': //二进制打印
value = va_arg(ap,int);
term_putbin_byte(value);
term_putc('b');
break;
#endif
case 'f': //浮点数输出,默认精度为3位小数
fdata = va_arg(ap, double);//windows默认存储方式是double类型,使用float将获不到正确结果
printf("fdata = %f/n",fdata);
ftoa(fdata, buf, DEFAULT_PRECI);
for (s = buf; *s != '/0'; s++) {
//putchar(*s);
term_putc(*s);
}
break;
case '.':
if(isdigit(*++fmt)){
preci = *fmt - '0'; //浮点数精度控制
if(*++fmt == 'f'){
fdata = va_arg(ap, double);
printf("fdata = %f/n",fdata);
ftoa(fdata, buf, preci);
for (s = buf; *s != '/0'; s++){
//putchar(*s);
term_putc(*s);
}
break;
}
preci = DEFAULT_PRECI;
}
//putchar(*fmt);
term_putc(*fmt);
break;
/* 在下面增加其他格式 */
default:
//putchar(*fmt);
term_putc(*fmt);
#if 0
s = "UNSURPORT FORMAT!";
for( ; *s != '/0'; s++){
term_putc(*s);
}
va_end(ap);
return 0;
#endif
}
fmt++;
}
va_end(ap);
return 1; /* Dummy return value */
}
很容易看出,通过检索格式串fmt,凡是遇到%,则判断紧跟其后的格式化字符s,d,f,x。f比较特殊点,因为其可能带精度输出,不过也just so so,没上面大不了滴。获得想要格式符号后,马上从myprintf()参数列表中取出对应的参数,这里要注意的是,参数类型一定要正确,因为可变参数是通过把参数压进栈实现的,进栈类型和从栈里取数类型必须对应才可能获得正确的数据。
将VS帮助文档关于可变参数解释拷贝如下:
Access variable-argument lists.
type va_arg( va_list arg_ptr, type ); void va_end( va_list arg_ptr ); void va_start( va_list arg_ptr ); // (UNIX version) void va_start( va_list arg_ptr, prev_param ); // (ANSI version) |
va_arg returns the current argument; va_start and va_end do not return values.
The va_arg, va_end, and va_start macros provide a portable way to access the arguments to a function when the function takes a variable number of arguments. Two versions of the macros are available: The macros defined in STDARG.H conform to the ANSI C standard, and the macros defined in VARARGS.H are compatible with the UNIX System V definition. The macros are:
Both versions of the macros assume that the function takes a fixed number of required arguments, followed by a variable number of optional arguments. The required arguments are declared as ordinary parameters to the function and can be accessed through the parameter names. The optional arguments are accessed through the macros in STDARG.H or VARARGS.H, which set a pointer to the first optional argument in the argument list, retrieve arguments from the list, and reset the pointer when argument processing is completed.
The ANSI C standard macros, defined in STDARG.H, are used as follows:
All required arguments to the function are declared as parameters in the usual way. va_dcl is not used with the STDARG.H macros.
va_start sets arg_ptr to the first optional argument in the list of arguments passed to the function. The argument arg_ptr must have va_list type. The argument prev_param is the name of the required parameter immediately preceding the first optional argument in the argument list. If prev_param is declared with the register storage class, the macro's behavior is undefined. va_start must be used before va_arg is used for the first time.
va_arg retrieves a value of type from the location given by arg_ptr and increments arg_ptr to point to the next argument in the list, using the size of type to determine where the next argument starts. va_arg can be used any number of times within the function to retrieve arguments from the list.
After all arguments have been retrieved, va_end resets the pointer to NULL.
取到相应参数值后,接下就是把其值转换成字符串,转换完成后把字符串发送到串口上。这里用到两个函数 itoa和ftoa,关于转换成16进制和二进制的函数,由于比较简单,并且最后也是转换成字符串用于显示,故不在这里给出了.
由于VS的stdlib.h里也有itoa函数,个VS下用不用这里给出的itoa都可以.但是作为函数原型开发,在PC上验证好在拷到嵌入式平台下使用的好又快的方法,值得推荐.
char * my_itoa(long n, char *buf, int base)
{
register char *p;
register int minus;
char buf2[36];
p = &buf2[36];
*--p = '/0';
//p = &buf2[35];
//*p = '/0';
if (n < 0) { minus = 1; n = -n; } else minus = 0;
if (n == 0) *--p = '0';
else while (n > 0) { *--p = "0123456789abcdef"[n % base]; n /= base; }
if (minus) *--p = '-';
strcpy(buf,p);
return buf;
}
这里给出的只要函数体也是从网上找啊找,找到的.兼顾了效率,选它最只要的原因是程序结构非常清晰,逻辑清楚.修改部分是增加 strcpy(buf,p); 改变了函数返回值.去掉stycpy,修改参数,返回值,也是很容易的是.
下面重点的ftoa函数体,一个是完全自己实现的,另外两个也是网上找的,都给出而下:
#if 0
/*----------------------------------------------------
函数名:ftoa()
功能 :将一个浮点数转为一个精度为jd的字符串,存于s中
注意 : 不要超过缓存长度,防止溢出
TODO : 123.0456789这样的数输出不正确,原因:没有考虑小数点后的零
作者 :
------------------------------------------------------*/
char* ftoa(float dat,char *s,unsigned char jd)
{
assert(s != NULL);
int len,temp;
int flag=dat<0?dat=-dat,1:0;
int i;
char t[10];
temp=(int)dat;
for( len = 0;temp>0;temp/=10,len++)
t[len]=temp%10+48;
if(flag){t[len++] = '-';}
for(i=0;i<=len;i++)//翻转
s[len-i-1]=t[i];
s[len++]='.';
for(i=0,temp=(int)((dat-(int)dat)*pow(10.0,jd));temp>0;temp/=10,i++)
t[i]=temp%10+48;
for(i=0;i
s[len]=0;
return s;
}
#endif
/*
Copyright 1982
Alcyon Corporation
8716 Production Ave.
San Diego, Ca. 92121
*/
/*char *version "@(#) ftoa - jan 24, 1982"; */
/*
*
* char *
* ftoa(f,buf,prec)
* float f;
* char *buf;
* int prec;
*
* No more than 9 decimal digits are allowed in single precision.
* Largest positive number is 3.4 * 10^33 and the smallest positive
* number is 1.2 * 10^-38.
* Rely's on the fact that a long and a float are both 32 bits.
*/
#if 0
char * ftoa(float f ,char *buf ,int prec)
{
register char *bp;
register int exp, digit;
prec = (prec <= 0) ? 1 : (prec <= 9) ? prec : 9;
bp = buf;
if (f < 0.0) { /* negative float */
*bp++ = '-';
f = -f; /* make it positive */
}
if (f == 0.0) {
*bp++ = '0'; *bp++ = '.';
while (prec--)
*bp++ = '0';
*bp = 0;
return(buf);
}
for (exp=0; f < 1.0; f = f * 10.0) /* get negative exp */
exp--;
for ( ; f >= 1.0; f = f / 10.0) /* 0.XXXXXXE00 * 10^exp */
exp++;
if (exp<=0) /* one significant digit */
*bp++ = '0';
for ( ; exp>0; exp--) { /* get significant digits */
f = f * 10.0;
digit = f; /* get one digit */
f = f - digit;
*bp++ = digit + '0';
}
*bp++ = '.';
for( ; exp<0 && prec; prec--, exp++) /* exp < 0 ? */
*bp++ = '0';
while(prec-- > 0) {
f = f * 10.0;
digit = f; /* get one digit */
f = f - digit;
*bp++ = digit + '0';
}
*bp = 0;
return (buf);
}
#else
//浮点数转字符
//问题:12.034转换成12.34
char* ftoa(float val, char* buf, int preci){
char *res = buf;
int tempData;
char *tempBuf;
int backStep;
int count;
tempData=(int)( val<0?*buf++ = '-', val=-val: val);
itoa(tempData, buf, 10);
while(*++buf != '/0'){}
*buf++ = '.'; //将小数点添加到整数末尾
tempData=(int) ((val-(int)val) * pow(10.0, preci));//自动做了四舍五入
//printf("tempData= %d ",tempData);
itoa(tempData, buf, 10);
tempBuf = buf;
while(*++buf != '/0'){}
*buf = '/0';
//考虑123.0034这样的小数
tempBuf += preci;
if(tempBuf != buf){
backStep = tempBuf - buf;
for(count = preci - backStep + 1; count != 0; count--){
*tempBuf-- = *buf--;
}
for(count= backStep; count != 0; count--){
*tempBuf-- = '0';
}
}
return res;
}
#endif
//浮点数转字符
//已修复 :123.0456789这样的数输出不正确,原因:没有考虑小数点后的零
char* floatToString(float val, char* buf, int preci){
char *res = buf;
int tempData;
char *tempBuf;
int backStep;
int count;
tempData=(int)( val<0?*buf++ = '-', val=-val: val);
itoa(tempData, buf, 10);
while(*++buf != '/0'){}
*buf++ = '.'; //将小数点添加到整数末尾
tempData=(int) ((val-(int)val) * pow(10.0, preci));//自动做了四舍五入
printf("tempData= %d ",tempData);
itoa(tempData, buf, 10);
tempBuf = buf;
while(*++buf != '/0'){}
*buf = '/0';
//考虑123.0034这样的小数
tempBuf += preci;
if(tempBuf != buf){
backStep = tempBuf - buf;
for(count = preci - backStep + 1; count != 0; count--){
*tempBuf-- = *buf--;
}
for(count= backStep; count != 0; count--){
*tempBuf-- = '0';
}
}
return res;
}
到这里为止,适合自己用于的myprintf()完全完成了.一下是测试结果
msp430F149 IAR测试结果:
myprintf("ftoa(123.345,buf,5) = %s/r/n",ftoa(123.345,buf,5));
myprintf("ftoa(-123.345,buf2,5) = %s/r/n",ftoa(-123.345,buf,5));
#if 0
myprintf("pow(10,3) = %d",(int)pow(10,3));
myprintf("/r/ntest ftoa :-123.345 = %f/r/n",-123.345);
myprintf("/r/ntest ftoa :-123.345 = %a54/r/n",-123.345,7);
myprintf("test float:%./r/n",12.34f);
myprintf("test float:%.3/r/n",12.34);
myprintf("test float:%.4f; float2:%.5f;%f/r/n",12.34f,34.5326f,-12.098);
#endif
myprintf( "test1:%s/r/n",ftoa(123.456, buf, 5) );
myprintf( "test2:%s/r/n",ftoa(-123.456, buf, 5) );
myprintf( "test3:%s/r/n",ftoa(12.34f, buf, 5) );
myprintf( "test4:%s/r/n",ftoa(4.5326f, buf, 5) );
myprintf( "test5:%s/r/n",ftoa(-12.098, buf, 5) );
myprintf( "test11:%s/r/n",ftoa(123.456, buf, 3) );
myprintf( "test22:%s/r/n",ftoa(-123.456, buf, 3) );
myprintf( "test33:%s/r/n",ftoa(12.34f, buf, 3) );
myprintf( "test44:%s/r/n",ftoa(4.5326f, buf, 3) );
myprintf( "test55:%s/r/n",ftoa(-12.098, buf, 3) );
myprintf( "test13:%f/r/n",123.456 );
myprintf( "test23:%f/r/n",-123.456);
myprintf( "test33:%f/r/n",12.34f);
myprintf( "test43:%f/r/n",4.5326f);
myprintf( "test53:%f/r/n",-12.098);
myprintf( "test14:%.5f/r/n",123.456 );
myprintf( "test24:%.5f/r/n",-123.456);
myprintf( "test34:%.5f/r/n",12.34f);
myprintf( "test44:%.5f/r/n",4.5326f);
myprintf( "test54:%.5f/r/n",-12.098);
输出结果:
ftoa(123.345,buf,5) = 123.34500
ftoa(-123.345,buf2,5) = -123.34500
test1:123.45600
test2:-123.45600
test3:12.34000
test4:4.53259
test5:-12.09799
test11:123.456
test22:-123.456
test33:12.340
test44:4.532
test55:-12.097
test13:123.456
test23:-123.456
test33:12.340
test43:4.532
test53:-12.097
test14:123.45600
test24:-123.45600
test34:12.34000
test44:4.53259
test54:-12.09799
MCP2515 Interface Demo (c) WGJ
test = 0xAA = 10101010
init SPI2 as Master
CAN init - FAIL
CAN-DEBUG 1
CANCTRL = 0x00 = 00000000
CANSTAT = 0x00 = 00000000
CNF1 = 0x00 = 00000000
CNF2 = 0x00 = 00000000
CNF3 = 0x00 = 00000000
Setting Normal-Mode - OK
CANCTRL = 0x00 = 00000000
RXB0CTRL = 0x00 = 00000000
RXB1CTRL = 0x00 = 00000000
Sending a message...Transmit timeout
Sending a message...Transmit timeout
MTSHELL>uart init finished, Baudrate = 9600
D:/My Documents/IAR/Projects/MSP430_Project/canMcp2515/main.c
121
main
test DEBUG_PRINTtest int 1324:1324;test string:String;test binary:01011010b;test
hex:0xAF;
file name:D:/My Documents/IAR/Projects/MSP430_Project/canMcp2515/main.c;line:123
file name:D:/My Documents/IAR/Projects/MSP430_Project/canMcp2515/main.c; line:12
4; debug_test
ftoa(123.345,buf,5) = 123.34500
ftoa(-123.345,buf2,5) = -123.34500
test1:123.45600
test2:-123.45600
test3:12.34000
test4:4.53259
test5:-12.09799
test11:123.456
test22:-123.456
test33:12.340
test44:4.532
test55:-12.097
test13:123.456
test23:-123.456
test33:12.340
test43:4.532
test53:-12.097
test14:123.45600
test24:-123.45600
test34:12.34000
test44:4.53259
test54:-12.09799
VS2008 测试结果:
printf( "test1:%s/r/n",ftoa(123.456, floatBuf, 5) );
printf( "test2:%s/r/n",ftoa(-123.456, floatBuf, 5) );
printf( "test3:%s/r/n",ftoa(12.34f, floatBuf, 5) );
printf( "test4:%s/r/n",ftoa(4.5326f, floatBuf, 5) );
printf( "test5:%s/r/n",ftoa(-12.098, floatBuf, 5) );
myprintf( "test11:%s/r/n",ftoa(123.456, buf, 5) );
myprintf( "test21:%s/r/n",ftoa(-123.456, buf, 5) );
myprintf( "test31:%s/r/n",ftoa(12.34f, buf, 5) );
myprintf( "test41:%s/r/n",ftoa(4.5326f, buf, 5) );
myprintf( "test51:%s/r/n",ftoa(-12.098, buf, 5) );
myprintf( "test12:%s/r/n",ftoa(123.456, buf, 3) );
myprintf( "test22:%s/r/n",ftoa(-123.456, buf, 3) );
myprintf( "test32:%s/r/n",ftoa(12.34f, buf, 3) );
myprintf( "test42:%s/r/n",ftoa(4.5326f, buf, 3) );
myprintf( "test52:%s/r/n",ftoa(-12.098, buf, 3) );
myprintf( "test13:%f/r/n",4.5326f );
myprintf( "test23:%f/r/n",-123.456);
myprintf( "test33:%f/r/n",12.34f);
myprintf( "test43:%f/r/n",4.5326f);
//myprintf( "test43:%s/r/n",ftoa(4.5326f, buf, 3));
myprintf( "test53:%f/r/n",-12.098);
myprintf( "test14:%.5f/r/n",123.456f );
myprintf( "test24:%.5f/r/n",-123.456);
myprintf( "test34:%.5f/r/n",12.34f);
myprintf( "test44:%.5f/r/n",4.5326f);
//myprintf( "test44:%s/r/n",floatToString(4.5326f, buf, 3));
myprintf( "test54:%.5f/r/n",-12.098);
printf("test floatToString1: 123.03445 = %s/r/n", floatToString(123.03445, floatBuf, 6) );
printf("test floatToString2: -123.004567 = %s/r/n", floatToString(-123.004567, floatBuf, 6) );
printf("test floatToString3: 23.345 = %s/r/n",floatToString(123.345,buf,4));
printf("test floatToString4: -123.345 = %s/r/n",floatToString(-123.345,buf,4));
输出结果:
test1:123.45600
test2:-123.45600
test3:12.34000
test4:4.53259
test5:-12.09799
test11:123.45600
test21:-123.45600
test31:12.34000
test41:4.53259
test51:-12.09799
test12:123.456
test22:-123.456
test32:12.340
test42:4.532
test52:-12.097
test13:fdata = 4.532600
4.532
test23:fdata = -123.456001
-123.456
test33:fdata = 12.340000
12.340
test43:fdata = 4.532600
4.532
test53:fdata = -12.098000
-12.097
test14:fdata = 123.456001
123.45600
test24:fdata = -123.456001
-123.45600
test34:fdata = 12.340000
12.34000
test44:fdata = 4.532600
4.53259
test54:fdata = -12.098000
-12.09799
tempData= 34446 test floatToString1: 123.03445 = 123.034446
tempData= 4570 test floatToString2: -123.004567 = -123.004570
tempData= 3450 test floatToString3: 23.345 = 123.3450
tempData= 3450 test floatToString4: -123.345 = -123.3450
还不知道DIY的这个printf编译出来是多大,估计不是很大,应该能接收.
要注意的问题就是计算结果溢出,
看官若发现问题,请不吝赐教.谢谢