一、中断系统介绍
众所周知,轮询是 CPU 通过不断地查询某个外部设备的状态,如果外部设备准备好,就可以向其发送数据或者读取数据,这种方式由于CPU不断查询总线,导致指令执行受到影响,效率非常低。
而与之相对应的就是中断,正常情况 CPU 会处理其他的事情,如果设备有需要 CPU 处理的事情就产生一个中断,CP服务器托管网U 就会停下正在做的事情来处理中断。
中断的执行流程如下:
STM32 中断包含很多中断源(中断通道),并且使用 NVIC 统一管理中断,由左边的地址组成的表称为中断向量表,表中的内容为中断入口的地址:
NVIC 为嵌套向量中断控制器(Nested Vectored Interrupt Controller),在 STM32 中是用来统一分配中断优先级和管理中断的,它是一个内核外设
STM32 的 NVIC 可以对优先级进行分组,抢占优先级(pre-emption priority)高的中断源可以打断当前中断,来执行自己的中断,而响应优先级(subpriority)高的中断源可以优先被 CPU 响应执行,在 STM32 官方的编程手册中有 NVIC 相关的介绍。
二、EXTI(外部中断)
中断系统是管理和执行中断的逻辑结构,外部中断(EXTI)是众多能产生中断的外设之一。在触发响应方式中,中断响应是正常的流程,引脚电平变化触发中断;而事件响应不会触发中断,而是触发别的外设操作,如 DMA 等。
外部中断的基本结构如下图。相同的 Pin 不能同时触发中断,比如 pA0、pB0… 只有一个能接到后面的 16 个 GPIO_Pin 上,所以需要经过 AFIO 数据选择器。在经过 EXTI 边沿检测控制模块后,输出中断输出 EXTI0~15,还有 20 个引向其他外设,即事件响应。
AFIO 结构,本质上是一系列数据选择器,可以用于引脚复用功能的选择和重定义,或者中断引脚的选择。
EXTI 内部框图如下:
需要使用外部中断的设备一般是外部驱动的很快的突发信号,比如旋转编码器:
2.1 对射式红外传感器计数
接线图:
初始化函数CountSensor_Init(),首先要把从左到又涉及到的外设的时钟打开;第二步把 GPIO 配置为输入模式;第三步配置 AFIO,将选择的 GPIO 连接到后面的 EXTI;第四步,配置 EXTI,选择边沿触发方式,如上升沿、下降沿和双边沿等,并且选择触发相应方式,可以选择中断响应或者事件响应;第五步配置 NVIC,给中断选择一个合适的优先级。这里涉及到的外设比较多,有 RCC、GPIO、AFIO、EXTI、NVIC 等。
EXTI 相关函数:
/* stm32f10x_exti.h */
/* 可以将EXTI的配置清除,恢复成上电默认的状态 */
void EXTI_DeInit(void);
/* 根据结构体初始化EXTI外设,和GPIO的差不多 */
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
/* 可以给结构体赋予一个默认值 */
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);
/* 软件触发一次外部中断 */
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);
/* 获取指定的flag是否置1 */
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);
/* 对标志位进行清除 */
void EXTI_ClearFlag(uint32_t EXTI_Line);
/* 获取中断标志位是否被置1,在中断程序中使用 */
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
NVIC 相关函数:
/* misc.h */
/* 用来中断优先级分组,参数为中断分组的方式 */
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
/* 根据指定结构体初始化NVIC */
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
/* 设置中断向量表 */
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);
/* 系统低功耗配置 */
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);
/* 配置系统定时器的时钟源 */
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource);
编写中断函数 EXTI15_10_IRQHandler(在启动文件中可以找到):
#include "stm32f10x.h"
uint16_t count;
void CountSensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// 配置GPIO
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD; // 上拉输入
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 输入模式下该配置没用
GPIO_Init(GPIOB, &GPIO_InitStruct);
// 配置AFIO,选择GPIOB的14引脚,对应EXTI14
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
// 初始化EXTI
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line14; // 指定配置的中断线
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 配置EXTI的模式,选择中断模式,而不是事件模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 选择下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 指定选择的中断线的新状态
EXTI_Init(&EXTI_InitStructure);
// 配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 配置中断优先级分组
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = EXTI15_10_IRQn; // 指定中断通道,External Line[15:10] Interrupts
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; // 响应优先级
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
uint16_t GetCount(void)
{
return count;
}
// 中断处理函数
void EXTI15_10_IRQHandler(void)
{
// 判断EXTI14是否发生中断
if(EXTI_GetITStatus(EXTI_Line14) == SET)
{
count++;
// 清除中断标志位,防止重复请求中断
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
三、TIM 定时器中断
基本定时器,内部时钟一般是 72MHz,到 PSC 预分频器会先对其进行分频,如果预分频器写 1,则为 2 分频(72MHz/2 = 36MHz),写 2 则为 3 分频。。。计数时钟 CK_CN 每来一个上升沿,则 CNT 计数器加一;当 CNT 计数值等于自动重装载寄存器(ARR)的值的时候,就相当于计时时间到来,产生一个中断信号,并且清零 CNT 计数器(下图的向上箭头 UI 即为更新中断,向下的箭头 U 为更新事件)。
通用定时器,基本定时器只支持向上计数一种模式,而通用定时器和高级定时器支持向上计数、向下计数、中央计数三种模式。
定时中断基本结构图, 其中粉红色部分的时基单元就是上面将的基本定时器框图中的结构;运行控制寄存器可以控制时基单元的工作,比如启动停止,向上计数或向下计数等;左边的部分能为时基单元提供时钟,可以选择 RCC 提供的内部时钟、ETR 引脚提供的外部时钟模式 2;右边部分输出中断信号会先在状态寄存器里置一个中断标志位,这个标志位会通过中断输出控制,到 NVIC 申请中断,而因为有很多地方都要申请中断,所以中间的中断输出控制模块可以控制中断的通断。
预分频器时序:
计数器时序,更新中断标志位 UIF 置 1 后,就会去申请中断,中断响应后,就需要在中断程序中手动清 0。
计数器无预装时序,自动加载寄存器从 FF 更新为 36 后,计数器寄存器到 36 后自动更新。
计数器有预装时序,自动加载寄存器从 F5 更新到 36 时,计数器寄存器还是会先计数到 F5 后再更新,此时自动加载寄存器的 36 才被更新到自动影子寄存器中,从而使得下一个计数周期 36 才生效。影子寄存器的目的是为了值的变化和更新事件同步,防止运行途中更改造成错误。
3.1 定时器定时中断
因为定时器模块都在 STM32 内部,所以无需外接其他模块,此部分现象为显示屏每秒计数加一:
时钟源选择(时基单元左边部分)要用到的一些接口:
/* stm32f10x_tim.h */
/* 选择内部时钟 */
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);
/* 选择 ITRx 外部时钟 */
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
/* 选择 TIx 外部时钟 */
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource, uint16_t TIM_ICPolarity, uint16_t ICFilter);
/* 选择 ETR 外部时钟模式 1 */
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
/* 选择 ETR 外部时钟模式 2 */
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,服务器托管网 uint16_t ExtTRGFilter);
/* 配置 ETR 引脚的预分频器、极性、滤波器 */
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
时基单元部分要用到的一些接口:
/* stm32f10x_tim.h */
/* 恢复定时器默认配置 */
void TIM_DeInit(TIM_TypeDef* TIMx);
/* 时基单元初始化 */
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
/* 定时器运行控制 */
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
中断输出控制(时基单元右边部分)需要用到的接口:
/* 使能中断输出信号 */
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
NVIC 相关函数:
/* misc.h */
/* 用来中断优先级分组,参数为中断分组的方式 */
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
/* 根据指定结构体初始化NVIC */
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
/* 设置中断向量表 */
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);
/* 系统低功耗配置 */
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);
/* 配置系统定时器的时钟源 */
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource);
这里开启 TIM2 通用定时器时钟,可以从下图中看到 TIM2 属于 APB1:
Timer.c 代码如下:
#include "stm32f10x.h"
extern uint16_t number;
void Timer_Init(void)
{
// 开启 TIM2 通用定时器时钟使能,TIM2 属于 APB1 的外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 使用内部时钟(这里不配置也可以,默认使用内部时钟)
TIM_InternalClockConfig(TIM2);
// 初始化时基单元,1s的间隔
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_Prescaler = 7200 - 1; // PSC预分频器的值
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数
TIM_TimeBaseInitStruct.TIM_Period = 10000 - 1; // ARR自动重装器的值
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; // 1分频
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; // 重复计数器的值
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);
// 避免刚初始化就进入中断
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
// 中断输出控制
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// 配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 配置中断优先级分组
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn; // 指定中断通道,External Line[15:10] Interrupts
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; // 响应优先级
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
// 启动定时器TIM2
TIM_Cmd(TIM2, ENABLE);
}
void TIM2_IRQHandler(void)
{
// 获取中断更新标志位
if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
number++;
// 清除中断更新标志位
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
3.2 定时器外部时钟
可以查看引脚图来看 ETR 引脚对应的哪个,用对射式红外传感器模拟外部时钟源,此部分现象为每次挡住对射式红外传感器计数值加一,并且计数 10 次后触发中断,number 加一。
Timer.c 代码:
#include "stm32f10x.h"
extern uint16_t number;
void Timer_Init(void)
{
// 开启 TIM2 通用定时器时钟使能,TIM2 属于 APB1 的外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置GPIO PA0
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD; // 下拉输入
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 输入模式下该配置没用
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 选择 ETR 外部时钟模式 2
TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x00);
// 初始化时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_Prescaler = 1 - 1; // PSC预分频器的值
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数
TIM_TimeBaseInitStruct.TIM_Period = 10 - 1; // ARR自动重装器的值
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; // 1分频
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; // 重复计数器的值
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);
// 避免刚初始化就进入中断
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
// 中断输出控制
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// 配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 配置中断优先级分组
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn; // 指定中断通道,External Line[15:10] Interrupts
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; // 响应优先级
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
// 启动定时器TIM2
TIM_Cmd(TIM2, ENABLE);
}
// 获取计数器值
uint16_t Timer_GetCounter(void)
{
return TIM_GetCounter(TIM2);
}
void TIM2_IRQHandler(void)
{
// 获取中断更新标志位
if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
number++;
// 清除中断更新标志位
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
3.3 TIM 输出比较
上图所说的 CCR 寄存器即为下图中的捕获/比较寄存器,通过比较 CNT 计数器和 CCR 寄存器的值,来对输出电平进行控制,可以用于输出 PWM 波形。输出比较功能只在通用定时器和高级定时器上拥有,在基本定时器上不存在该部分,所以无法在基本定时器上实现此功能。
分辨率就是占空比变化的细粒程度。
通用定时器输出比较通道,当 CNT >= CCR1 时就会给输出模式控制器输出一个信号,然后输出模式控制器控制 oc1ref 参考信号,该信号会通过一个二路选择器,通过 CC1P 来控制二路选择来选择其输出的高低电平。
通过 OC1M[2:0] 可以配置不同的输出比较模式:
下图中,上面的波形图为计数器 CNT 向上计数的示意图,下面的波形图为通过 CCR 寄存器比较后的输出 PWM 波形。
各个参数的计算,PWM 的频率和定时器时钟频率相同,:
舵机硬件电路:
电机驱动模块硬件电路,通过 PWMA、AIN2、AIN1 可以控制 AO1、AO2电机驱动输出端。STBY 可以控制电机的待机模式。
3.3.1 PWM 驱动 LED 呼吸灯
连接线路,使用 pA0 输出一个 PWM 波形来控制 LED 实现呼吸灯:
输出比较单元有四个,所以对应四个初始化函数:
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
配置强制输出模式,即配置强制高电平或者低电平,用的不多:
void TIM_ForcedOC1Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC2Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC3Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
void TIM_ForcedOC4Config(TIM_TypeDef* TIMx, uint16_t TIM_ForcedAction);
单独更改 CCR 寄存器值的函数,重要:
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);
首先要初始化定时器,初始化方式和 3.1 节的定时器定时中断一样;然后初始化输出比较通道,这里我们使用 pA0 口,所以使用TIM_OC1Init 函数。pA0 上复用了很多引脚,其中就有 TIM2_CH1_ETR。
假如想使用的引脚冲突了,可以使用 AFIO 来重映射引脚,如下图中的 USART2_TX 和 TIM2_CH3:
PWM.c 代码如下:
#include "stm32f10x.h"
// 频率为 1kHz,占空比为50%
void PWM_Init(void)
{
// 开启 TIM2 通用定时器时钟使能,TIM2 属于 APB1 的外设
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/*GPIO重映射,GPIO_Pin_0 改成 GPIO_Pin_15*/
//RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,重映射必须先开启AFIO的时钟
//GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE); //将TIM2的引脚部分重映射,具体的映射方案需查看参考手册
//GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //将JTAG引脚失能,作为普通GPIO引脚使用
// 配置引脚;
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出,将引脚的控制权交给片上外设,而不是寄存器
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 使用内部时钟(这里不配置也可以,默认使用内部时钟)
TIM_InternalClockConfig(TIM2);
// 初始化时基单元,1s的间隔
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_Prescaler = 720 - 1; // PSC预分频器的值
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数
TIM_TimeBaseInitStruct.TIM_Period = 100 - 1; // ARR自动重装器的值
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; // 1分频
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; // 重复计数器的值
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);
// 初始化输出比较单元
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCStructInit(&TIM_OCInitStruct);
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; // 设置输出比较模式
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable; // 设置输出比较使能
TIM_OCInitStruct.TIM_Pulse = 0; // 设置CCR
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; // 设置输出比较极性
TIM_OC1Init(TIM2, &TIM_OCInitStruct);
// 启动定时器TIM2
TIM_Cmd(TIM2, ENABLE);
}
// 修改CCR的值
void PWM_SetCompare1(uint16_t Compare1)
{
TIM_SetCompare1(TIM2, Compare1);
}
main 函数代码:
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
uint16_t i;
int main(void)
{
// 初始化OLED
OLED_Init();
PWM_Init();
while(1)
{
for(i = 0; i
3.4 TIM 输入捕获
3.5 TIM 编码器接口
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
相关推荐: 前端封装websocket类,实现消息注册和全局回调
实现消息注册和回调函数,实现全局使用同一个webscoket对象,并实现断线重连和心跳连接等功能,可以实现全局使用唯一实例,可以另外进行拓展配置 // WebSocket类对象 class WebSocketCli { // 构造函数 constructor(…