volatile关键词的作用
是为了改善编译器的优化能力,例如,可以将一个指针指向某个硬件位置,其中包含了来自窗口的时间或者信息。
在这种情况下,硬件而不是程序可能修改其中的内容,例如,假如编译器发现,程序在几条语句中两次使用了某个变量的值,比如*p,则编译器可能不是
让程序去查找这个值两次,而是把这个值缓存到寄存器中,这种优化假设变量的值在这两次使用之间不会变化。如果不将变量声明为volatile,则编译器将
进行这种优化,使用后,将告诉编译器,不要进行这种优化;
volatile unsigned char *p = (volatile unsigned char *)&s3c2440nand->NFDATA;;
不要对p中的内容即*p 进行优化
1.用汇编语言点亮一盏灯. 全过程涉及到2个寄存器
.global _start
_start::
LDR R0,=0x56000050 @RO为GPFCON寄存器。配置GPIO口的功能
MOV R1,#0x00000100
STR R1,【R0】
LDR R0,=0x56000054
MOV R1,#0x00000000 //立即数不能超过8位
STR R1,【R0】
MAIN_LOOP:
B MAIN_LOOP @主循环
对应的Makefile文件
led_on.bin:led_on.S
arm-linux-gcc -g -c -o led_on,o led_on.S //-g Add some debug imformation
arm-linux-ld -Ttext0x0000000 -g led_on.o -o led_on_elf // -Ttext 0x0000000 代表代码段的地址是0 elf文件为UNIX系统二进制文件接口
arm-linux-objcopy -O binary -S led_on_elf
clean:
rm -f led_on.bin led_on_elf *.o
预处理
编译 .c ->.S
链接 多个.o合并成一个执行文件.bin
-S
--strip-all
不从源文件拷贝符号信息和relocation信息。
.text .global 是arm-gcc编译器的关键词。 .text 指定了后续编译出来的内容放在代码段【可执行】; .global 告诉编译器后续跟的是一个全局可见的名字【可能是变量,也可以是函数名】; 在本例中,_start是一个函数的起始地址,也是编译、链接后程序的起始地址。由于程序是通过加载器来加载的,必须要找到 _start名字的函数,因此_start必须定义成全局的,以便存在于编译后的全局符合表中,供其它程序【如加载器】寻找到。
另外还有一个就是ldr伪指令,虽然ldr伪指令和ARM的ldr指令很像,但是作用不太一样。
ldr伪指令可以在立即数(这里个人觉得常数更适合)前加上=,以表示把一个地址写到某寄存器中,比如:
ldr r0, =0x12345678
这样,就把0x12345678这个地址写到r0中了。所以,ldr伪指令和mov是比较相似的。只不过mov指令限制了立即数的长度为8位,也就是不能超过512。
而ldr伪指令没有这个限制。如果使用ldr伪指令时,后面跟的立即数没有超过8位,那么在实际汇编的时候该ldr伪指令是被转换为mov指令的。
ldr伪指令和ldr指令不是一个同东西。
MOV可以把寄存器内容,立即数传入一个寄存器里(注意,这里绝对不可以是内存),该立即数必须可以通过一个8位数循环移位偶数次得到。
LDR
如果label是立即数:
如果name是个标识符:
LDR
LDR
LDR
STR
STR
STR
2.通过c语言点亮一盏灯
需要自己编写
启动文件包括硬件初始化,软件初始化
1.设置栈 SP->某块内存 2440为SRAM 自动完成初始化。假如为外挂SDRAM,则需要手动初始化。硬件方面的初始化:
关看门狗,
初始化时钟,(2440最快400M,上电默认12M)
初始化SDRAM。没有不需要
2.设置返回地址
3.调用main
4.清理工作
对应的汇编启动文件
file:crt0.S
.text
.global_start
_start:
ldr r0,=0x53000000 @看门狗寄存器地址
mov r1,#0x0
str r1,[r0]
ldr sp,=1024*4
bl main @call main
halt_loop:
b halt_loop
lr为调用子程序命令,halt_loop:为一个自定义的标号
B指令(Branch)表示无条件跳转.
B main ;跳转到标号为main地代码处
BL指令(Branch with Link)表示带返回值的跳转.
BL比B多做一步,在跳转前,BL会把当前位置保存在R14(即LR寄存器),当跳转代码结束后,用MOV PC,LR指令跳回来,这实际上就是C语言执行函数的用法,
汇编里调子程序都用BL,执行完子函数后,可以用MOV PC,LR跳回来.
BL delay ;执行子函数或代码段delay ,delay可以为C函数.
上电时NAND前4K内容复制到片内SRAM,PC->0x0地址,初始化时让SP->最高地址,因为底部地址已经被使用。让栈向下增长。
For instance: 按键控制LED,按键按下LED亮,松开LED灭。
启动文件包括硬件初始化,软件初始化
1.设置栈 SP->某块内存 2440为SRAM 自动完成初始化。假如为外挂SDRAM,则需要手动初始化。硬件方面的初始化:
关看门狗,
初始化时钟,(2440最快400M,上电默认12M)
初始化SDRAM。没有不需要
2.设置返回地址
3.调用main
4.清理工作
crt0.S 启动文件
.text
.global _start
_start:
ldrr0,=0x53000000
movr1,#0x0
strr1,[r0] //close Watch dog
ldr sp,=1024*4
bl main
halt_loop:
b halt_loop
#define GPFCON (*(volatile unsigned long *)0x56000050)
#define GPFDAT (*(volatile unsigned long *)0x56000054)
#define GPGCON (*(volatile unsigned long *)0x56000060)
#define GPGDAT (*(volatile unsigned long *)0x56000064)
/*
* LED1,LED2,LED4对应GPF4、GPF5、GPF6
*/
#define GPF4_out(1<<(4*2))
#define GPF5_out(1<<(5*2))
#define GPF6_out(1<<(6*2))
#define GPF4_msk(3<<(4*2))
#define GPF5_msk(3<<(5*2))
#define GPF6_msk(3<<(6*2))
/*
* S2,S3,S4对应GPF0、GPF2、GPG3
*/
#define GPF0_in (0<<(0*2))
#define GPF2_in (0<<(2*2))
#define GPG3_in (0<<(3*2))
#define GPF0_msk (3<<(0*2))
#define GPF2_msk (3<<(2*2))
#define GPG3_msk (3<<(3*2))
int main()
{
unsigned long dwDat;
// LED1,LED2,LED4对应的3根引脚设为输出
GPFCON &= ~(GPF4_msk | GPF5_msk | GPF6_msk);
GPFCON |= GPF4_out | GPF5_out | GPF6_out;
// S2,S3对应的2根引脚设为输入
GPFCON &= ~(GPF0_msk | GPF2_msk);
GPFCON |= GPF0_in | GPF2_in;
// S4对应的引脚设为输入
GPGCON &= ~GPG3_msk;
GPGCON |= GPG3_in;
while(1){
//若Kn为0(表示按下),则令LEDn为0(表示点亮)
dwDat = GPFDAT; // 读取GPF管脚电平状态
if (dwDat & (1<<0)) // S2没有按下
GPFDAT |= (1<<4); // LED1熄灭
else
GPFDAT &= ~(1<<4); // LED1点亮
if (dwDat & (1<<2)) // S3没有按下
GPFDAT |= (1<<5); // LED2熄灭
else
GPFDAT &= ~(1<<5); // LED2点亮
dwDat = GPGDAT; // 读取GPG管脚电平状态
if (dwDat & (1<<3)) // S4没有按下
GPFDAT |= (1<<6); // LED3熄灭
else
GPFDAT &= ~(1<<6); // LED3点亮
}
return 0;
}
//********* Important
这种位操作很常见。
条件位 |
掩码位 &
对应一种状态
思想:先屏蔽掉某些位对其置0,然后按位或确定状态 |。