因了命途中的你们,我才没有荒芜了青春。
– 莫言
CM3 采用了哈佛结构,拥有独立的指令总线和数据总线,可以让取指与数据访问并行不悖。指令总线和数据总线共享同一个存储器空间(一个统一的存储器系统)。换句话说,不是因为有两条总线,可寻址空间就变成 8GB 了(还是只能最高4GB)。
M3 一共有 R0-R15,16个寄存器。R0-R12是通用寄存器,R13-R14是特殊功能寄存器,绝大多数 16 位 Thumb 指令只能访
问 R0‐R7,而 32 位 Thumb‐2 指令可以访问所有寄存器。
R13(SP)寄存器是堆栈指针,有两个(一次只能使用一个),MSP(主堆栈指针)常用于异常和操作系统内核,PSP(进程堆栈指针)用于用户程序,是复位后缺省使用的堆栈指针
堆栈指针的最低两位永远是 0,这意味着堆栈总是 4 字节对齐的
R14(LR):连接寄存器,呼叫一个子程序时,由 R14 存储返回地址(RTT用来回收线程使用空间),把返回地址直接存储在寄存器中。这样足以使很多只有 1 级子程序调用的代码无需访问内存(堆栈内存),从而提高了子程序调用的效率
如果多于一级,则压到堆栈。
(P27:在 RISC 处理器中,为了强调访内操作越过了处理器的界线,并且带来了对性能的不利影响,给它取了一个专业的术语:溅出。这个翻译看一下英文原文)
R15(PC):程序计数寄存器
指向当前的程序地址。如果修改它的值,就能改变程序的执行流
特殊功能寄存器
程序状态字寄存器组(PSRs)
记录 ALU 标志(0 标志,进位标志,负数标志,溢出标志),执行状态,以及
当前正服务的中断号
中断屏蔽寄存器组(PRIMASK, FAULTMASK, BASEPRI):都是中断相关的
控制寄存器(CONTROL): 定义特权状态,并且决定使用哪一个堆栈指针
操作模式
Cortex‐M3 处理器支持两种处理器的操作模式。处理者模式(handler mode,以后不再把 handler 中译——译注)和线程模
式(thread mode)。引入两个模式的本意,是用于区别普通应用程序的代码和异常服务例程的代码——包括中断服务例程的代码。
特权极别
特权的分级——特权级和用户级。这可以提供一种存储器访问的保护机制,使得普通的用户程序代码不能意外地,甚至是恶意地执行涉及到要害的操作。处理器支持两种特权级,这也是一个基本的安全模型。
中断模式一定是特权级,线程模式也有机会是特权级()
这部分可以再仔细看看
M3的存储器映射:将内核相关寄存器、外设、扩展外部存储器、片上外设、片上SRAM、代码区等映射在4GB(32位)的地址空间。过把片上外设的寄存器映射到外设区,就可以简单地以访问
内存的方式来访问这些外设的寄存器,从而控制外设的工作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vsTFxClR-1641195339951)(笔记.assets/image-20211130172807269.png)]
Cortex‐M3 内部有若干个总线接口,以使 CM3 能同时取址和访内(访问内存)。采用的是哈佛结构。
Cortex‐M3 只使用 Thumb‐2 指令集(其实只使用了其中的一部分),该指令集可同时使用16位和32位,再也不需要花时间来切换于 32 位 ARM 状态和16 位 Thumb 状态之间了。
(一般来说,32位更快,16位代码密度更高)
低功耗(实现的逻辑门较少且支持多种节能模式)
调试方便
m3的堆栈使用的是“向下生长的满栈”
寄存器的 PUSH 和 POP 操作永远都是 4 字节对齐的——也就是说他们的地址必须是
0x4,0x8,0xc,……。这样一来,R13的最低两位被硬线连接到0,并且总是读出0 (Read As Zero)。
(待理解)如果向 PC 中写数据,就会引起一次程序的分支(但是不更新 LR 寄存器) )。CM3 中的指令至少是半字对齐的,所以 PC 的 LSB 总是读回 0。然而,在分支时,无论是直接写 PC 的值还是使用分支指令,都必须保证加载到 PC 的数值是奇数(即 LSB=1),用以表明这是在
Thumb 状态下执行。倘若写了 0,则视为企图转入 ARM 模式,CM3 将产生一个 fault 异
常。
Cortex‐M3 中的特殊功能寄存器只能被专用的 MSR 和 MRS 指令访问,而且它们也没有存储器地址
有三个屏蔽中断寄存器,限制程度不一样
CONTROL寄存器:堆栈指针的选择、线程模式用户模式还是特权模式的选择。只有在特权模式才能修改该寄存器。
handler模式总是特权级的。在复位后,处理器进入线程模式+特权级。
为了决定 handler 的入口地址,CM3 使用了“向量表查表机制”。这里使用一张向量表。向量表其实是一个 WORD(32 位整数)数组,每个下标对应一种异常,该下标元素的值则是该异常 handler 的入口地址。向量表的存储位置是可以设置的,通过 NVIC 中的一个重定位寄存器来指出向量表的地址。
push和pop的成对使用,才能有效的恢复栈中的内容
RTOS如何将局部变量存放在线程栈中?
OS以修改 PSP,用于实现多任务中的任务上下文切换。
在进入 ISR 时,CM3 会自动把一些寄存器压栈,这里使用的是进入 ISR 之前使用的 SP
指针(MSP 或者是 PSP)。离开 ISR 后,只要 ISR 没有更改过 CONTROL[1],就依然使用先前
的 SP 指针来执行出栈操作。
复位序列
从地址 0x0000,0000 处取出 MSP 的初始值。
从地址 0x0000,0004 处取出 PC 的初始值——这个值是复位向量,LSB 必须是 1。然后从这个值所对应的地址处取指。可以将其引向启动引导代码
数据传送
两个寄存器间传送数据
寄存器间传送数据的指令是 MOV(MOV R8, R3)
寄存器与存储器间传送数据
用于访问存储器的基础指令是“加载(Load)”和“存储(Store)”。加载指令 LDR 把存
储器中的内容加载到寄存器中,存储指令 STR 则把寄存器的内容存储至存储器中,传送过程
中数据类型也可以变通(通过在指令后面加后缀,演变出多种用法)
寄存器与特殊功能寄存器间传送数据
把一个立即数加载到寄存器
大多数情况下,汇编器都在遇到 LDR 伪指令时,都会把它转换成一条相对于 PC 的加载
指令,来产生需要的数据。
对于 LDR,如果汇编器发现要产生立即数是一个程序地址,它会自动地把 LSB 置位(加载到 PC 的数值保证是奇数)。
如果汇编器发现要加载的是数据地址,则不会自作聪明(把LSB置位)
数据处理
即加减乘除、逻辑运算(如果要用到存储器的值,先将其加载到寄存器,然后运算)
MRS 和 MSR(只有在特权模式下才能使用)
MRS , ;加载特殊功能寄存器的值到 Rn
MSR , ;存储 Rn 的值到特殊功能寄存器
#### 存储器系统
CM3 在定义了存储器映射之外,还为存储器的访问规定了 4 种属性,分别是:
可否缓冲(Bufferable)
可否缓存(Cacheable)
可否执行(Executable)
可否共享(Sharable)
有两个区中实现了位带(支持位带操作的地址区)。其中一个是 SRAM 区的最低 1MB 范围,第二个则是片内外设区的最低 1MB 范围。这两个区中的地址除了可以像普通的 RAM 一样使用外,它们还都有自己的“位带别名区”,位带别名区把每个比特膨胀成一个 32 位的字。当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的。
位带别名:对别名地址的访问最终作用到位带区的访问上(有一个地址映射过程)
在 CM3 中,非对齐的数据传送只发生在常规的数据传送指令中,如 LDR/LDRH/LDRSH。
其它指令则不支持。(对齐能够提高程序的运行效率,所以存取数据最好是对齐的)
支持大小端模式,不过一般使用小端模式。
Cortex‐M3 处理器使用一个 3 级流水线。流水线的 3 级分别是:取指,解码和执行
SysTick 定时器:系统滴答定时器是一个非常基本的倒计时定时器,用于在每隔一
定的时间产生一个中断,即使是系统在睡眠模式下也能工作。它使得 OS 在各 CM3
器件之间的移植中不必修改系统定时器的代码,移植工作一下子容易多了。SysTick
定时器也是作为 NVIC 的一部分实现的。
嵌套向量中断控制器 NVIC:NVIC 是一个在 CM3 中内建的中断控制器。中断的具体
路数由芯片厂商定义。NVIC 是与 CPU 紧耦合的,它还包含了若干个系统控制寄存
器。因为 NVIC 支持中断嵌套,使得在 CM3 上处理嵌套中断时清爽而强大。它还
采用了向量中断的机制。在中断发生时,它会自动取出对应的服务例程入口地址,
并且直接调用,无需软件判定中断源,为缩短中断延时做出了非常重要的贡献。
存储器保护单元:MPU 是一个选配的单元,有些 CM3 芯片可能没有配备此组件。
如果有,则它可以把存储器分成一些 regions,并分别予以保护。例如,它可以让
某些 regions 在用户级下变成只读,从而阻止了一些用户程序破坏关键数据。
ROM 表:它只是一个简单的查找表,提供了存储器映射信息,这些信息供包括了多种
系统设备和调试组件
AHB : AHB (Advanced High-performance Bus)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XCxe7Hzx-1641195339954)(F:\学习资料\Cortex-M3权威指南(中文)]\笔记.assets\image-20200929105432398.png)
复位信号
上电复位
在器件上电时需要把复位置为有效(assert),把处理器核心和调试系统一起复位
系统复位
只影响处理器核心、NVIC(与调试相关的除外)以及MPU,不复位调试系统
测试复位
只复位调试系统
编号为 1-15 的对应系统异常,大于等于 16 的则全是外部中断。除了个别异常
的优先级被定死外,其它异常的优先级都是可编程的
m3对于每个异常源,在被悬起的情况下,都会有一个对应的“悬起状态寄存器”保存其异常请求,直到该异常能够执行为止,这与传统的 ARM 是完全不同的。在以前,是由产生中断的设备保持住请求信号。现在NVIC 的悬起状态寄存器的出现解决了这个问题,即使后来设备已经释放了请求信号,曾经的中断请求也不会错失。
为了使抢占机能变得更可控,CM3 还把 256 级优先级(不一定有那么多,厂家会裁剪)按位分成高低两段,分别是抢占优先级和亚优先级,如下所述。
优先级分组规定:亚优先级至少是 1 个位。因此抢占优先级最多是 7 个位,造成了最多只有 128 级抢占的现象。
在计算抢占优先级和亚优先级的有效位数时,必须先求出下列值:
如果优先级完全相同的多个异常同时悬起,则先响应异常编号最小的那一个。如 IRQ #3
会比 IRQ #5 先得到响应
某个中断得到响应之前,其悬起状态被清除了(例如,在 PRIMASK 或 FAULTMASK 置位的时候软件清除了悬起状态标志),则中断被取消
如果某个中断在得到响应之前,其请求信号以若干的脉冲的方式呈现,则被
视为只有一次中断请求,多出的请求脉冲全部错失——这是中断请求太快,以致于超出处理
器反应限度的情况。
Fault 类异常
总线 Faults
当 AHB 接口上正在传送数据时,如果回复了一个错误信号(error response),则会产生总
线 faults.
哪些因素会导致 AHB 回复一个错误信号?
AHB 回复的错误信号会触发总线 fault,诱因可以是:
发生了总线 fault 后,我们将如何找出该 fault 的事故原因呢?在这里,NVIC 提供
了若干个 fault 状态寄存器,其中一个名为“总线 fault 状态寄存器”(BFSR)的。通过它,总
线 fault 服务例程可以确定产生 fault 的场合:是在数据访问时,在取指时,还是在中断的堆
栈操作时。
存储器管理 faults
存储器管理faults多与MPU有关,其诱因常常是某次访问触犯了MPU设置的保护策略
了调查 MemManage fault 的案发现场,NVIC 中有一个“存储器管理 fault 状态寄存器
(MFSR)”,它指出导致 MemManage fault 的原因。如果是因为一个数据访问违例(DACCVIOL位)或是一个取指访问违例(IACCVIOL 位),则违例指令的地址已经被压入栈中。如果还有MMARVALID 位被置位,则还能进一步查出引发此 fault 时访问的地址——读取 NVIC“存储器管理地址寄存器(MMAR)”的值。
用法 fault
硬 fault
硬 fault 是上文讨论的总线 fault、存储器管理 fault 以及用法 fault 上访的结果。如果这
些 fault 的服务例程无法执行,它们就会成为“硬伤”——上访(escalation)成硬 fault。
SVC 和 PendSV
SVC(系统服务调用,亦简称系统调用)和 PendSV(可悬起系统调用)
PendSV(可悬起的系统调用),它和 SVC 协同使用。一方面,SVC
异常是必须立即得到响应的(若因优先级不比当前正处理的高,或是其它原因使之无法立即
响应,将上访成硬 fault——译者注),应用程序执行 SVC 时都是希望所需的请求立即得到响
应。另一方面,PendSV 则不同,它是可以像普通的中断一样被悬起的(不像 SVC 那样会上
访)。OS 可以利用它“缓期执行”一个异常——直到其它重要的任务完成后才执行动作。悬
起 PendSV 的方法是:手工往 NVIC 的 PendSV 悬起寄存器中写 1。悬起后,如果优先级不够
高,则将缓期等待执行。
PendSV 的典型使用场合是在上下文切换时(在不同任务之间切换)
PendSV 异常会自动延迟上下文切换的请求,
直到其它的 ISR 都完成了处理后才放行。为实现这个机制,需要把 PendSV 编程为最低优先级的异常。如果 OS 检测到某 IRQ 正在活动并且被 SysTick 抢占,它将悬起一个 PendSV 异常,
以便缓期执行上下文切换。
PRIMASK 用于除能在 NMI 和硬 fault 之外的所有异常,它有效地把当前优先级改为 0(可
编程优先级中的最高优先级)
FAULTMASK更绝,它把当前优先级改为‐1。这么一来,连硬fault都被掩蔽了。使用方案
与PRIMASK的相似。但要注意的是,FAULTMASK 会在异常退出时自动清零。
BASEPRI寄存器
用于掩蔽优先级低于某一阈值的中断
SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15)
中断/异常的响应序列
当相应一个中断时,会有三个操作
入栈: 把8个寄存器的值压入栈
响应异常的第一个行动,就是自动保存现场的必要部分:依次把xPSR, PC, LR, R12以及R3‐R0由硬件自动压入适当的堆栈中:如果当响应异常时,当前的代码正在使用PSP,则压入PSP,即使用线程堆栈;否则压入MSP,使用主堆栈。一旦进入了服务例程,就将一直使用主堆栈
取向量:从向量表中找出对应的服务程序入口地址
选择堆栈指针MSP/PSP,更新堆栈指针SP,更新连接寄存器LR,更新程序计数器PC
异常返回
启动了中断返回序列(3种途径可以触发异常返回序列)后,执行以下处理
所有服务例程都只使用主堆栈。所以当
中断嵌套加深时,对主堆栈的压力会增大:每嵌套一级,就至少再需要8个字,即32字节的
堆栈空间——而且这还没算上ISR对堆栈的额外需求,并且何时嵌套多少级也是不可预料的。
相同的异常是不允许重入的。因为每个异常都有自己的优先级,并
且在异常处理期间,同级或低优先级的异常是要阻塞的,因此对于同一个异常,只有在上次
实例的服务例程执行完毕后,方可继续响应新的请求。