1.使用并联电容的方式
电容消抖:在开光并联一个电容,电容不能突变,平稳后才输出,达到消抖目的,但是这样会增加产品的硬件成本,一般项目更倾向于使用软件处理中断,降低成本
2.使用外部中断服务函数触发标志位
使用外部中断,在外部中断服务函数中设置一个标志位,在主函数中进行标志位判断,延时50ms,然后再判断和相应操作,这样会影响主函数的效率
#include "stm32f4xx.h"
static GPIO_InitTypeDef GPIO_InitStructure;
static EXTI_InitTypeDef EXTI_InitStructure;
static NVIC_InitTypeDef NVIC_InitStructure;
void delay_ms(uint32_t ms)
{
while (ms--)
{
SysTick->CTRL = 0; // 关闭系统定时器后才能配置寄存器
SysTick->LOAD = 21000; // 设置计数值,用于设置定时的时间
SysTick->VAL = 0; // 清空当前值还有计数标志位
SysTick->CTRL = 1; // 使能系统定时器工作,且时钟源为系统时钟的8分频(168MHz/8=21MHz)
while ((SysTick->CTRL & (1 << 16)) == 0)
; // 等待系统定时器计数完毕
SysTick->CTRL = 0; // 关闭系统定时器
}
}
#define PEout(n) (*(volatile uint32_t *)(0x42000000 + (GPIOE_BASE + 0x14 - 0x40000000) * 32 + n * 4))
#define PFout(n) (*(volatile uint32_t *)(0x42000000 + (GPIOF_BASE + 0x14 - 0x40000000) * 32 + n * 4))
#define PAin(n) (*(volatile uint32_t *)(0x42000000 + (GPIOA_BASE + 0x10 - 0x40000000) * 32 + n * 4))
static uint32_t g_key_event = 0;
/* 主函数 */
int main(void)
{
/* 打开端口A的硬件时钟(就是对硬件供电),默认状态下,所有时钟都是关闭 */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
/* 打开端口F的硬件时钟(就是对硬件供电),默认状态下,所有时钟都是关闭 */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
/* 打开系统配置syscfg时钟(就是对硬件供电),默认状态下,所有时钟都是关闭 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; // 指定9 10号引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; // 引脚工作在输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; // 速度越高,响应时间越短,但是功耗就越高,电磁干扰也越高
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // 如果外部没有上拉电阻,就配置推挽输出模式
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; // 不需要使能上下拉电阻
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // 指定0号引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; // 引脚工作在输入模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; // 速度越高,响应时间越短,但是功耗就越高,电磁干扰也越高
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; // 不需要使能上下拉电阻
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 将外部中断连接到指定的引脚,特别说明:引脚编号决定了使用哪个外部中断 */
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
/* 配置外部中断0 */
EXTI_InitStructure.EXTI_Line = EXTI_Line0; // 指定外部中断0
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 工作在中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发:就是发现有从高电平到低电平的跳变,就向CPU中断请求处理
EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 允许外部中断0工作
EXTI_Init(&EXTI_InitStructure);
/* 通过NVIC管理外部中断0的中断请求:中断号、优先级、中断打开/关闭 */
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; // 中断号
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F; // 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F; // 响应优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 中断打开
NVIC_Init(&NVIC_InitStructure);
PFout(9) = 1;
PFout(10) = 1;
while (1)
{
if (g_key_event)
{
delay_ms(50); // 延时消抖
if (PAin(0) == 0) // 按键按下
PFout(9) ^= 1;
while (PAin(0) == 0)
;
g_key_event = 0;
/* 打开EXTI0外部中断请求 */
NVIC_EnableIRQ(EXTI0_IRQn);
}
}
}
/* 中断服务函数 */
void EXTI0_IRQHandler(void)
{
/* 检测中断是否有触发 */
if (EXTI_GetITStatus(EXTI_Line0) == SET)
{
/* 添加用户代码:灯的控制 */
#if 1
g_key_event = 1;
/* 关闭EXTI0外部中断请求 */
NVIC_DisableIRQ(EXTI0_IRQn);
#else
PFout(9) ^= 1; // 0^1=1 1^1=0 .........
#endif
/* 清空中断标志位,告诉CPU当前事件已经处理完毕
思考题:如果不清空标志位,会出现什么现象?
*/
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
3.使用外部中断服务函数调用另外一个定时器中断服务函数
使用外部中断(EXTI)使能一个定时器(TIM),定时器设置为50ms左右,在外部中断中使能定时器,然后在定时器中断服务函数中进行判断和相应操作,这样就不会影响到主函数的运行,提高效率
void EXTI2_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line2) == SET)
{
/* 清除定时器3的时间更新标志位 */
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
/* 关闭定时器3 */
TIM_Cmd(TIM3, DISABLE);
/* 清空当前计数值 */
TIM_SetCounter(TIM3,0);
/* 启动定时器3,就算有一些纹波存在,也会重置定时器 */
TIM_Cmd(TIM3, ENABLE);
EXTI_ClearITPendingBit(EXTI_Line2);
}
}
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update) == SET)
{
/* 检测按键状态*/
if(PEin(2)==0)
PFout(9)^=1;
/* 关闭定时器3 */
TIM_Cmd(TIM3, DISABLE);
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
}
}