前提
之前用过cortex系列单片机,时隔多年,居然很多都忘记了,现在又要重新使用,为了避免再次忘记,开始做些笔记。
简介
ARM在1990年成立,当初的名字是“Advanced RISC Machines Ltd.”,是一个处理器设计公司,采用精简指令集,专注于嵌入式芯片解决方案。
该公司只卖处理器核,至于其它外设,完全交给合作伙伴来设计,因此具有很大的扩展功能,它的设计图如下:
目前包含4个系列:
- 款式A:设计用于高性能的“开放应用平台”——越来越接近电脑了
- 款式R:用于高端的嵌入式系统,尤其是那些带有实时要求的——又要快又要实时。
- 款式M:用于深度嵌入,更小,更省电。
- 款式SecurCore:安全加密芯片
这些系列都是采用不同版本的架构,目前已经经过V4,V5,V6,V7,V8几个迭代了。
目前有些架构还带有高级特性,比如Jazelle,MPU,MMU。
-
Jazelle是ARM处理器的硬件Java加速器。
-
MMU,存储器管理单元,用于实现虚拟内存和内存的分区保护,这是应用处理器与嵌入式处 理器的分水岭。电脑和数码产品所使用的处理器几乎清一色地都带MMU。但是MMU也引入了不确定性, 这有时是嵌入式领域——尤其是实时系统不可接受的。然而对于安全关键(safety‐critical)的嵌入式系统, 还是不能没有内存的分区保护的。为解决矛盾,于是就有了MPU。可以把MPU认为是MMU的功能子集,它 只支持分区保护,不支持具有“定位决定性”的虚拟内存机制。
由于历史原因(从ARM7TDMI开始),ARM处理器一直支持两种形式上相对独立的指令 集,它们分别是:
- 32位的ARM指令集。对应处理器状态:ARM状态
- 16位的Thumb指令集。对应处理器状态:Thumb状态
可见,这两种指令集也对应了两种处理器执行状态。在程序的执行过程中,处理器可以 动态地在两种执行状态之中切换。实际上,Thumb指令集在功能上是ARM指令集的一个子集, 但它能带来更高的代码密度,给目标代码减肥。这对于要勒紧裤腰带的应用还是很经济的。
寄存器
以M3为例,一下是它的寄存器图:
R0‐R12 都是32 位通用寄存器,用于数据操作。但是注意:绝大多数16 位Thumb 指令只能访 问R0‐R7,而32 位Thumb‐2 指令可以访问所有寄存器。
Cortex‐M3 拥有两个堆栈指针,然而它们是banked,因此任一时刻只能使用其中的一个。
- 主堆栈指针(MSP):复位后缺省使用的堆栈指针,用于操作系统内核以及异常处理例程(包 括中断服务例程)
- 进程堆栈指针(PSP):由用户的应用程序代码使用。
堆栈指针的最低两位永远是0,这意味着堆栈总是4 字节对齐的。
在ARM 编程领域中,凡是打断程序顺序执行的事件,都被称为异常(exception)。除了外部中断外,当有指令执 行了“非法操作”,或者访问被禁的内存区间,因各种错误产生的fault,以及不可屏蔽中断发生时,都会打断程序的 执行,这些情况统称为异常。在不严格的上下文中,异常与中断也可以混用。另外,程序代码也可以主动请求进入 异常状态的(常用于系统调用)。
R14:连接寄存器。当呼叫一个子程序时,由R14 存储返回地址不像大多数其它处理器,ARM 为了减少访问内存的次数(访问内存的操作往往要3 个以上指令周期,带MMU 和cache 的就更加不确定了),把返回地址直接存储在寄存器中。这样足以使很多只有1 级子程序调用的代码无需访 问内存(堆栈内存),从而提高了子程序调用的效率。如果多于1 级,则需要把前一级的R14 值压到堆栈里。在ARM 上编程时,应尽量只使用寄存器保存中间结果,迫不得以时才访问内存。在RISC 处理器中,为了强调访内操作越过 了处理器的界线,并且带来了对性能的不利影响,给它取了一个专业的术语:溅出。
R15:程序计数寄存器。指向当前的程序地址。如果修改它的值,就能改变程序的执行流。
Cortex‐M3 还在内核水平上搭载了若干特殊功能寄存器,例如:
Cortex‐M3 处理器支持处理者模式,线程模式两种模式,还支持特权级,用户级两级特权操作。
在CM3 运行主应用程序时(线程模式),既可以使用特权级,也可以使用用户级;但是异常服 务例程必须在特权级下执行。复位后,处理器默认进入线程模式,特权极访问。在特权级下,程序 可以访问所有范围的存储器(如果有MPU,还要在MPU 规定的禁地之外),并且可以执行所有指令。
在特权级下的程序可以为所欲为,但也可能会把自己给玩进去——切换到用户级。一旦进入用 户级,再想回来就得走“法律程序”了——用户级的程序不能简简单单地试图改写CONTROL 寄存器 就回到特权级,它必须先“申诉”:执行一条系统调用指令(SVC)。这会触发SVC 异常,然后由异常 服务例程(通常是操作系统的一部分)接管,如果批准了进入,则异常服务例程修改CONTROL 寄存 器,才能在用户级的线程模式下重新进入特权级。
存储器
总体来说,Cortex‐M3 支持4GB 存储空间。
中断
0号异常的功能则是个另类,它并不是什么入口地址,而是给出了复位后MSP 的初值。
CM3 的所有中断机制都由NVIC 实现。除了支持240 条中断之外,NVIC 还支持16‐4‐1=11 个内 部异常源,可以实现fault 管理机制。结果,CM3 就有了256 个预定义的异常类型。
Cortex‐M3 在进入异常服务例程时,自动压栈了R0‐R3, R12, LR, PSR 和PC,并且在返回时自 动弹出它们。
复位
在离开复位状态后,CM3 做的第一件事就是读取下列两个32 位整数的值:
- 从地址 0x0000,0000 处取出MSP 的初始值。
- 从地址 0x0000,0004 处取出PC 的初始值——这个值是复位向量,LSB 必须是1。然 后从这个值所对应的地址处取指。
请注意,这与传统的ARM 架构不同——其实也和绝大多数的其它单片机不同。传统的 ARM 架构总是从0 地址开始执行第一条指令。它们的0 地址处总是一条跳转指令。在CM3 中,0 地址处提供MSP 的初始值,然后就是向量表(向量表在以后还可以被移至其它位置)。 向量表中的数值是32 位的地址,而不是跳转指令。向量表的第一个条目指向复位后应执行 的第一条指令。
因为CM3 使用的是向下生长的满栈,所以MSP 的初始值必须是堆栈内存的末地址加1。 举例来说,如果你的堆栈区域在0x20007C00‐0x20007FFF 之间,那么MSP 的初始值就必须是 0x20008000。堆栈指针SP 指向最后一个被压入堆栈的32 位数值。在下一次压栈时,SP 先自减4,再存入新的数值。