Chinaunix首页 | 论坛 | 博客
  • 博客访问: 87192
  • 博文数量: 21
  • 博客积分: 547
  • 博客等级: 中士
  • 技术积分: 217
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-22 09:41
文章分类

全部博文(21)

文章存档

2013年(3)

2012年(2)

2011年(10)

2010年(6)

分类: 嵌入式

2011-04-27 15:00:46

G-biosprintf的实现分析

Hyfeng     E-mailhyfeng18@126.com

         以前不论是在linuxwindow下写C程序,我一直都很喜欢使用printf来调试错误,基本上就没怎么用过debug去调试错误,个人觉得printf调试错误比较的方便,而且很多程序用debug是没办法调试的,因此我还是很喜欢通过观察输出来判断错误。可是现在转到嵌入式平台上面开发程序,没有了最爱的printf有的时候还真不知道怎么去调试程序了,之前一直都会用ads或者keildebug功能跟踪调试程序,不过说真话我是真的不习惯用。不过庆幸的是很多嵌入式设备都有串口,有串口就可以输出字符,也就可以当做输出调试的接口了,上次的在LPC1700上面调试LWIP的时候就是通过在串口上面输入程序的执行流程,最后很容易就定位到网卡硬件初始化函数不正确,知道错误的函数改起来也就比较方便了。可是通过简单的串口发送程序调试是有一点缺陷的,就是一般情况下如果没有对串口发送函数做特殊的处理的话,只能输出字符串这样调试起来就没有那么方便了。

         虽然觉得只能输出字符串调试很不方便,但也没有想去改进它,直到前几天移植g-bios的时候看到了程序中提供了一个stdio.c的文件,在这个文件中就实现了将printf的输出设备变成了串口,也就是标准C中的printf是输出在屏幕上的,而通过修改以后的pirntf是通过串口输出的,这样一来在调试嵌入式程序的时候我就可以使用printf来输出调试信息了。不过嵌入式设备中printf和标准C的还是有些差别的,简单的说嵌入式开发中使用的printf是标准C使用的printf的阉割版,为什么下面分析程序的时候会讲到。

 

先看一下printf函数

  1. int printf(const char *pchFmt, ...)
  2. {
  3.     /*获得第二个参数的地址*/
  4.     int *nPara = (int *)&pchFmt + 1;
  5.     int nPrtedlen;
  6.     char chArraybuf[512];
  7.     char *pchBuf = chArraybuf;

  8.     nPrtedlen = vsprintf(chArraybuf, pchFmt, nPara);
  9.     
  10.     while (*pchBuf)
  11.         putchar(*pchBuf++);    //通过在putchar函数调用串口输出函数,就可实现printf在串口上面输出了。

  12.     return nPrtedlen;
  13. }

这里printf是一个可变长参数函数,参数pchFmt保存的是参数的地址,在printf中第一个参数就是那个字符串,而且参数值就是从第二个参数开始的。从上面的函数可以看出printf相对比较的简单,而大部分的解析字符的工作都放在了vsprintf函数中完成,在看vsprintf之前我觉得又必要了解一下printf对于参数输出格式的定义,printf中输出定义格式如下:

%[flags][width][.perc][F|N|h|l]type

(1). Type就是指输出值的类型,例如:d有符号10进制整形,等等。。。

(2). Flags规定输出格式,取值和含义如下:

           右对齐,左边填充0和空格

-           左对齐,右边填充空格

+   在数字前增加符号+-

0          将输出的前面不上0,直到占满指定列宽为止(不可以搭配使用-

空格 输出值为正时冠以空格,为负时冠以负号

#type=o,x,X时,分别在数值前增加‘0‘,’0x‘,’0X

(3) width用于控制显示数值的宽度,取值和含义如下:

         n表示宽度至少为n位,不够以空格填充

         0n表示宽度至少为n为,不够左边以0填充

         *格式列表中,下一个参数还是width

(4) prec用于控制小数点后面的位数,在嵌入式应用中一般不处理浮点型的数据,这个就不介绍了

(5) F|N|h|l表示指针是否是远指针或整数是否是长整数

vsprintf函数的主要功能就是按照这个格式去解析字符串,并将参数替换到%对于的地方,vsprintf的源码如下:

  1. #define LEFT        0x1
  2. #define SIGN        0x2
  3. #define SPACE        0x4
  4. #define PLUS        0x8
  5. #define ZERO        0x10
  6. #define SIGNINT        0x20
  7. /*
  8. *        Format的格式 ==> %[flags][width][.perc][F|N|h|l]type
  9. */
  10. static int vsprintf(char *pchBuf, const char *pchFormat, int *pPara)
  11. {
  12.     int nOutputStyle, nWidth, nBase;
  13.     const char *pchFmtRoll;
  14.     char *pchStrTemp;
  15.     int nStrLen;
  16.     char chTempArray[32];
  17.     int nCount = 0;
  18.     char *pchBufTemp = pchBuf;


  19.     while (*pchFormat)
  20.     {
  21.         /*不是%符号不需要解析*/
  22.         if ('%' != *pchFormat)
  23.         {
  24.             *pchBufTemp++ = *pchFormat++;
  25.             continue;
  26.         }
  27.         pchFmtRoll = pchFormat++;
  28.         /*连续出现%那么从第二%开始输出*/
  29.         if ('%' == *pchFormat)//handle e.g "%%"
  30.         {
  31.             *pchBufTemp++ = *pchFormat++;
  32.             continue;
  33.         }
  34.         pchFormat--;

  35.         /*解析flags*/
  36.         nOutputStyle = 0;
  37. get_sign :
  38.         pchFormat++;
  39.         switch(*pchFormat)
  40.         {
  41.         case '-' :
  42. /*输出格式:左对齐,右边以空格填充*/
  43.             nOutputStyle |= LEFT;
  44.             nOutputStyle &= (~ZERO);
  45.             goto get_sign;
  46.         case '+' :
  47. /*输出格式:在数字前面增加+-*/
  48.             nOutputStyle |= SIGN;
  49.             goto get_sign;
  50.         case ' ' :
  51. /*输出格式:输出值为正时加空格,输出值为负数加-*/
  52.             nOutputStyle |= SPACE;
  53.             goto get_sign;
  54.         case '#' :
  55. /*输出格式:对16进制和8进制添加相应的前缀*/
  56.             nOutputStyle |= PLUS;
  57.             goto get_sign;
  58.         case '0' :
  59. /*输出格式:将输出值前面补0直到暂满width为止*/
  60.             if (!(nOutputStyle & LEFT))
  61.                 nOutputStyle |= ZERO;
  62.             goto get_sign;
  63.         }
  64.         
  65.         /*解析width*/
  66.         nWidth = 0;
  67.         /*解析数字的值*/
  68.         while (ISDIGIT(*pchFormat))
  69.         {
  70.             nWidth = nWidth * 10 + *pchFormat - '0';
  71.             pchFormat++;
  72.         }

  73.         //handle '*',replace '*'
  74.         if ('*' == *pchFormat)
  75.         {
  76.             pchFormat++;
  77.             
  78.             if (!ISDIGIT(*pchFormat))
  79.             {
  80.                 nOutputStyle &= ~LEFT;
  81.                 nWidth = *pPara++;
  82.             }
  83.             else
  84.             {
  85.                 while (*pPara)
  86.                 {
  87.                     chTempArray[nCount++] = *pPara%10 + '0';
  88.                     *pPara /= 10;
  89.                 }
  90.                 
  91.                 while (nCount)
  92.                     (*pchBufTemp++) = chTempArray[--nCount];

  93.                 pPara++;
  94.                 continue;
  95.             }
  96.         }
  97.         
  98.         nBase = 10;
  99. /*下面是对于格式中type字段的解析,主要就设定一下输出的格式,而且对于字符串和字符是直接放到最后的输出串当中的,而对于非字符的,例如:整形,是需要将整数转换成字符的,这个是由NumToAscii完成的*/
  100.         //fixme:handle '%l(o,x,i,d)' and '%ll(o,x,i,d)',filter 'l' and 'll'
  101.         if (('l' == *pchFormat) && ('l' == *++pchFormat))
  102.             *pchFormat ++;

  103.         switch(*pchFormat)
  104.         {
  105.         case 'u':
  106.             nBase = 10;
  107.             nOutputStyle &= ~(SIGN | PLUS);
  108.             break;
  109.             
  110.         case 'd' :
  111.         case 'i' :
  112.             nBase = 10;
  113.             if (!(nOutputStyle & SIGN))
  114.                 nOutputStyle |= SIGNINT | SIGN;
  115. //            nOutputStyle &= (~PLUS);
  116.             break;
  117.         case 'p' :
  118.             nOutputStyle |= PLUS;
  119.         case 'X' :
  120.         case 'x' :
  121.             nBase = 16;
  122.             break;
  123.         case 'o' :
  124.             nBase = 8;
  125.             break;
  126.         case 's' :
  127.             pchStrTemp = (char *)*pPara;
  128.             nStrLen = strlen(pchStrTemp);
  129.             if (!(nOutputStyle & LEFT))
  130.             {
  131.                 if (nOutputStyle & ZERO)
  132.                 {
  133.                     while (nWidth-- > nStrLen)
  134.                         *pchBufTemp++ = '0';
  135.                 }
  136.                 else
  137.                 {
  138.                     while (nWidth-- > nStrLen)
  139.                         *pchBufTemp++ = ' ';
  140.                 }
  141.             }
  142.             while (*pchStrTemp)
  143.                 *pchBufTemp++ = *pchStrTemp++;
  144.             while (nWidth-- > nStrLen)
  145.                 *pchBufTemp++ = ' ';
  146.             pPara++;
  147.             pchFormat++;
  148.             continue;
  149.         case 'c' :
  150.             if (!(nOutputStyle & LEFT))
  151.             {
  152.                 if (nOutputStyle & ZERO)
  153.                 {
  154.                     while (nWidth-- > 1)
  155.                         *pchBufTemp++ = '0';
  156.                 }
  157.                 else
  158.                 {
  159.                     while (nWidth-- > 1)
  160.                         *pchBufTemp++ = ' ';
  161.                 }
  162.             }
  163.             *pchBufTemp++ = (char)*pPara;
  164.             while (nWidth-- > 1)
  165.                 *pchBufTemp++ = ' ';
  166.             pPara++;
  167.             pchFormat++;
  168.             continue;
  169.         default :
  170.             *pchBufTemp++ = *pchFmtRoll++;
  171.             pchFormat = pchFmtRoll;//roll back pchFormat
  172.             continue;
  173.         }
  174.     pchBufTemp = NumToAscii(pchBufTemp, *pPara, nWidth, nBase, nOutputStyle);
  175.     pPara++;
  176.     pchFormat++;
  177.     }
  178.     *pchBufTemp = '\0';
  179.     return pchBufTemp - pchBuf;
  180. }

对于上面的程序switch中的continue是对外面的循环使用的,并不是和break一样只是简单跳过下面case的选项,而是跳过while循环中的下面未执行的部分,这种用法我是第一次看到的,刚开始还没理解它的意思以为作用与break一样的呢?

对于下面的NumToAscii函数就是按照上面解析的格式将数字转换成对于的格式,最后全部都转换成字符的形式输出到相应的地方,下面就给NumToAscii的代码吧

  1. static char * NumToAscii(char *pBuf, \
  2.                          unsigned int nNum, \
  3.                          int nWidth, \
  4.                          int nBase, \
  5.                          int nOutputStyle)
  6. {
  7.     char chTempArray[32], chFillChar, chSign = '\0';
  8.     const char *pchDigit = "0123456789abcdef";
  9.     int nCount = 0;
  10.     int nPrefixNum = 0;


  11.     chFillChar = (nOutputStyle & ZERO) ? '0' : ' ';//set ZERO attribute

  12.     //set SIGN and SPACE
  13.     if (nOutputStyle & SIGN && (10 == nBase))
  14.     {
  15.         /*有符号的整数,最高位为1是负数*/
  16.         if (nNum & 0x1 << 31)
  17.         {
  18.             chSign = '-';
  19.             nNum = -nNum;
  20.         }
  21.         else if (!(nOutputStyle & SIGNINT))
  22.         {
  23.             chSign = '+';
  24.         }
  25.     }
  26.     else if (!((nOutputStyle & SIGN) || (nNum & 0x1 << 31)) && (10 == nBase) && (nOutputStyle & SPACE))
  27.         chSign = ' ';

  28.     //if num == 0
  29.     if (nNum == 0)
  30.         chTempArray[nCount++] = '0';

  31.     //convert number
  32.     while (nNum != 0)
  33.     {
  34.         chTempArray[nCount++] = pchDigit[nNum % nBase];
  35.         nNum = nNum / nBase;
  36.     }

  37.     //set PLUS
  38.     if (nOutputStyle & PLUS)
  39.     {
  40.         if (nBase == 16)//handle hex
  41.             nPrefixNum = 2;
  42.         if (nBase == 8)//handle oct
  43.             nPrefixNum = 1;
  44.     }

  45.     //EccGenerate number of chFillChar
  46.     nWidth = nWidth - nPrefixNum - (chSign ? 1 : 0) - nCount;

  47.     if (!(nOutputStyle & LEFT))
  48.     {
  49.         while (nWidth-- > 0)
  50.             *pBuf++ = chFillChar;
  51.     }

  52.     switch(nPrefixNum)
  53.     {
  54.     case 2:
  55.         *pBuf++ = '0';
  56.         *pBuf++ = 'x';
  57.         break;
  58.     case 1:
  59.         *pBuf++ = '0';
  60.         break;
  61.     default :
  62.         break;
  63.     }

  64.     if (chSign)
  65.         *pBuf++ = chSign;

  66.     while (nCount > 0)
  67.         *pBuf++ = chTempArray[--nCount];

  68.     while (nWidth-- > 0)
  69.         *pBuf++ = chFillChar;

  70.     return pBuf;
  71. }

一个比较简单的printf函数算是完成了,这几天一直在看<<0bug-c/c++商用工程之道>>它上面也谈到了一些关于debug调试的内容,觉得他讲的的还是很不错的,书上说要建立自己的工程库,我觉得这个还是可以去慢慢做的,毕竟以后可能一直都要做计算机这一行的。要是想看一下具体的源码可以去下载g-bios的源码,对于g-bios其实很多东西和uboot是很像的不过个人觉得g-bios更适合初学的人看,毕竟先g-bios只支持几种芯片,在程序方面并不像uboot一个函数当中有一堆的宏,通过宏的定义来实现对于不同功能的支持看的真的比较的麻烦,g-bios的源码看的还是比较简洁的就是文档少了点,而且程序基本没什么注释的幸亏程序命名是按照匈牙利命名法写的,根据函数名你还能知道这个函数的大体意思。

阅读(2037) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~