前段时间贴主看了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”源文件里
- #include "stm32f10x.h"
- #define GPIOA_ODR_Addr (GPIOA_BASE+0x0C) //GPIOA端口寄存器ODR的地址
- #define BitBand(Addr, BitNum) *(__IO uint32_t *)((Addr&0xF0000000)+0x02000000+ \
- ((Addr&0x000FFFFF)<<5)+(BitNum<<2))
- #define PAout(n) BitBand(GPIOA_ODR_Addr, n) //偏移地址取的是32位寄存器的第一字节的,所以n的取值为0~31
- FlagStatus led = SET; //外部调用的全局变量(声明在stm32f10x_it.h头文件)
- static void RCC_Configuration(void); //函数原型声明
- static void GPIO_Configuration(void);
- /**********************************************************************
- * Function Name : MAIN
- * Description : main program
- * Input : None
- * Output : None
- * Return : None
- **********************************************************************/
- int main(void)
- {
- RCC_Configuration();
- GPIO_Configuration();
- PAout(11) = led; //上电关闭LED
-
- NVIC->ISER[0] = NVIC_ISER_SETENA_7 | NVIC_ISER_SETENA_8;
- EXTI->IMR |= EXTI_Line1 | EXTI_Line2;
-
- EXTI->SWIER = EXTI_Line2;
-
- while(1)
- {
- PAout(11) = led;
- }
-
- // return (0);
- }
- /**********************************************************************
- * Function Name : RCC_Configuration
- * Description : 配置时钟参数,使能外设时钟
- * Input : None
- * Output : None
- * Return : None
- **********************************************************************/
- static void RCC_Configuration(void)
- {
- RCC_DeInit(); //将外设RCC寄存器重设为缺省值(选择HSI作为系统时钟,其它保持复位值)
- RCC_HSEConfig(RCC_HSE_ON); //HSE晶振ON
- if(RCC_WaitForHSEStartUp() == SUCCESS) //等待HSE晶振稳定
- {
- RCC_HCLKConfig(RCC_SYSCLK_Div1); //设置AHB时钟(HCLK)—— HCLK = SYSCLK/1
- RCC_PCLK2Config(RCC_HCLK_Div1); //设置高速AHB时钟(PCLK2)—— PCLK2 = HCLK/1
- RCC_PCLK1Config(RCC_HCLK_Div2); //设置低速AHB时钟(PCLK1)—— PCLK1 = HCLK/2
- FLASH_SetLatency(FLASH_Latency_2); //在SYSCLK周期与闪存访问时间插入2个等待周期
- FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); //启用闪存预取缓冲区
- RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); //设置PLL时钟源及倍频系数 —— PLLCLK = (HSECLK/1) * 9 = 72MHz
- RCC_PLLCmd(ENABLE); //使能PLL
- while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); //等待PLL就绪
- RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); //设置系统时钟(SYSCLK)—— 选择PLL作为系统时钟
- while(RCC_GetSYSCLKSource() != 0x08); //返回用作系统时钟的时钟源 —— 0x08:PLL作为系统时钟
- }
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
- }
- /**********************************************************************
- * Function Name : GPIO_Configuration
- * Description : 配置GPIO参数
- * Input : None
- * Output : None
- * Return : None
- **********************************************************************/
- static void GPIO_Configuration(void)
- {
- GPIO_InitTypeDef GPIO_InitStructure;
-
- /* 将GPIOA.11引脚设置为通用推挽输出模式,最大速度50MHz */
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIOA.9已配置
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
- GPIO_Init(GPIOA, &GPIO_InitStructure);
-
- /* 将GPIOA.1和GPIOA.2引脚设置为浮空输入模式 */
- // GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
- // GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
- // GPIO_Init(GPIOA, &GPIO_InitStructure);
-
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource1);
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource2);
- }
复制代码 “stm32f10x_it.c”源文件里:
- /**
- * @brief This function handles External lines 1 interrupt request.
- * @param None
- * @retval None
- */
- void EXTI1_IRQHandler(void)
- {
- EXTI->PR = EXTI_Line1;
- led = RESET;
- }
- /**
- * @brief This function handles External lines 2 interrupt request.
- * @param None
- * @retval None
- */
- void EXTI2_IRQHandler(void)
- {
- EXTI->PR = EXTI_Line2; //同时清除了EXTI_SWIER寄存器对应位
- EXTI->SWIER = EXTI_Line1; //EXTI1触发请求
-
- EXTI->PR = EXTI_Line1; //清除触发请求
- NVIC->ICPR[0] = NVIC_ICPR_CLRPEND_7;//清除中断悬起标志【正常情况下,该寄存器对应的中断悬起标志在进入ISR时会自动清除】
- }
复制代码 “stm32f10x_it.h”源文件里:
- /****** 用户自定义 ******/
- 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的值赋给其本身的赋值语句:
- int main(void)
- {
- RCC_Configuration();
- GPIO_Configuration();
- PAout(11) = led; //上电关闭LED
-
- NVIC->ISER[0] = NVIC_ISER_SETENA_7 | NVIC_ISER_SETENA_8;
- EXTI->IMR |= EXTI_Line1 | EXTI_Line2;
-
- EXTI->SWIER = EXTI_Line2;
-
- flag = flag;
- flag = flag;
- flag = flag;
-
- while(1)
- {
- PAout(11) = led;
- }
-
- // return (0);
- }
复制代码 在线调试时在该三条语句均设置了断点,第一次全速运停在了第一条赋值语句处,再次全速运行时,程序进入到了EXTI2的ISR。如此说明软件触发一个中断,处理器并不会立即响应,这期间存在一个响应时间。学过8051单片机的童鞋应该知道,8051单片机的中断有一个响应过程:采样→查询→处理(各占1个机器周期)。Cortex-M3内核同样也存在,它将“中断延迟”定义为:从检测到某中断请求,到执行了其服务例程的第一条指令时,已经流逝了的时间。在CM3中,若存储器系统够快,且总线系统允许入栈与取指同时进行,同时该中断可以立即响应,则中断延迟是雷打不动的12周期(满足硬实时所要求的确定性)。在与时间赛跑的这12个周期里,处理器内部一直开足马力,进行了入栈、取向量、更新寄存器以及服务例程取指的一系列操作。但若存储器太慢以至于引入等待周期,或者还有其它因素,则会引入额外的延时。
本帖谈论到这,再来谈在线调试为何进不去ISR,以及最开始的工程为何实现不了预期的目标,原因已很明了。
在线调试为何进不去ISR:中断延迟的存在,以及中断响应机制。
对于最开始的工程为何实现不了预期的目标:
在EXTI2的ISR里,软件是这样设置的:
- /**
- * @brief This function handles External lines 2 interrupt request.
- * @param None
- * @retval None
- */
- void EXTI2_IRQHandler(void)
- {
- EXTI->PR = EXTI_Line2; //同时清除了EXTI_SWIER寄存器对应位
- EXTI->SWIER = EXTI_Line1; //EXTI1触发请求
-
- EXTI->PR = EXTI_Line1; //清除触发请求
- NVIC->ICPR[0] = NVIC_ICPR_CLRPEND_7;//清除中断悬起标志【正常情况下,该寄存器对应的中断悬起标志在进入ISR时会自动清除】
- }
复制代码 也就是语句“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
在EXTI1的ISR里的第一条语句也设置一个断点,然后继续全速运行,程序果真进入到了EXTI1的ISR,而且是在执行完EXTI2的ISR后再进入的。如下图所示:
图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赋值的语句:
- /**
- * @brief This function handles External lines 2 interrupt request.
- * @param None
- * @retval None
- */
- void EXTI2_IRQHandler(void)
- {
- EXTI->PR = EXTI_Line2; //同时清除了EXTI_SWIER寄存器对应位
- EXTI->SWIER = EXTI_Line1;
- flag = flag; /****** !!!此句必须有!!!否则程序运行有问题 ******/
- /*** 以下是判断EXTI1是否已经发生中断嵌套(两条清除语句缺一不可) ***/
- EXTI->PR = EXTI_Line1; //清除触发请求【必须先清除触发请求,然后再清除中断悬起标志 —— 软件仿真无区别,但硬件必须如此】
- NVIC->ICPR[0] = NVIC_ICPR_CLRPEND_7;//清除中断悬起标志【正常情况下,该寄存器对应的中断悬起标志在进入ISR时会自动清除】
- }
复制代码
总结下来,其实就是基本知识没掌握,所以每天都是在瞎搞又瞎搞………………
Cortex-M3中文资料.rar
(5.19 MB, 下载次数: 23)
|
也就是第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”的窗口提示! 前一个翻译出来也就是提示连接有问题。
(回复不能从本地上传图片,没图片显示形象,各位大虾将就下
回复时选择“高级模式”就可以添加图片了。
也可以使用ST-LINK/V2-1调试器。
哈哈,多谢大神提示,看到喃。(今后可以愉快的斗图了
目前身边只有JLink仿真器,其它仿真器今后应该用得到。
借此将图片上传:
一般用FC头2*10P线连接,不会出现这种情况(前提是目标板本身已将对应的JLink输入引脚的第1脚连到VCC上。比如帖主的板子就是的,下图),容易出现这种情况是在用杜邦线去连接,又是1用JLink仿真器给目标板供电,就很容易少连这一根线。
--------------------------------------------------------------
感谢@zero99 的支持。
--------------------------------------------------------------