第三章 嵌入式系统程序设计
文章目录
学习目标
- 了解ARM汇编相关知识
- 掌握常用重点指令及伪指令
- 掌握ARM汇编语言程序设计*
- 了解BordLoader及启动文件
- 了解CMSIS规范
- 掌握嵌入式C程序设计*
重点内容
- 1、ARM常用指令集(ARM\Thumb\Thumb-2)及特点
- 2、ARM指令六种寻址方式
- 3、熟悉常用指令:MOV,ADD, SUB,AND,ORR,CMP,B,LDR,STR及LDR伪指令
- 4、熟悉条件域的使用
- 5、子程序及子程序调用PROC/MOV PC,LR/ENDP/BL
- 6、CMSIS层次结构
- 7、启动文件的功能
- 8、C中的常用运算符 << -> | & != ^ ~
- 9、嵌入式系统的程序结构(包括OS与第9章结合)
- 10、ARM汇编、寄存器、固件库对硬件的基本操作步骤
ARM指令分类及指令格式
ARM指令特点
- (1)使用标准的、固定长度的32位指令格式
- (2)所有指令都使用4位条件码来决定指令是否执行
- (3)RISC结构,指令是加载/存储型的
ARM指令分类
- 分支指令
- 数据处理指令
- 程序状态寄存器(CPSR)处理指令
- 加载/存储指令
- 协处理器指令和异常产生指令。
ARM指令格式
< o p c o d e > { < c o n d > } { S } < R d > , < R n > , < o p 2 > <opcode>\{<cond>\}\{S\} <Rd>,<Rn>,<op2> <opcode>{<cond>}{S}<Rd>,<Rn>,<op2>
ARM指令的条件码
当处理器工作在ARM状态时,几乎所有的指令均根据CSPR中条件码的状态和指令的条件域有条件地执行。
每一条ARM指令包含4位的条件码,位于指令的最高4位[31:28]。
ARM指令中的操作数符号
- # 立即数符号:32位立即数
- 0x 十六进制符号
- ! 更新基址寄存器符号
- ^ 复制SPSR到CPSR符号
- - 指示寄存器列表符号
ARM指令中的移位操作
移位操作符opsh | 操作含义 | 示例 | 示例说明 |
---|---|---|---|
LSL | 逻辑左移 | MOV R0,R1, LSL#2 | 将R1中的内容左移2位送R0中,低位用0填充 |
ASL | 算术左移 | MOV R0,R2, ASL#3 | 将R2中的内容左移3位送R0中,LSL与ASL效果相同,可以互换 |
LSR | 逻辑右移 | MOV R0, R1, LSR#2 | 将R1中的内容右移两位后传送到R0中,左端用零来填充 |
ASR | 算术右移 | MOV R0, R1, ASR#2 | 将R1中的内容右移两位后传送到R0中,左端用第31位的值来填充 |
ROR | 循环右移 | MOV R0, R1, ROR#2 | 将R1中的内容循环右移两位后传送到R0中 |
RRX | 扩展的循环右移 | MOV R0, R1, RRX#2 | 将R1中的内容进行带扩展(C标志)的循环右移两位后传送到R0中 |
ARM指令的寻址方式
- 7种常见的寻址方式
- 1、立即寻址:ADD R0,R1,#10
- 2、寄存器寻址:MOV R1,R2
- 3、寄存器间接寻址:STR R0,[R1]
- 4、基址加变址寻址:LDR R1,[R1,#4]
- 5、相对寻址:BL SUB1
- 6、堆栈寻址
- 7、块拷贝(多寄存器)寻址:LDMIA R0,{R1-R5}
ARM 指令集
- 数据处理指令 P60
- 程序状态指令 P61
- 分支指令 P62
- 加载/存储指令 P63
- 协处理器指令 P66
- 异常中断指令 P67
Thumb 指令集
- Thumb指令集指令编码长度为16位,但指令的操作数和指令地址仍是32位;
- Thumb指令集是ARM指令系统的一个子集 ;
- Thumb指令集在保留32代码优势的同时,大大的节省了系统的存储空间。
Thumb-2指令集
- Thumb-2是一种新型混合指令集,融合了16位和32位指令,用于实现密度和性能的最佳平衡。
ARM 处理器支持的伪指令
-
ADR
-
功能:将程序相对偏移地址或寄存器偏移地址加载到指定寄存器中。
-
Mloop MOV R1,#0xf9 ADR R0,mloop ;将mloop对应相对偏移地址传送到R0中
-
-
LDR
-
功能:将32位常量或一个地址加载到到指定寄存器中 。(MOV不能加载32位的数)
-
LDR R1,=0x1234 ;R1=0x1234 Mloop LDR R2,=0xABCDEF98 ;R2=0xABCDEF98 LDR R3,=mloop ;将mloop对应地址传送到R3中
-
-
NOP
-
功能:空操作,产生所需的ARM无操作代码,用于简单延时,与MOV Rd,Rd等效 。
-
LDR R7,=1000 LOOP NOP ;作为延时主体 SUB R7,R7,#1 BNZ LOOP
-
ARM汇编语言程序设计
一些伪指令
汇编语言的程序结构
AREA Init,CODE,READONLY
ENTRY
Start
LDR R0,=0x3FF5000
MOV R1,#0xFF ;或LDR R1,=0xFF
STR R1,[R0]
LDR R0,=0x3FF5008
MOV R1,#0x01 ;或LDR R1,=0x01
STR R1,[R0]
┉┉
Handler1 PROC ;子程序名Handler1
: ;子程序主体
ENDP ;子程序结束
END ;整个汇编语言程序结束
子程序的调用
ARM汇编语言子程序都有一个子程序的名称,也有子程序的返回指令,采用MOV PC,LR返回。
如果一个子程序不在调用的同一个文件中,则需要先用IMPORT声明是外部的,IMPORT声明可以放在AREA语句之前。
在ARM汇编语言程序中,子程序的调用一般是通过BL指令来实现的。
BL 子程序名
子程序:
Label PROC
...
MOV PC,LR
ENDP
子程序调用
BL Label
![image-20220525202529839](https://i-blog.csdnimg.cn/blog_migrate/6f7bb68a48ac18cc729d8e1600b46fe2.png)
CMSIS 及其规范
CMSIS(Cortex Microcontroller Software Interface Standard)是ARM Cortex™ 微控制器软件接口标准,是 Cortex-M 处理器系列与供应商无关的硬件抽象层。使用CMSIS,可以为处理器和外设实现一致且简单的软件接口,从而简化软件的重用、缩短微控制器新开发人员的学习过程,并缩短新产品上市时间。
- (1)内核外设访问层(CPAL=Core Peripheral Access Layer) 。
- 由ARM公司负责实现,包括对寄存器名称、地址的定义,对核寄存器、NVIC、调试子系统的访问接口定义等。
- (2)中间件访问层(MWAL=Middleware Access Layer)。
- 定义了访问中间件的一些通用API函数,该层也有ARM公司负责,但芯片厂家也要根据自己器件的设备特性更新。
- (3)片上外设访问层(DPAL, Device Peripheral Access Layer)。
- 由芯片厂商负责实现,其实现与CPAL类似,负责对硬件寄存器地址以及外设访问接口进行定义。该层可调用CPAL层提供的接口函数,同时根据设备特性对异常向量表进行扩展,以处理相应外设的中断请求。
BootLoard及启动文件
Boot Loader:是应用程序运行之前的引导加载程序,类似于PC的BIOS程序,目的是将PC指针引导到C语言程序main入口。
ARM处理器启动过程:
嵌入式系统的Boot Loader是在操作系统内核或用户应用程序运行之前运行的一段小程序,这段小程序可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态﹐以便为嵌入式系统准备好正确的环境。
嵌入式C程序设计
常用运算符举例
uint32_t temp;//或u32 temp
例1:让temp的指定位n置位
temp|=(1<<n);//n=0~31
例2:让temp的指定位n清0
temp&=~(1<<n);//n=0~31
例3:让temp的指定位n取反
temp^=(1<<n);//n=0~31
例4:LED1闪烁(LED1为GPIO的PD2引脚)
//法(1)
#define LED1(x) ((x) ? (GPIO_SetBits(GPIOD, GPIO_Pin_2)) : (GPIO_ResetBits(GPIOD, GPIO_Pin_2)));
while(1)
{
LED1(0);
Delay();
LED1(1);
Delay();
}
//法(2)
while(1)
{
GPIOD->ODR^=(1<<2);
Delay();
}
嵌入式程序设计过程
嵌入式应用程序有4种基本的处理流程
- (1)基于轮询的处理流程;
- (2)基于中断驱动的处理流程;
- (3)基于轮询与中断相结合的处理流程;
- (4)基于处理并发任务的处理流程。
混合编程
(1)汇编中调用C语言子程序
注意事项
- 寄存器R0~R3以及R12和LR可能会被修改,若这些寄存器在调用后还要使用的话,必须在调用前将这些寄存器压入栈保护起来,调用完再弹出
- SP的值应该是双字对齐
- 确保输入参数的存储在正确的寄存器中尽管使用R0~R3
- 返回值存于R0中
C语言部分:
int SumFourDataC(uint32_t data1, uint32_t data2, uint32_t data3, uint32_t data4)
{
uint32_t sum;
sum = data1 + data2 + data3 + data4;
return sum;
}
汇编部分:
MOVS R0, #0x12
LDR R1, =#0x12345678
LDR R2,=#0xABCDEF10
LDR R3,=#0x87654321
IMPORT SumFourDataC
BL SumFourDataC
(2)C语言中调用汇编子程序
注意事项
- 如果要改变寄存器R4~R11中的值,需要将原始数值保存到栈中,在返回到C代码前恢复这些原来的值。
- 如果在汇编程序中调用另外一个汇编子程序,需要将LR的值保存在栈中,并且利用它执行返回操作。
- 函数返回值存放在R0中
汇编部分:
EXPORT SumFourDataASM
SumFourDataASM PROC
ADDS R0,R0,R1
ADDS R0,R0,R2
ADDS R0,R0,R3
BX LR
ENDP
C语言部分:
extern int SumFourDataASM(uint32_t data1, uint32_t data2, uint32_t data3, uint32_t data4)
uint32_t sum
sum = SumFourDataASM(1,2,3,4);
(3)C语言嵌入汇编程序
__asm 函数
{
汇编语言程序段
BX LR;返回的值在R0中
}