你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

NVIC优先级+软件中断(SWIER)+软件仿真+硬件仿真(JTAG)+中断延迟

[复制链接]
吐息间丶时光中 发布时间:2017-3-7 22:58
前段时间贴主看了Cortex-M3的NVIC,对其中断嵌套甚是着迷,于是瞎搞了几下下……瞎搞就瞎搞嘛,但却搞出了问题……

废话不多说,直奔主题!

通过查看
Cortex-M3有关异常和NVIC章节的资料,贴主本来是想实现这么几个工程目标:
(贴主板子上的芯片型号是STM32F103C8T6,PA.8接的一个LED的阴极;MDK版本5.17&5.23)

================================================================
【1】不操作优先级寄存器,软件触发EXTI2中断进入其ISR里,再软件触发EXTI1。判断是否会发生中断嵌套?
  
工程预估:不会。
         why:①当不操作优先级寄存器时,Cortex-M3内核默认从比特0分组(因为SCB_AIRCR寄存器的优先级分组三位复位值为0),即优先级寄存器高四位均表示抢占优先级;
                  ②优先级寄存器NVIC_IPRx高四位复位值为0,由于没重新赋值,所以抢占优先级均为0;【也就类似于除能了CM3的中断嵌套机制。】
                  ③优先级寄存器高四位均表示抢占优先级且为0,亚优先级均为0。所以优先级取默认的硬件异常号,虽然EXTI1的默认异常号高于EXTI2,但不会发生中断嵌套。
-------------------------------------------------------------------------------------------------------------------------------
【2】优先级寄存器的高三位表示抢占优先级,且EXTI1和EXTI2抢占优先级相同,但EXTI2的亚优先级高于EXTI1。软件触发EXTI1中断进入其ISR里,再软件触发EXTI2。判断是否会发生中断嵌套?
  工程预估:不会。

         why:
二者抢占优先级相同,亚优先级的高低不导致中断嵌套。只是当抢占优先级相同的异常有不止一个同时悬起时,就优先响应亚优先级最高的异常。
-------------------------------------------------------------------------------------------------------------------------------
【3】优先级寄存器的高二位表示抢占优先级,且EXTI2抢占优先级高于EXTI1,但EXTI2的亚优先级低于EXTI1,在软件触发EXTI1中断进入其ISR里,再软件触发EXTI2。判断是否会发生中断嵌套?
  工程预估:会。

         why:
EXTI2抢占优先级高于EXTI1,亚优先级的高低不导致中断嵌套。

-------------------------------------------------------------------------------------------------------------------------------
【4】无抢占优先级,优先级寄存器的高四位均表示亚优先级,但EXTI2的亚优先级高于EXTI1,在软件触发EXTI1中断进入其ISR里,再软件触发EXTI2。判断是否会发生中断嵌套?
  工程预估:不会。

         why:
无抢占优先级【相当于除能了CM3中断嵌套机制】,亚优先级的高低不导致中断嵌套。

================================================================
很明显,每一个程序均非常简单。但将程序下载到硬件上跑的效果会不会如预估一样呢?

第一个工程的代码如下:

“main.c”源文件里
  1. #include "stm32f10x.h"

  2. #define GPIOA_ODR_Addr        (GPIOA_BASE+0x0C)                //GPIOA端口寄存器ODR的地址

  3. #define BitBand(Addr, BitNum)        *(__IO uint32_t *)((Addr&0xF0000000)+0x02000000+ \
  4.                                                                                                         ((Addr&0x000FFFFF)<<5)+(BitNum<<2))       
  5. #define PAout(n)        BitBand(GPIOA_ODR_Addr, n)        //偏移地址取的是32位寄存器的第一字节的,所以n的取值为0~31

  6. FlagStatus led = SET;                                                        //外部调用的全局变量(声明在stm32f10x_it.h头文件)

  7. static void RCC_Configuration(void);                        //函数原型声明
  8. static void GPIO_Configuration(void);

  9. /**********************************************************************
  10. * Function Name : MAIN
  11. * Description         : main program
  12. * Input                 : None
  13. * Output                 : None
  14. * Return                 : None
  15. **********************************************************************/
  16. int main(void)
  17. {
  18.         RCC_Configuration();
  19.         GPIO_Configuration();
  20.         PAout(11) = led;                                                        //上电关闭LED
  21.        
  22.         NVIC->ISER[0] = NVIC_ISER_SETENA_7 | NVIC_ISER_SETENA_8;
  23.         EXTI->IMR |= EXTI_Line1 | EXTI_Line2;
  24.        
  25.         EXTI->SWIER = EXTI_Line2;
  26.        
  27.         while(1)
  28.         {
  29.                 PAout(11) = led;
  30.         }
  31.        
  32. //        return (0);
  33. }

  34. /**********************************************************************
  35. * Function Name : RCC_Configuration
  36. * Description         : 配置时钟参数,使能外设时钟
  37. * Input                 : None
  38. * Output                 : None
  39. * Return                 : None
  40. **********************************************************************/
  41. static void RCC_Configuration(void)
  42. {       
  43.         RCC_DeInit();                                        //将外设RCC寄存器重设为缺省值(选择HSI作为系统时钟,其它保持复位值)
  44.         RCC_HSEConfig(RCC_HSE_ON);                //HSE晶振ON
  45.         if(RCC_WaitForHSEStartUp() == SUCCESS)        //等待HSE晶振稳定
  46.         {
  47.                 RCC_HCLKConfig(RCC_SYSCLK_Div1);        //设置AHB时钟(HCLK)—— HCLK = SYSCLK/1
  48.                 RCC_PCLK2Config(RCC_HCLK_Div1);                //设置高速AHB时钟(PCLK2)—— PCLK2 = HCLK/1
  49.                 RCC_PCLK1Config(RCC_HCLK_Div2);                //设置低速AHB时钟(PCLK1)—— PCLK1 = HCLK/2
  50.                 FLASH_SetLatency(FLASH_Latency_2);        //在SYSCLK周期与闪存访问时间插入2个等待周期
  51.                 FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);        //启用闪存预取缓冲区
  52.                 RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);        //设置PLL时钟源及倍频系数 —— PLLCLK = (HSECLK/1) * 9 = 72MHz
  53.                 RCC_PLLCmd(ENABLE);                                        //使能PLL
  54.                 while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);                //等待PLL就绪
  55.                 RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);                                //设置系统时钟(SYSCLK)—— 选择PLL作为系统时钟
  56.                 while(RCC_GetSYSCLKSource() != 0x08);                                        //返回用作系统时钟的时钟源 —— 0x08:PLL作为系统时钟
  57.         }
  58.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
  59. }

  60. /**********************************************************************
  61. * Function Name : GPIO_Configuration
  62. * Description         : 配置GPIO参数
  63. * Input                 : None
  64. * Output                 : None
  65. * Return                 : None
  66. **********************************************************************/
  67. static void GPIO_Configuration(void)
  68. {
  69.         GPIO_InitTypeDef GPIO_InitStructure;
  70.        
  71.         /* 将GPIOA.11引脚设置为通用推挽输出模式,最大速度50MHz */
  72.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
  73.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;        //GPIOA.9已配置
  74.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  75.         GPIO_Init(GPIOA, &GPIO_InitStructure);
  76.        
  77.         /* 将GPIOA.1和GPIOA.2引脚设置为浮空输入模式 */
  78. //        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
  79. //        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  80. //        GPIO_Init(GPIOA, &GPIO_InitStructure);
  81.        
  82.         GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource1);
  83.         GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource2);
  84. }
复制代码
“stm32f10x_it.c”源文件里:
  1. /**
  2.   * @brief  This function handles External lines 1 interrupt request.
  3.   * @param  None
  4.   * @retval None
  5.   */
  6. void EXTI1_IRQHandler(void)
  7. {
  8.         EXTI->PR = EXTI_Line1;
  9.         led = RESET;
  10. }

  11. /**
  12.   * @brief  This function handles External lines 2 interrupt request.
  13.   * @param  None
  14.   * @retval None
  15.   */
  16. void EXTI2_IRQHandler(void)
  17. {
  18.         EXTI->PR = EXTI_Line2;                                //同时清除了EXTI_SWIER寄存器对应位
  19.         EXTI->SWIER = EXTI_Line1;                        //EXTI1触发请求
  20.        
  21.         EXTI->PR = EXTI_Line1;                                //清除触发请求
  22.         NVIC->ICPR[0] = NVIC_ICPR_CLRPEND_7;//清除中断悬起标志【正常情况下,该寄存器对应的中断悬起标志在进入ISR时会自动清除】
  23. }
复制代码
“stm32f10x_it.h”源文件里:
  1. /****** 用户自定义 ******/
  2. extern FlagStatus led;                //声明一个外部调用的全局变量(控制LED亮灭的变量)
复制代码
显然代码很简单,软件仿真时各寄存器也是按照程序的意思显示各项数据指标。但是………………很残酷………………实际与理论发生了偏差 —— 将程序烧进硬件跑,LED亮了(即PA.8被置低了。但初始化时PA.8已被置高,而且EXTI1的ISR里才有将PA.8置低的指令!)。不应该是这样啊:虽然EXTI1的优先级高于EXTI2,但二者的抢占优先级相同,因此并不会发生中断嵌套,而且EXTI2的ISR里已将EXTI1的触发请求标志和中断悬起标志均清除了。原因何在?

贴主找啊找,调啊调………………好吧,既然软件仿真解决不了,咋就来瞎搞硬件仿真。

连接好JLink与板子后,在MDK里点击单步调试“Step one line(F11)”,执行触发EXTI2中断的指令“EXTI->SWIER = EXTI_Line2;”后却不进入EXTI2的ISR,而且无论点击多少次单步调试,程序一直停在while里………………问题真是接踵而至啊
………………

初入ARM,练习背黑锅。

这时贴主想起了前不久的一次调试经历:单步不行,就全速(贴主本坛第一帖),于是点击全速运行
“Start code execution(F5)”试了下。哈哈,LED亮了!当然,之前的问题并没有得到解决,但是后来的在线调试问题却有了头绪 —— 通过在ISR里设置断点,只要对应的中断被触发且被使能,点击全速运行就可以进入ISR。有了这样一个认知后,贴主又开始瞎搞起来了:软件触发EXTI2后,是何时进入其ISR的?

为此,贴主就在主函数添加了三条将变量flag的值赋给其本身的赋值语句:
  1. int main(void)
  2. {
  3.         RCC_Configuration();
  4.         GPIO_Configuration();
  5.         PAout(11) = led;                                                        //上电关闭LED
  6.        
  7.         NVIC->ISER[0] = NVIC_ISER_SETENA_7 | NVIC_ISER_SETENA_8;
  8.         EXTI->IMR |= EXTI_Line1 | EXTI_Line2;
  9.        
  10.         EXTI->SWIER = EXTI_Line2;
  11.        
  12.         flag = flag;
  13.         flag = flag;
  14.         flag = flag;
  15.        
  16.         while(1)
  17.         {
  18.                 PAout(11) = led;
  19.         }
  20.        
  21. //        return (0);
  22. }
复制代码
在线调试时在该三条语句均设置了断点,第一次全速运停在了第一条赋值语句处,再次全速运行时,程序进入到了EXTI2的ISR。如此说明软件触发一个中断,处理器并不会立即响应,这期间存在一个响应时间。学过8051单片机的童鞋应该知道,8051单片机的中断有一个响应过程:采样→查询→处理(各占1个机器周期)。Cortex-M3内核同样也存在,它将“中断延迟”定义为:从检测到某中断请求,到执行了其服务例程的第一条指令时,已经流逝了的时间。在CM3中,若存储器系统够快,且总线系统允许入栈与取指同时进行,同时该中断可以立即响应,则中断延迟是雷打不动的12周期(满足硬实时所要求的确定性)。在与时间赛跑的这12个周期里,处理器内部一直开足马力,进行了入栈、取向量、更新寄存器以及服务例程取指的一系列操作。但若存储器太慢以至于引入等待周期,或者还有其它因素,则会引入额外的延时。

本帖谈论到这,再来谈在线调试为何进不去ISR,以及最开始的工程为何实现不了预期的目标,原因已很明了。

在线调试为何进不去ISR:
中断延迟的存在,以及中断响应机制。

对于最开始的工程为何实现不了预期的目标:
在EXTI2的ISR里,软件是这样设置的:
  1. /**
  2.   * @brief  This function handles External lines 2 interrupt request.
  3.   * @param  None
  4.   * @retval None
  5.   */
  6. void EXTI2_IRQHandler(void)
  7. {
  8.         EXTI->PR = EXTI_Line2;                                //同时清除了EXTI_SWIER寄存器对应位
  9.         EXTI->SWIER = EXTI_Line1;                        //EXTI1触发请求
  10.        
  11.         EXTI->PR = EXTI_Line1;                                //清除触发请求
  12.         NVIC->ICPR[0] = NVIC_ICPR_CLRPEND_7;//清除中断悬起标志【正常情况下,该寄存器对应的中断悬起标志在进入ISR时会自动清除】
  13. }
复制代码
也就是语句“EXTI->SWIER = EXTI_Line1;”触发EXTI1的中断请求,紧跟着就有清除EXTI1触发中断请求的指令“EXTI->PR = EXTI_Line1;”,显然由于CM3中断响应机制的约束,后面清除EXTI1触发中断请求的指令“EXTI->PR = EXTI_Line1;”实际无效。比如贴主在EXTI2的ISR里的第一条语句和最后一条语句各设置了一个断点,点击全速运行到最后一条指令,实际并没有清除EXTI_PR寄存器中EXTI1的挂起位(但若是在EXTI2的ISR里点击单步调试,就会清除该位,后面全速运行也不会进入EXTI1的ISR。原因前面已说明)。如下图所示:

图1

图1

在EXTI1的ISR里的第一条语句也设置一个断点,然后继续全速运行,程序果真进入到了EXTI1的ISR,而且是在执行完EXTI2的ISR后再进入的。如下图所示:

图2

图2

这里说明了两个问题:
   一是
前面所探讨的关于
CM3中断响应机制;
   二是只要
没有清除EXTI_PR寄存器中EXTI1的挂起位,即使有清除EXTI_ICPRx寄存器中断悬起标志的指令,该中断悬起标志仍还是会悬起(前提:不是该ISR对应的中断的中断悬起标志。若是该ISR对应的中断的中断悬起标志,在跳出ISR的同时该悬起标志再次被置位
   比如:在本例的EXTI2的ISR中,由于指令
“EXTI->PR = EXTI_Line1;”实际无效,所以它下一条清除EXTI1中断悬起标志的语句即使执行了,其对应的中断悬起标志仍是置位状态;而若将EXTI1的ISR里的“EXTI->PR = EXTI_Line1;”注释掉再全速运行,程序会一直停在EXTI1的ISR里。所以有时需要注意二者清除的顺序!)。关于第二点,参考手册里没有指明,但各童鞋需注意!


经过以上分析,本帖最开始建立的工程的bug已经很明显:在EXTI2的ISR里的“EXTI->SWIER = EXTI_Line1;”语句后同样增加一条给变量flag赋值的语句:
  1. /**
  2.   * @brief  This function handles External lines 2 interrupt request.
  3.   * @param  None
  4.   * @retval None
  5.   */
  6. void EXTI2_IRQHandler(void)
  7. {
  8.         EXTI->PR = EXTI_Line2;                                //同时清除了EXTI_SWIER寄存器对应位
  9.         EXTI->SWIER = EXTI_Line1;
  10.         flag = flag;                                                /****** !!!此句必须有!!!否则程序运行有问题 ******/
  11.         /*** 以下是判断EXTI1是否已经发生中断嵌套(两条清除语句缺一不可) ***/
  12.         EXTI->PR = EXTI_Line1;                                //清除触发请求【必须先清除触发请求,然后再清除中断悬起标志 —— 软件仿真无区别,但硬件必须如此】
  13.         NVIC->ICPR[0] = NVIC_ICPR_CLRPEND_7;//清除中断悬起标志【正常情况下,该寄存器对应的中断悬起标志在进入ISR时会自动清除】
  14. }
复制代码



总结下来,其实就是基本知识没掌握,所以每天都是在瞎搞又瞎搞………………


Cortex-M3中文资料.rar (5.19 MB, 下载次数: 23)

评分

参与人数 1 ST金币 +10 收起 理由
zero99 + 10

查看全部评分

收藏 1 评论4 发布时间:2017-3-7 22:58

举报

4个回答
吐息间丶时光中 回答时间:2017-3-8 09:50:39
关于硬件连接,补充一下:JLink仿真器端口的1、2脚均定义为VCC,但第2脚被标注为可选择。如以下链接所示

也就是第1脚必须与板子的VCC相连,至于第2脚呢?
经过帖主测试发现:JLink仿真器端口的第2脚连接的是仿真器内AMS1117稳压芯片的输出脚(或许有些JLink没有集成3.3V稳压输出),所以是可以给目标板子提供电源的。若板子没有独立供电,那么必须将JLink仿真器端口的第2脚与目标板的VCC相连。
注意:无论目标板是否独立供电,如若不连接JLink仿真器端口的第1脚与目标板的VCC,下载程序时会依次弹出内容为“No Cortex-M Device found in JTAG chain. Please check the JTAG cable and the connected devices.”和“Error: Flash Download failed - Target DLL has been cancelled”的窗口提示! 前一个翻译出来也就是提示连接有问题。
(回复不能从本地上传图片,没图片显示形象,各位大虾将就下
moyanming2013 回答时间:2017-3-8 13:28:19
吐息间丶时光中 发表于 2017-3-8 09:50
关于硬件连接,补充一下:JLink仿真器端口的1、2脚均定义为VCC,但第2脚被标注为可选择。如以下链接所示

...

回复时选择“高级模式”就可以添加图片了。
也可以使用ST-LINK/V2-1调试器。
吐息间丶时光中 回答时间:2017-3-9 11:53:52
moyanming2013 发表于 2017-3-8 13:28
回复时选择“高级模式”就可以添加图片了。
也可以使用ST-LINK/V2-1调试器。 ...

哈哈,多谢大神提示,看到喃。(今后可以愉快的斗图了
目前身边只有JLink仿真器,其它仿真器今后应该用得到。

借此将图片上传:
1.PNG
一般用FC头2*10P线连接,不会出现这种情况(前提是目标板本身已将对应的JLink输入引脚的第1脚连到VCC上。比如帖主的板子就是的,下图),容易出现这种情况是在用杜邦线去连接,又是1用JLink仿真器给目标板供电,就很容易少连这一根线。
2.PNG

吐息间丶时光中 回答时间:2017-3-11 13:32:30
纠正一个文中的描述错误:测试用的LED连接的是GPIOA端口的11脚,文中被错误的描述为第8脚(程序中是PA.11)。
--------------------------------------------------------------
感谢@zero99 的支持。
--------------------------------------------------------------

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版