全文引用自:http://blog.chinaunix.net/uid-18921523-id-187419.html
本来想自己实现一个printf函数,顺便再回顾一下变参函数的处理,但是时间紧张,在这里就先通过移植库函数来实现自己的printf函数,等有时间,在重新自己实现下。
代码基本上是在前面已有的基础上继续添加:首先来看下lboot.lds
- ENTRY(_start)
- SECTIONS
- {
- . = 0x00000000;
- .init : AT(0){
- start.o nand.o
- }
- . = 0x30000000;
- .text : AT(4096){
- *(.text)
- }
- . = ALIGN(32);
- .data :{
- *(.data)
- }
- . = ALIGN(32);
- .bss :{
- *(.bss)
- }
- }
这里将代码段分为init段和text段,init段中存放了start.o和nand.o,如果生成的lboot.bin文件是下到nandflash中的话,这两段代码将会装载到nandflash的0地址处,并且他们的运行地址也是位于0地址处,开始执行时,这两段的代码会自动拷贝到内部SRAM中去执行,实现系统的初始化及代码的搬移。然后把其余的代码段放在从nandflash4096处即4k之后排放,并且他们的运行地址是0x30000000。
再来看下Makefile,这里Makefile使用了递归的技术,及代码存放的根目录下是总的Makefile,然后总的Makefile会递归调用其子目录下的makefile,总的Makefile如下:
- CC = arm-lwm-linux-gnueabi-gcc
- LD = arm-lwm-linux-gnueabi-ld
- AR = arm-lwm-linux-gnueabi-ar
- OBJCOPY = arm-lwm-linux-gnueabi-objcopy
- OBJDUMP = arm-lwm-linux-gnueabi-objdump
-
- INCLUDEDIR := $(shell pwd)/include
- CFLAGS := -Wall -O2
- CPPFLAGS := -nostdinc -fno-builtin -I$(INCLUDEDIR)
-
- export CC LD AR OBJCOPY OBJDUMP INCLUDEDIR CFLAGS CPPFLAGS
-
- objs := start.o nand.o serial.o main.o lib/libc.a
-
- lboot.bin : $(objs)
- ${LD} -Tlboot.lds -o lboot_elf $^
- ${OBJCOPY} -O binary -S lboot_elf $@
- ${OBJDUMP} -D -m arm lboot_elf > lboot.dis
-
- .PHONY : lib/libc.a
- lib/libc.a:
- cd lib; make; cd ..
-
- %.o:%.c
- ${CC} $(CPPFLAGS) ${CFLAGS} -c -o $@ $<
- %.o:%.S
- ${CC} $(CPPFLAGS) ${CFLAGS} -c -o $@ $<
- clean:
- make clean -C lib
- 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使用
- .PHONY : lib/libc.a
- lib/libc.a:
- 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函数:
- #include "s3c2440.h"
- #include "serial.h"
-
- #define TXD0READY (1<<2)
- #define RXD0READY (1)
-
- void init_uart(void)
- {
- GPHCON |= 0xa0; //GPH2,GPH3 used as TXD0,RXD0
- GPHUP = 0x0c; //GPH2,GPH3禁止上拉
-
- ULCON0 = 0x03; //8N1,8位数据,一位停止位,无奇偶校验
- UCON0 = 0x05; //使用查询方式,时钟采用PCLK
- UFCON0 = 0x00; //不使用FIFO
- UMCON0 = 0x00; //不使用流控
- UBRDIV0 = 26; //波特率为115200
-
- }
-
- void putc(unsigned char c)
- {
- while( ! (UTRSTAT0 & TXD0READY) );
- UTXH0 = c;
- }
-
- unsigned char getc(void)
- {
- #ifdef SERIAL_ECHO
- unsigned char ret;
- #endif
- while( ! (UTRSTAT0 & RXD0READY) );
- ret = URXH0;
- #ifdef SERIAL_ECHO //如果支持回显,将接收到的数据立即显示在串口上
- if (ret == 0x0d || ret == 0x0a) //一个是换行一个是回车
- {
- putc(0x0d);
- putc(0x0a);
- }
- else
- {
- putc(ret);
- }
- #endif
- return ret;
- }
这里0x0d代表CR,0x0a代表LF,在dos和windows中CR/LF表示下一行,而在unix/linux中LF代表下一行,程序中就是如果收到这两个字符的话,在windows中就输出CR/LF,以实现换行的目的。
init_uart实现了串口的初始化,putc和getc分别读写2440的串口寄存器,下面再来看看标准库的printf函数和scanf函数做了什么修改:
- #include "vsprintf.h"
- #include "string.h"
- #include "printf.h"
-
- extern void putc(unsigned char c);
- extern unsigned char getc(void);
-
- #define OUTBUFSIZE 1024
- #define INBUFSIZE 1024
-
-
- static unsigned char g_pcOutBuf[OUTBUFSIZE];
- static unsigned char g_pcInBuf[INBUFSIZE];
-
-
- int printf(const char *fmt, ...)
- {
- int i;
- int len;
- va_list args;
-
- va_start(args, fmt);
- len = vsprintf((char *)g_pcOutBuf,fmt,args);
- va_end(args);
- for (i = 0; i < strlen((char *)g_pcOutBuf); i++)
- {
- putc(g_pcOutBuf[i]);
- }
- return len;
- }
-
-
-
- int scanf(const char * fmt, ...)
- {
- int i = 0;
- unsigned char c;
- va_list args;
-
- while(1)
- {
- c = getc();
- if((c == 0x0d) || (c == 0x0a))
- {
- g_pcInBuf[i] = '\0';
- break;
- }
- else
- {
- g_pcInBuf[i++] = c;
- }
- }
-
- va_start(args,fmt);
- i = vsscanf((char *)g_pcInBuf,fmt,args);
- va_end(args);
-
- return i;
- }
len = vsprintf((char *)g_pcOutBuf,fmt,args);
这一句vsprintf函数的意思是将args中的变量按照fmt中规定的格式保存到临时缓冲区g_pcOutBuf中,
- for (i = 0; i < strlen((char *)g_pcOutBuf); i++)
- {
- putc(g_pcOutBuf[i]);
- }
接着用调用了putc函数,将缓冲区里的内容通过2440的串口发送了出去,现在明白printf函数是怎么和实际的串口联系起来了吧,scanf的原理类似,不在讲述。
最后通过main.c函数测试下是否好用:
- #include "s3c2440.h"
- #include "stdio.h"
- #include "serial.h"
-
- #define GPB5_out (1<<(5*2))
- #define GPB6_out (1<<(6*2))
- #define GPB7_out (1<<(7*2))
- #define GPB8_out (1<<(8*2))
-
- void wait(volatile unsigned long dly)
- {
- for(; dly > 0; dly--);
- }
-
- int main(void)
- {
- unsigned int i = 0;
- int n;
-
- init_uart(); //波特率为115200,8位数据,一位停止位,无奇偶校验,无流控
-
- printf("\nthis is my own bootloader for mini2440\n\r");
- printf("\nwrite by li weimeng\n\r");
- printf("\nplease input a number: \n\r");
- scanf("%d",&n);
- printf("\nyou have just input: %d \n\r", n);
-
- GPBCON = GPB5_out|GPB6_out|GPB7_out|GPB8_out; // 将LED1-4对应的GPB5/6/7/8四个引脚设为输出
-
- while(1){
- wait(300000);
- GPBDAT = ~(i<<5);
- if(++i == 16)
- i = 0;
- }
-
- return 0;
- }
最后的效果图如下: