ARM基础笔记

本文最后更新于:2022年6月6日 晚上

产业链

  • CUP核
  • SOC芯片 — System On Chip (CPU核 + 外围控制器)
  • 产品

ARM核工作模式(USA UFI SM)

八大工作模式

用户模式(USR:User)— 唯一非特权

  • 用户程序的工作模式,运行在操作系统的用户态没有操作其他硬件的权限
  • 只有它不是特权模式(privilege mode)
  • 只能执行处理自己的数据,不能切换到其他模式下,访问硬件资源切换到其他模式只能通过软中断或者异常

系统模式(SYS:System)— 特权

  • 不受用户模式的限制,用户模式核系统模式共用一套寄存器
  • 操作系统的一些特权任务,可以用该模式访问一些受控资源

终止模式(ABT:Abort)— 异常

  • 用域支持虚拟内存或存储器保护
  • 用户程序访问非法地址、没有权限的地址,进入该模式(如:段错误)

未定义模式(UDF:Undefined)— 异常

  • 用于支持硬件协处理器的软件仿真
  • CPU在指令的译码阶段不能识别该指令操作时,进入该模式

快速中断模式(FIQ:Fast Interrupt Request)— 异常

  • 用于处理对时间要求紧急的中断请求, 主要用于高速数据传输及通道处理

一般中断模式(IRQ:Interrupt Request)— 异常

  • 也叫普通中断,用于处理一般中断请求,通常在硬件产生中断信号会自动进入该模式
  • 可以自由访问系统硬件资源

管理模式(SVC:Supervisor)— 异常

  • CPU上电后的默认模式,主要用作系统初始化(复位异常),软中断处理
  • 用户模式下,用户程序请求使用硬件资源时,通过软中断进入该模式

安全监控模式(MON:Monitor)

  • TrustZone — 讲SOC的硬件和软件资源划分程安全和非安全两个世界
  • 安全世界:所有需要保密的操作 — 指纹识别、密码处理、数据加解密等
  • 非安全世界:用户操作系统、各种应用程序
  • 两个世界通过Monitor Mode的模式进行转换

总结

  • 唯一非特权:User
  • 五大异常模式:ABT、UDF、FIQ、IRQ、SVC
  • 安全模式:MON
  • 中断和异常的区别:异常包含了中断,中断只是异常的一种

寄存器资源

寄存器分类 (Registers)

  • ARM态通用寄存器和程序计数器 — R0~R15

  • ARM态程序状态寄存器 — CPSR、SPSR

用途

  • R0~R10 — 存放用户数据

  • R11 (fp:frame-pointer)— 栈帧指针 记录一个栈空间的起始地址

  • R12(ip:The Intra-Procedure-call scratch register)— 临时存储sp(R13)

  • R13(sp:stack pointer)— 栈指针寄存器, 栈空间的结束地址

  • R14(lr:link register)— 发送跳转,保存PC寄存器的值

  • R15(pc:program counter)— 程序计数器,存放CPU需要执行的下一条指令的内存地址

  • CPSR(Current Program Status Register)— 记录当前CPU状态

  • SPSR(Saved Program Status Register)— 异常产生时候,保存CPSR的值

总结

  • ARM寄存器 — 40个,一种模式最多 —18个寄存器

  • 所有模式共享 — R0~R7、R15、CPSR

  • 除FIQ其他模式共享 — R8~R12

  • 5大异常模式私有 — R13~R14、SPSR

  • MON私有 — R13~R14、SPSR

  • FIQ — 中断更快的三大原因

    • 更多私有寄存器,私有寄存器使用不需要做保护,FIQ恢复现场更快
    • FIQ在异常向量表的最高位,省去了跳转的过程,速度更快
    • FIQ的处理优先级比IRQ更高,甚至可以打断正在执行的IRQ
  • User 和System模式没有SPSR,因为它们产生异常时,跳转到其他模式执行,是他们的状态被其他模式保存

常用ARM核指令

指令格式

<opcode> {<cond>} {S} <Rd> ,<Rn>{, <operand2>}

  • opcode : 操作码

  • cond :条件

    • NE、EQ、GT、LT、GE、LE
  • S:指令执行结果,影响CPSR的N,Z,C,V

  • Rd:目标寄存器,存放指令执行结果R0~R15

  • Rn:操作数1,必须是寄存器

  • operand2:操作数2

    • 立即数:#100 ,一个常数,一个8位常数通过循环右移偶数位,得到它,则该数字为合法立即数

      把一个数转换为32bit,16进制

      • 除零外,仅有一位数,为合法立即数
      • 除零外,仅有两位数,且相邻(包括首尾),为合法立即数
      • 除零外,仅有三位数字,且相邻(包括中间有0,首尾相邻),这三位数,最高位只能取1、2、3;最低位只能取4、8、C,这样的组合为合法立即数
    • 寄存器

    • 寄存器移位:只能寄存器移位

      • LSL:逻辑左移
      • LSR:逻辑右移
      • ASR:算数右移

注意:操作数1,只能是寄存器

数据传送指令

  • MOV:MOV 目标寄存器,操作数2

    将操作数2的值 赋值给目标寄存器

  • MVN:MVN 目标寄存器 , 操作数2

    将操作数2 取反的值,给目标寄存器

  • LDR:LDR 目标寄存器, =数据

    任意数据传送到目标寄存器

    • 如果后面是一个合法立即数,翻译成MOV指令
    • 如果是非法立即数,翻译成 LDR Rn, [PC]

注意: MOV指令 用立即数效率更高,CPU获取指令时,指令和数据都翻译成机器码,指令和数据同时获取,因此效率更高

但,不是所有数据都能被和指令同时获取,因此,有了合法立即数的概念,一条指令中八位是数据位

数据计算指令

  • ADD:ADD 目标寄存器,操作数1,操作数2

  • SUB:SUB 目标寄存器,操作数1,操作数2

  • MUL:目标寄存器, 操作数1,操作数2

    注意:MUL的目标寄存器,和操作数1,编号不能相同

    MUL 两个操作数都要是简单寄存器

位运算指令

  • AND:AND 目标寄存器,操作数1,操作数2

    将操作数1,按位与 ,操作数2,结果存放在目标寄存器

  • ORR:ORR 目标寄存器,操作数1,操作数2

    操作数1,按位或,操作数2,结果存放在目标寄存器

  • EOR:EOR 目标寄存器, 操作数1,操作数2

    将操作数1,按位异或,操作数2,结果放在目标寄存器

  • BIC:BIC 目标寄存器,操作数1,操作数2

    目标寄存器 = 操作数1 & ~操作数2

    如果想把data的某些位,变成自己想要的值:

    • 先将对应的位清0,然后再或上对应的值(对应的值左移得到)

    • 清0:先对应位数,几位就几个1,从第几位开始,就左移几位,再取反,再和源数字按位与&

      置1:目标数字,左移,从第几位开始就左移几位,再和源数字按位或 |

比较指令

  • CMP:CMP 寄存器,操作数2

    CMP指令会自动影响CPSR的N、Z、C、V

    CMP执行时不关心之前执行了什么指令,只关心CPSR的NZCV表示的条件是否满足条件,满足执行,不满足不执行

跳转指令

  • B/BL :只能跳转+/- 32M范围,跳转到一个指定标签

    B 标签

    BL 标签 :BL跳转之前,将跳转前的PC(R15)值保存在LR(R14)

  • 给PC赋值:没有范围限制

    LDR PC , =标签名

内存访问指令

单个数据访问

  • LDR(Load Register):将内存的值加载到寄存器(读内存)

  • STR(Store Register):将寄存器的值写入内存(写内存)

  • 寄存器间接寻址:

    • LDR r0, [r1] —-> (r0 = *r1)
    • STR r0,**[r1]** —-> (*r1 = r0)
  • 基址变址寻址:将基地址寄存器 + 指令中给出的偏移量 = 数据存放的地址

    • 前索引:

      • STR r0, [r1, #4] —-> *(r1 + 4) = r0
      • LDR r0, [r1, #4] —-> r0 = *(r1 + 4)
    • 后索引:

      • STR r0, [r1], #4 —-> *r1 = r0 ; r1 = r1 + 4
      • LDR r0, [r1], #4 —-> r0 = *r1; r1 = r1 + 4
    • 自动索引:

      • STR r0, [r1, #4] ! —-> *(r1 + 4) = r0; r1 = r1 + 4

      • LDR r0, [r1, #4] ! —-> r0 = *(r1 + 4); r1 = r1 + 4

多个数据访问

  • LDM :读内存数据,加载到多个寄存器

    LDM {条件}{s} <MODE> 基质寄存器 {!} , {Reglist}^

  • STM:将多个寄存器的值,存储到一块内存

    STM {条件}{s} <MODE> 基质寄存器 {!} , {Reglist}^

  • MODE:

    • IA (increase after)— 后增加地址
    • IB (increase before)— 先增加地址
    • DA(decrease after)— 后减少地址
    • DB(decrease before)— 先减少地址
  • 基址寄存器 :存放内存的起始地址

  • !:最后更新基址寄存器的值

  • Reglist :寄存器列表

    • 多个寄存器从小到大,中间用“,”隔开{r0, r1, r2} 或 {r0, r7- r10}
    • 寄存器编号大的 —- 内存高地址; 寄存器编号小的 —- 内存低地址
  • ^ : 它存在:

    • Reglist 没有PC寄存器的时候,操作的寄存器是用户模式下的寄存器
    • 在LDM指令中,有PC的时候,数据传输时,会将SPSR的值拷贝到CPSR,用于异常返回

栈操作指令

  • 进(压)栈: stmfd sp! , {寄存器列表}

  • 出栈:ldmfd sp! , {寄存器列表}

  • 进行栈操作前,必须先设置sp的值

  • 进栈和出栈方式一样,ATPCS标准规定满减栈

  • 几种栈操作方式:

CPSR/SPSR操作指令

  • 读操作:

    • MRS Rn, CPSR/SPSR

      将状态寄存器的值,读到通用寄存器

  • 写操作:

    • MSR CPSR/SPSR, Rn

      将通用寄存器的值,写到状态寄存器

指令流水线

(instruction pipeline)—- 以三级流水线为例

  • 预取 (fetch)— PC寄存器工作在预取阶段
  • 译码 (decode)
  • 执行 (execute)

伪指令

**链接地址:**编译器在编译程序的时候,指定的地址,指定这个地址的目的是期望程序在指定的链接地址执行

**运行地址:**程序实际在内存中的运行地址

伪指令:为了方便程序员使用,编译器设计的指令,该指令ARM核无法识别,需要编译器进行翻译

常用的伪指令

  • LDR r0, =0x999 —-> LDR r0, [PC]

  • LDR r0, **=Label ** —-> LDR r0, [PC ,# 固定偏移量]

    读取Label标签表示的地址,存放到r0中,这个标签最终表示的地址受链接地址的影响

    编译器:根据指定的代码开始地址,算出Label标签对应地址,存放在内存中,,通过内存访问指令,根据PC + 固定偏移量,读取内存值。换言之,代码编译结束时候,PC + 固定偏移量表示的内存地址中存放的数据就已经确定死了

  • LDR r0, Label —-> LDR r0, [PC, # 固定偏移量]

    读取Label标签表示的地址的内容

  • ADR r0, Label —> 根据当前PC的值 +/-偏移量,动态获取当前Label表示的内存地址

  • 问题:如何判别代码在实际内存中运行的地址?

    • ADR r0,_start 可以知道,因为它根据PC值,动态获取
    • LDR r0, =_start 无法知道,这条指令不论在哪运行,r0的值都是固定的(取决于指定的链接地址)

汇编与C混合编程

汇编调用C语言

  • ATPCS:

    • 参数传递:函数参数传递的时候,前4个参数通过r0 - r3来传递,超过4个的参数通过栈传递

    • 函数返回:函数返回值通过 r0 带回

  • 注意:!!!调用C语言之前,必须先设置SP

    1
    2
    3
    4
    mov r0, #3
    mov r1, #5
    ldr sp, =0x40001ff0 @!!! 必须设置sp
    bl add //add是.c文件中的一个函数

C语言中内嵌汇编 — GCC编译器为例

1
2
3
4
5
6
7
8
asm (
"指令1\n"
"指令2\n"
...
:输出列表
:输入列表
:修改列表(通用的寄存器)
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int c;
int d;

//输出列表 --- 将寄存器值输出到C变量
:"=r"(c), "=r"(d)

//输入列表 ---- 将C变量输入到寄存器
int a = 10;
int b = 10;

:"r"(a), "r"(b)

//修改列表 ---- 在内联汇编的时候,发生修改的寄存器
:"r0", "r1", "r2"

C变量的引用, 从输出列表到输入列表开始编号:第一个C变量%0,第二个C变量 %1….

Volatile关键字 (重点)

gcc优化

优化思想

  • 如果之前已经把变量的对应的内存数据读到寄存器中,当需要再次读取该变量所对应的内存数据的时候,为了提高效率,编译器会直接使用上一次寄存器中的值,而不再重新从内存读值

优化级别

  • O1:一级优化
  • O2(speed)/Os(size):二级优化
  • O3:三级优化

优化的问题

如果内存中的值,已经被其他的执行单元(比如其他线程、中断)进行了更改,而优化后的代码,每次从寄存器读值,就会造成寄存器中的值和内存中的值不一致的问题

Volatile的作用

volatile修饰一个变量,防止编译器优化(本质),告诉编译器每次使用该变量时,必须从变量所在的内存重新读值

  • 有中断处理函数的代码,使用了全局变量,需要注意什么问题?

    定义全局变量的时候,需要加volatile修饰

ARM 编程命令

1
2
3
4
arm-none-eabi-gcc -c .\start.s -o .\start.o  //只编译不链接
arm-none-eabi-gcc -c .\key_led.c -o .\key_led.o
arm-none-eabi-ld -Ttext=0x40000000 .\start.o .\key_led.o -o key_led.elf //链接,指定链接地址
arm-none-eabi-objcopy -O binary .\key_led.elf .\key_led.bin //格式转换,去除elf信息头

PWM定时器

PWM 脉冲宽度调制

FS4412开发板中,PWM的时钟频率为 100MHz

三星的计数器是递减计数器

串口通信接口

电平标准 — 外部的硬件电路决定

RS232 和 RS485 (重点)

RS232 vs RS485

  • RS232 三个线(RXD,TXD,GND), 全双工,交叉连接
    RS485 三个线(A,B,GND), 半双工,A-A;B-B
  • RS485 传输距离更远
  • RS485 速度更快
  • RS485 抗干扰更好,RS485采用传输差分信号,A-B线电压差确定传输的一位数据
  • RS232 1: -(315v) 0: +(315)v , RS485 1: +(26v) 0: -(26v)
  • RS485支持多点通信,RS232不支持

UART — 芯片内部的异步收发器

  • UART— 芯片内部的串口通信标准的数据发送格式

UART (Universal Asynchronous Receiver and Transmitter)—- 通用异步收发器

串口:数据是一位一位的发
并口:数据是多位一起发

  • UART串口通信参数:

    • 波特率 :双方的通信速度
    • 数据位:发送数据的位数 —- 先发送低位
    • 停止位:表示收发停止
    • 校验位:检查数据是否错误,奇偶校验(只能发现一位错误)
    • 流量控制:需要硬件支持

串口通信数据错误:

  • 电平标准
  • 波特率
  • 数据位
  • 停止位
  • 校验位

系统三大总线

数据、地址、控制

DMA模式

Direct Memory Access:设备直接访问内存,不需要经过CPU

异常处理

  • 异常处理流程

ARM核硬件上自动做的事情

  • CPSR —-拷贝—-> SPSR

  • 设置CPSR对应位:

    • 进入ARM态
    • 进入对应的异常模式
    • 禁止中断
  • 保存PC的值到异常模式的 LR

  • 将PC设置为异常向量表的对应位置:

    偏移量 异常
    0x1C FIQ
    0x18 IRQ
    0x14 (Reserved)
    0x10 Data Abort
    0x0C Prefetch Abort
    0x08 Software Interrupt
    0x04 Undefined Instruction
    0x00 Reset

    <Vector Table>

  • 异常优先级、对应处理器模式、返回地址:

    优先级 异常 模式 返回地址
    1 最高 Reset SVC -
    2 Data Abort Abort LR-8
    3 FIQ FIQ LR-4
    4 IRQ IRQ LR-4
    5 Prefetch Abort Abort LR-4
    6 Software Interrupt SVC LR
    7 最低 Undefined Instruction Undefined LR

程序员需要做的事情

  • 设置异常向量表(在异常向量表中,写跳转指令,跳转指定异常处理函数)

  • 告诉ARM核异常向量表的基地址

    • cortex-A系列以前,异常向量表可以存放在 0x0000,0000 (低地址)或 0xffff,0000(高地址);cp15(协处理器).c1(寄存器)决定异常向量表存放在高地址还是低地址
    • cortex-A系列以后,异常向量表可以在任意位置,cp15.c12保存异常向量表的基地址
  • 编写异常处理函数:

    • 设置SP寄存器

    • 将通用寄存器R0~R12,进行压栈保护

    • 异常处理

    • 异常返回

      • 恢复R0~R12 (出栈)
      • 恢复CPSR
      • 恢复PC

中断处理

  • CPU主要两种工作模式:轮询和中断

    • 轮询:不断询问是否要处理事情,但很多时候不满足条件,浪费了CPU的时间
    • 中断方式:当需要CPU处理的时候,产生一个信号,打断CPU正在做的事情,让CPU处理当前的事情,处理完后,回到打断之前的地方继续执行
  • 中断处理注意点:

    • 中断打断其他程序的执行,所以中断处理要尽可能的快,不能中断处理耗时过长
    • 中断打断程序的执行,所以中断处理的时候,需要先保存现场(CPU的状态和CPU内部寄存器的值:压栈保存),中断处理结束,恢复现场

中断的五大概念(重点)

  • 中断源:产生中断的源头

  • 中断号:SOC芯片厂家对SOC芯片内部中断源的编号

  • 中断处理函数:中断产生后,需要调用执行的函数

  • 中断控制器:控制中断的优先级、中断是否被允许产生

  • 内部中断和外部中断:

    • 内部中断:SOC芯片内部控制器产生的中断

    • 外部中断:SOC芯片外部管脚通过电平触发产生的中断

      高电平触发、低电平触发

      上升沿触发、下降沿触发

      双边触发

学习使用的FS4412开发板,是三星设计的SOC,一共有160个中断源

中断处理过程

  • 中断处理过程
  • MPCore 分布式中断控制系统

  • GIC: (Generic Interrupt Controller)

A/D转换器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include "exynos_4412.h"
#include "exynos_setup.h"

void adc_handler(void)
{
CLRINTADC = 0x0; //INT_ADCn interrupt clear. Cleared if any value is written.

int data = ADCDAT & 0xfff;
int v = (1800 * data) / 0xfff;

uart_printf("%d : %d mv\r\n", data, v);

/*
1.8v 0xfff
x data
*/
delay_time(3);

// A/D conversion starts by enable.
ADCCON |= (1 << 0);
}

void adc_interrupt_init(void)
{
//1.init ADC
/*
16bit 1: 12bit A/D conversion
14bit 1: enable A/D converter prescaler
6¬13bit 132: prescaler value data
ADCMUX 0x3: Analog input channel select
*/
ADCCON = (1 << 16) | (1 << 14) | (132 << 6);
ADCMUX = 0x3;
//2.init GIC
request_irq(42, adc_handler);
//3.init ARM
enable_irq();
//4.init Combiner
INTCOMBINER.IESR2 |= (1 << 19);
//5.A/D conversion starts by enable.
ADCCON |= (1 << 0);

return;
}

void adc_interrupt_test(void)
{
adc_interrupt_init();

//do_something();

}

IIC

IIC总线

概念

IIC(Inter-Integrated Circuit,又称 IIC)—- 由PHILIPS公司开发的串口总线,用于连接微控制器及其外围设备

特点

  • 只有两条总线线路:一条串行数据线**(SDA),一条串行时钟线(SCL)**
  • 每个连接到总线的器件都可以使用软件根据它的唯一的地址来识别
  • 传输数据的设备间是简单的主/从关系
  • 主机可以用作主机发送器或主机接收器
  • 它是一个真正的多主机总线,两个或多个主机同时发起数据传输时,可以通过冲突检测和仲裁来防止数据被破坏
  • 串行的8位双向数据传输先发送高位,位速率在标准模式下可达100kbit/s,在快速模式下可达400kbit/s,在高速模式下可达3.4Mbit/s

IIC总线信号类型

有3种类型信号:开始信号、结束信号、响应信号

  1. 开始信号(S):SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据
  2. 结束信号(P):SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据
  3. 响应信号(ACK):接收器在接收到8位数据后,在第9个时钟周期,拉低 SDA 电平

注意: SDA 上传输的数据必须在 SCL 为高电平期间保持稳定,SDA 上的数据只能在 SCL 为低电平期间变化

IIC signal

IIC 总线的数据传输格式

发送到 SDA 线上的每个字节必须是8位的,每次传输可以发送的字节数量不受限制。首先传输的是数据的最高位(MSB)

启动一个传输时,主机先发送 S 信号,然后发出8位数据。这8位数据中前7位为从机的地址,第8位表示传输的方向(0表示写操作,1表示读操作)。从机收到后会发出一个 ACK 信号

Read Sequence

Write Sequence

I^2^C Terms

Signal Description
S Start Condition: SDA goes from high to low while SCL is high
AD Slave I^2^C address
W Write bit (0)
R Read bit (1)
ACK Acknowledge: SDA line is low while the SCL line is high at the 9 ^th^ clock cycle
NACK Not-Acknowledge: SDA line stays high at the 9th clock cycle
RA MPU-60X0 internal register address
DATA Transmit or received data
P Stop condition: SDA going from low to high while SCL is high

操作芯片的步骤:

  • 通过原理图或手册,确定通信接口
  • 确定是IIC或者是其他通信方式之后,找到从机地址(或者其他对应的地址信息)
  • 查看手册确定寄存器操作方式(读写)

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!