移植自己的printf,scanf函数(引用:http://blog.chinaunix.net/uid-18921523-id-187419.html)

全文引用自:http://blog.chinaunix.net/uid-18921523-id-187419.html


本来想自己实现一个printf函数,顺便再回顾一下变参函数的处理,但是时间紧张,在这里就先通过移植库函数来实现自己的printf函数,等有时间,在重新自己实现下。


代码基本上是在前面已有的基础上继续添加:首先来看下lboot.lds
  1. ENTRY(_start)
  2. SECTIONS
  3. {
  4.     . = 0x00000000;
  5.     .init : AT(0){
  6.         start.o nand.o
  7.     }
  8.     . = 0x30000000;
  9.     .text : AT(4096){
  10.         *(.text)
  11.     }    
  12.     . = ALIGN(32);
  13.     .data :{
  14.         *(.data)
  15.     }
  16.     . = ALIGN(32);
  17.     .bss :{
  18.         *(.bss)
  19.     }
  20. }
这里将代码段分为init段和text段,init段中存放了start.o和nand.o,如果生成的lboot.bin文件是下到nandflash中的话,这两段代码将会装载到nandflash的0地址处,并且他们的运行地址也是位于0地址处,开始执行时,这两段的代码会自动拷贝到内部SRAM中去执行,实现系统的初始化及代码的搬移。然后把其余的代码段放在从nandflash4096处即4k之后排放,并且他们的运行地址是0x30000000。
再来看下Makefile,这里Makefile使用了递归的技术,及代码存放的根目录下是总的Makefile,然后总的Makefile会递归调用其子目录下的makefile,总的Makefile如下:
  1. CC        = arm-lwm-linux-gnueabi-gcc
  2. LD        = arm-lwm-linux-gnueabi-ld
  3. AR        = arm-lwm-linux-gnueabi-ar
  4. OBJCOPY    = arm-lwm-linux-gnueabi-objcopy
  5. OBJDUMP    = arm-lwm-linux-gnueabi-objdump

  6. INCLUDEDIR    := $(shell pwd)/include
  7. CFLAGS        := -Wall -O2
  8. CPPFLAGS    := -nostdinc -fno-builtin -I$(INCLUDEDIR)

  9. export CC LD AR OBJCOPY OBJDUMP INCLUDEDIR CFLAGS CPPFLAGS

  10. objs := start.o nand.o serial.o main.o lib/libc.a

  11. lboot.bin : $(objs)
  12.     ${LD} -Tlboot.lds -o lboot_elf $^
  13.     ${OBJCOPY} -O binary -S lboot_elf $@
  14.     ${OBJDUMP} --m arm lboot_elf > lboot.dis
  15.     
  16. .PHONY : lib/libc.a
  17. lib/libc.a:
  18.     cd lib; make; cd ..

  19. %.o:%.c
  20.     ${CC} $(CPPFLAGS) ${CFLAGS} --$@ $<
  21. %.o:%.S
  22.     ${CC} $(CPPFLAGS) ${CFLAGS} --$@ $<
  23. clean:
  24.     make clean -C lib
  25.     rm -f lboot.dis lboot.bin lboot_elf *.o
这里-nostdinc的意思是:Do not search the standard system directories for header files
-fno-builtin的意思是:
在C语言标准中,有些通用函数被定义为built-in function(内建函数),像printf,strchr,memset等等,这些函数不需要包含头文件中的声明,就可以编译连接该函数的。但有时候你想自己实现,就会出现冲突的提示。
解决办法:在编译是加上-fno-builtin或者-fno-builtin-FUNCTION 选项,你就可以自己实现这些函数而不冲突了。
例如在上面的提示中,你编译时加上-fno-builtin-strchr ,就可以正常编译了。
export CC LD AR OBJCOPY OBJDUMP INCLUDEDIR CFLAGS CPPFLAGS
用export声明这些变量使得他们可以被子目录的Makefile使用
  1. .PHONY : lib/libc.a
  2. lib/libc.a:
  3.     cd lib; make; cd ..
.PHONY是一个伪目标,简单的说,用.PHONY声明的伪目标,不管这个目标存不存在,要执行这个目标,只需要执行make 目标名就可以了。这里要注意的一点是cd lib;make;cd ..这句,不能换行写,必须在一行写,用分号隔开,才能达到首先进入lib目录,在执行make,执行完之后在返回上一级目录的目的。
make clean -C lib
这一句也是同样达到先进入lib目录,再执行make的的目的。
关于makefile的相关知识可以参考下面博客:
这里移植printf库是通过移植东山哥的stdio里面而得来的。
通过mini2440往串口终端上打印东西最简单的思想就是在标准库的printf函数和scanf函数中调用putc和getc函数,而putc函数和getc函数里面就是具体的读写我们s3c2440的串口寄存器,这样就把标准的printf函数scanf函数和我们mini2440的串口联系起来了。
下面我们看一下putc函数和getc函数:
  1. #include "s3c2440.h"
  2. #include "serial.h"

  3. #define    TXD0READY    (1<<2)
  4. #define    RXD0READY    (1)

  5. void init_uart(void)
  6. {
  7.     GPHCON |= 0xa0;            //GPH2,GPH3 used as TXD0,RXD0
  8.     GPHUP    = 0x0c;            //GPH2,GPH3禁止上拉

  9.     ULCON0    = 0x03;            //8N1,8位数据,一位停止位,无奇偶校验
  10.     UCON0    = 0x05;            //使用查询方式,时钟采用PCLK
  11.     UFCON0     = 0x00;            //不使用FIFO
  12.     UMCON0     = 0x00;            //不使用流控
  13.     UBRDIV0 = 26;            //波特率为115200

  14. }

  15. void putc(unsigned char c)
  16. {
  17.     while( ! (UTRSTAT0 & TXD0READY) );
  18.     UTXH0 = c;
  19. }

  20. unsigned char getc(void)
  21. {
  22. #ifdef SERIAL_ECHO
  23.     unsigned char ret;
  24. #endif    
  25.     while( ! (UTRSTAT0 & RXD0READY) );
  26.     ret = URXH0;
  27. #ifdef SERIAL_ECHO                    //如果支持回显,将接收到的数据立即显示在串口上
  28.     if (ret == 0x0d || ret == 0x0a) //一个是换行一个是回车
  29.     {
  30.         putc(0x0d);
  31.         putc(0x0a);
  32.     }
  33.     else
  34.     {
  35.         putc(ret);        
  36.     }
  37. #endif    
  38.     return ret;
  39. }
这里0x0d代表CR,0x0a代表LF,在dos和windows中CR/LF表示下一行,而在unix/linux中LF代表下一行,程序中就是如果收到这两个字符的话,在windows中就输出CR/LF,以实现换行的目的。
init_uart实现了串口的初始化,putc和getc分别读写2440的串口寄存器,下面再来看看标准库的printf函数和scanf函数做了什么修改:
  1. #include "vsprintf.h"
  2. #include "string.h"
  3. #include "printf.h"

  4. extern void putc(unsigned char c);
  5. extern unsigned char getc(void);

  6. #define    OUTBUFSIZE    1024
  7. #define    INBUFSIZE    1024


  8. static unsigned char g_pcOutBuf[OUTBUFSIZE];
  9. static unsigned char g_pcInBuf[INBUFSIZE];


  10. int printf(const char *fmt, ...)
  11. {
  12.     int i;
  13.     int len;
  14.     va_list args;

  15.     va_start(args, fmt);
  16.     len = vsprintf((char *)g_pcOutBuf,fmt,args);
  17.     va_end(args);
  18.     for (= 0; i < strlen((char *)g_pcOutBuf); i++)
  19.     {
  20.         putc(g_pcOutBuf[i]);
  21.     }
  22.     return len;
  23. }



  24. int scanf(const char * fmt, ...)
  25. {
  26.     int i = 0;
  27.     unsigned char c;
  28.     va_list args;
  29.     
  30.     while(1)
  31.     {
  32.         c = getc();
  33.         if((== 0x0d) || (== 0x0a))
  34.         {
  35.             g_pcInBuf[i] = '\0';
  36.             break;
  37.         }
  38.         else
  39.         {
  40.             g_pcInBuf[i++] = c;
  41.         }
  42.     }
  43.     
  44.     va_start(args,fmt);
  45.     i = vsscanf((char *)g_pcInBuf,fmt,args);
  46.     va_end(args);

  47.     return i;
  48. }
len = vsprintf((char *)g_pcOutBuf,fmt,args);
这一句vsprintf函数的意思是将args中的变量按照fmt中规定的格式保存到临时缓冲区g_pcOutBuf中,
  1. for (= 0; i < strlen((char *)g_pcOutBuf); i++)
  2.     {
  3.         putc(g_pcOutBuf[i]);
  4.     }
接着用调用了putc函数,将缓冲区里的内容通过2440的串口发送了出去,现在明白printf函数是怎么和实际的串口联系起来了吧,scanf的原理类似,不在讲述。
最后通过main.c函数测试下是否好用:
  1. #include "s3c2440.h"
  2. #include "stdio.h"
  3. #include "serial.h"

  4. #define    GPB5_out    (1<<(5*2))
  5. #define    GPB6_out    (1<<(6*2))
  6. #define    GPB7_out    (1<<(7*2))
  7. #define    GPB8_out    (1<<(8*2))

  8. void wait(volatile unsigned long dly)
  9. {
  10.     for(; dly > 0; dly--);
  11. }

  12. int main(void)
  13. {
  14.     unsigned int i = 0;
  15.     int n;
  16.     
  17.     init_uart();                                        //波特率为115200,8位数据,一位停止位,无奇偶校验,无流控
  18.     
  19.     printf("\nthis is my own bootloader for mini2440\n\r");
  20.     printf("\nwrite by li weimeng\n\r");
  21.     printf("\nplease input a number: \n\r");
  22.     scanf("%d",&n);
  23.     printf("\nyou have just input: %d \n\r", n);

  24.     GPBCON = GPB5_out|GPB6_out|GPB7_out|GPB8_out;        // 将LED1-4对应的GPB5/6/7/8四个引脚设为输出

  25.     while(1){    
  26.             wait(300000);
  27.             GPBDAT = ~(i<<5);
  28.             if(++== 16)
  29.                 i = 0;    
  30.     }

  31.     return 0;
  32. }
最后的效果图如下:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值