首先明确目标:点亮LED
怎样才能点亮LED?
查看原理图,LED灯连接在CPU管脚上,只要控制该管脚输出高低电平就可以控制LED通断。
怎么控制管脚输出高低电平?
两个步骤配置相应寄存器:
1.配置管脚功能(输入/输出/其他功能);
2.设置输出值0/1,即高/低;
怎么布置代码文件及编译?
有两种方式布置代码文件:
1.直接用汇编代码配置CPU寄存器;
2.汇编+C代码;
为什么不能只用C代码实现?
C语言程序是以main函数为入口,main函数并没有什么特别之处,也要被别人调用,并执行返回。在没有启动文件的情况下,C程序自己不能被执行。因此不能只用C代码实现点灯的功能。
下面我们来讨论使用汇编+C代码实现的方式实现目标过程中遇到的问题:
C程序中main函数被谁返回?返回到哪里?
我们平时在有操作系统的主机上开发程序时,系统中有一些库或启动文件,加上我们的应用程序。那么我们的应程序中的main函数就由启动文件或库来调用。main函数执行完,再回到系统库函数中,由其来做一些清理工作。
在裸机程序中,类似的库或启动文件就没有操作系统提供了,都要开发者自己写。这里的库或启动文件也可称作软件相关的初始化。这一部分就是在汇编代码中实现的。除此之外,我们的汇编代码中还应包括硬件初始化。
软件初始化要做的哪些事情?
1设置栈
2设置main的返回地址;
3调用main;
4清理工作。
硬件初始化做哪些事情?
1.关闭看门狗;
2.初始化时钟;
3.sdram初始化;
下面我们针对以上提到的汇编代码中软硬件初始化工作来分析,为什么要做这些工作:
在大型软件的编写过程中,为了提高代码的可读性和易移植性,我们一般选用C语言进行代码编写。C语言中,函数的调用是非常普遍的。函数调用、跳转过程中需要传递的参数和函数返回时要返回的值需要放在一段内存中,这个过程叫数据压栈。因此在调用main函数之前要指定栈,即设置栈。
什么是设置栈?即把栈指针sp指向某块内存。
既然要把栈指针指向某块内存,就要涉及到内存介质初始化的问题。上面设置的栈的内存正好是片内的sram上,不需要单独做初始化。如果该内存指向片外的sdram,就要先初始化sdram,以便读写。sdram初始化就是属于硬件初始化。
1.为什么要关看门狗?
什么是看门狗?
对于看门狗的初步认识,在一篇短文中有介绍 《 认识看门狗 》 http://blog.csdn.net/eric41050808/article/details/17336507 。
简单的说,看门狗就是为防止系统长时间挂起或跑飞而设置的一个开关。其实质是可以定时重启系统的东西。如果系统在看门狗的定时器定时耗尽之前把它清零,则系统不会重启,我们也认为此事系统没有出现长时间挂起或跑飞的问题。反之,则看门狗会把系统重启。
为什么要关闭呢?
在我们的程序中,由于不是很复杂的系统,没有独立的部分用来定时清零看门狗的定时器,所以索性把看门狗关闭,否则系统会不断重启,影响我们进行实验。
可不可以不关闭?
想不关闭看门狗也可以,不过按照上面介绍的,我们必须在其定时器耗尽之前将其清零。但是在我们一段只用汇编语言完成的LED点亮代码中,确实没有关闭看门狗,也没有出现什么异常现象,为什么?由于点亮LED操作简单,汇编只有对两个寄存器的操作,程序运行时间很短,即使我们没有关闭看门狗,系统发生重启,对于我们点亮LED也毫无影响,因为CPU重启及代码运行很快,人眼分辨不出LED闪烁,说以没有影响实验。该实验代码为方便才没做关闭看门狗的工作。
2.初始化时钟:为什么初始化时钟?
系统刚启动时,由外部晶振提供系统时钟,晶振频率很低,有些开发板上只有几兆几十兆。这个频率对CPU来说太低。目前市面上的CPU工作频率一般在1.5G左右,外部晶振频率不能直接使用,需要初始化设置后达到高频率状。除CPU频率外,系统一般还会有sdram频率(HCLK)和串口定时器(PCLK)等,这些都需要在初始化时钟时进行设置。这就是初始化时钟部分的工作。具体系统时钟工作另行说明。
3.初始化sdram(为什么初始化sdram?sdram是外部扩展的,存储空间较大,速度较慢,如果程序放在sdram中)另行说明。
以上是硬件初始化说明。
软硬件初始化合起来就是启动文件。本实验中不关心速度的问题,因此不做时钟初始化;而且代码在片内sram中直接启动,因此不适用sdram,故不用初始化。因此上面硬件初始化中,只需做关闭看门狗的工作。
在点亮led的实验中,硬件初始化只需做关闭看门狗的工作。原因是,点亮led时钟慢点没关系,不用初始化时钟;实验平台用的是片内sram,不用初始化sdram。
下面是采用汇编+C语言的方式:
C代码
Makefile:
怎样才能点亮LED?
查看原理图,LED灯连接在CPU管脚上,只要控制该管脚输出高低电平就可以控制LED通断。
怎么控制管脚输出高低电平?
两个步骤配置相应寄存器:
1.配置管脚功能(输入/输出/其他功能);
2.设置输出值0/1,即高/低;
怎么布置代码文件及编译?
有两种方式布置代码文件:
1.直接用汇编代码配置CPU寄存器;
2.汇编+C代码;
为什么不能只用C代码实现?
C语言程序是以main函数为入口,main函数并没有什么特别之处,也要被别人调用,并执行返回。在没有启动文件的情况下,C程序自己不能被执行。因此不能只用C代码实现点灯的功能。
下面我们来讨论使用汇编+C代码实现的方式实现目标过程中遇到的问题:
C程序中main函数被谁返回?返回到哪里?
我们平时在有操作系统的主机上开发程序时,系统中有一些库或启动文件,加上我们的应用程序。那么我们的应程序中的main函数就由启动文件或库来调用。main函数执行完,再回到系统库函数中,由其来做一些清理工作。
在裸机程序中,类似的库或启动文件就没有操作系统提供了,都要开发者自己写。这里的库或启动文件也可称作软件相关的初始化。这一部分就是在汇编代码中实现的。除此之外,我们的汇编代码中还应包括硬件初始化。
软件初始化要做的哪些事情?
1设置栈
2设置main的返回地址;
3调用main;
4清理工作。
硬件初始化做哪些事情?
1.关闭看门狗;
2.初始化时钟;
3.sdram初始化;
下面我们针对以上提到的汇编代码中软硬件初始化工作来分析,为什么要做这些工作:
在大型软件的编写过程中,为了提高代码的可读性和易移植性,我们一般选用C语言进行代码编写。C语言中,函数的调用是非常普遍的。函数调用、跳转过程中需要传递的参数和函数返回时要返回的值需要放在一段内存中,这个过程叫数据压栈。因此在调用main函数之前要指定栈,即设置栈。
什么是设置栈?即把栈指针sp指向某块内存。
既然要把栈指针指向某块内存,就要涉及到内存介质初始化的问题。上面设置的栈的内存正好是片内的sram上,不需要单独做初始化。如果该内存指向片外的sdram,就要先初始化sdram,以便读写。sdram初始化就是属于硬件初始化。
1.为什么要关看门狗?
什么是看门狗?
对于看门狗的初步认识,在一篇短文中有介绍 《 认识看门狗 》 http://blog.csdn.net/eric41050808/article/details/17336507 。
简单的说,看门狗就是为防止系统长时间挂起或跑飞而设置的一个开关。其实质是可以定时重启系统的东西。如果系统在看门狗的定时器定时耗尽之前把它清零,则系统不会重启,我们也认为此事系统没有出现长时间挂起或跑飞的问题。反之,则看门狗会把系统重启。
为什么要关闭呢?
在我们的程序中,由于不是很复杂的系统,没有独立的部分用来定时清零看门狗的定时器,所以索性把看门狗关闭,否则系统会不断重启,影响我们进行实验。
可不可以不关闭?
想不关闭看门狗也可以,不过按照上面介绍的,我们必须在其定时器耗尽之前将其清零。但是在我们一段只用汇编语言完成的LED点亮代码中,确实没有关闭看门狗,也没有出现什么异常现象,为什么?由于点亮LED操作简单,汇编只有对两个寄存器的操作,程序运行时间很短,即使我们没有关闭看门狗,系统发生重启,对于我们点亮LED也毫无影响,因为CPU重启及代码运行很快,人眼分辨不出LED闪烁,说以没有影响实验。该实验代码为方便才没做关闭看门狗的工作。
2.初始化时钟:为什么初始化时钟?
系统刚启动时,由外部晶振提供系统时钟,晶振频率很低,有些开发板上只有几兆几十兆。这个频率对CPU来说太低。目前市面上的CPU工作频率一般在1.5G左右,外部晶振频率不能直接使用,需要初始化设置后达到高频率状。除CPU频率外,系统一般还会有sdram频率(HCLK)和串口定时器(PCLK)等,这些都需要在初始化时钟时进行设置。这就是初始化时钟部分的工作。具体系统时钟工作另行说明。
3.初始化sdram(为什么初始化sdram?sdram是外部扩展的,存储空间较大,速度较慢,如果程序放在sdram中)另行说明。
以上是硬件初始化说明。
软硬件初始化合起来就是启动文件。本实验中不关心速度的问题,因此不做时钟初始化;而且代码在片内sram中直接启动,因此不适用sdram,故不用初始化。因此上面硬件初始化中,只需做关闭看门狗的工作。
在点亮led的实验中,硬件初始化只需做关闭看门狗的工作。原因是,点亮led时钟慢点没关系,不用初始化时钟;实验平台用的是片内sram,不用初始化sdram。
为什么设置栈?从反汇编从看,main一调用就能看到{fp, ip, lr, pc}几个寄存器被保存到sp中,调用返回是{fp,sp,pc}又被恢复,因此一定好设置栈,否则出错。
以下是在mini2440的开发板上点灯的示例代码,源码来自韦东山老师的学习资料,第一段是只用汇编语言实现点亮LED,第二段是对应makefile。代码本身没有可参考性,主要看注释,以便学习。
汇编代码:
- @******************************************************************************
- @ File:led_on.S
- @ 功能:LED点灯程序,点亮LED1
- @******************************************************************************
- .text
- .global _start
- _start:
- LDR R0,=0x56000010 @ R0设为GPBCON寄存器。此寄存器
- @ 用于选择端口B各引脚的功能:
- @ 是输出、是输入、还是其他
- MOV R1,#0x00000400
- STR R1,[R0] @ 设置GPB5为输出口, 位[10:9]=0b01
- LDR R0,=0x56000014 @ R0设为GPBDAT寄存器。此寄存器
- @ 用于读/写端口B各引脚的数据
- MOV R1,#0x00000000 @ 此值改为0x00000020,
- @ 可让LED1熄灭
- STR R1,[R0] @ GPB5输出0,LED1点亮
- MAIN_LOOP:
- B MAIN_LOOP
Makefile文件:
- led_on.bin : led_on.S
- arm-linux-gcc -g -c -o led_on.o led_on.S
- arm-linux-ld -Ttext 0x0000000 -g led_on.o -o led_on_elf
- arm-linux-objcopy -O binary -S led_on_elf led_on.bin
- clean:
- rm -f led_on.bin led_on_elf *.o
- #第一条命令中-g表示加入一些调试信息;-c编译不连接;-o表示输出文件名称
- #预处理->编译.c2.S->汇编.S2.o->链接(多个.o合成1个可执行文件)
- #第二条链接命令, -Ttext 0x0000000表示代码段的起始地址为0;
- #第三条转成二进制文件,-O binary表示以二进制进行复制,注意此处是大写O
- #为什么把代码段地址指定为0?原因如下:
- #2440有两种启动方式,一种是nand启动,二是nor启动
- #2440内部有4ksram,外接nand和sdram
- #以nand flash启动时,1.会把nand前4k拷贝到sram中,2.CPU从0地址取址执行,这两步是硬件自动完成的,不管这4k放什么东西,放不放东西,都会如此!
- #如果外接nor flash,nand不会像片内内存那样直接执行,必须拷贝到片内sram开始执行,0x00000000地址在sram起始位置
- #一旦选择从nor启动,而nor这种硬件,可以像片内内存那样读数据,但不能像内存那样写数据,因此可以直接从nor启动,即此时地址空间的0地址是在nor的起始位置
- #CPU
启动文件的汇编代码
- @******************************************************************************
- @ File:crt0.S
- @ 功能:通过它转入C程序
- @******************************************************************************
- .text
- .global _start
- _start:
- ldr r0, =0x56000010 @ WATCHDOG寄存器地址
- mov r1, #0x0
- str r1, [r0] @ 写入0,禁止WATCHDOG,否则CPU会不断重启
- ldr sp, =1024*4 @ 设置堆栈,注意:不能大于4k, 因为现在可用的内存只有4K
- @ nand flash中的代码在复位后会移到内部ram中,此ram只有4K
- bl main @ 调用C程序中的main函数
- halt_loop:<span style="white-space:pre"> </span>@清理工作。这里没有清理工作可做,仅做循环
- b halt_loop
- #define GPBCON (*(volatile unsigned long *)0x56000010)
- #define GPBDAT (*(volatile unsigned long *)0x56000014)
- int main()
- {
- GPBCON = 0x00000400; // 设置GPB5为输出口, 位[11:10]=0b01
- GPBDAT = 0x00000000; // GPB5输出0,LED1点亮
- return 0;
- }
Makefile:
- led_on_c.bin : crt0.S led_on_c.c
- arm-linux-gcc -g -c -o crt0.o crt0.S
- arm-linux-gcc -g -c -o led_on_c.o led_on_c.c
- arm-linux-ld -Ttext 0x0000000 -g crt0.o led_on_c.o -o led_on_c_elf
- arm-linux-objcopy -O binary -S led_on_c_elf led_on_c.bin
- arm-linux-objdump -D -m arm led_on_c_elf > led_on_c.dis
- clean:
- rm -f led_on_c.dis led_on_c.bin led_on_c_elf *.o