Cortex-M3
作者:dogsun88
一、进入异常之前处理器可能的状态有:
1、 handler
2、 线程,MSP
3、 线程,PSP
二、产生异常时:
1、 有一个压栈的过程,产生异常时使用PSP,就压入到PSP中,产生异常时使用MSP,就压入到MSP中
2、 会根据处理器的模式和使用的堆栈,设置LR的值(当然设置完的LR的值再压栈)
三、异常返回时:
根据LR的值,判读使用那个堆栈,然后再从相应的堆栈中POP数据到寄存器。
举例说明:
在利用OSStartHighRdy->OSPendSV->OSPendSV_nosave启动第一个线程时,在异常进入的时候,压栈到MSP(不会影响PSP的内容),在通过BX LR指令从异常返回之前,有一句ORR LR, LR, #0x04来设置LR的值,保证我们从异常返回时是从PSP中POP数据到寄存器里,而此时PSP的值是OSTCBHighRdy->OSTCBStkPtr,即任务创建时定义的堆栈数组,同时在OSTaskStkInit函数中对该任务数组初始化时,要保证其组织结构和异常产生时处理器自动压栈产生的栈的结构相同。
四、栈中的内容分析:
1、 变量
2、 调用函数时,如果子函数比较复杂,会由编译器自动的压入r4-r11, lr(压入多少寄存器,由子函数的复杂程度决定的)
3、 异常保存,压入栈的是r0-r3,r12,LR,PC,xSPR(硬件直接完成的)
4、 进程切换,所有寄存器全部压栈
由于异常死机了之后的具体操作:
产生异常时,两个值我们需要,一个是pc,一个是LR,通过LR找到栈, 再通过栈找到pc
1、 如果LR=0xFFFFFFF9说明产生异常的时候使用的是MSP,我们只需要读出当前sp的值,
sp += 0x1c;读出的内容就是产生异常时压入栈的PC的值,这个值跟反汇编代码对比,就能得到具体哪句话产生的异常。
2、 如果LR=0xFFFFFFFD明产生异常的时候使用的是PSP,我们需要读出PSP的值,不要直接来读sp的值,在keil集成开发环境中,调试时寄存器窗口有个Banked选项,会给出当前PSP的值,当然也可以在异常处理中加入两句话:
MRS R0,PSP
PUSH {R0}
我们就能在当前MSP中得到,我们PSP的值了,之后操作和上面一样,psp+=0x1c;读出的内容就是产生异常时压入栈的PC的值,这个值跟反汇编代码对比,就能得到具体哪句话产生的异常。
文章来源: ChinaUnix
在项目开发的过程中,发现程序总是死在判断DMA一次传输是否完成这个标志位上。进一步回退分析,发现是在I2C读的过程中,有使用到DMA去取外部I2C设备的data。
但是data并没有读完,Data为32bits,DMA在读到18bits时,就出现读不到data bit了。导致I2C硬件模块不能进一步动作,SCK一直被拉低,没有clock输出,SDA也是如此。
下面是通过示波器抓到的波形:
在上面的波形图中,绿色的是SCK,蓝色的是SDA。
在第一幅波形图中,有2段波形,第一段连续的I2C波形,经过确认I2C硬件和DMA配合是正常的。第二段则是有一段I2C波形,然后就SCK和SDA就都被拉低了。
将第一幅图的第2段波形放大,就是第二副图看到的情况。可以很明显的看到SCK输出有被其他因素打断。I2C吐出几个clock,被其他因素打断了,clock线即SCK被拉低一段时间,然后clock线再继续吐出几个clock。
直到I2C被频繁中断,clock吐不出来为止,SCK和SDA都被拉低,此时明显的I2C和DMA的配合过程被其他因素频繁的干扰打死了。
通过示波器抓到的波形验证了这一点,然后再来分析代码和串口输出,发现是外部GPIO一直有中断输入,Cortex-M3 MCU频繁的响应中断,导致I2C&DMA操作被打挂了。
有什么办法来解决这个问题?
方法就是在I2C和DMA操作的过程开始处关闭所有中断,而在操作结束的时候重新打开中断,以免I2C&DMA操作被其他中断打断。
ARM MDK编译环境自带的编译器ARMCC,含有内置的c函数,可供操作中断用:
__enable_irq();
__disable_irq();
不过debug发现这两个函数只会在privileged mode使用。也就是说需要Cortex-M3 MCU先进入privileged mode,才能调用这两个函数。
用什么方法让MCU从user mode切换到privileged mode呢?
exception handler!
可以用SVC啦,软件可以利用SVC制造一个exception,然后在exception handler中利用MCU的privileged mode来完成自己的任务。有点类似于linux里面的系统调用。
SVC exception可以调用SVC函数,而SVC函数可以传入参数,也可以返回参数。转为系统调用而设计。
举个例子,用户程序调用read()这个系统调用,read()会引发SVC exception,进而调用SVC函数,read()函数的参数传递给SVC函数,SVC在内核态执行硬件动作,并将SVC函数的返回结果,作为read()函数的返回,返回给用户程序。当然linux里面并不一定是SVC,这里只是做个类比。
也就是说SVC可以完成从用户态到内核态的转变,不让用户直接操作硬件。用户只需要记住系统调用API的名字和函数即可,而不用管硬件的具体实现。
所以这里我们就把I2C读的操作放在一个SVC函数里面去实现,并且在SVC函数的开始处调用__disable_irq();在函数的结束处,调用__enable_irq()。
经过验证,I2C&DMA操作再也不会被中断打断了。
参考资料:
1、http://www.keil.com/pack/doc/cmsis/Core/html/group___core___register__gr...
2、cortex-M3权威指南
文章来源: 博客园
大部分采用Cortex-M3/M4 MCU的目标应用是便携式的,并且供电电源来自电池或能源收集系统,因此我们所探讨的大部分概念涉及如何减少系统整体能耗的技术。然而,在许多情况下,这些节能技术也是处理器应用设计的有力工具,可提供:
●更符合成本效益的解决方案
●更大的升级和采用新特性的设计冗余
●有助于产品在激烈竞争市场上脱颖而出的性能和特性
小知识:Cortex-M3对比Cortex-M4
Cortex-M3架构背后的指导思路是设计一种既要满足应用的成本效益又要提供高性能计算和控制1的处理器。类似的应用包括汽车车身系统、工业控制系统和无线网络/传感器产品等。M3系列为32位的ARM处理器架构引进了多项重要特性,包括:
●不可屏蔽式中断
●高度确定性、嵌套、向量式中断
●原子位操作
●可选的存储保护(MPU)
除了绝佳的计算性能,Cortex-M3处理器先进的中断结构还能确保系统迅速响应真实世界的事件,同时仍然提供极低的动态与静态功耗。
SWO接口节省I/O引脚并加速调试
除了传统的串行调试(Serial Wire Debug)功能之外,基于ARM Cortex-M的MCU还可以通过它的单引脚串行监视器输出(Serial Wire Viewer Output,SWO)3提供指令跟踪接口,如图2所示。这个接口可以直接把“printf格式的”调试信息传递给应用代码。SWO允许调试信息直接在任何标准的IDE中浏览。此外,这些信息也可以用独立的SWO监视器(例如,Segger的J-Link SWO Viewer软件4,或是Silicon Labs的energyAware Commander 4)进行浏览。由于SWO输出内建于内核硬件本身,因此它是Cortex-M内核与生俱来的优点。SWO不占用MCU的任何UART接口,这些接口它们可能早已被分配给了应用。
基于SWO的调试还有一个重要的优势在于,它让微控制器在进入最低的休眠模式时,保持调试连接有效,而在大多数情况下,传统的调试连接这时是不能正常工作的。SWO的指令追踪还可以用于跟踪程序计数器,以帮忙IDE统计出程序各项功能所占用的时间。这些统计数字能够与电流测量结合起来,帮助开发人员对设计功耗进行微调。
目前一些供应商正在开始重新认识这项优点,而且有些厂商已经为了这个目的而把功耗模式和电流测量硬件纳入到本身的开发平台。例如,Silicon Labs的EFM32 Gecko MCU入门级和开发级工具包都包含功耗测量输出,并可搭配energyAware Profiler工具6中的程序代码追踪功能。图3显示了如何让设计人员精确定位到哪个程序功能块最耗费能源,并且能够快速调试其它与能源有关的问题。
智能休眠节省每一微瓦
ARM Cortex-M处理器的Sleep-on-Exit(中断完成时直接进入休眠)是另一项“一箭双雕”的功能,可同时节省CPU周期和能耗。这点在由中断所驱动的应用中格外有用,因为处理器的大部分时间不是在执行中断处理,就是在中断事件之间休眠。在进入中断服务例程(ISR)时,MCU必须花费好几个指令周期把当前线程状态入栈,然后在退出中断处理返回时恢复原有线程状态,即“出栈”。当应用需要处理器在退出ISR后直接进入休眠状态时,传统MCU仍然必须恢复原先存储的状态信息,然后线程代码才能让MCU进入休眠状态。同样地,当下次的中断唤醒MCU时,它的状态必须再次入栈。
而当使能ARM Cortex-M微控制器上的Sleep-on-Exit功能后,MCU就会在中断处理完成后直接进入休眠状态,而不用先返回到原有线程上(见图4)。这会使处理器仍然保持在中断状态,因为消除了唤醒再入栈过程,因而节省下许多宝贵的机器周期。消除入栈出栈过程既节省了时间也节省了能耗,否则电能就会被不必要的指令周期白白消耗,也包括哪些传统MCU在休眠和唤醒之间管理堆栈的代码。而且,当处理器被中止调试请求(Halt Debug Request)唤醒时,出栈过程将会自动进行。
ARM Cortex-M4运行更快、休眠功耗更低
像许多MCU一样,Cortex-M3/4处理器通常能够采用高时钟速率的方法在中断驱动的应用中节省能耗。如果处理器大部分时间处于休眠状态,这种看似违背直觉但普遍采用的节能策略就会很好,因为运行时间减少所节省的能耗远远大于稍高的操作电流。简单来说,多花10%的电可以省掉 20%的时间,总体来说是节能了。
这种技术可以应用在任何Cortex-M系列的处理器上,而涉及密集运算任务的应用也能从Cortex-M4处理器的额外能力中受益。它的单周期DSP指令和可选的浮点加速器能大大减少诸如数字信号处理、过滤、分析或波形合成等功能所需要的执行周期数。
上面提及的节能特性也能带来其它优势。例如,在超音波/声学水表之类的应用中,它们必须在小电池供电下运行多年,需要MCU尽可能长的保持在休眠状态。除了有助于减少MCU唤醒时间之外,Cortex-4 DSP和浮点算术指令也能使用成熟的滤波功能从廉价声学传感器输出中获得所需的信息,从而避免采用昂贵的超声波流量传感器。在这个应用实例中,Wonder Gecko MCU的外设还能够作为模拟状态机提供额外的能量节省,它仅仅在需要时才唤醒Cortex-M4处理器。
虽然并不完备,但这些林林总总的秘诀与妙方应该能让各位产生好的思路,可以在下一次设计中充分利用Cortex-M系列中一些较不为人知的特性所带来的好处。为了发挥ARM Cortex-M系列的这些和其它重要功能,可参考本文末段的参考资料,它们提供了所需的更多细节。
文章来源: 畅学电子网
作者:sunheshan
1、 异常
M3支持15个系统异常,240个外部异常IRQ,其中NMI,复位,hardfault三个异常的优先级固定不可更改且是负数,其余的都可编程。
M3的异常分为抢占优先级和子优先级。
2 、关于中断优先级
M3中,除了复位,NMI以及硬fault有固定的优先级,其他优先级都是可编程的。原则上,CM3支持3个固定的高优先级和多达256级的可编程优先级,并且支持128级抢占。但是,绝大多数CM3芯片都会精简设计,以致实际上支持的优先级数会更少,如8级,16级,32级等。它们在设计时会裁掉表达优先级的几个低端有效位。
如使用3个位表示优先级则配置寄存器的结构如图所示:
在M3中,优先级分为两个部分,一个是抢占优先级优先级,一个是子优先级。在计算抢占优先级和子优先级的有效位数时,必须要知道:
1、芯片实际使用了多少位来表达优先级
2、优先级组是如何划分的。
举个例子,如果只使用3个位来表达优先级([7:5]),并且优先级组的值是5(从比特5处分组),则得到4级抢占优先级,且在每个抢占优先级的内部有2个子优先级,
当然也可设置全部是抢占优先级没有子优先级
在编写应用程序的时候,只需要系统所需要的中断分组和组内优先级即可,直接调用m3提供的API函数即可。
在CM3中,允许使用3个位到8个位来表达优先级。为了确定具体的位数,可以先往一个优先级寄存器中写0xFF,再读回来,读出多少个1,就表示使用多少个位来表达优先级。
关于中断配置基础
每个外部中断都在NVIC的下列寄存器中“挂号”:
1、使能与除能寄存器
2、悬起与“解悬”寄存器
3、优先级寄存器
4、活动状态寄存器
5、异常掩蔽寄存器(PRIMASK, FAULTMASK以及BASEPRI)
另外,下列寄存器也对中断处理有重大影响:
1、向量表偏移量寄存器
2、软件触发中断寄存器
3、优先级分组位段
其他异常配置寄存器
系统Handler控制及状态寄存器SHCSR用法fault,总线fault以及存储器管理fault都是特殊的异常,因此给它们开了小灶。它们的使能控制都是通过SHCSR来实现,各种faults的悬起状态和大多数系统异常的活动状态也都在该寄存器中。
中断控制及状态寄存器ICSR
在大多数情况下,它们对于应用软件都没有什么用处,只有悬起位对应用程序常常比较有参考价值。
异常屏蔽寄存器
PRIMASK用于除能在NMI和硬fault之外的所有异常,它有效地把当前优先级改为0(可编程优先级中的最高优先级)。该寄存器可以通过MRS和MSR以下例方式访问:
1、关中断
MOV R0, #1
MSR PRIMASK, R0
2、开中断
MOV R0, #0
MSR PRIMASK, R0
此外,还可以通过CPS指令快速完成上述功能:
CPSID i ;关中断
CPSIE i ;开中断
FAULTMASK更绝,它把当前优先级改为-1。这么一来,连硬fault都被掩蔽了。使用方案与PRIMASK的相似。但要注意的是,FAULTMASK会在异常退出时自动清零。
掩蔽寄存器虽然能一手遮天,却都动不了NMI,因为NMI是用在最危急的情况下的。因此系统为它开出单行道,无需挂号只是不要迟到。当NMI激活时,“谁都是省略号,唯独是你不得了,第一优先谁比你重要”!试想,如果NMI被连接到系统的掉电报警线上,且系统是体外循环机的电源管理器……如果因为中断被除能就视而不见,则会使体外循环机因断电而失能,体外循环序列可以被意外终止,病人的生命也将丢失。
Active状态寄存器
每个外部中断都有一个活动状态位。在处理器执行了其ISR的第一条指令后,它的活动位就被置1,并且直到ISR返回时才硬件清零。由于支持嵌套,允许高优先级异常抢占某个ISR。然而,哪怕中断被抢占,其活动状态也依然为1。
3、使用中断
对于应用程序存储在ROM中,不需要更改异常服务程序。
建立优先级组
为该中断指定优先级
使能该中断
4、 cm3.h中操作中断相关函数
__STATIC_INLINE void NVIC_SetPriorityGrouping(uint32_t PriorityGroup) //---设置组优先级
__STATIC_INLINE uint32_t NVIC_GetPriorityGrouping(void) //---获得组优先级
__STATIC_INLINE void NVIC_EnableIRQ(IRQn_Type IRQn) //---使能中断
__STATIC_INLINE void NVIC_DisableIRQ(IRQn_Type IRQn) //---除能中断
__STATIC_INLINE uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn) //---获得被挂起的中断
__STATIC_INLINE void NVIC_SetPendingIRQ(IRQn_Type IRQn) //---挂起中断
__STATIC_INLINE void NVIC_ClearPendingIRQ(IRQn_Type IRQn) //---清除挂起中断
__STATIC_INLINE uint32_t NVIC_GetActive(IRQn_Type IRQn) //---中断是否活动
__STATIC_INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority) //---设置中断优先级
__STATIC_INLINE uint32_t NVIC_GetPriority(IRQn_Type IRQn) //---获取中断优先级
__STATIC_INLINE void NVIC_SystemReset(void) //---NVIC 复位
5、 用户EFM32中断
(1)优先级数目
首先对于特定处理器,支持的外部中断数不一样,故在设置中断优先级时需要知道芯片实际使用了多少位来表达优先级。在EFM32中:__NVIC_PRIO_BITS定义efm32g280f128.h当中。
#define __NVIC_PRIO_BITS 3 /**< NVIC interrupt priority bits */
在cm3.h的优先级设置函数设置优先级语句为:
NVIC->IP[(uint32_t)(IRQn)] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff);
从cm3.h设置优先级函数可知芯片实际用了(8-__NVIC_PRIO_BITS)表达优先级。这个EFM32在startup.s提供的30中断向量数目符合。
(2)优先级组
优先级数目是定死了的,对于优先级组AIPCR[8:10]复位值为0,对于(8-__NVIC_PRIO_BITS)优先级数目来说就全部是抢占优先级而没有子优先级。
对于应用程序,可以根据工程需要对优先级组进行设定。
void NVIC_SetPriorityGrouping (uint32_t PriorityGroup)
PriorityGroup
有7-PriorityGroup位表示优先级组。如PriorityGroup为5,则[7:6]表示优先级抢占位段。
(3)开关可屏蔽中断
在core_cmFunc.h中定义了与PRIMASK中断操作相关的函数
__disable_irq(void);
__enable_irq(void);
__get_PRIMASK(void);
__set_PRIMASK(uint32_t priMask);
6、使用SVC
SVC指令带一个8位的立即数,可以视为是它的参数,被封装在指令本身中,如:
SVC 3 ;呼叫3号系统服务
SVC是用于呼叫OS所提供API的正道。用户程序只需知道传递给OS的参数,而不必知道各API函数的地址。
文章来源:CSDN