- 正如编写第一个程序所打印“hello world”那样,对TQ2440开发板的初次使用也是从最简单的部分入手,点亮led灯无疑是最简单的,起码只要设置几个寄存器就好,比起打印容易得多了,后续会讲到串口部分再来实现我们这句金典的对白~~~~
- 板子共有4个led灯,如果能够成功点亮其中一个,那么其他几个将不是问题,故而我们的目标是先点亮第一个先,在此我们必须理清几个问题:
a. led的引脚连接的相关寄存器
b. 编写的语言
c. 用什么来编译链接成可执行文件
d. 怎么download到板子去,烧入到哪里 nor flash 还是 nand flash
现在我们慢慢来处理:
a. 查看tq2440原理图发现这4个led分别连接GPB5~8,由于一端连接VCC, 所以引脚设置为输出且为0(低电平)是灯亮,1(高电平)为灭,相关的寄存器有GPBCON(0x56000010) , GPBDAT(0x56000014), 其余可以不用考虑。
b. 本次实验同时实现汇编和C,其中汇编主要实现一个led灯亮,在于了解汇编基本flow,实现叫简单,C可以稍微“复杂”点,比如多个led,跑流水灯等,更灵活。
c. 由于我们的开发环境是在PC机上的,想最终到板子上的话必须借助交叉编译工具,所谓的交叉编译工具其实就是将源码编译成适合特定架构的处理器识别的机器语言,像PC机就是X86架构的,而板子是ARM架构的,这里采用arm-linux-3.4.5 ,至于如何安装交叉编译工具链可以自行上网查阅,记得安装后要添加到PATH这个环境变量,这样你用到相关编译命令才能搜索得到,以我为例,我是添加到我的home 目录下有个.profile文件的
vim .profile
export PATH=$PATH:/opt/EmbedSky/crosstools_3.4.5_softfloat/gcc-3.4.5-glibc-2.3.6/arm-linux/bin
可以看一下这个路径bin下有没有arm-linux-gcc arm-linux-ld 命令之类的,如果是gcc ld 那就是路径搞错了哦,之前就是添加这个路径 /opt/EmbedSky/crosstools_3.4.5_softfloat/gcc-3.4.5-glibc-2.3.6/arm-linux/arm-linux/bin出了乌龙……………..
保存退出后记得
source .profile
使其生效,为确保是否生效可以敲入相关的命令看能否执行,比如:
arm-linux-gcc -v
这个是查看交叉编译工具链的version命令,如果没有添加到PATH的话就会报错说找不到该命令,否则会打印一堆信息,在最后会看到这行信息:
gcc version 3.4.5
d. 由于天嵌的bootloader具有烧入功能,所以将天嵌的bootloader烧入到nor flash,然后利用该bootloader将我们的code烧入到nand flash,后续的示例都会采用该方法将code烧入到nand flash,这是最省事的做法,如果要“任性”通过外部工具将code烧入到nand flash读者可以自己查阅。刚买的板子nor flash已经烧入好天嵌的bootloader,将拨码开关拨到nor 选项再启动电源就会在串口看到download界面(前提是串口配置正确),如果没有可以通过工具将bootloader烧入到nor flash。至于如何烧入bootloader到nor flash 请查阅添加附带关光盘文档。
PS:由于我们的code是在ubuntu下编写的,而烧入是通过win7环境,我是采用samba实现win7访问ubuntu的,具体的做法就是在home目录建一个目录“samba”,然后所有的相关code在这个目录实现,同时开启samba server(请自行上网查看如何搭建samba),然后通过win7访问并且映射到本地一个盘符,就如同访问自己硬盘一样简单。同时操作ubuntu也并不是直接在Ubuntu界面操作,而是通过SecureCRT访问。 - 现在讲解具体代码编写,先从汇编入手
vim led.S
代码如下:
1 .text
2 .global _start
3 _start:
4 ldr r0, =0x56000010
5 ldr r1, =0x400
6 str r1, [r0]
7
8 ldr r0, =0x56000014
9 ldr r1, =0x0
10 str r1, [r0]
11
12 loop:
13 b loop
由于我是显示行号的,所以如果你直接粘贴我的code记得删除行号哦~,另外Linux Ubuntu 默认是安装vi而非vim,so 自己上网查怎么装, 或者查看我的其他博文看有没有写,另一件事即是makefile,基本写code离不开makefiel,读者可自行上网查阅,或者看我的博文是否有涉及,code如下:
1 led.bin : led.S
2 arm-linux-gcc -c led.S -o led.o
3 arm-linux-ld -Ttext 0x0 led.o -o led.elf
4 arm-linux-objcopy -O binary -S led.elf led.bin
5 arm-linux-objdump -D -S led.elf > led.dis
6
7 clean:
8 rm -f *.o led.elf led.bin led.dis
注意在arm-linux-gcc -c led.S -o led.o要加 -c选项 编译不链接,否则会自动去找crt1.o启动文件,可能会出现如下error提示:
/tmp/ccaxBn3C.o(.text+0x0): In function `_start':
: multiple definition of `_start'
/opt/EmbedSky/crosstools_3.4.5_softfloat/gcc-3.4.5-glibc-2.3.6/arm-linux/lib/gcc/arm-linux/3.4.5/../../../../arm-linux/lib/crt1.o(.text+0x0): first defined here
/opt/EmbedSky/crosstools_3.4.5_softfloat/gcc-3.4.5-glibc-2.3.6/arm-linux/lib/gcc/arm-linux/3.4.5/../../../../arm-linux/lib/crt1.o(.text+0x30): In function `_start':
: undefined reference to `main'
collect2: ld returned 1 exit status
make: *** [led.bin] 错误 1
他的作用就是初始化系统然后跳到我们用户编写的main()函数,其实一个可执行文件需要三部分,一个是启动文件crt1.o crti.o crtend.o crtn.o 等,而是相应的库,最后就是用户编写的code,这很好理解,启动文件替用户设置好会关开门狗,设置好栈,最后跳转到main()函数,也就是用户编写的main()函数,而库就是用户经常include那一些头文件,里面提供了标准的函数接口,不用用户一个一个实现,按照不同用途又再细分什么什么库了…………………………….以上是教简单的makefile,后面会写的更加有质量~
make
接着会生成相应的elf bin 文件。
4. 运行DNW或者天嵌提供的串口终端,我用的是TQBoardDNW(EmbedSkyDownLoadToolV2.1.0)将拨码开关拨到nor flash那边上电,会提示“Boot for Nor Flash Main Menu” 选择数字1将code烧入到nand flash 接着就会提示打开要烧入的文件led.bin,由于我是通过samba访问Ubuntu的并且将其映射到Win7本地一个盘符,所以就像打开本地硬盘的某个分区一样简单,烧入好后关电,将拨码开关拨到nand flash启动,上电,接着就会看到板子的第一个led亮了,其他3个是暗的~~~~当然,虽然我们是选择从nand启动,但是其实并非从nand flash 0处执行code,因为CPU无法直接访问nand flash,他是将nand flash的前4kcode copy到IC内置的4k SRAM,然后从这个cpu能够识别的地址0(也就是SRAM)处开始跑code,所以我们的code不能大于4k,若大于4k得将其copy到更大的sram处,也就是外设SDRAM,后面的uboot会讲到这点。
5. 如果上述都能顺利执行的话,就可以开始写C code了,并且以后我们也尽量用C写,正如前面讲的,初始code必须用汇编写,然后为C做好相应的准备(like 设置栈),这个启动文件跟标准C的crtxxx.o作用一样,所以我们自己实现:
vim crt0.S
1 .text
2 .global _start
3 _start:
4 ldr r0, =0x53000000 @ close watch dog,or it will reboot again and again
5 ldr r1, =0x0
6 str r1, [r0]
7
8 ldr sp, =4096 @ set stack for call C function
9
10 bl main @ call C main,that's why the user code entry is main,and it will return
11 loop:
12 b loop @ call but not return
vim led_main.c
1 #define GPBCON (*(volatile unsigned long *)0x56000010)
2 #define GPBDAT (*(volatile unsigned long *)0x56000014)
3
4 int main()
5 {
6 GPBCON = 0x400;
7 GPBDAT = 0x0;
8
9 return 0;
10 }
11
重复上面的烧入flow,现象是一样的,都是点亮第一盏灯,其它3盏是暗的,C code写得不是很好,阅读性差,不易拓展,可以改成如下,同时实现第一 三亮:
1 #define GPBCON (*(volatile unsigned long *)0x56000010)
2 #define GPBDAT (*(volatile unsigned long *)0x56000014)
3
4 int main()
5 {
6 GPBCON |= ((1<<10) | (1<<16)); // set led1 led4 output
7 GPBDAT &= ~((1<<5) | (1<<8)); // enable led1 led4
8
9 return 0;
10 }
初学者可能对这个((volatile unsigned long )0x56000010)有疑惑,首先这个0x56000010是什么,平常我们当作数值,但是这个指的是寄存器的地址,所以我们要声明为指针类型的,由于cpu是32bit,每个寄存器都是32bit,所以此地址的连续4个字节都是用于这个寄存器的 所以声明为long型 (unsigned long )0x56000010,volatile是告诉CPU直接不要优化(有时即使编译不使能优化-O -O2选项也会优化),也不要保留到cach,虽然我们的code没有优化也没有用到cach,但以后会用,知道寄存器是要直接取值的而不能保留在cach就好,因为寄存器保留的值是经常变的,不能放到cach,最外围的‘’就是取这个地址(寄存器)的值了,所以才有了后面的赋值GPBCON |= ((1<<10) | (1<<16)); 如果没有‘*’,那赋值就是这样了 *GPBCON |= ((1<<10) | (1<<16));