一、ARM汇编语言(GUN-gcc编译器下)
1、语句格式
{symbol} {instruction|directive|pseudo-instruction} {@ comment}
symbol:为符号。
在ARM汇编语言中,符号必须从一行的行头开始,并且符号中不能包含空格。
在指令和伪指令中,符号用作地址标号(label);
在一些伪操作中,符号用作变量或常量。
instruction:为指令。
在ARM汇编语言中,指令不能从一行的行头开始。
在一行语句中,指令的前面必须有空格或者符号。
directive:为伪操作。
pseudo-instruction:为伪指令。
comment:为语句的注释。
在ARM汇编语言中,注释以(@)开头。
注释结尾即为一行的结尾。
注释也可以单独占用一行。
注意:
在ARM汇编语言中,各个指令,伪指令以及伪操作的助记符必须全部用大写或小写字母,不可以在一个伪操作助记符中既有大写又有小写字母。
2、伪指令
ARM伪指令不属于ARM指令集中的指令。
定义这些指令可以使ARM汇编程序设计变得更加方便。
ARM伪指令可以像其它ARM指令一样使用。
汇编器会自动用一条或多条ARM指令替换ARM伪指令。
ARM的伪指令包括:ADR、ADRL、LDR、NOP
1)ADR指令(小范围的地址读取伪指令)
语法格式:
ADR{<cond>} register,expr
<cond>:指令执行的条件码。(可选)
register:目标寄存器。
expr:基于PC或者寄存器的地址表达式。
①当地址值不是字对齐的,其取值范围为 -255 ~ 255
②当地址值是字对齐的,其取值范围为 -1020 ~ 1020
使用说明:
该指令将基于PC的地址值或基于寄存器的地址值读取到寄存器中。
①在汇编程序处理源程序时,ADR伪指令被编译器替换成一条合适的指令。通常,编译器用一条ADD指令或者SUB指令来实现该伪指令的功能。
②如果不能用一条指令来实现ADR伪指令的功能,编译器将报错,因为ADR伪指令中的地址基于PC或者基于寄存器的,所以ADR读取到的地址为位置无关的地址。当ADR伪指令中的地址是基于PC时,该地址与ADR伪指令必须在同一代码段中。
2)ADRL(中等范围的地址读取伪指令)
语法格式:
ADRL{cond} register, expr
<cond>:指令执行的条件码。(可选)
register:目标寄存器。
expr:基于PC或者寄存器的地址表达式。
①当地址值不是字对齐时,其取值范围为-64KB~64KB。
②当地址值是字对齐时,其取值范围为-256KB~256KB。
使用说明:
该指令将基于PC或基于寄存器的地址值读取到寄存器中。
ADRL伪指令比ADR伪指令可以 读取更大范围的地址。
ADRL伪指令在汇编时被编译器替换成两条指令。
①在汇编编译器处理源程序时,ADRL伪指令被编译器替换成两条合适的指令,即使一条指 令可以完成该伪指令的功能,编译器也将用两条指令来替换该ADRL伪指令。
②如果不能用两条 指令来实现ADRL伪指令的功能,编译器将报告错误。
3)LDR(大范围的地址读取伪指令)
语法格式:
LDR{cond} register, =[expr|label-expr]
<cond>:指令执行的条件码。(可选)
register:目标寄存器。
expr:32位的常量。
编译器将根据expr的取值情况,处理LDR伪指令如下:
①当expr表示的地址值没有超过MOV或MVN指令中地址的取值范围(±32M)时,
编译器用合适的MOV或者MVN指令代替该LDR伪指令。
②当expr表示的地址值超过了MOV或MVN指令中地址的取值范围(±32M)时,
编译器将该常数放在数据缓冲区中,同时用一条基于PC的LDR指令读取该常数。
label-expr:基于PC的地址表达式或者是外部表达式。
①当label-expr为基于PC的地址表达式时,
编译器将label-expr表示的数值放在数据缓冲区中,
同时用一条基于PC的LDR指令读取该数值。
②当label-expr为外部表达式或非当前段的表达式时,
汇编编译器将在目标文件中插入连接重定位伪操作,
这样连接器将在连接时生成该地址。
使用说明:
LDR伪指令将一个32位的常数或者一个地址值读取到寄存器中。
①当需要读取到寄存器中的数据超过了MOV及MVN指令可以操作的范围时,可以使用 LDR伪指令将该数据读取到寄存器中。
②将一个基于PC的地址值或者外部的地址值读取到寄存器中。由于这种地址值是在连接时确定的,所以这种代码不是位置无关的。同时,LDR伪指令处的PC值到数据缓冲区中的目标数据所在的地址的偏量要小于4KB。
4)NOP(空操作伪指令)
语法格式:
NOP
NOP伪指令不影响CPSR中的条件标志位。
使用说明:
NOP伪指令在汇编时,将被替换成ARM中的空操作,用来占用CPU的调度时间。
比如,可能为MOV R0, R0等。
3、伪操作
告诉编译器怎么去编译指令,本身不会生成机器码,类似于C语言中的宏定义,伪操作一般都以“.”开头,类似于C语言中的“#”
.global _start @声明全局标号
.equ DATA, 0x101 @#define DATA 0X101
.space 32 @开辟32个字节的连续存储空间,并初始化为0
二、汇编驱动外设
1、驱动LED灯
1)外观
通过外观了解有几盏LED灯?4盏(可控)1盏(常亮)
2)开发板原理图
通过开发板原理图了解LED灯连接的芯片管脚(高【灭】/ 低【亮】)
3)芯片手册
通过芯片手册了解需要驱动哪些寄存器
GPIOB26 |
| ||||||||||||||||||||
GPIOC11 |
| ||||||||||||||||||||
GPIOC7 |
| ||||||||||||||||||||
GPIOC12 |
|
4)汇编代码
.text
.global _start
.arm//宏定义GPIOC12寄存器
.equ GPIOCALTFN0, 0xC001C020 //[25:24] 01
.equ GPIOCOUTENB, 0xC001C004 //[12] 1
.equ GPIOCOUT, 0xC001C000 //[12] 0/1_start://配置GPIOC12管脚的复用功能ldr r0, =GPIOCALTFN0//把地址标号放到r0寄存器中ldr r1, [r0]//将存储器地址为r0的内容加载到r1寄存器中mov r2, #0x03//把立即数0x03传送到r2寄存器中bic r1, r1, r2, lsl #24//r1 = r1 位清楚 r2 << 24mov r3, #0x01//把立即数0x01传送到r3寄存器中orr r1, r1, r3, lsl #24//r1 = r1 | r3 << 24str r1, [r0]//将寄存器r1中的内容存储到存储器地址为r0的空间中//设置为输出模式ldr r0, =GPIOCOUTENB//把地址标号放到r0寄存器中ldr r1, [r0]//将存储器地址为r0的内容加载到r1寄存器中orr r1, r1, r3, lsl #12//r1 = r1 | r3 << 12str r1, [r0]//将寄存器r1中的内容存储到存储器地址为r0的空间中loop://亮ldr r0, =GPIOCOUT//把地址标号放到r0寄存器中ldr r1, [r0]//将存储器地址为r0的内容加载到r1寄存器中bic r1, r1, r3, lsl #12//r1 = r1 位清除 r3 << 12str r1, [r0]//将寄存器r1中的内容存储到存储器地址为r0的空间中bl delay//带返回链接的跳转//灭ldr r0, =GPIOCOUT//把地址标号放到r0寄存器中ldr r1, [r0]//将存储器地址为r0的内容加载到r1寄存器中orr r1, r1, r3, lsl #12//r1 = r1 | r3 << 12str r1, [r0]//将寄存器r1中的内容存储到存储器地址为r0的空间中bl delay//带返回链接的跳转b loop//跳到loop标号delay:push {r5}ldr r5, =0x5000000
delay_loop:sub r5, r5, #1cmp r5, #0bne delay_looppop {r5}mov pc, lr.end
5)Makefile
TARGET := bare
BIN := $(TARGET).bin
LD_ADDR := 0x48000000
OBJS := led.oCROSS_COMPILE := arm-cortex_a9-linux-gnueabi-CC :=$(CROSS_COMPILE)gcc
AS :=$(CROSS_COMPILE)as
LD :=$(CROSS_COMPILE)ld
OBJCOPY :=$(CROSS_COMPILE)objcopy -O binaryCFLAGS += -nostartfiles
CFLAGS += -nostdlib$(BIN) : $(TARGET)$(OBJCOPY) $(TARGET) $(BIN)
$(TARGET) : $(OBJS)$(LD) -Ttext=$(LD_ADDR) $^ -o $@copy :cp $(BIN) /tftpbootclean :rm -rf $(OBJS) $(TARGET) $(BIN)
2、按键(button)、蜂鸣器(beep)
.text
.global _start
.arm//宏
.equ GPIOAOUTENB, 0xC001A004
.equ GPIOAPAD, 0xC001A018
.equ GPIOAALTFN1, 0xC001A024
.equ GPIOA_PULLSEL, 0xC001A058
.equ GPIOA_PULLENB, 0xC001A060.equ GPIOCOUT, 0xC001C000
.equ GPIOCOUTENB, 0xC001C004
.equ GPIOCALTFN0, 0xC001C020_start://KEY0 GPIOA28 设置为复用功能0ldr r0, =GPIOAALTFN1//把地址标号放到r0寄存器中ldr r1, [r0]//将存储器地址为r0的内容加载到r1寄存器中mov r2, #0x03//将立即数0x03传输到r2寄存器中bic r1, r1, r2, lsl #24//r1 = r1 位清除 (r2 << 24)str r1, [r0]//将r1寄存器中的数据存储到存储器地址为r0的空间中//KEY0 GPIOA28 设置为输入模式ldr r0, =GPIOAOUTENB//把地址标号放到r0寄存器中ldr r1, [r0]//将存储器地址为r0的内容加载到r1寄存器中mov r3, #0x01//将立即数0x01传输到r3寄存器中bic r1, r1, r3, lsl #28//r1 = r1 位清除 (r3 << 28)str r1, [r0]//将r1寄存器中的数据存储到存储器地址为r0的空间中//KEY0 GPIOA28 设置为上拉输入ldr r0, =GPIOA_PULLSEL//把地址标号放到r0寄存器中ldr r1, [r0]//将存储器地址为r0的内容加载到r1寄存器中orr r1, r1, r3, lsl #28//r1 = r1 | r3 << 28str r1, [r0]//将r1寄存器中的数据存储到存储器地址为r0的空间中//key0 GPIOA28 上拉输入使能ldr r0, =GPIOA_PULLENB//把地址标号放到r0寄存器中ldr r1, [r0]//将存储器地址为r0的内容加载到r1寄存器中orr r1, r1, r3, lsl #28//r1 = r1 | r3 << 28str r1, [r0]//将r1寄存器中的数据存储到存储器地址为r0的空间中//beep GPIOC14 设置为复用功能1ldr r0, =GPIOCALTFN0//把地址标号放到r0寄存器中ldr r1, [r0]//将存储器地址为r0的内容加载到r1寄存器中bic r1, r1, r2, lsl #28//r1 = r1 位清除 (r2 << 28)orr r1, r1, r3, lsl #28//r1 = r1 | r3 << 28str r1, [r0]//将r1寄存器中的数据存储到存储器地址为r0的空间中//beep GPIOC14 设置为输出模式ldr r0, =GPIOCOUTENB//把地址标号放到r0寄存器中ldr r1, [r0]//将存储器地址为r0的内容加载到r1寄存器中orr r1, r1, r3, lsl #14//r1 = r1 | r3 << 14str r1, [r0]//将r1寄存器中的数据存储到存储器地址为r0的空间中bl beep_offloop:ldr r0, =GPIOAPAD//把地址标号放到r0寄存器中ldr r1, [r0]//将存储器地址为r0的内容加载到r1寄存器中ands r1, r1, r3, lsl #28//r1 = r1 & (r3 << 28)bleq beep_onblne beep_offb loopbeep_off://不响ldr r0, =GPIOCOUT//把地址标号放到r0寄存器中ldr r1, [r0]//将存储器地址为r0的内容加载到r1寄存器中bic r1, r1, r3, lsl #14//r1 = r1 位清除 (r3 << 14)str r1, [r0]//将r1寄存器中的数据存储到存储器地址为r0的空间中mov pc, lrbeep_on://响ldr r0, =GPIOCOUT//把地址标号放到r0寄存器中ldr r1, [r0]//将存储器地址为r0的内容加载到r1寄存器中orr r1, r1, r3, lsl #14//r1 = r1 | (r3 << 14)str r1, [r0]//将r1寄存器中的数据存储到存储器地址为r0的空间中mov pc, lr.end
三、ARM裸板的异常(中断)处理
1、初始化
1)设置中断源(产生中断的情况)
2)设置中断控制器(屏蔽、优先级)
3)设置CPU总开关(使能中断)
2、执行程序
3、产生中断
4、CPU每执行完一条指令,都会检查有无中断/异常产生
5、发现有中断/异常产生,开始处理,对于不同的异常,会跳去不同的地址执行程序,在这些地址上,只是一条跳转指令,跳去执行摸个函数。
6、这些函数的任务:保存现场(各类寄存器);处理异常,调用不同的函数;恢复现场。