一个关于STM8中断应用异常的话题

转载:Miler Shao@ST MCU 信息交流


某日一工程师跟我反映,他在使用STM8S芯片开发产品,用到某ADC通道,使用连续采集模式,开启ADC转换结束中断。整个中断程序执行时间大概200多us,因为连续采集转换,在这个ISR处理过程中可能会有新的EOC标志产生。
他发现一个奇怪的现象,ADC中断服务程序能够不停的自己嵌套自己,仿佛进入了递归嵌套,最后导致堆栈溢出跑飞而令系统复位。在调试过程中也的确能发现ADC中断服务程序有连续多次入栈的情况。
一个关于STM8中断应用异常的话题
文章图片



后来他发现这个异常跟在ADC ISR中首尾分别加了一句关中断和开中断语句有关。即在ISR的开头加了disableInterrupt(); ISR结尾部分加了 enableInterrupt();
一个关于STM8中断应用异常的话题
文章图片

一个关于STM8中断应用异常的话题
文章图片


如果拿掉首尾那2句开关中断的代码就一切正常。现在最大的疑问就是为什么加了这两句开关中断代码会导致ADC中断服务程序能自己嵌套自己?按道理说应该没有影响才对啊?

关于中断函数不少人可能会这样写,在ISR的首尾分别加上关中断和开中断指令,尤其51系列过来的人,经常会在进中断后来一句CLR EA,中断退出之前补一句 SETB EA之类的指令。51系列芯片那样写是没有问题的。
那对于STM8芯片来讲,这样写很可能就会出问题。有时或许有跟上面一样的写法而没有出问题,那是因为你程序简单没有复杂的多中断响应事件,或者说即使某时段只有单一中断源,但在中断响应过程中并无新的中断请求发生。
说实在的,要解释上面工程师的疑问,有点说来话长。不妨先直接说出原因,有兴趣的话就细看技术资料或下面的分析。
显然,问题就出在他在ISR里面做了关中断、开中断的操作,随意了改变正在执行的中断服务程序的中断优先级而导致的问题。
那个disableInterrupt(); 函数所对应的汇编指令就是SIM,该指令就是关闭所有可屏蔽的中断请求,此时执行程序所处中断优先级为L3,即软件中断禁用级。正因为如此,此时不会发生RESET/TRAP/TLI以外的中断。
下面截图是STM8技术手册关于SIM指令的描述。在复位后的程序开始使用该指令是没用的。【为什么没用呢,因为程序复位后所处中断优先级就是L3。等会下面还会提到。】
红色方框内那句明确说明,在中断执行程序的开头不需使用该指令【SIM】,因为当前中断程序的优先级已经被自动的在CC寄存器中L1,L0位设定。【到底怎么设定自动的呢,下面文字在讲ITC_SPR寄存器时有描述。】
一个关于STM8中断应用异常的话题
文章图片



再来看看 enableInterrupt(); 其实它所对应的汇编指令就是 RIM.该指令就是解除所有中断屏蔽,允许各路中断请求。执行该指令后,此时执行程序所处中断优先级为L0,即主程序运行级别,属于最低优先级别。换句话说,此时任何新的或待处理的中断请求都可以中断或打断当前的执行程序。

下面是STM8相关技术手册对RIM指令的描述。它说该指令一般放在复位之后的主程序代码里,并提示在WFI/HALT指令前无须使用该指令。【此处并未对该提示作细节展开。它意思是说WFI/HALT指令还具备跟RIM指令同样的解除中断屏蔽、允许中断请求的功效。】
一个关于STM8中断应用异常的话题
文章图片


好,那我们结合上面案例具体分析下。
先假设上面那位工程师对ADC中断的软件优先级设置为2.【当然也可设置为3或1】
当ADC产生EOC中断请求进入中断服务程序【ISR】后,他首先来个SIM指令【即disableInterrupt(); 】,此时ISR程序不管刚才响应时刻的中断优先级是多少,现在被强行更改变为L3。从这点看,这个指令用得就显得随意或任性,在有多个中断源的情况下,有可能无意中改变了设计者关于各个中断响应轻重缓急的先后秩序的初衷。
继续回到这个话题。当ADC ISR执行一定时间后,在IRET返回前他又来个RIM指令。前面我们介绍了RIM指令的作用,此时ISR的中断优先级又由刚才的L3跳到L0,即最低软件优先级。可此时外边侯着的新的ADC EOC中断请求,它的软件中断优先级一定高于L0,机会来了,不用等本次ISR执行完,立即嵌套进入下一轮ADC ISR 程序。每进一次还要压一次栈,这样多次循环下去,直至堆栈穿底,PC乱飞,系统崩溃复位。

原因大致就这样,不要想当然、随意地在ISR里面添加些影响执行程序优先级的指令,换句话说你在使用类似SIM/RIM指令时一定要心里清楚在做什么,有什么目的,会对当前中断程序优先级产生什么影响。

如果希望在执行某中断程序时不要被其它可屏蔽的中断请求打断,怎么办呢?很简单,将相关中断的ITC_SPR寄存器中相应软件优先级位配置为L3即可。当中断程序执行完毕退出后再回到之前的软件中断优先级。

关于STM8中断原理,个人觉得技术资料讲得不是很细致。这里根据个人的理解做了个简单的总结,一起分享下。并在后面用个实例验证上面的分析结论。



STM8中断可分为不可屏蔽中断和可屏蔽中断。不可屏蔽中断是指RESET,TRAP,TLI.可屏蔽中断是指那些基于GPIO的外部中断和其它外设中断。


一个关于STM8中断应用异常的话题
文章图片




中断优先级分硬件优先级和软件优先级。硬件优先级是固定的,参照各芯片数据手册里的中断矢量表的序号来定,序号越小硬件优先级越高。各中断源或事件的软件中断优先级可以通过寄存器编程配置,其中RESET,TRAP非屏蔽中断事件无软件中断优先级。


一个关于STM8中断应用异常的话题
文章图片



中断的管理主要基于两个寄存器,一个是CCR,一个是ITC_SPRx. 其中CCR寄存器中的L1和L0位表示当前程序执行代码所处中断优先级。ITC_SPRx.各寄存器分别对应各个中断服务程序的软件中断优先级,用户可自行配置各中断服务程序所处的中断优先级。软件中断优先级分为4级,从低往高依次是LEVEL0,LEVEL1,LEVEL2,LEVEL3,分别对应CCR寄存器中的L1、L0位的10,01,00,11。
L0级即用户主程序级,就是程序处在按部就班的用户主程序代码执行状态,为最低软件优先级。此时CCR寄存器中的L1、L0为10。
L3级又称软件中断禁用级。这个地方的表述和理解可能有点费劲。在L3软件中断优先级状态下,不会发生软件中断,即所谓的中断关闭状态。此时CCR寄存器中的L1、L0为11。其中,RESET\TRAP\TLI三个中断源是特例,可以打断L3优先级状态下的执行程序而产生中断。
总之,STM8芯片任何时刻的程序执行总处于某一软件中断优先级状态,其软件中断优先级由当前CCR寄存器中的L1、L0位决定。理解这个概念很重要。

下面截图是对CCR寄存器描述,系统复位后的默认值为0x28,对应到L1\L0就是11。即系统复位后中断软件优先级为L3,软件中断禁用级,也就是中断关闭状态。这也正好印证了上面讲SIM指令时,芯片复位后使用SIM没啥意义。因为芯片复位后程序执行就处于软件中断禁用级。
一个关于STM8中断应用异常的话题
文章图片


一个关于STM8中断应用异常的话题
文章图片




上面提到,各个可屏蔽中断源的软件中断优先级可以通过ITC_SPRx 寄存器进行配置。一共8个ITC_SPR寄存器,每个寄存器最多可以配置4个中断源的软件中断优先级。当某中断请求得到响应时,该寄存器里相应中断源的VECTnSPR[1:0] 两位数据会自动载入到CCR寄存器中的L1、L0位进而决定该中断服务程序所处软件中断优先级。所以,在各中断服务程序里除非有需要,不要在中断服务程序里随意使用类似SIM/RIM这些导致改变执行程序中断优先级的指令。否则,程序优先级改变了而自己可能还不知晓。
一个关于STM8中断应用异常的话题
文章图片


我们说中断,提优先级,无非就是为了拟定妥善处理多个中断事件发生竞争时的处理规则。对于STM8芯片,在同一时刻多个中断请求发生时,响应规则如下:
中断请求中相对最高软件优先级的中断先响应;
如果软件优先级一样,硬件优先级相对最高的先响应;
一个关于STM8中断应用异常的话题
文章图片


一个关于STM8中断应用异常的话题
文章图片


当某中断程序正在执行时外部又产生了新的中断请求的处理规则:
如果新的中断请求的软件中断优先级高于正在执行的中断程服务序的中断优先级时,当前中断程序将被打断,保护好当前现场后转而去执行新的高优先级中断程序。即发生中断程序嵌套。因为软件中断优先级最高为3,不难理解当正在执行的中断程序的优先级为L3时是不可能再被RESET,TRAP,TLI以外的中断事件打断的,即呈现软件中断关闭状态。
另外,如果在中断服务程序里使用HALT,POPCC,RIM,SIM和WFI都可能会改变当前中断服务程序的中断优先级。
当在某ISR执行过程中调整L1、L0的值修改软件中断优先级,同时该中断请求标志没来及清除或新的中断请求已经产生,如果修改L1\L0后的新中断请求之软件中断优先级高于当前中断服务程序的优先级,这时就会发生中断重入,即该中断会被重新响应,重新运行该中断服务程序。上面工程师遇到的异常现象就是这种情形。

最后,以TIM1溢出更新中断服务程序为例,结合具体的测试代码一起验证下。此处把TIM1的溢出周期设置为2MS,TIM1中断执行时间大概16ms【当然是指不被中断的情况】,这样设计的目的是为了保证在中断服务程序执行过程中有新的TIM1溢出中断请求发生。中断程序要做的事情很简单,就是将某个IO口的电平先高后底各近8MS然后退出。【除了TIM1外,测试程序也没有别的其它中断使能。】

不妨看看下面两种不同情形,TIM1溢出中断服务程序的运行结果。

一、ISR里面不添加SIM/RIM指令时的情形。
__interrupt void TIM1_UPD_OVF_TRG_BRK_IRQHandler( void )
{
TIM1_ClearFlag(TIM1_FLAG_UPDATE); //清除TIM1更新标志
GPIO_WriteHigh(GPIOC,GPIO_PIN_6 ); // PC6 HIGH
// disableInterrupts();
Delay(21000); //延时约 7.8ms
GPIO_WriteLow(GPIOC,PGPIO_PIN_6 _); // PC6 LOW
// enableInterrupts();
Delay(21000); //延时约 7.8ms
}

这种情况下,整个中断服务程序运行过程中程序优先级没有变化。运行过程中不存在被中断的情况。Pc6输出波形如下图一。
一个关于STM8中断应用异常的话题
文章图片


输出波形图一【PC6 输出信号】
【一个关于STM8中断应用异常的话题】
二、ISR里面添加SIM和RIM或者只放RIM指令时的情形。
__interrupt void TIM1_UPD_OVF_TRG_BRK_IRQHandler( void )
{
TIM1_ClearFlag(TIM1_FLAG_UPDATE); //清除TIM1更新标志
GPIO_WriteHigh(GPIOC,GPIO_PIN_6 ); // PC6 HIGH
// disableInterrupts();
Delay(21000); //延时约 7.8ms
GPIO_WriteLow(GPIOC,GPIO_PIN_6 ); // PC6 LOW
enableInterrupts();
Delay(21000); //延时约 7.8ms
}

此时pc6波形输出混乱无章,如下图2。TIM1中断程序不停的嵌套重入,导致堆栈溢出,PC跑飞。显然跟上面的图1相差悬殊。另外,通过示波器观察芯片复位脚也可看到绵延不断的复位脉冲如下图3。
一个关于STM8中断应用异常的话题
文章图片



输出波形图二【PC6 输出信号】
从上面图二不难看出是因为中断不断自我重入,而进中断是先将PC6置高,所以大量的高电平呈现。
一个关于STM8中断应用异常的话题
文章图片


输出波形图三【复位脚信号】

最后的最后,估计很多工程师在编写STM8中断程序时,像上面的工程师那样写代码。有时因此而出现些莫名其妙的异常会让人很难找到错误的原因

    推荐阅读