分类: 嵌入式
2010-05-12 14:41:11
一.简介 本书面向由传统51单片机转向ARM嵌入式开发的硬件工程师、由硬件转嵌 入式软件开发的工程师、没有嵌入式开发经验的软件工程师。分9个部分: 1、开发环境建立 2、S3C2410功能部件介绍与实验(含实验代码) 3、bootloader vivi详细注释 4、linux移植 5、linux驱动 6、yaffs文件系统详解 7、调试工具 8、GUI开发简介 9、UC/OS移植 通过学习第二部分,即可了解基于ARM CPU的嵌入式开发所需要的外围器 件及其接口。对应的实验代码实现了对这些接口的操作,这可以让硬件工程师形 成一个嵌入式硬件开发的概念。这部分也可以当作S3C2410的数据手册来使用。 一个完整的嵌入式linux系统包含4部分内容:bootloader、parameters、kernel、 root file system。3、4、5、6部分详细介绍了这4部分的内容,这是linux底层软 件开发人员应该掌握的。通过学习这些章节,您可以详细了解到如何在一个裸板 上裁减、移植linux,如何构造自己的根文件系统,如何编写适合客户需求的驱 动程序——驱动程序这章将结合几个经典的驱动程序进行讲解。您还可以了解到 在用在nand flash上的非常流行的yaffs文件系统是如何工作的,本书将结合yaffs 代码详细介绍yaffs文件系统。 第7部分介绍了嵌入式linux开发中使用gdb进行调试的详细过程。 此文档目前完成了1、2、3部分,后面部分将陆续完成。希望能对各位在嵌 入式开发方面献上棉力。 欢迎来信指出文中的不足与错误,欢迎来信探讨技术问题。 Email :thisway.diy@163.com MSN :thisway.diy@163.com QQ :17653039 二.建立开发环境 (1)编译器arm-linux-gcc-3.4.1 下载地址: ftp://ftp.handhelds.org/projects/toolchain/arm-linux-gcc-3.4.1.tar.bz2 执行如下命令安装: bunzip2 arm-linux-gcc-3.4.1.tar.bz2 tar xvf arm-linux-gcc-3.4.1.tar -C / 生成的编译工具在目录/usr/local/arm/3.4.1/bin下,修改/etc/profile, 增加如下一行。这可以让我们直接运行arm-linux-gcc,而不必将其绝对路径都 写出来,不过这得重新启动后才生效: pathmunge /usr/local/arm/3.4.1/bin (2)Jflash-s3c2410:S3C2410芯片的JTAG工具 我们的第一个程序就是通过它下载到开发板上的nor flash或者nand flash 上去的。把它放到/usr/local/bin目录下。 下载地址:e ftp://ftp.mizi.com/pub/linuette/SDK/1.5/target/box/Jflash/Jflash- s3c2410 注意:步骤3您现在不必理会,可以等进行到“调试”部分时再回过头来看。 (3)安装gdb调试工具 下载地址: 执行如下命令安装: a.安装在主机上运行的arm-linux-gdb工具: tar xvzf gdb-6.3.tar.gz cd gdb6.3 ./configure --target=arm-linux make make install 此时,在/usr/local/bin中生成arm-linux-gdb等工具 b.继续上面的步骤,安装gdbserver。需要将此工具下载到开发板上运 行,这在后面会详细描述: cd gdbserver export CC=/usr/local/arm/3.4.1/bin/arm-linux-gcc ./configure arm-linux make 此时在当前目录中生成了gdbserver工具,当我们讲到如何调试时, 会把这个文件下载到开发板上去。 三.S3C2410基础实验 本章将逐一介绍S3C2410各功能模块,并结合简单的程序进行上机实验。您 不必将本章各节都看完,完全可以看了一、两节,得到一个大概的印象之后,就 开始下一章。本章可以当作手册来用。 注意:了解S3C2410各部件最好的参考资料是它的数据手册。本文不打算翻 译该手册,在进行必要的讲解后,进行实际实验——这才是本文的重点。 (1)实验一:LED_ON led_on.s只有7条指令,它只是简单地点亮发光二极管LED1。本实验的目 的是让您对开发流程有个基本概念。 实验步骤: a.把PC并口和开发板JTAG接口连起来、确保插上“BOOT SEL”跳线、上 电(呵呵,废话,如果以后实验步骤中未特别指出,则本步骤省略) b.进入LED_ON目录后,执行如下命令生成可执行文件led_on: make c.执行如下命令将led_on写入nand flash: i. Jflash-s3c2410 led_on /t=5 ii.当出现如下提示时,输入0并回车: iii.当出现如下提示时,输入0并回车: iv.当再次出现与步骤ii相同的提示时,输入2并回车 d.按开发板上reset键后可看见LED1被点亮了 实验步骤总地来说分3类:编写源程序、编译/连接程序、烧写代码。 先看看源程序led_on.s: 1 .text 2 .global _start 3 _start: 4 LDR R0,=0x56000010 @R0设为GPBCON寄存器。此寄存器 @用于选择端口B各引脚的功能: @是输出、是输入、还是其他 5 MOV R1,#0x00004000 6 STR R1,[R0] @设置GPB7为输出口 7 LDR R0,=0x56000014 @R0设为GPBDAT寄存器。此寄存器 @用于读/写端口B各引脚的数据 8 MOV R1,#0x00000000 @此值改为0x00000080, @可让LED1熄灭 9 STR R1,[R0] @GPB7输出0,LED1点亮 10 MAIN_LOOP: 11 B MAIN_LOOP 对于程序中用到的寄存器GPBCON、GPBDAT,我稍作描述,具体寄存器的操作 可看实验三:I/O PORTS。GPBCON用于选择PORT B的11根引脚的功能:输出、输 入还是其他特殊功能。每根引脚用2位来设置:00表示输入、01表示输出、10表 示特殊功能、11保留不用。LED1-3的引脚是GPB7-GPB10,使用GPBCON中位[12:13]、 [13:14]、[15:16]、[17:18]来进行功能设置。GPBDAT用来读/写引脚:GPB0对应 位0、GPB1对应位1,诸如此类。当引脚设为输出时,写入0或1可使相应引脚输出 低电平或高电平。 程序很简单,第4、5、6行3条指令用于将LED1对应的引脚设成输出引脚; 第7、8、9行3条指令让这条引脚输出0;第11行指令是个死循环。 实验步骤b中,指令“make”的作用就是编译、连接led_on.s源程序。Makefile 的内容如下: 1 led_on:led_on.s 2 arm-linux-gcc -g -c -o led_on.o led_on.s 3 arm-linux-ld -Ttext 0x0000000 -g led_on.o -o led_on_tmp.o 4 arm-linux-objcopy -O binary -S led_on_tmp.o led_on 5 clean: 6 rm -f led_on 7 rm -f led_on.o 8 rm -f led_on_tmp.o make指令比较第1行中文件led_on和文件led_on.s的时间,如果led_on 的时间比led_on.s的时间旧(led_on未生成时,此条件默认成立),则执行第2、 3、4行的指令更新led_on。您也可以不用指令make,而直接一条一条地执行2、 3、4行的指令——但是这样多累啊。第2行的指令是预编译,第3行是连接, 第4行是把ELF格式的可执行文件led_on_tmp.o转换成二进制格式文件led_on。 执行“make clean”时强制执行6、7、8行的删除命令。 注意:Makefile文件中相应的命令行前一定有一个制表符(TAB) 汇编语言可读性太差,现在请开始实验二,我用C语言来实现了同样的功能, 而以后的实验,我也尽可能用C语言实现。 (2)实验二:LED_ON_C C语言程序执行的第一条指令,并不在main函数中。当我们生成一个C程序 的可执行文件时,编译器总是在我们的代码前加一段固定的代码——crt0.o,它 是编译器自带的一个文件。此段代码设置C程序的堆栈等,然后调用main函数。 很可惜,在我们的裸板上,这段代码无法执行,所以我们得自己写一个。这段代 码很简单,只有3条指令。 crt0.s代码: 1 .text 2 .global _start 3 _start: 4 ldr sp, =1024*4 @设置堆栈,注意:不能大于4k @nand flash中的代码在复位后会 @移到内部ram中,它只有4k 5 bl main @调用C程序中的main函数 6 halt_loop: 7 b halt_loop 现在,我们可以很容易写出控制LED的程序了,led_on_c.c代码如下: 1 #define GPBCON (*(volatile unsigned long *)0x56000010) 2 #define GPBDAT (*(volatile unsigned long *)0x56000014) 3 int main() 4 { 5 GPBCON = 0x00004000; //设置GPB7为输出口 6 GPBDAT = 0x00000000; //令GPB7输出0 7 return 0; 8 } 最后,我们来看看Makefile: 1 led_on_c : crt0.s led_on_c.c 2 arm-linux-gcc -g -c -o crt0.o crt0.s 3 arm-linux-gcc -g -c -o led_on_c.o led_on_c.c 4 arm-linux-ld -Ttext 0x0000000 -g crt0.o led_on_c.o -o led_on_c_tmp.o 5 arm-linux-objcopy -O binary -S led_on_c_tmp.o led_on_c 6 clean: 7 rm -f led_on_c 8 rm -f led_on_c.o 9 rm -f led_on_c_tmp.o 10 rm -f crt0.o 第2、3行分别对源程序crt0.s、led_on_c.c进行预编译,第4行将预编译 得到的结果连接起来,第5行把连接得到的ELF格式可执行文件led_on_c_tmp.o 转换成二进制格式文件led_on_c。 好了,可以开始上机实验了: 实验步骤: a.进入LED_ON_C目录后,执行如下命令生成可执行文件led_on_c: make b.执行如下命令将led_on_c写入nand flash: i. Jflash-s3c2410 led_on_c /t=5 ii.当出现如下提示时,输入0并回车: K9S1208 NAND Flash JTAG Programmer Ver 0.0 0:K9S1208 Program 1:K9S1208 Pr BlkPage 2: Exit Select the function to test : iii.当出现如下提示时,输入0并回车: Input target block number: iv.当出现与步骤ii相同的提示时,输入2并回车 c.按开发板上reset键后可看见LED1被点亮了 目录LEDS中的程序是使用4个LED从0到15轮流计数,您可以试试: a.进入目录后make b.Jflash-s3c2410 leds /t=5 c.reset运行 另外,如果您有兴趣,可以使用如下命令看看二进制可执行文件的反汇编码: arm-linux-objdump -D -b binary -m arm xxxxx(二进制可执行文件名) 注意:本文的所有程序均在SOURCE目录中,各程序所在目录均为大写,其 可执行文件名为相应目录名的小写,比如LEDS目录下的可执行文件为leds。以 后不再赘述如何烧写程序:直接运行Jflash-s3c2410即可看到提示。 (3)实验三:I/O PORTS 请打开S3C2410数据手册第9章IO/ PORTS,I/O PORTS含GPA、GPB、..、 GPH八个端口。它们的寄存器是相似的:GPxCON用于选择引脚功能,GPxDAT用 于读/写引脚数据,GPxUP用于确定是否使用内部上拉电阻(x为A、B、..、H, 没有GPAUP寄存器)。 1、PORT A与PORT B-H在功能选择方面有所不同,GPACON中每一位对应一根引 脚(共23根引脚)。当某位设为0时,相应引脚为输出引脚,此时我们可以在 GPADAT中相应位写入0或1让此引脚输出低电平或高电平;当某位设为1时, 相应引脚为地址线或用于地址控制,此时GPADAT无用。一般而言GPACON通 常设为全1,以便访问外部存储器件。PORT A我们暂时不必理会。 2、PORT B-H在寄存器操作方面完全相同。GPxCON中每两位控制一根引脚:00 表示输入、01表示输出、10表示特殊功能、11保留不用。GPxDAT用于读/ 写引脚:当引脚设为输入时,读此寄存器可知相应引脚的状态是高是低;当 引脚设为输出时,写此寄存器相应位可令此引脚输出低电平或高电平。GpxUP: 某位为0时,相应引脚无内部上拉;为1时,相应引脚使用内部上拉。 其他寄存器的操作在后续相关章节使用到时再描述;PORT A-H中引脚的特殊 功能比如串口引脚、中断引脚等,也在做相关实验时再描述。 目录KEY_LED中的程序功能为:当K1-K4中某个按键按下时,LED1-LED4中 相应LED点亮。 key_led.c代码: 1 #define GPBCON (*(volatile unsigned long *)0x56000010) 2 #define GPBDAT (*(volatile unsigned long *)0x56000014) 3 #define GPFCON (*(volatile unsigned long *)0x56000050) 4 #define GPFDAT (*(volatile unsigned long *)0x56000054) /* LED1-4对应GPB7-10 */ 5 #define GPB7_out (1<<(7*2)) 6 #define GPB8_out (1<<(8*2)) 7 #define GPB9_out (1<<(9*2)) 8 #define GPB10_out (1<<(10*2)) /* K1-K3对应GPF1-3 K4对应GPF7 */ 9 #define GPF1_in ~(3<<(1*2)) 10 #define GPF2_in ~(3<<(2*2)) 11 #define GPF3_in ~(3<<(3*2)) 12 #define GPF7_in ~(3<<(7*2)) 13 int main() { //LED1-LED4对应的4根引脚设为输出 14 GPBCON =GPB7_out | GPB8_out | GPB9_out | GPB10_out ; //K1-K4对应的4根引脚设为输入 15 GPFCON &= GPF1_in & GPF2_in & GPF3_in & GPF7_in ; 16 while(1){ //若Kn为0(表示按下),则令LEDn为0(表示点亮) 17 GPBDAT = ((GPFDAT & 0x0e)<<6) | ((GPFDAT & 0x80)<<3); } 18 return 0; } 实验步骤: a.进入目录KEY_LED,运行make命令生成key_led b.烧写key_led (4)实验四:arm-linux-ld 在开始后续实验之前,我们得了解一下arm-linux-ld连接命令的使用。在 上述实验中,我们一直使用类似如下的命令进行连接: arm-linux-ld -Ttext 0x00000000 crt0.o led_on_c.o -o led_on_c_tmp.o 我们看看它是什么意思:-o选项设置输出文件的名字为led_on_c_tmp.o; “--Ttext 0x00000000”设置代码段的起始地址为0x00000000;这条指令的作用就 是将crt0.o和led_on_c.o连接成led_on_c_mp.o可执行文件,此可执行文件的代 码段起始地址为0x00000000。 我们感兴趣的就是“—Ttext”选项!进入LINK目录,link.s代码如下: 1 .text 2 .global _start 3 _start: 4 b step1 5 step1: 6 ldr pc, =step2 7 step2: 8 b step2 Makefile如下: 1 link:link.s 2 arm-linux-gcc -c -o link.o link.s 3 arm-linux-ld -Ttext 0x00000000 link.o -o link_tmp.o 4 # arm-linux-ld -Ttext 0x30000000 link.o -o link_tmp.o 5 arm-linux-objcopy -O binary -S link_tmp.o link 6 arm-linux-objdump -D -b binary -m arm link >ttt.s 7 # arm-linux-objdump -D -b binary -m arm link >ttt2.s 8 clean: 9 rm -f link 10 rm -f link.o 11 rm -f link_tmp.o 实验步骤: 1.进入目录LINK,运行make生成arm-linux-ld选项为“-Ttext 0x00000000” 的反汇编码ttt.s 2.make clean 3.修改Makefile:将第4、7行的“#”去掉,在第3、6行前加上“#” 4.运行make生成arm-linux-ld选项为“-Ttext 0x30000000”的反汇编码ttt2.s link.s程序中用到两种跳转方法:b跳转指令、直接向pc寄存器赋值。我们先 把在不同“—Ttext”选项下,生成的可执行文件的反汇编码列出来,再详细分析这 两种不同指令带来的差异。 ttt.s: ttt2.s 0: eaffffff b 0x4 0: eaffffff b 0x4 4: e59ff000 ldr pc, [pc, #0] ; 0xc 4: e59ff000 ldr pc, [pc, #0] ; 0xc 8: eafffffe b 0x8 8: eafffffe b 0x8 c: 00000008 andeq r0, r0, r8 c: 30000008 tsteq r0, #8 ; 0x8 先看看b跳转指令:它是个相对跳转指令,其机器码格式如下: [31:28]位是条件码;[27:24]位为“1010”时,表示B跳转指令,为“1011”时,表示BL 跳转指令;[23:0]表示偏移地址。使用B或BL跳转时,下一条指令的地址是这样计算的:将指 令中24位带符号的补码立即数扩展为32(扩展其符号位);将此32位数左移两位;将得到的值 加到pc寄存器中,即得到跳转的目标地址。我们看看第一条指令“b step1”的机器码eaffffff: 1. 24位带符号的补码为0xffffff,将它扩展为32得到:0xffffffff 2.将此32位数左移两位得到:0xfffffffc,其值就是-4 3.pc的值是当前指令的下两条指令的地址,加上步骤2得到的-4,这恰好是第 二条指令step1的地址 各位不要被被反汇编代码中的“b 0x4”给迷惑了,它可不是说跳到绝对地址0x4 处执行,绝对地址得像上述3个步骤那样计算。您可以看到b跳转指令是依赖于当 前pc寄存器的值的,这个特性使得使用b指令的程序不依赖于代码存储的位置—— 即不管我们连接命令中“--Ttext”为何,都可正确运行。 再看看第二条指令ldr pc, =step2:从反汇编码“ldr pc, [pc, #0]”可以看出, 这条指令从内存中某个位置读出数据,并赋给pc寄存器。这个位置的地址是当前 pc寄存器的值加上偏移值0,其中存放的值依赖于连接命令中的“--Ttext”选项。 执行这条指令后,对于ttt.s,pc=0x00000008;对于ttt2.s, pc=0x30000008。于 是执行第三条指令“b step2”时,它的绝对地址就不同了:对于ttt.s,绝对地址 为0x00000008;对于ttt.s,绝对地址为0x30000008。 ttt2.s上电后存放的位置也是0,但是它连接的地址是0x30000000。我们以后 会经常用到“存储地址和连接地址不同”(术语上称为加载时域和运行时域)的特性: 大多机器上电时是从地址0开始运行的,但是从地址0运行程序在性能方面总有很 多限制,所以一般在开始的时候,使用与位置无关的指令将程序本身复制到它的连 接地址处,然后使用向pc寄存器赋值的方法跳到连接地址开始的内存上去执行剩下 的代码。在实验5、6中,我们将会作进一步介绍。 arm-linux-ld命令中选项“-Ttext”也可以使用选项“-Tfilexxx”来代替,在 文件filexxx中,我们可以写出更复杂的参数来使用arm-linux-ld命令——在实验 6中,我们就是使用这种方法来指定连接参数的。 |