最近在做毕设,使用的STM32f103C8T。有一个功能被我设计成使用按键中断实现,当按下按键时执行此功能,不影响主程序循环。
那么,在就需要使用EXTI中断来实现。可是问题来了,在EXTI中断内使用延迟函数时,发现主程序死机了;最明显的就是UNIX时间戳,本来应该每秒++的,直接彻底不动了,这是不可以的,是一个很严重的Bug,必须要解决。
经过我的搜索,终于找到了解决这个问题的核心思路:“在外部中断服务函数内,除了必要的判断和清除,仅开启定时器。使用定时器延时10-20ms,然后在定时器中断服务函数内读取gpio电平。”
这段话是论坛某位前辈所留的,那么核心解决解决思路,开始实践。
开始实践却并不像说说这么简单,一行短短的汉字背后要隐藏了多少硬件和软件上的风云变幻。
那么好,路在脚下,还是要迎难而上的。
我一开始的思路是想把这个定时函数封装在Timer.c模块中的
后来由于能力有限,各种不知名Bug满天飞,故而放弃。
后来又问了AI,AI给出的解决方法是定义一个
volatile uint32_t msTicks = 0;//该变量随时可能改变,每次都从地址读取
volatile这个修饰符我是第一次见,原谅我才疏学浅,不过以后就不是第一次见了。查询资料以后,发现这个定义的意思是,告诉编译器,被这个修饰的变量随时可能改变,要每次从地址重新读取。
也就是说,我们的msTicks会跟随定时器溢出而改变,我们设置好溢出频率,就可以设置msTicks的计时单位了(us/ms/s)。
虽然最终AI提供的代码我没有调试通,但是极大的为我们指引了方向。
这里我们设置溢出时间为1ms
此附:定时器溢出频率计算公式
ARR(16位0~65535):自动重装计数器 PSC(16位0~65535):预分频计数器
这里注意,配置好后是溢出的频率,就是溢出时间的倒数!
想要每1ms溢出一次,1ms=0.001s,那么:
代入公式,
也就是说ARR*PSC = 72000
以下是TIM3配置代码,配置好后每1ms溢出一次
void Timer3_Init(void)
{
//开启APB1时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
//选择内部时钟输入
TIM_InternalClockConfig(TIM3);
//配置时基单元 定时1 ms
//CK_PSC = 72MHz / ARR+1 / PSC+1
TIM_TimeBaseInitTypeDef TimBaseInitStructure;
TimBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//滤波分频
TimBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
TimBaseInitStructure.TIM_Period = 72 - 1;//ARR自动重装器值 0~65535
TimBaseInitStructure.TIM_Prescaler = 1000 - 1;//PSC预分频计时器 0~65535
TimBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TimBaseInitStructure);
TIM_ClearFlag(TIM3, TIM_IT_Update);
//使能中断,开启中断到NVIC
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;//中断通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
//启动定时器
TIM_Cmd(TIM3,ENABLE); //启动定时器
}
设置好定时器后,我们就要编写main.c了。
逻辑是这样的,在系统初始化时不初始化TIM3;当按键按下时才初始化TIM3,TIM3设置好的溢出频率就是天然的延迟。这里由于设置的溢出频率比较小,所以我们由于得到了AI的启发,直接在main.c里定义一个TImer变量,手动创造一个虚拟计数器。
uint16_t Timer = 0; //计数器
这里的Timer要设置成uint16_t,定时器溢出时间为1ms一次,uint8_t范围是0~255,最多也就是计时0.255s(255ms),这明显是不够的。所以定义为uint16_t,范围0~65535,最多计时65.535s,这就富裕多了。
好,接下来编写中断内部的代码:
按下按键后进入EXTI中断,在中断内初始化定时器,定时器每1ms溢出一次,溢出一次Timer++一次,Timer=1就是经过了1ms,Timer=20就是经过了20ms。嗯,非常好!来吧!
先对按键消抖,按下后20ms内不允许执行任何代码,进入死循环,也就是
while(Timer <= 20);// 20ms延时消抖
Timer++到20以后才能执行其他代码。
以下是中断完整代码:
uint16_t Timer = 0;
void EXTI1_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line1) == SET)
{
Timer3_Init();//使能定时器TIM3,开始计时
while(Timer <= 20);// 20ms延时消抖
//在这里写你的代码吧
Timer3_Disable(); //失能定时器
EXTI_ClearITPendingBit(EXTI_Line1);//清除中断标志位
}
}
//每0.001s Timer++
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)
{
Timer++;
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
}
中断执行结束时要把定时器失能,进行下次定时。
不要忘了清楚中断标志位哦。
另附:EXTI中断配置代码:
void EXTI_Init(void)
{
//配置AFIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//打开AFIO
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);//选择引脚
//配置EXTI
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line1;//1号线路
EXTI_InitStructure.EXTI_LineCmd = ENABLE;//使能
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//中断模式,0x04事件模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发
//EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;//上升沿触发
EXTI_Init(&EXTI_InitStructure);
//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//配置抢占和响应优先级 a,b a+b=4;4抢占0响应
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 13;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//响应优先级
NVIC_Init(&NVIC_InitStructure);
}