1 中断系统
定义:主程序运行过程中,出现了特定得中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而处理中断程序,处理完成后再返回主程序中暂停的位置继续运行。
STM32F10x芯片有84个中断通道,包括16个内核中断和68个可屏蔽中断, 包括EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设.
嵌套向量中断控制器(Nested Vector Interrupt Controller, NVIC)属于M3内核的一个外设,控制着芯片的中断相关功能.
中断控制相关寄存器在固件库core_cm3.h文件NVIC结构体内。
中断优先级:每个中断通道都拥有16个可编程的优先等级,而优先级可以分为组成抢占式优先级和响应式优先级。高抢占优先级可以进行中断嵌套,高响应优先级则可以优先响应。比如,当前正在执行中断函数,此时有一个抢占优先级更高的中断信号过来,那么程序将进行中断嵌套;而当这个中断信号的抢占优先级和当前正在执行的中断函数相同,则需要等待当前的中断完成后再进行;而当两个抢占优先级相同的中断同时到达,那么响应优先级高的中断优先响应;而当两个抢占优先级和响应优先级均相同的中断同时到达,则按中断表中的序号决定响应的顺序。
2 外部中断
EXTI可以监测指定的GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将向NVIC发出中断申请,通过NVIC的裁决后即可执行中断程序。
触发方式
- 上升沿:由低电平变为高电平瞬间。
- 下降沿:由高电平变化低电平瞬间。
- 双边沿:产生电平变化瞬间,这里的电平变化可以指高电平变为低电平和低电平变为高电平。
- 软件触发:当程序执行了某个函数时自动调用中断程序。
外部中断/事件线映射: STM32F10x的EXTI具有20个中断/事件线,即16个GPIO_PIN和PVD输出、RTC闹钟、USB唤醒、以太网唤醒.
所有的GPIO口均可以支持外部中断,但是相同的PIN脚不能同时触发中断。每个GPIO口相同PIN接入了AFIO中的对应位置用于触发对应的外部中断,当不同GPIO口的相同PIN脚均产生中断信号,实际上经过AFIO选择后只会响应其中一个中断。
响应方式:中断响应/事件响应.
- 中断响应:申请中断,请求执行对应的中断函数。
- 事件响应:不触发中断,而是触发其他外设进行操作,属于外设间的联合工作。
3 外部中断初始化
- 时钟使能:包括GPIO口时钟、AFIO时钟.(EXTI不需要使能时钟,而NVIC属于内核外设,同样不需要使能时钟)(配置RCC_APB2ENR寄存器)
- 配置GPIO:设置为输入/输出模式(具体的外设对应的配置模式需要查询参考手册).
- 配置AFIO:选择中断引脚
- 配置EXTI的触发方式和响应方式
- 配置NVIC选择中断优先级
4 外部中断库函数实现
- 使能时钟
- 函数原型:
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
- 输入:
RCC_APB2Periph
门控APB2外设时钟,也就是指定对应的外设来选定其对应的时钟,GPIOx
口对应为RCC_APB2Periph_GPIOA
和RCC_APB2Periph_AFIO
;NewState
: 指定使能(ENABLE)还是禁用(DISABLE)该外设的时钟。
- 设置IO口模式
- 函数原型:
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
- 输入:
GPIOx
用于指定使用的GPIO口(注意不是PIN);GPIO_InitStruct
为指向GPIO_InitTypeDef
类型结构体的指针,其成员为GPIO口的状态(输入/输出模式、输出速度、PIN脚).
- 配置AFIO
- 函数1
- 函数原型:
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
- 输入:
GPIO_Remap
用于指定具体的PIN来进行重映射;NewState
: 指定重映射功能的状态.
- 函数原型:
- 函数2
- 函数原型:
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
- 输入:
GPIO_PortSource
用于指定GPIO口;GPIO_PinSource
用于指定具体的PIN脚.
- 函数原型:
- 配置EXTI
函数1
- 函数原型:
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
输入:
EXTI_InitStruct
为指向外部中断初始化结构体的指针.typedef struct { uint32_t EXTI_Line; // 指定中断线,即将GPIO口的PIN挂到AFIO的输出上 EXTIMode_TypeDef EXTI_Mode; // 配置响应方式为中断响应还是事件响应 EXTITrigger_TypeDef EXTI_Trigger; // 配置触发方式 FunctionalState EXTI_LineCmd; // 指定EXTI的初始状态 }EXTI_InitTypeDef;
- 函数功能:初始化EXTI
- 函数原型:
- 函数2
- 函数原型:
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);
- 输入:
EXTI_InitStruct
为指向外部中断初始化结构体的指针 - 功能:
- 函数原型:
- 函数3
- 函数原型:
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);
- 输入:
EXTI_Line
为指定中断线 - 功能:用于软件触发
- 函数原型:
- 配置NVIC
- 函数1
- 函数原型:
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
- 输入:
NVIC_PriorityGroup
为指定中断分组的形式. - 功能:中断分组
- 函数原型:
函数2
- 函数原型:
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
输入:
NVIC_InitStruct
为指向NVIC_InitTypeDef初始化结构体的指针.typedef struct { uint8_t NVIC_IRQChannel; // 指定通道 uint8_t NVIC_IRQChannelPreemptionPriority; // 指定抢占优先级 uint8_t NVIC_IRQChannelSubPriority; // 指定响应优先级 FunctionalState NVIC_IRQChannelCmd; // 指定通道启用或禁用 } NVIC_InitTypeDef;
功能:初始化NVIC
- 函数原型:
- 代码实现
指定PB12为中断通道,配置为下降沿触发、中断响应.
void EXTI_Init(void){
// 使能GPIOB时钟和AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// 配置GPIO口为输入模式
GPIO_InitStruct GPIO_InitStructrue;
GPIO_InitStructrue.GPIO_Mode = GPIO_MODE_IPU;
GPIO_InitStructrue.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructrue.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructrue);
// 配置AFIO,选择PB12作为中断通道
GPIO_EXTILineConfig(GPIOB_PortSource, GPIO_PinSource_12);
// 配置EXTI,通道为12,中断响应,下降沿触发,并使能该通道
EXTI_InitTypeDef EXTI_InitStructrue;
EXTI_InitStructrue.EXTI_Line = EXTI_Line12;
EXTI_InitStructrue.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructrue.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructrue.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructrue);
// 配置NVIC优先级分组,只有一个中断所以随便配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 初始化NVIC,EXTI10-15共用一个通道,抢占优先级和响应优先级随便,并使能该通道
NVIC_InitTypeDef NVIC_InitStructtrue;
NVIC_InitStructtrue.NVIC_IRQChannel = EXTI15_10_IRQn;
NVIC_InitStructtrue.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructtrue.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructtrue.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructtrue)
}
// 中断处理函数
void EXTI15_10_IRQHandler(void){
// 由于该通道是共有通道,先判断是否为我们设置的通道申请了中断
if(EXTI_GetITStatus(EXTI_Line_12) == SET){
// 清除中断标志位,否则会一直进入中断函数
EXTI_ClearITPendingBit(EXTI_Line_12);
// 进行中断处理
...
}
}
5 定时器中断
- TIM(Timer)定时器可以对输入的时钟进行计数,当计数值达到设定值时触发中断。定时器的计数器为16位,定时最大为59.65s。除了基本的定时中断功能外,还有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能,TIM可以分为高级定时器、通用定时器和基本定时器三种类型。
- 对于基本定时器,其时钟只能选择内部时钟,频率为内部时钟频率72MHz,通过预分频器分频来到计数器,当计数器得到一个方波信号则自增1(仅支持向上计数),当计数器的值达到预先设定好的值时就会产生中断,而预先设定的值则是存放在自动重装载寄存器中。预分频器为16位,为0则进行1分频,此时输出频率等于输入频率;为1时进行2分频,输出频率等于输入频率的一半,以此类推;分频器的作用其实就是改变到达计数器的方波间的时间间隔。因此,当预分频器设置为65535、自动重装载寄存器(也是16位)也设置为65535,则达到最大计时时间,为:
- CNT_EN未使能时计数器CK_CNT不工作,当CNT_EN使能后CK_CNT才开始计数。而预分频控制分为两个寄存器,一个未预分频控制寄存器,是提供给用户进行写操作来修改分频,另一个则是预分频缓冲器,是定时器中实际操控分频的寄存器;当用户通过预分频控制寄存器修改分频时,分频会写入预分频控制寄存器而不会直接写入预分频缓冲器,即定时器并不会马上对分频进行修改,而是在中断产生后才修改分频,这可以避免同个定时周期内前后频率不一致的问题(可以通过修改ARPE寄存器来控制是否预装时序)。根据下面的时序图可以知道,当定时器的计数器CK_CNT达到设定值时并不会马上触发中断,而是需要等到下一个上升沿到来之后才进行中断并重装。而分频的实现实际上也是通过计数器实现,即预分频计数器,当预分频计数器重装时才会输出一个方波信号。
6 定时器中断初始化
- 使能时钟:RCC使能时钟
- 配置时钟源: 选择使用内部时钟源还是外部时钟源
- 配置时基单元:包括PSC、CNT、ARR
- 配置输出中断控制
- 配置NVIC
- 使能计数器
7 定时器中断库函数实现
使能时钟
配置时钟源
- 函数1
- 函数原型:
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);
- 输入:
TIMx
选择使用的定时器编号 - 功能:时钟源选择内部时钟
- 函数原型:
- 函数2
- 函数原型:
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
- 输入:
TIMx
选择使用的定时器编号,TIM_InputTriggerSource
选择接入的定时器 - 功能:选择ITRx其他定时器的时钟,即实现定时器的串联
- 函数原型:
- 函数3
- 函数原型:
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource, uint16_t TIM_ICPolarity, uint16_t ICFilter);
- 输入:
TIMx
选择使用的定时器编号,TIM_TIxExternalCLKSource
选择TIx具体的引脚,TIM_ICPolarity
选择极性,ICFilter
选择滤波器 - 功能:选择TIx捕获通道的时钟
- 函数原型:
- 函数4
- 函数原型:
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
- 输入:
TIMx
选择使用的定时器编号,TIM_ExtTRGPrescaler
对ETR外部时钟进行预分频,TIM_ExtTRGPolarity
选择极性,ExtTRGFilter
选择滤波器。 - 功能:使用ETR通过外部时钟模式1输入的时钟
- 函数原型:
- 函数5
- 函数原型:
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
- 输入:
TIMx
选择使用的定时器编号,TIM_ExtTRGPrescaler
对ETR外部时钟进行预分频,TIM_ExtTRGPolarity
选择极性,ExtTRGFilter
选择滤波器。 - 功能:使用ETR通过外部时钟模式2输入的时钟
- 函数原型:
- 函数6
- 函数原型:
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
- 输入:
TIMx
选择使用的定时器编号,TIM_ExtTRGPrescaler
选择预分频,TIM_ExtTRGPolarity
选择极性,ExtTRGFilter
选择滤波器。 - 功能:配置ETR引脚的预分频器、极性、滤波器等参数。
- 函数原型:
- 配置时基单元
- 函数1
- 函数原型:
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
- 输入:
TIMx
选择使用的定时器编号,TIM_TimeBaseInitStruct
为指向定时器初始化结构体TIM_TimeBaseInitTypeDef
类型的指针, 结构体定义为:typedef struct { uint16_t TIM_Prescaler; // 设置预分频器 uint16_t TIM_CounterMode; // 设置计数模式 uint16_t TIM_Period; // ARR自动重装器的值 uint16_t TIM_ClockDivision; // 设置时钟分频,即采样频率 uint8_t TIM_RepetitionCounter; // 重复计数器的值 } TIM_TimeBaseInitTypeDef;
- 功能:配置时基单元
- 函数原型:
- 函数2
- 函数原型:
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);
- 输入:
TIMx
选择使用的定时器编号,Prescaler
选择预分频,TIM_PSCReloadMode
配置是否采用影子寄存器. - 功能:配置分频
- 函数原型:
- 函数3
- 函数原型:
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);
- 输入:
TIMx
选择使用的定时器编号,TIM_CounterMode
选择计数模式(向上、向下、中央对称). - 功能:配置计数器
- 函数原型:
- 函数4
- 函数原型:
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
- 输入:
TIMx
选择使用的定时器编号,NewState
选择是否有预装功能 - 功能: 自动重装载寄存器预装功能配置
- 函数原型:
- 函数5
- 函数原型:
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);
- 功能:给计数器写入一个值
- 函数原型:
- 函数6
- 函数原型:
void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);
- 功能:给自动重装器写入一个值
- 函数原型:
- 函数7
- 函数原型:
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);
- 功能:读取计数器当前的值
- 函数原型:
- 函数8
- 函数原型:
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);
- 功能:读取预分频器的值
- 函数原型:
- 函数9-12
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
、ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
:获取定时器的中断标志位void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
、void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
:清除定时器的中断标志位
- 配置输出中断控制
- 函数原型:
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
- 输入:
TIMx
选择使用的定时器编号,TIM_IT
选择中断输出,NewState
为使能/禁用定时器中断
- 配置NVIC
- 使能计数器
- 函数原型:
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
- 输入:
TIMx
选择使用的定时器编号,NewState
为使能/禁用定时器
- 代码实现
配置初始化定时器TIM2, 定时为1s.
void TIM_Init(void){
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_InternalClockConfig(TIM2); // 默认使用内部时钟,可不写
// 配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructrue;
TIM_TimeBaseInitStructrue.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructrue.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructrue.TIM_Prescaler = 7200 - 1;;
TIM_TimeBaseInitStructrue.TIM_Period = 10000 - 1;
TIM_TimeBaseInitStructrue.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructrue);
// 配置完时基单元后会将中断标志位置1,这里需要将标志位清空
TIM_ClearFlag(TIM2, TIM_IT_Update);
// 使能更新中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// 配置NVIC
// 配置NVIC优先级分组,只有一个中断所以随便配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 初始化NVIC,EXTI10-15共用一个通道,抢占优先级和响应优先级随便,并使能该通道
NVIC_InitTypeDef NVIC_InitStructtrue;
NVIC_InitStructtrue.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructtrue.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructtrue.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructtrue.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructtrue)
// 使能定时器中断
TIM_Cmd(TIM2, ENABLE);
}
void TIM2_IRQHandler(void){
if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
// 中断操作
...
}
}