Cortex-M3
1.寄存器
Cortex-M3拥有R0~R15通用寄存器和一些特殊功能寄存器。
R0~R12这些通用寄存器,复位初始值都是不可预料的。
2.Cortex-M3有R0到R15的通用寄存器组
注:绝大部分的16位thumb只能访问R0到R7,而32位thumb-2可以访问全部寄存器
3.特殊功能寄存器
3.1程序状态寄存器组(应用程序PSR+中断号PSR+执行PSR)
3.2中断屏蔽寄存器组:用于控制异常的除能和使能
3.3控制寄存器:用于定义特权级别和当前使用哪个堆栈指针
4.操作模式和特权级别:
两种操作模式(处理器模式):Handler模式和线程模式(用于区分异常服务例程的代码和普通程序的代码)
两种特权等级:特权级和用户级(是指在硬件层面上对存储器访问权限的设置)
注:Cortex-M3在运行主程序(即线程模式)可以使用特权级别和用户级别;但是异常服务例程(即handler模式)只能使用特权级别。当处于线程+用户模式时一些访问权限将被禁止
将代码区分成用户级和特权级,有利于程序架构的稳定,如某一个用户代码出问题,不会使其成为害群之狗,因为用户级别的代码是禁止对一些要害寄存器操作的。
5.异常处理
5.1CONTROL[0]=0;
5.2CONTROL[0]=1;
CONTROL[0]只有在特权级别下可以访问,若在用户级别想访问先通过"系统服务呼叫指令(SVC)"来触发SVC异常,然后在该异常的服务例程中可以修改CONTROL[0]。
6.下面是各操作模式的转换
7.异常和中断
可以有11个系统异常和最多240个外部中断(IRQ),具体芯片使用了多少要看芯片制造厂商。
作为中断功能的强化,NVIC 还有一条NMI输入信号线,具体做什么由芯片制造商决定,NMI(not masked interrupted)
8.向量表:当一个异常被Cortex-M3内核接受。对应的异常Handler就会执行,向量表用来决定Handler的入口地址。
9.Cortex-M3的双堆栈:主堆栈(MSP)和进程堆栈(PSP)。是由CONTROL[1]控制的。
10.复位序列:
先从0X00地址取出MSP的值再从0x04地址取出PC的初始值,0X04处存的值是复位向量,而不是跳转指令。
此处Cortex-M3与ARM及单片机不同。以前ARM都是从0X00地址开始执行第一条指令,一般第一条指令都是跳转指令
11.MSP及PC初始化的一个例程
本文转载自:网络
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。
1.寄存器
CM3拥有R0~R15通用寄存器和一些特殊功能寄存器
R0~R12这些通用寄存器,复位初始值都是不可预料的
2.CM3有R0到R15的通用寄存器组
注:绝大部分的16位thumb只能访问R0到R7,而32位thumb-2可以访问全部寄存器
3.特殊功能寄存器
3.1程序状态寄存器组(应用程序PSR+中断号PSR+执行PSR)
3.2中断屏蔽寄存器组:用于控制异常的除能和使能
3.3控制寄存器:用于定义特权级别和当前使用哪个堆栈指针
4.操作模式和特权级别:
两种操作模式(处理器模式):Handler模式和线程模式(用于区分异常服务例程的代码和普通程序的代码)
两种特权等级:特权级和用户级(是指在硬件层面上对存储器访问权限的设置)
注:CM3在运行主程序(即线程模式)可以使用特权级别和用户级别;但是异常服务例程(即handler模式)只能使用特权级别。当处于线程+用户模式时一些访问权限将被禁止
将代码区分成用户级和特权级,有利于程序架构的稳定,如某一个用户代码出问题,不会使其成为害群之狗,因为用户级别的代码是禁止对一些要害寄存器操作的。
5.异常处理
5.1CONTROL[0]=0;
5.2CONTROL[0]=1;
CONTROL[0]只有在特权级别下可以访问,若在用户级别想访问先通过"系统服务呼叫指令(SVC)"来触发SVC异常,然后在该异常的服务例程中可以修改CONTROL[0]。
6.下面是各操作模式的转换
7.异常和中断
可以有11个系统异常和最多240个外部中断(IRQ),具体芯片使用了多少要看芯片制造厂商。
作为中断功能的强化,NVIC 还有一条NMI输入信号线,具体做什么由芯片制造商决定,NMI(not masked interrupted)
8.向量表:当一个异常被CM3内核接受。对应的异常Handler就会执行,向量表用来决定Handler的入口地址。
9.CM3的双堆栈:主堆栈(MSP)和进程堆栈(PSP)。是由CONTROL[1]控制的。
10.复位序列:
先从0X00地址取出MSP的值再从0x04地址取出PC的初始值,0X04处存的值是复位向量,而不是跳转指令。
此处CM3与ARM及单片机不同。以前ARM都是从0X00地址开始执行第一条指令,一般第一条指令都是跳转指令
11.MSP及PC初始化的一个例程
转自:fx427103-博客园
1.不再像别的ARM7那样从thumb状态和ARM状态来回切换
Thumb-2指令集横空出世,Cortex-M3不支持ARM指令集
2.BKP备份寄存器(42个16位寄存器组成),用来存储用户应用程序数据。。在Vdd掉电时由Vbat供电。。在待机复位、系统复位、电源复位后,这些寄存器不会被复位
3.DMA用来提供外设和存储器以及存储器和存储器之间的高速数据传输,而不需要CPU干预。
当DMA和CM3核同时访问相同的目标(外设或者RAM)时,总线仲裁器会循环调度,确保CM3核得到至少一半的系统总线带宽。
4.Cortex-M3的内核是指MCU的CPU,而完整的MCU还要加上其他外设,如存储器、IO等其他模块。
下图是一个MCU内部组成结构图
5.ARM结构的发展V4(ARM7)、V5(ARM9)、V6(ARM11)、V7A/R/M(Cortex系列)
6.指令集
32位ARM指令集:对应ARM状态
16位Thumb指令集:对应Thumb状态(是ARM指令集的一个子集)
7.Cortex-M3使用的是Thumb-2指令集:支持16位/32位指令,因此不用再来回切换
8.Cortex-M3为32位处理器内核。存储器机构、寄存器、内部的数据路径都是32位的。采用哈弗结构,拥有独立的指令总线和数据总线,使得指令总线和数据总线并行不悖。但是指令总线和数据总线共享同一个存储器空间。
转自:fx427103
【工作模式】
线程模式(Thread mode):处理器复位或异常退出时为此模式。此模式下的代码可以是特权代码也可以是用户代码,通过CONTROL[0]控制。
处理模式(Handler mode):出现异常(包括中断)时进入此模式,此模式下所有代码为特权访问。
【代码限权】
特权访问:对处理器资源拥有完全访问限权;处理器复位后进入此访问模式;清零 CONTROL[0]进入用户模式。
用户访问:禁止访问多数系统寄存器。只能通过进入异常(中断)来返回特权模式。进入异常前是用户级访问,则退出异常时自动回到用户及,除非在异常中修改CONTROL[0]位。
【工作状态】
Thumb状态(正常执行指令状态)和调试状态;
【寄存器】
r0-r12:通用寄存器,其中r8-r12只能被32位指令访问。
r13(SP):堆栈指针;线程模式时可以在线程堆栈和主堆栈之间切换,但处理模式只使用主堆栈。两个堆栈同一时刻只有一个可见,进入、退出异常时自动切换堆栈。
r14(LR):链接寄存器,保存子程序或异常的返回地址(要实现嵌套,必须入栈)。
r15(PC):程序计数器。
xPSR:特殊用途的程序状态寄存器。
【异常】
进入异常步骤:
1、处理器在当前堆栈上把xPSR、PC、LR、r12、r3~r0八个寄存器自动依次入栈。
2、读取向量表(如果是复位中断,更新SP值)
3、根据向量表更新PC值
4、加载新PC处的指令(2、3、4步与1步同时进行)
5、更新LR为EXC_RETURN(EXC_RETURN表示退出异常后返回的模式及使用的堆栈)。
退出异常步骤:
1、根据EXC_RETURN指示的堆栈,弹出进入中断时被压栈的8个寄存器。
2、从刚出栈的IPSR寄存器[8:0]位检测恢复到那个异常(此时为嵌套中断中),若为0则恢复到线程模式。
3、根据EXC_RETURN,选择使用相应SP。
末尾连锁(Tail-chaining):当前正在执行中断,又有一个中断到来且这个中断优先级比正在执行的中断优先级低(如果有其他被压栈的低优先级中断则要比这些中断优先级高),这个中断暂时被挂起,等到当前中断执行完后不再执行堆栈操作,而直接进入挂起的中断。
迟来:前一个中断还没有进入执行阶段(但处理器状态已经保存),后面来了一个高优先级中,则前一个中断被抢占,后来的高优先级中断不需要再保存寄存器状态。
【中断】
Cortex-M3中有两个优先级的概念——抢占式优先级和响应优先级,有人把响应优先级称作'亚优先级'或'副优先级',每个中断源都需要被指定这两种优先级。
具有高抢占式优先级的中断可以在具有低抢占式优先级的中断处理过程中被响应,即中断嵌套,或者说高抢占式优先级的中断可以嵌套低抢占式优先级的中断。
当两个中断源的抢占式优先级相同时,这两个中断将没有嵌套关系,当一个中断到来后,如果正在处理另一个中断,这个后到来的中断就要等到前一个中断处理完之后才能被处理。如果这两个中断同时到达,则中断控制器根据他们的响应优先级高低来决定先处理哪一个;如果他们的抢占式优先级和响应优先级都相等,则根据他们在中断表中的排位顺序决定先处理哪一个。
转自: uTank
一、工作模式
线程模式和手柄模式。
当处理器处在线程状态下时,既可以使用特权级,也可以使用用户级;另一方面, handler模式总是特权级的。在复位后,处理器进入线程模式+特权级。
二、异常和中断
Cortex-M3 支持大量异常,包括 16-4-1=11个系统异常,和最多 240 个外部中断——简称 IRQ。具体使用了这 240 个中断源中的多少个,则由芯片制造商决定。由外设产生的中断信号,除了 SysTick的之外,全都连接到 NVIC 的中断输入信号线。典型情况下,处理器一般支持 16 到 32 个中断。
类型编号为 1-15 的系统异常,从 16 开始是外部中断类型。
三、向量表
因为地址 0 处应该存储引导代码,所以它通常映射到 Flash或者是 ROM 器件,并且它们的值不得在运行时改变。然而,为了支持动态重分发中断, CM3 允许向量表重定位——从其它地址处开始定位各异常向量。这些地址对应的区域可以是代码区,但更多是在 RAM 区。在 RAM 区就可以修改向量的入口地址了。为了实现这个功能, NVIC中有一个寄存器,称为“向量表偏移量寄存器”(在地址 0xE000_ED08处),通过修改它的值就能重定位向量表。
向量表偏移量寄存器(VTOR)(地址:0xE000_ED08)
来源:嵌入式ARM
前几天刚好同事问起在Cortex-M上延时不准的问题,在网上也没找到比较满意的答案,干脆自己对这个问题做一个总结。
根据我们的经验,最容易想到的大概通过计算指令周期来解决。该思路在Cortex上并不是很适用:一方面MCU从Flash取指是有延时的,另一方面Cortex的指令集不是固定周期的,特别从M3加入分支预测后,分支指令在Cortex-M不同型号上的结果都不相同。因此除了指令周期外,我们需要考虑的东西还有很多,才能得到正确的结果。
不带分支预测器的情况
仍然先从不带分支预测器的Cortex-M0开始,通过计算指令周期延时的实现代码如下:
void delay_us(us) {
delay_ntimes((us * sysclk - 8) / 4);
}
__asm void delay_ntimes(unsigned int n)
{
L1
SUBS R0, #1
BCS L1
BX LR
}
从这段代码可发现两个主要问题:
一、delay_us里的公式是怎么来的:
假如想延时us微秒,系统时钟为48MHz,即sysclk=48,那么周期数period_count满足以下公式:
period_count = us * sysclk;
然后再delay_ntimes这个函数,又能推出period_count还满足以下公式(见第二个问题的分析):
period_count = 8 + 4 n
于是:
n = (us sysclk - 8 ) / 4;
这就解决了第一个问题,需要注意的是:该公式忽略了跳转到delay_us和(us * sysclk -8 )/4的几个固定周期。
二、delay_ntimes的周期数怎么算:
它的周期数满足以下公式:
period_count = 8 + 4 * n;
这个要根据指令集的周期数来确定,请看下表:
先考虑n为0的情况,
SUBS为1周期+BCS为1周期+BX为3周期+外层调用delay_times(相当于BLX指令)的3周期=8周期。
当n不为0时,将再执行n次SUBS和BCS执行,SUBS仍为1周期,BCS有跳转3周期,所以是4n个周期,因此该函数的执行周期数为:
period_count=8+4n;
好了,在了解了原理之后,是时候到真正的板子上去测试了。
然而在MCU上的实测结果却不如预期,延时5MS,实测为7.5MS;延时10MS,实测15MS。为什么会出现这样的现象?
这个跟MCU的设计有关。一般代码都放在FLASH上,MCU中Cortex核要从FLASH上先取出指令,然后才能将指令放到指令流水线上执行。而上面的分析忽略了Cortex核从FLASH取出指令的时间,因此实测值与理论值分析不一致。
不同的MCU从FLASH读取指令的时间消耗各不相同,因此需要根据不同MCU去调整公式,这是一个比较繁琐的过程,比如这款MCU,将公式修改为(us * sysclk - 8) / 6就得到了正确结果。
另外一个做法是不修改公式,将延时代码放到RAM中,许多MCU从RAM取出指令没有等待周期。使用该方法再次测试,延时结果与理论计算一致。
但值得注意的是,不是所有MCU都满足RAM取值零等待周期的条件,因此一定要做测试。
读者若对MCU如何从FLASH读取指令感兴趣,参考资料[4]的分析是比较清楚的。
带分支预测器的情况
将上面的代码放到Cortex-M3和Cortex-M4的芯片上测试,测试结果是错误的,不论在FLASH还是在RAM中,这个是由于Cortex-M3,Cortex-M4上的指令流水线带有分支预测器引起的。
要了解分支预测器,就不得不提指令流水线。Cortex-M3是三级流水线:取指,解码,执行。但是没找到CORTEX方面较好的图,以下讨论就基于下图的4级流水线,该图多了一步:写回。这并不影响我们的讨论。
(该图引用自参考资料[1])
假设一条指令从执行开始到执行结束需要4个时钟周期,在没有流水线的情况下,需要等待第一条指令执行结束,才能取第二条指令,这时两条指令就用了8个周期,效率是很低的。
引入4级流水线将指令拆成4个步骤:取指、解码、执行、写回。当第一条指令处于解码时,同时对第二条指令取指;对第一条指令执行时,同时对第二条指令解码,对第三条指令取指;对第一条指令写回时,同时对第二条执行,第三条解码,第四条取指;如此这般。最终达到的效果就如上图所示,只有第一条指令需要4个周期,其他后续的指令都只需要1个周期,极大地提高了处理效率。
流水线的高效率是基于指令顺序执行的前提,在执行跳转指令时,流水线将被清空,又回到了上图中的第一步,跳转后的第一条指令要执行仍然需要4周期。因此如果程序频繁跳转,流水线的作用就大打折扣。
为了解决这个问题,就引入了分支预测器:它会提前检测到跳转指令,并根据预判结果取指。如果预判结果是不跳转,就按顺序取下一条指令;如果预判结果是跳转,就从跳转的目的地址取下一条指令。假如预测对了,那么流水线就不会被清空,仍然可以一条指令1个周期;如果预测错了,下一条指令仍然要4周期。从这里看出,分支预测器对于提高流水线效率是有帮助的。值得一提的是,预判对了能减少指令延迟,但是否是零延迟取决于MCU的设计;预判错了清空流水线也未必是唯一的做法,同样取决于MCU的设计。
回到Cortex-M3的延时问题,网络上找到的资料提到分支预测器将延迟减小到1个周期,没有找到更详细的说明。那么理论上计算公式就应该调整为(us * sysclk - 8) / 3,在两款Cortex-M3和两款Cortex-M4上测试,测试结果与理论值一致。
微秒级精确延时的其他方法
对于Cortex而微秒级延时最通用的方法,大概便是通过比较SysTick的SYST_CVR寄存器来做延时,理论误差在1us内(基于48MHz主频)。以下为实现代码:
/*
* 使用SysTick的CVR实现微秒级精确延时,一般SysTick周期设置为10MS,因此该方法适用于10MS以内的延时
*/
void delay_us(int us) {
unsigned t1, t2, count, delta, sysclk;
sysclk = 48;//假设为48MHz主频
t1 = SYST_CVR;
while (1) {
t2 = SYST_CVR;
delta = t2 < t1 ? (t1 - t2) : (SYST_RVR - t2 + t1) ;
if (delta >= us * sysclk)
break;
}
}
其他补充点
1、本文假设在延时过程中没有产生任何中断,如果有中断产生,将影响延时精确性。
2、这部分的内容属于计算机体系结构。
3、以上测试时间范围在[0,10MS),该范围之外未详细测试,建议采用其他方法。
4、覆盖测试的MCU:1款Cortex-M0,2款Cortex-M3,2款Cortex-M4。
5、在我测试的两款Cortex-M3 MCU上,将代码都放RAM上,测试结果比放在FLASH差,而在Cortex-M4 MCU上,测试结果都一样,目前没有找到合理的解释。
参考资料
1、浅谈分支预测、流水线与条件转移
2、Cortex-M0指令集
3、CPU性能衡量参数-主频,MIPS,CPI,时钟周期,机器周期,指令周期
4、Cortex-M3的周期判断的依据是什么
5、计算机体系结构——流水线中的相关——延迟分支方法
文章来源:博客园