中断程序

  前段时间用STM32F103VBT6写了一个中断的函数,借此机会想了解下STM32的中断机制,用过之后发现STM32的中断配置相当灵活,稳定行很高,测试发现几乎没出过什么差错。我在程序里开了三个中断,一个计数器用于精确延时用,另外两个为外部事件处理中断,下面一一详细介绍,方便初学者入门。

在进行STM32中断配置之前首先需要了解下它的中断部分:

一、Cortex-M3中断机制

    在STM32处理器中有43个可屏蔽中断通道(?包含 16个 Cortex?-M3的中断线)。共设置了16个可编程的优先等级(使用? 4位中断优先级);它的嵌套向?中断控制器(NVIC)和处?器核的接口紧密相连,可以实现低延迟的中断处?和有效处?地处?晚到的中断。嵌套向?中断控制器管?着包括核异常等中断。

    Cortex—M3是一个32位的核,在传统的单片机领域中,有一些不同于通用32位CPU应用的要求。比如在工控领域,用户要求具有更快的中断速度,Cortex-M3采用了Tail-Chaining中断技术,完全基于硬件进行中断处理,最多可减少12个时钟周期数,在实际应用中可减少70%中断。  
    异常或者中断是处理器响应系统中突发事件的一种机制。当异常发生时,Cortex—M3通过硬件自动将编程计数器(PC)、编程状态寄存器(XPSR)、链接寄存器(LR)和R0~R3、R12等寄存器压进堆栈。在Dbus(数据总线)保存处理器状态的同时,处理器通过Ibus(指令总线)从一个可以重新定位的向量表中识别出异常向量,并获取ISR函数的地址,也就是保护现场与取异常向量是并行处理的。一旦压栈和取指令完成,中断服务程序或故障处理程序就开始执行。执行完ISR,硬件进行出栈操作,中断前的程序恢复正常执行。图1为Cortex—M3处理器的异常处理流程。


二、STM32 SysTick 介绍

    Cortex-M3的内核中包含一个SysTick时钟。SysTick为一个24位递减计数器,SysTick设定初值并使能后,每经过1个系统时钟周期,计数值就减1 。计数到0时SysTick计数器自动重装初值并继续计数,同时内部的COUNTFLAG 标志会置位,触发中断( 如果中断使能情况下 ) 。

    对于STM32系列微处理器来说,执行一条指令只有几十个 ns ,进行 for 循环时,要实现N毫秒的x值非大,而且由于系统频率的宽广,很难计算出延时N毫秒的精确值。针对STM32微处理器,需要重新设计一个新的方法去实现该功能,因此,在STM32的应用中,使用Cortex-M3内核的SysTick 作为定时时钟,设定每一毫秒产生一次中断,在中断处理函数里对N减一,在Delay(N)函数中循环检测N是否为0,不为0则进行循环等待;若为 0 则关闭 SysTick 时钟,退出函数,这种延时函数的做法能很高效地实现精确定时。

三、SysTick编程实现Delay(N)函数

    思路:利用systick定时器为递减计数器,设定初值并使能它后,它会每个系统时钟周期计数器减 1 ,计数到 0 时 ,SysTick 计数器自动重装初值并继续计数,同时触发中断 。那么每次计数器减到 0 ,

   时间经过了:

             T = 系统时钟周期x计数器初值 

   比如使用 72M 作为系统时钟,那么每次计数器减 1 所用的时间是 1/72M ,计数器的初值如果是 72000 , 那么每次计数器减到 0 , 时间经过 (1/72M) * 72000 =0.001s ,即 1ms.

有了以上思路做铺垫后,为了实现首先我们需要一个72MHz的SysTick时钟。

第一步 配置RCC寄存器和SysTick寄存器

    由于系统时钟(SysTick)可选择为PLL输出、HSI或者HSE,在这里选择9倍频的PLL作为SysTick的时钟源,同时HCLK(AHB Clock)时钟也相应的配置成72MHz了,因为最终SysTick是需要通过AHB后输出的,所以在配置的同时也需要选择AHB 时钟,这里选择为RCC_SYSCLK_Div1(咖啡色部分)表示AHB 时钟 = 系统时钟,相关配置见下面函数(RCC_Configuration)红色字体部分。这里需要特别强调一点,有关书籍里常提到"SysTick的最高频率为 9MHz (最大为HCLK/ 8),在这个条件下,把SysTick重装载值设置为9000,将SysTick时钟设置为9MHz,就能够产生1ms 的时间基值"刚开始对这句话感到很迷惑,因为,有的地方介绍SysTick没有说最大频率智能9MHz,这里却指出会被8分频,两者出现了矛盾!相信有过我这种疑惑的人不在少数!究其原因我猜想是原文作者没有说明这点,转载的人见到有相关的知识便直接转载了,自己也没去想,估计也没弄明白过,这样便一个个都转开了,所以我建议在吸取别人精华时要多多思考,只有注入了自己的新元素知识才是被真正吸收了,否则即使涉猎的再多,也只是收藏!现在再来分析下上面的那个矛盾点,其实应该这么理解的,在STM32中,SysTick的架构其实是这么回事的:首先选择时钟源-->AHB-->这里便分走两路,其一被8分频,也便出现了最高频率9MHz的结果;其二作为FCLK(CM3上的自由运行时钟)直接从AHB输出,这里却是没有再分频的,其频率就是AHB时钟频率,最大可以达到72MHz,下面程序对其设置也是在72MHz的的情况下的,具体可以参考STM32时钟架构这幅图,如下:


void RCC_Configuration(void)
{
 
 RCC_DeInit();
 
 RCC_HSEConfig(RCC_HSE_ON);
 
 HSEStartUpStatus=RCC_WaitForHSEStartUp();
 if(HSEStartUpStatus==SUCCESS)
 {
  
  RCC_HCLKConfig(RCC_SYSCLK_Div1);
  
  RCC_PCLK2Config(RCC_HCLK_Div1);
  
  RCC_PCLK1Config(RCC_HCLK_Div2);

  FLASH_SetLatency(FLASH_Latency_2);

  FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);

  RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);
  
  RCC_PLLCmd(ENABLE);
  
  while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY)==RESET)
  {
  }
  
  RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
  
  while(RCC_GetSYSCLKSource()!=0x08)
  {
  }
 }
 
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE|
       RCC_APB2Periph_AFIO,ENABLE);
}

配置完了RCC后,接下来便是需要配置SysTick了,使用 ST 的函数库使用 systick 的方法一般步骤如下所示:

1 、调用 SysTick_CounterCmd() -- 失能 SysTick 计数器
2 、调用 SysTick_ITConfig () -- 失能 SysTick 中断
3 、调用 SysTick_CLKSourceConfig() -- 设置 SysTick 时钟源。
4 、调用 SysTick_SetReload() -- 设置 SysTick 重装载值。
5 、调用 SysTick_ITConfig () -- 使能 SysTick 中断
6 、调用 SysTick_CounterCmd() -- 开启 SysTick 计数器

SysTick_Configuration: 配置 SysTick
void SysTick_Configuration(void)
{
   
    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
   
    NVIC_SystemHandlerPriorityConfig(SystemHandler_SysT
   
    SysTick_ITConfig(ENABLE);
}

编写响应的中断服务子函数,这个先对比较简单,直接在stm32f10x_it.h的void SysTickHandler(void)函数里填充计数值便可:

vu32 TimingDelay = 0;

void SysTickHandler(void)
{
    TimingDelay--; 
}

记住,在调用它的.C文件里记得申明TimingDelay这个变量为全局变量,否则无法使用这个计数值:

extern vu32 TimingDelay;

上面函数只是完成了前5步,接下来需要开启SysTick计数器以便让其工作,前面已经说过在SysTick一般多用于做精确延时用,故而对于这个延时函数它的生命周期便在调用开始到调用结束,所以第6部一般放在被调用的这个函数中(Delay(N)):

void Delay(u32 nTime)
{
   

    SysTick_CounterCmd(SysTick_Counter_Enable);
    TimingDelay = nTime;
    while(TimingDelay != 0);
   
    SysTick_CounterCmd(SysTick_Counter_Disable);
   
    SysTick_CounterCmd(SysTick_Counter_Clear);
}

至此,一个小的时钟便算配置好了,接下来配置其他两个中断,道理是一样的,这两个为按键输入,作为外部中断事件,分为两个部分,其一为端口配置在GPIO_Configration函数中,选择工作模式为上拉输入,用作外部中断线路,下降沿触发

void GPIO_Configration(void)
{

   
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
    GPIO_Init(GPIOA,&GPIO_InitStructure);
    
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
    GPIO_Init(GPIOA,&GPIO_InitStructure);
   
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource11);
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource12);
    EXTI_InitStructure.EXTI_Line=EXTI_Line11|EXTI_Line12;
    EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;
    EXTI_InitStructure.EXTI_LineCmd=ENABLE;
    EXTI_Init(&EXTI_InitStructure);
}

其二是NVIC嵌入式中断配置,包括中断源(中断向量)、优先级、使能等常规设置,具体在前一篇STM32中断机制中介绍得很详细了,这里就不多说了,具体配置在void NVIC_Configuration(void)函数里
void NVIC_Configuration(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    #ifdef VECT_TAB_RAM
        NVIC_SetVectorTable(NVIC_VectTab_RAM,0X0);//向量表位于RAM区
    #else
        NVIC_SetVectorTable(NVIC_VectTab_FLASH,0X0);//向量表位于FLASH区
    #endif
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);//选择第一组
    //使能EXTI15_10中断,按键PA11
    NVIC_InitStructure.NVIC_IRQChannel=EXTI15_10_IRQChannel;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;// 指定抢占式优先级别0
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;// 指定响应优先级别0
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//
    NVIC_Init(&NVIC_InitStructure);
    //使能EXTI15_10中断,按键PA12
    NVIC_InitStructure.NVIC_IRQChannel=EXTI15_10_IRQChannel;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;// 指定抢占式优先级别0
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;// 指定响应优先级别0
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//
    NVIC_Init(&NVIC_InitStructure);
}

最后是相应的中断服务子函数,还是在stm32f10x_it.h中,该中断为EXTI15_10中断,故而其中断服务子函数在void EXTI15_10_IRQHandler(void)中惊醒执行,具体格式如下:

void EXTI15_10_IRQHandler(void)
{

    if(EXTI_GetITStatus(EXTI_Line11)!=RESET)//判断标志,中断是否发生

    {

        ...

        EXTI_ClearITPendingBit(EXTI_Line11); //清标志位

    }

    if(EXTI_GetITStatus(EXTI_Line12)!=RESET)//判断标志,中断是否发生

    {

        ...

        EXTI_ClearITPendingBit(EXTI_Line12); //清标志位

    }

}

最后下载运行,主函数中让一个LED闪烁,按键1让其他四个LED连续闪烁三次,按键2让另外4个LED依次流水,下载运行,测试通过!详细代码可以直接下载如下压缩文件,编译环境为MDK350PRC,固件库在安装目录下的子文件夹中,版本差别不大

阅读(23890) |  评论(9)
推荐
 
STM32之启动文件理解
 
STM32时钟理解
marginwidth="0" marginheight="0" id="lmid_iframe" width="590" height="100" frameborder="0" scrolling="no" allowtransparency="true" src="http://g.163.com/r?site=netease&affiliate=blog&cat=article&type=column590x100&location=1" style="margin: 0px; padding: 0px; border-width: 0px; border-style: initial; display: block;">

在LOFTER的更多文章

id="morecontent_frame" width="100%" height="125" allowtransparency="true" scrolling="no" frameborder="0" src="http://www.lofter.com/[email protected]" style="margin: 10px 0px 10px 5px; padding: 0px; border-width: 0px; border-style: initial; max-width: 780px; display: block;">
关闭
玩LOFTER,免费冲印20张照片,人人有奖!      我要抢>

评论

  登录后你可以发表评论,请先登录。 登录>>
2014-03-04 21:13
GoGo加油
楼主,怎么知道 //使能EXTI15_10中断,按键PA11
    NVIC_InitStructure.NVIC_IRQChannel=EXTI15_10_IRQChannel;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;// 指定抢占式优先级别0
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;// 指定响应优先级别0
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//
    NVIC_Init(&NVIC_InitStructure);
    //使能EXTI15_10中断,按键PA12
    NVIC_InitStructure.NVIC_IRQChannel=EXTI15_10_IRQChannel;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;// 指定抢占式优先级别0
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;// 指定响应优先级别0
2014-03-07 17:27
  尘封已久  回复  GoGo加油
十分抱歉,这篇文章是学习的时候转载的,并没有细致的考究。今天仔细看了一下,上述两段确实是重复,后一段程序应该会覆盖掉前一段程序所赋的值。

2014-03-07 17:31
  尘封已久  回复  GoGo加油
因为例程里,PA11、PA12共用的是EXTI15_10_IRQChannel通道,共用一个中断函数,所以,这两个外部中断应该会共享一个优先级(你在中断函数内通过判别中断标志位另做处理除外)。因为很久没有做开发了,无法测试,你还是可以通过调试,查看相应的寄存器变化就可以确定的。
2013-12-26 11:23
jasonfu

//使能EXTI15_10中断,按键PA11

//使能EXTI15_10中断,按键PA12
-----------------------------------------

这两段代码仅仅差别在修改了优先级设置(后段覆盖前段),并不能区别PA11/PA12. 这里是个错误么? 


 

2014-03-05 09:51
GoGo加油  回复  jasonfu
我也是这个疑问  ,这一块怎么设置呢?
2012-12-02 18:00
haolei432370

PA0和PA1可以同时设置成外部中断吗??

新手不太懂。

2012-12-02 22:52
  尘封已久  回复  haolei432370
完全可以!

2012-08-26 12:16
wu_jian_long
博主,你好。我有一个问题: PA11、PA12使用中断线都是EXTI15_10_IRQChannel?

2012-08-26 12:39
  尘封已久  回复  wu_jian_long
STM32中,PA1、PB1、PC1等都是通过 EXTI1_IRQChannel,其他引脚类似, EXTI15_10_IRQChannel 代表的是10--15序号的引脚外中断,PA11、PA12都在其中,自然是都在这个中断函数里,假如不懂得话晚上说,记着出去!

 
 
 
 
我的照片书  -  博客风格  -  手机博客  -  下载LOFTER APP  -  订阅此博客

网易公司版权所有 ©1997-2016

marginwidth="0" marginheight="0" class="ztag adv-iframe" width="300" height="250" frameborder="0" scrolling="no" allowtransparency="true" src="http://g.163.com/r?site=netease&affiliate=blog&cat=homepage&type=logo300x250&location=10" style="margin: 0px; padding: 0px; border-width: 0px; border-style: initial; display: block;">

你可能感兴趣的:(中断程序)