本来想自己实现一个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;
-
}
最后的效果图如下:
附源代码:
阅读(7175) | 评论(0) | 转发(1) |