嵌入式 ARM 技术概论
Cortex-A9 八种工作模式
处理器工作模式 | 缩写 | 描述 |
---|---|---|
用户模式 (User) | usr | 正常程序执行模式,大部分任务执行在这种模式下 |
快速中断模式 (FIQ) | fiq | 当一个高优先级中断产生时会进入这种模式,一般用于高速数据传输和通道处理 |
外部中断模式 (IRQ) | irq | 当一个低优先级中断产生会进入这种模式一般用于通常的中断处理 |
特权模式 (Supervisor) | svc | 当复位或者软中断指令执行时将会进入这种模式,一般用于通常的中断处理 |
数据访问终止模式 (Abort) | abt | 当存取异常时进入这种模式,用于虚拟存储或者存储保护 |
未定义指令终止模式 (Undef) | und | 当执行未定义指令时进入这种模式,有时用于通过软件仿真协处理器硬件的工作方式 |
系统模式 (System) | sys | 使用和 user 模式相同寄存器的模式,用于特权级操作系统任务 |
监控模式 (Monitor) | mon | 可以在安全模式和非安全模式之间进行转换 |
除了用户模式之外的其他 7 种处理器模式被称为特权模式 (Privileged Mode), 除了用户模式和系统模式之外的 6 种模式被称为异常模式。
寄存器组织
Cortex-A 处理器内部有 40 个 32 位寄存器,其中包括:
- 32 个通用寄存器
- 7 个状态寄存器(1 个 CPSR,6 个 SPSR)
- 1 个程序计数器 PC
CPSR(Current Program Status Register):当前程序状态寄存器
SPSR(Saved Program Status Register):备份程序状态寄存器
特殊的几个寄存器:
- R13: SP(Stack Pointer) 寄存器(用作堆栈指针)
- R14: LR(Link Register) 寄存器(连接寄存器)
- R15: PC(Program Counter) 寄存器(程序计数器)
程序状态寄存器
条件位
- N = Negative result from ALU
- Z = Zero result from ALU
- C = ALU operation Carried out or borrow
- V = ALU operation oVerflowed
条件位 | 含义 |
---|---|
N | 当两个由补码组成的有符号整数运算时,N=1 表示运算的结果为负数,N=0 表示运算的结果为正数或 0。 |
Z | Z=1 表示运算的结果为 0,Z=0 表示结果为不为 0 |
C | 加法指令(包括比较指令 CMN),当结果产生了进位,则 C=1,表示无符号数运算发生上溢出;其他情况下 C=0。在减法指令中(包括比较指令 CMP),当运算中发生了错位(即无符号数运算发生下溢出),则 C=0;其他情况下 C=1。对于在操作数中包含移位操作的运算指令(非加减指令),C 被设置为被移位寄存器最后移出去的位。对于其他非加减运算指令,C 的值通常不受影响。 |
V | 对于加减指令,当运算结果溢出时 V=1,表示符号位溢出,没有溢出 V=0。对于非加减指令,V 的值通常不受影响 |
Q 位
- 仅 ARM v5TE-J 架构支持
- 指示饱和状态
J 位
- 仅 ARM v5TE-J 架构支持
- T = 0; J = 1 处理器处于 Jazelle 状态
- 也可以和其他位组合
DNM 位
Do Not Modify
GE[3:0]
大于或等于(当执行 SIMD 指令时有效)
IT[7:2]
IF…THEN… 指令执行状态位
E 位
大小端控制位
A 位
A=1 禁止不精确的数据异常
中断禁止位
- I = 1: 禁止 IRQ
- F = 1: 禁止 FIQ
T 位
- T = 0; J=0; 处理器处于 ARM 状态
- T = 1; J=0 处理器处于 Thumb 状态
- T = 1; J=1 处理器处于 ThumbEE 状态
Mode 位
处理器模式位
- 10000 User mode
- 10001 FIQ mode
- 10010 IRQ mode
- 10011 SVC mode
- 10110 Monitor mode
- 10111 Abort mode
- 11011 Undfined mode
- 11111 System mode
关于 PSR 的操作见:程序状态寄存器读写指令
指令系统
汇编语言程序设计
GNU ARM 内联汇编
格式:
asm volatile ("asm code": output: input: changed);
如果后面没有任何内容, :
可以省略,前面或中间的 :
不能省略。
汇编语句放到字符串中,多个字符串之间只要不加任何符号编译完之后就会变成一个字符串,汇编指令之间必须要换行。
"mov r0, r0\n\t"
"mov r1, r1\n\t"
"mov r2, r2"
output 格式
asm volatile ("asm code": "constraint"(variable));
constraint
定义variable
的存放位置:r
: 使用任何可用的通用寄存器m
: 使用变量的内存地址
output
修饰符:+
: 可读可写=
: 只写&
: 该输出操作数不能使用输出部分使用过的寄存器,只能+&
或=&
方式使用
input 格式
asm volatile ("asm code"::"constraint" (variable/immediate));
constraint
定义variable / immediate
的存放位置:r
: 使用任何可用的通用寄存器(变量和立即数都可以)m
: 使用变量的内存地址(不能用立即数)i
: 使用立即数(不能用变量)
使用占位符
int a = 100, b = 200;
int result;
asm volatile(
"mov %0,%3\n\t"
"ldr r0,%1\n\t"
"ldr r1,%2\n\t"
"str r0,%2\n\t"
"str r1,%1\n\t"
: "=r"(result), "+m"(a), "+m"(b)
: "i"(123)
);
引用占位符
int num = 100;
asm volatile(
"add %0,%1,#100\n\t"
: "=r"(a)
: "0"(a) // 引用时不能加%,只能是 input 引用 output,引用是为了更能分清输出输入部分
);
&
修饰符
int num;
asm volatile(
"mov %0,%1\n\t"
: "=&r"(num) // 输入和输出的寄存器不相同
: "r"(123)
);
内联汇编示例
实现位交换
#include <stdio.h>
unsigned long ByteSwap(unsigned long val)
{
int ch;
asm volatile(
"eor r3, %1, %1, ror #16\n\t"
"bic r3, r3, #0x00FF0000\n\t"
"mov %0, %1, ror, #8\n\t"
"eor %0, %0, r3, lsr #8"
: "=r"(val)
: "0"(val)
: "r3");
}
int main(void)
{
unsigned long test_a = 0x1234, result;
result = ByteSwap(test_a);
printf("Result:%d\r\n", result);
return 0;
}
求 1~100 的和
int a = 1, b = 1;
int result;
asm volatile(
"LOOP:"
"ADD %2, %2, 1"
"ADD %1, %1, %2"
"CMP %2, #100"
"BNE LOOP"
"MOV %0, %1"
: "=r"(result)
: "+r"(a), "+r"(b)
);
求 5!
int a = 5, b = 4;
int result;
asm volatile(
"LOOP:"
"MUL %1, %1, %2"
"SUB %2, %2, #1"
"CMP %2, #0"
"BNE LOOP"
"MOV %0, %1"
: "=r"(result)
: "+r"(a), "+r"(b)
)
汇编和 C 语言混合编程
C 程序调用汇编程序
#include <stdio.h>
extern void strcopy(char* d, const char* s);
int main()
{
const char* srcstr = "First string - source ";
char dststr[] = "Second string - destination";
printf("Before copying:\n");
printf("%s\n%s\n", srcstr, dststr);
strcopy(dststr, srcstr);
printf("After copying:\n");
printf("%s\n%s\n", srcstr, dststr);
return 0;
}
.global strcopy
strcopy: ; R0 指向目的字符串,R1 指向源字符串
LDRB R2, [R1], #1 ; 加载字节并更新源字符串指针地址
STRB R2, [R2], #1 ; 存储字节并更新目的字符串指针地址
CMP R2, #0 ; 判断是否为字符串结尾
BNE strcopy ; 如果不是,程序跳转到 strcopy 继续复制
MOV PC, LR ; 程序返回
汇编程序调用 C 程序
int g(int a, int b, int c, int d, int e){
return a + b + c + d + e;
}
# int f(i){ return g(i, 2*i, 3*i, 4*i, 5*i); }
.text
.global _start
_start:
STR LR, [SP, #-4]! ; 保存返回地址 LR
ADD R1, R0, R0 ; 计算 2*i(第 2 个参数)
ADD R2, R1, R0 ; 计算 3*i(第 3 个参数)
ADD R3, R1, R2 ; 计算 5*i
STR R3, [SP, #-4]! ; 第 5 个参数需要通过堆栈传递
ADD R3, R1, R1 ; 计算 4*i(第 4 个参数)
BL g ; 调用 C 程序
ADD SP, SP, #4 ; 从堆栈中删除第 5 个参数
LDR PC, [SP], #4 ; 返回
GPIO
常用寄存器:
- 引脚控制寄存器 (GPxCON)
- 引脚数据寄存器 (GPxDAT)
- 引脚上下拉寄存器 (GPxPUD)
- 引脚驱动能力寄存器 (GPxDRV)
异常及中断处理
ARM 的 7 种异常类型
异常类型 | 处理器模式 | 执行低地址 | 执行高地址 |
---|---|---|---|
复位异常 | 特权模式 | 0x00000000 | 0xFFFF0000 |
未定义指令异常 | 未定义指令中止模式 | 0x00000004 | 0xFFFF0004 |
软中断异常 | 特权模式 | 0x00000008 | 0xFFFF0008 |
预取异常 | 数据访问中止模式 | 0x0000000C | 0xFFFF000C |
数据异常 | 数据访问中止模式 | 0x00000010 | 0xFFFF0010 |
外部中断异常 | 外部中断请求模式 | 0x00000018 | 0xFFFF0018 |
快速中断异常 | 快速中断请求模式 | 0x0000001C | 0xFFFF001C |
复位异常中断处理
- 设置异常中断向量表
- 初始化数据栈和寄存器
- 初始化存储系统,如系统中的 MMU 等
- 初始化关键 I/O 设备
- 使能中断
- 处理器切换到合适的模式
- 初始化 C 变量,跳转到应用程序执行
FIQ 和 IRQ 中断
中断软件分支处理 (NVIC 和 GIC)
两个必须处理的步骤:
- 现场保护
- 将中断控制器中的中断标志位清除
通用中断控制器 (GIC)
GIC 架构分为 分配器 (Distributor), CPU 接口 (CPU interface), 虚拟 CPU 接口 (Virtual CPU interface)
GIC 中断控制器中断类型
软中断 (SGI)
中断号 0-15 为 SGI 保留专用外设中断 (PPI)
中断号 16-31 为 PPI 保留共享外设中断 (SPI)
中断号 32-1020 用于共享外设中断
GIC 中断控制器中断状态
GIC 中断在不同的状态间切换:
Inactive(无效)
中断没有发生Pending(待处理)
中断已经发生,但是等待核心来处理。待处理中断都做为 CPU 接口发送到核心处理的候选者Active(正在处理)
中断发送给了核心,目前正在进行中断处理Active and pending(处理和待处理)
一个中断源正在进行中断处理而 GIC 又接收到来自同一中断源的中断触发信号
GIC 寄存器
- CPU 中断通道使能寄存器 (ICCICR_CPUn n=0~3)
- 中断使能寄存器 (ICDISERm_CPUn m=0
4 n=03) - CPU 优先级过滤寄存器 (ICCPMR_CPUn n=0~3)
- GIC 中断使能寄存器 (ICDDCR)
- 中断目标 CPU 配置寄存器 (ICDIPTRm_CPUn m=0
39 n=03) - 中断响应寄存器 (ICCIAR_CPUn n=0~3)
- GIC 中断状态清除寄存器 (ICDICPRm_CPUn m=0
5 n=03) - 中断处理结束寄存器 (ICCEOIR_CPUn n=0~3)
- I2C 传输配置寄存器 (I2CCONn n=0~7)
PWM 定时器
PWM(脉冲宽度调制):是利用处理器的数字输出对模拟电路进行控制的一种非常有效的技术,广泛应用在测量、通信、功率控制个变换等多个领域。
PWM 控制技术优点:控制简单、灵活、动态响应好;从处理器到被控制系统信号都是数字形式的,在进行数模转换时,可将噪声影响降到最低。
实质:定时器的本质就是一个计数器,只不过计数器记录的是处理器外部发生的事情,定时器记录时钟脉冲的个数。
PWM 定时器寄存器
- 定时器配置寄存器 0 (TFCG0)
- 定时器配置寄存器 1 (TFCG1)
- 定时器控制寄存器 (TCON)
- 定时器 n 计数缓冲寄存器 (TCNTBn)
- 定时器 n 比较缓冲寄存器 (TCMPBn)
启动定时器
- 向 TCNTBn 和 TCMPBn 写入初始值
- 置位相应定时器的手动更新位
- 置位相应定时器的启动位启动定时器,清除手动更新位
看门狗定时器
看门狗定时器作用
看门狗定时器用于检测程序的正常运行,当微控制器受到干扰进入错误状态后,使系统在一定时间间隔内自动复位重启。因此看门狗是保证系统长期、可靠个稳定运行的有效措施。主要的作用就是防死机。
看门狗定时器原理
启动看门狗后,必须在看门狗复位之前向特定寄存器中写入数值,不让看门狗定时器溢出,这样看门狗就会重新计时。当用户程序在规定时间内没有向特定寄存器中依次写入数值,看门狗定时器计数溢出,引起看门狗复位,看门狗产生一个强制系统复位。这样可以使程序重新运行,减少程序跑死的危害。看门狗需要不停的接收信号或者重新设置计数值,保持计数值不为 0,如果一旦一段时间内接收不到信号或者计数值为 0,看门狗就会发出复位信号。
看门狗定时器特点
需要不停的接收信号或重新设置计数值,保持计数值不为 0。一旦一段时间接收不到信号,或计数值为 0,看门狗将发出复位信号复位系统或产生中断。
看门狗定时器寄存器
- 看门狗定时器控制寄存器 (WTCON)
- 看门狗定时器数据寄存器 (WTDAT)
- 看门狗计数寄存器 (WTCNT)
看门狗定时器软件设计
- 设置看门狗中断操作,包括全局中断和看门狗中断的使能及看门狗中断向量的定义。如果只是进行复位操作,这一步可以不用设置
- 对看门狗控制寄存器(WTCON)进行设置,包括设置预分频比例因子、分频器的分频值、中断使能和复位使能等
- 对看门狗数据寄存器(WTDAT)和看门狗计数寄存器(WTCNT)进行设置
- 启动看门狗定时器。
RTC 定时器
RTC 定时器作用
为一个嵌入式系统提供可靠的时间,并且要求系统处于关机状态下也能正常工作。
RTC 定时器寄存器
- RTC 控制寄存器 (RTCCON)
- RTC 时间值寄存器 (BCDSEC、BCDMIN、BCDHOUR 等)
RTC 定时器软件设计
- 设置 RTC 控制寄存器中的 CTLEN 为 1,使能时间值寄存器数据的读/写
- 设置 RTC 当前时钟时间
- 在掉电前,RTCEN 位应该清除为 0 来预防误写入 RTC 寄存器中
- 读取年、月、日等相关寄存器的数据通过串口打印到屏幕上
I2C 总线
I2C 总线是两线式串行总线,是同步通信的一种特殊形式。
I2C 接口是全双工的,只要求两条总线线路:一条串行数据线 SDA、一条串行时钟线 SCL。
I2C 总线的优点
接口线少,控制方式简单,器件封装形式小,通信速率较高。
I2C 总线术语
术语 | 描述 |
---|---|
发送器 | 发送数据到总线的器件 |
接收器 | 从总线接收数据的器件 |
主机 | 初始化发送,产生时钟信号和终止发送的器件 |
从机 | 被主机寻址的器件 |
多主机 | 同时有多于一个主机尝试控制总线但不破坏报文 |
仲裁 | 是一个在有多于一个主机尝试控制总线,但只允许其中一个控制总线并使报文不被破坏的过程 |
同步 | 两个或多个器件同步时钟信号的过程 |
I2C 总线信号和时序
I2C 总线传输过程中有三种信号:开始信号(S)、结束信号(P)、应答信号(ACK)
开始信号:SCL 为高电平时,SDA 从高电平向低电平跳变,开始发送数据
结束信号:SCL 为高电平时,SDA 从低电平向高电平跳变,结束发送数据
应答信号:接收设备在接收到 8bit(一个字节)的数据后,在第 9 个时钟周期,向发送设备发送低电平,表示成功收到数据
I2C 通信流程
- 开始时 SDA 和 SCL 线都是高电平,主机发送开始信号,SDA 向低电平跳变,开始数据传输。
- 数据一位一位的进行传输,SCL 时钟为低电平周期时发送器发送数据,SDA 线上数据可以发送变化,SCL 时钟为高电平周期时接收器接收数据,SDA 线上数据必须保持稳定
- 当传输到 8bit 时,在第 9 个时钟周期,发送器释放对 SDA 线的控制,SDA 线恢复高电平,若第 9 个时钟周期内,SDA 线始终为高电平,发送器没有接收到 ACK 信号,就会发出停止信号停止本次通信或者重新发送开始信号。如果接收到 ACK 信号就会继续发送下一个字节。
- 直到主机发送结束信号,结束本次通信。
I2C 总线寻址
I2C 设备用一个 7 位或 10 位的数字唯一标识自己。I2C 设备地址由固定部分和可编程部分组成,这样就可以支持一个 I2C 总线上挂载多个同样的器件,而地址不同。I2C 地址的可编程部分的最大数量就是可以连接到 I2C 总线上相同的数量。
通常是开始信号之后的第一个字节决定了主机选择哪一个从机(例外的情况是可以寻址所有器件的广播地址)。第一个字节的前 7 位组成了从机地址,第 8 位决定了数据传输的方向,如果第 8 位是 0 表示主机会向从机写信息,是 1 表示主机会向从机读信息。
如果是 10 位编址的话,用开始信号后的两个字节表示地址,第一个字节的前 7 位是 11110XX(XX 是 10 位地址的两个最高位),第一个字节的第 8 位是 R/W 位,决定了数据传输的方向,第二个字节是地址剩下的 8 位。
I2C 控制器寄存器
- I2C 传输配置寄存器 (I2CCONn n=0~7)
- I2C 控制状态寄存器 (I2CSTATn n=0~7)
- I2C 地址寄存器 (I2CADDn n=0~7)
- I2C 接收发送数据寄存器 (I2CDSn n=0~7)
- I2C 总线传输配置寄存器 (I2CLCn n=0~7)
I2C 控制器操作流程
- 配置主机发送模式
- 设置对应的 I2C 引脚的功能为 SDA 和 SCL
- 设置
I2CCON[6]
,配置 I2C 发送时钟和中断使能 - 设置 I2C 发送使能,
I2CSTAT[4]=0b1
- 将要通信的 I2C 从机的地址和读写位写入
I2CDS
寄存器 - 将
0xF0
写入I2CSTAT
寄存器- I2CSTAT[7:6] = 0b11 设置主机发送模式
- I2CSTAT[5] = 0b1 写 1,发送开始信号
- I2CSTAT[4] = 0b1 使能 I2C 串口发送
- I2C 控制器发出开始信号后,在 02 中写入的
I2CDS
寄存器地址自动发送到 SDA 总线上,用来寻找从机 - 在 ACK 周期后,I2C 控制器发生中断,
I2CCON[4]
被自动置 1,I2C 传输暂停 - I2C 数据通信是否结束,若结束跳转到 10 ,否则跳转到 07
- 将要传输的数据写入 I2CDS 寄存器准备发送
- 清除中断标志位,通过向
I2CCON[4]
中写 0 实现 - 清除中断标志位后,
I2CDS
寄存器内的数据就开始发送到 SDA 总线上。发送完成后,跳转到 05 - 将
0xD0
写入I2CSTAT
寄存器- I2CSTAT[7:6] = 0b11 设置主机发送模式
- I2CSTAT[5] = 0b0 写 0,发送停止信号
- I2CSTAT[4] = 0b1 使能 I2C 串口发送和接收
- 清除中断标志位,通过向
I2CCON[0]
中写 0 实现 - 延时等待一段时间,使得停止信号生效,I2C 通信结束
SPI 接口
SPI 总线引脚定义
SPI 总线主设备和从设备通信需要 4 条线连接(当单向传输时 3 条线也可以),每个 SPI 设备都有 4 个引脚供通信连接使用。四个引脚是:CLK, MISO, MOSI, CS
信号 | 信号描述 |
---|---|
CLK 时钟信号 | 主设备发出,用于控制数据发送和接受的时序 |
MISO 时钟信号 | 做为主设备时,从从设备接收输入数据;做为从设备时,向主设备发送输出数据 |
MOSI 时钟信号 | 做为主设备时,向从设备发送输出数据;做为从设备时,从主设备接收输入设备 |
CS 片选信号 | 从设备选择信号,当 CS 为低电平时,所有的数据发送/接收依次被执行 |
SPI 总线数据传输格式
通过设置 GPOL(极性)和 GPHA(相位)的值,我们选定当前要使用的 SPI 数据传输格式。
GPOL | GPHA | |
---|---|---|
功能 | 控制时钟极性 | 控制时钟相位 |
值为 0 | SPI 总线空闲时,SCK 为低电平 | SCK 第一个跳变沿采样 |
值为 1 | SPI 总线空闲时,SCK 为高电平 | SCK 第二个跳变沿采样 |
SPI 控制器时钟源寄存器
- SPI 时钟使能寄存器 (CLK_GATE_IP_PERIL)
- SPI 多路选择时钟输出屏蔽寄存器 (CLK_SRC_MASK_PERIL1)
- SPI 时钟源配置寄存器 (CLK_SRC_PERIL1)
- SPI 时钟分频寄存器 (CLK_DIV_PERILn n=1, 2)
- SPI 时钟分频状态寄存器 (CLK_DIV_STAT_PERIL1)
SPI 控制器寄存器
- SPI 传输配置寄存器 (CH_CFGn n=0~2)
- SPI 模式配置寄存器 (MODE_CFGn n=0~2)
- SPI 从机选择信号配置寄存器 (CS_REGn n=0~2)
- SPI 状态寄存器 (SPI_STATUSn n=0~2)
- SPI 数据发送寄存器 (SPI_TX_DATAn n=0~2)
- SPI 数据接收寄存器 (SPI_RX_DATAn n=0~2)
SPI 控制器软件设计
- 设置 SPI 控制器时钟源 (CLK_SRC_PERIL1、CLK_DIV_PERIL1)
- 设置 SPI 数据传输格式和通道使能 (CH_CFGn)
- 设置 SIP 工作模式 (MODE_CFG)
- 设置 SPI 中断 (SPI_INT_ENn 可选)
- 设置 SPI 包数量寄存器 (PACKET_CNT_REG 可选)
- 发出从设备选择信号
- 开始发送和接收数据