uart stdio的移植
uart stdio的移植1
什么是stdio
(1)#include <stdio.h>
(2)stdio:standard input output,标准输入输出
(3)标准输入输出就是操作系统定义的默认的输入和输出通道。一般在PC机的情况下,标准输入指的是键盘,标准输出指的是屏幕。
(4)printf函数和scanf函数可以和底层输入/输出函数绑定,然后这两个函数就可以和stdio绑定起来。也就是说我们直接调用printf函数输出,内容就会被从标准输出输出出去。
(5)在我们这里,标准输出当然不是屏幕了,而是串口。标准输出也不是键盘,而是串口。
printf函数的工作原理
(1)printf函数工作时内部实际调用了2个关键函数:一个是vsprintf函数(主要功能是格式化打印信息,最终得到纯字符串格式的打印信息等待输出),另一个就是真正的输出函数putc(操控标准输出的硬件,将信息发送出去)
移植printf函数的三种思路
(1)我们希望在我们的开发板上使用printf函数进行(串口)输出,使用scanf函数进行(串口)输入,就像在PC机上用键盘和屏幕进行输入输出一样。因此需要移植printf函数/scanf函数
(2)我们说的移植而不是编写,我们不希望自己完全从新编写而是想尽量借用也有的代码(叫移植)
(3)一般移植printf函数可以有3个途径获取printf的实现源码:最原始最原本的来源就是linux内核中的printk。难度较大、关键是麻烦;稍微简单些的方法是从uboot中移植printf;更简单的方法就是直接使用别人移植好的。
(4)我们课程中使用第三种方法,别人移植好的printf函数来自于友善之臂的Tiny210的裸机教程中提供的。
移植printf
将lib和include放到项目目录
相关的makefile文件:
objs := div64.o lib1funcs.o ctype.o muldi3.o printf.o string.o vsprintf.o
libc.a: $(objs)
${AR} -r -o $@ $^
%.o:%.c
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
%.o:%.S
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
clean:
rm -f libc.a *.o
libc.a是一个库文件
用AR工具变成库了
将我们之前的uart.c里面的函数名相应的改一下
现在就可以把main中包含stdio.h
#include "stdio.h"现在就可以调用printf()了
现在开始修改我们以前的makefile
改造makefile文件
现在我没增加一个相应的变量
objs := start.o led.o clock.o uart.o main.o 定义其他相关:
CC = arm-linux-gcc
LD = arm-linux-ld
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump
AR = arm-linux-ar现在就可以把下面的替代掉了
uart.bin: $(objs)
$(LD) -Tlink.lds -o uart.elf $^
$(OBJCOPY) -O binary uart.elf uart.bin
$(OBJDUMP) -D uart.elf > uart_elf.disexport
#导出这些变量到全局,其实就是给子文件夹下面的Makefile使用
export CC LD OBJCOPY OBJDUMP AR CPPFLAGS CFLAGSCPPFLAGS / CFLAGS
# C预处理器的flag,flag就是编译器可选的选项
CPPFLAGS := -nostdlib -nostdinc -I$(INCDIR)/include
# C编译器的flag
CFLAGS := -Wall -O2 -fno-builtin-nostdinc 不使用标准库
-Wall 显示警告信息
-O2 代码优化 2是优化等级
-fno-builtin 编译链接的时候只使用自己的东西
INCDIR := $(shell pwd)$(shell pwd) 获取当前目录路径
-I 指定include的目录
当前目录下的include目录:
-I$(INCDIR)/include
%.o : %.S
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c
%.o : %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -clib目录下makefile里面修改对应的内容
%.o:%.c
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
%.o:%.S
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<现在make后,lib下的文件却没有反应,现在在objs下加入一行代码:
objs := start.o led.o clock.o uart.o main.o
objs += lib/libc.a要生成uart.bin必须依赖$(objs)里的内容
现在执行后会报找不到相关规则,但是我们后面没有lib/libc.a相关的规则
所以我没需要再添加一个规则:
lib/libc.a:
cd lib; make; cd ..这个是进入子目录自己进行编译后调用。
现在clean发现子目录吓得没有清理
现在增加一个规则:
clean:
rm *.o *.elf *.bin *.dis mkx210 -f
cd lib; make clean; cd ..makefile:
CC = arm-linux-gcc
LD = arm-linux-ld
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump
AR = arm-linux-ar
INCDIR := $(shell pwd)
# C预处理器的flag,flag就是编译器可选的选项
CPPFLAGS := -nostdlib -nostdinc -I$(INCDIR)/include
# C编译器的flag
CFLAGS := -Wall -O2 -fno-builtin
#导出这些变量到全局,其实就是给子文件夹下面的Makefile使用
export CC LD OBJCOPY OBJDUMP AR CPPFLAGS CFLAGS
objs := start.o led.o clock.o uart.o main.o
objs += lib/libc.a
uart.bin: $(objs)
$(LD) -Tlink.lds -o uart.elf $^
$(OBJCOPY) -O binary uart.elf uart.bin
$(OBJDUMP) -D uart.elf > uart_elf.dis
gcc mkv210_image.c -o mkx210
./mkx210 uart.bin 210.bin
lib/libc.a:
cd lib; make; cd ..
%.o : %.S
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c
%.o : %.c
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c
clean:
rm *.o *.elf *.bin *.dis mkx210 -f
cd lib; make clean; cd ..
子目录下的makefile:
objs := div64.o lib1funcs.o ctype.o muldi3.o printf.o string.o vsprintf.o
libc.a: $(objs)
${AR} -r -o $@ $^
%.o:%.c
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
%.o:%.S
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
clean:
rm -f libc.a *.o
uart.c:
#define GPA0CON 0xE0200000
#define UCON0 0xE2900004
#define ULCON0 0xE2900000
#define UMCON0 0xE290000C
#define UFCON0 0xE2900008
#define UBRDIV0 0xE2900028
#define UDIVSLOT0 0xE290002C
#define UTRSTAT0 0xE2900010
#define UTXH0 0xE2900020
#define URXH0 0xE2900024
#define rGPA0CON (*(volatile unsigned int *)GPA0CON)
#define rUCON0 (*(volatile unsigned int *)UCON0)
#define rULCON0 (*(volatile unsigned int *)ULCON0)
#define rUMCON0 (*(volatile unsigned int *)UMCON0)
#define rUFCON0 (*(volatile unsigned int *)UFCON0)
#define rUBRDIV0 (*(volatile unsigned int *)UBRDIV0)
#define rUDIVSLOT0 (*(volatile unsigned int *)UDIVSLOT0)
#define rUTRSTAT0 (*(volatile unsigned int *)UTRSTAT0)
#define rUTXH0 (*(volatile unsigned int *)UTXH0)
#define rURXH0 (*(volatile unsigned int *)URXH0)
// 串口初始化程序
void uart_init(void)
{
// 初始化Tx Rx对应的GPIO引脚
rGPA0CON &= ~(0xff<<0); // 把寄存器的bit0~7全部清零
rGPA0CON |= 0x00000022; // 0b0010, Rx Tx
// 几个关键寄存器的设置
rULCON0 = 0x3;
rUCON0 = 0x5;
rUMCON0 = 0;
rUFCON0 = 0;
// 波特率设置 DIV_VAL = (PCLK / (bps x 16))-1
// PCLK_PSYS用66MHz算 余数0.8
//rUBRDIV0 = 34;
//rUDIVSLOT0 = 0xdfdd;
// PCLK_PSYS用66.7MHz算 余数0.18
// DIV_VAL = (66700000/(115200*16)-1) = 35.18
rUBRDIV0 = 35;
// (rUDIVSLOT中的1的个数)/16=上一步计算的余数=0.18
// (rUDIVSLOT中的1的个数 = 16*0.18= 2.88 = 3
rUDIVSLOT0 = 0x0888; // 3个1,查官方推荐表得到这个数字
}
// 串口发送程序,发送一个字节
void uart_putc(char c)
{
// 串口发送一个字符,其实就是把一个字节丢到发送缓冲区中去
// 因为串口控制器发送1个字节的速度远远低于CPU的速度,所以CPU发送1个字节前必须
// 确认串口控制器当前缓冲区是空的(意思就是串口已经发完了上一个字节)
// 如果缓冲区非空则位为0,此时应该循环,直到位为1
while (!(rUTRSTAT0 & (1<<1)));
rUTXH0 = c;
}
// 串口接收程序,轮询方式,接收一个字节
char uart_getc(void)
{
while (!(rUTRSTAT0 & (1<<0)));
return (rURXH0 & 0x0f);
}
main.c:
#include "stdio.h"
void uart_init(void);
int main(void)
{
uart_init();
int a = 12345678;
putc('a');
putc('b');
putc('c');
while (1)
{
printf("test for printf, a = %d.\n", a);
}
/*
while(1)
{
uart_putc('a');
delay();
}
*/
return 0;
}start.S
/*
* 文件名: start.S
* 作者: 朱老师
* 描述: 演示串口通信
*/
#define WTCON 0xE2700000
#define SVC_STACK 0xd0037d80
.global _start // 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
// 第1步:关看门狗(向WTCON的bit5写入0即可)
ldr r0, =WTCON
ldr r1, =0x0
str r1, [r0]
// 第2步:初始化时钟
bl clock_init
// 第3步:设置SVC栈
ldr sp, =SVC_STACK
// 第4步:开/关icache
mrc p15,0,r0,c1,c0,0; // 读出cp15的c1到r0中
//bic r0, r0, #(1<<12) // bit12 置0 关icache
orr r0, r0, #(1<<12) // bit12 置1 开icache
mcr p15,0,r0,c1,c0,0;
bl main
// 从这里之后就可以开始调用C程序了
//bl led_blink // led_blink是C语言实现的一个函数
// 汇编最后的这个死循环不能丢
b .
link.lds:
SECTIONS
{
. = 0xd0020010;
.text : {
start.o
* (.text)
}
.data : {
* (.data)
}
bss_start = .;
.bss : {
* (.bss)
}
bss_end = .;
}
uart stdio的移植
- 修改Makefile进行printf移植
- Makefile及gcc的库文件介绍
- 多文件夹裸机工程的结构解析
- 编译运行及测试
- 在移植后的uart stdio项目中添加link.lds链接脚本,指定连接地址到0xd0020010
gcc可变参数及va_arg介绍
- printf函数中首先使用了C语言的可变参数va_start/va_arg/va_end;
- 建议大家先去baidu“C语言可变参数”,然后按照别人的教程、博客实际写几个简单的变参的使用示例,先明白可变参数怎么工作,然后再来分析这里。
vsprintf函数详解
printf
vsprintf
vsnprintf
number
vsprintf函数的作用是按照我们的printf传进去的格式化标本,对变参进行处理,然后将之格式化后缓存在一个事先分配好的缓冲区中。
printf后半段调用putc函数将缓冲区中格式化好的字符串直接输出到标准输出。
串口实验烧录问题总结
usb下载的问题
(1)USB下载时在Win7 X64系统下,下载前面章节的小代码时没问题,下载串口通信的小代码时也没问题,下载uart stdio的移植就有问题了。有时候下载不动、有时候能下载但是不运行、有时候又正常下载运行。我已经试过下载其他的dnw或dnw驱动更新,都无法解决。
SD卡镜像烧录
(1)SD卡烧录镜像做裸机实验,在第四部分1.4.2节中有讲过。
(2)本次我们在Windows下烧录(linux下的烧录参考以前的)
(3)Windows下烧录镜像是使用九鼎提供的工具(X210光盘资料\A盘\tools\x210_Fusing_Tool.exe),注意运行时右键“以管理员身份运行”。
启动方式设置
(1)X210开发板的启动方式的选择,请参考1.2.11节。其实就是OM5的问题,OM5设置为VCC则从USB启动,OM5设置成GND,则从iNand/SD卡启动。
(2)开发板选择从iNand启动后,还要确保iNand中uboot是被擦除的。
(3)关于如何破坏uboot的问题,大家可以参考之前课程中讲的在linux/android系统中破坏uboot的方法。我之前讲过在uboot中破坏uboot的方法:movi write u-boot 0x30000000。很多同学反映擦除后错乱,进不了系统也从SD卡启动不了,只能通过USB刷机来解决。后来又分析,改为:mw 0x30000000 0x0 0x100000,然后再movi write u-boot 0x30000000
。但是反馈结果有人说可以了,有人说还是不行·······
(4)不管怎么擦除uboot,总之首先确保你的板子SD卡启动是成功的。怎么确保?先用SD卡烧录启动之前的LED闪烁的项目,确保看到现象就证明烧录SD卡方法和启动SD卡都成功了,再做本节课的实验。
链接脚本的影响
bin文件大于16KB怎么办?
通过USB下载最多也只能下载96KB大小的bin,如果bin大于96KB肯定SRAM放不下会出错。如果用SD卡启动,那么mkv210_image.c决定了bin文件最大不能超过16KB。
超过了怎么办?2种解法:
第一,在USB下载时,可以先下载一个x210_usb.bin,然后再将裸机程序连接到0x23E00000,然后再修改dnw中下载地址,将裸机代码下载到0x23E00000运行。(这时不需要重定位了)
第二,在SD卡启动时,将整个裸机工程分为2部分;第一部分大小16KB以内,第二部分放剩下的(放在SD卡的后面的某个扇区开始的位置,譬如放在第50个扇区开始的位置),然后在裸机代码中进行重定位(SD卡中重定位)。这个暂时没讲,以后如果有用到就讲。