测试平台:STM32F103C8T6 库函数使用标准版
在许多应用中进行人机交互通常也会使用按键功能进行设置或地震地震某个参数。
这次使用带有按键功能地 EC 11旋转编码器进行设计长按、短按、双击、旋转递增递减功能。
为了节省MCU利用率资源自然我们使用中断+通用定时器的方式进行 不使用轮询方式进行设计。
说明 SW1为按键功能 WS2、SW3为可扭动的旋转编码器。
可扭动的旋转编码器,连接的电容为100pF电容值,实际情况可使用示波器工具进行测量方波信号以获得完美的脉冲信号。
引脚中断初始化。
//----------------旋转编码器旋转变量----------------//
extern int Encoder_status; //旋转编码器当前状态
extern u8 Hxe8_Twinkle; //传递数码管闪烁时间
extern u16 Hxe8_paragraph; //传递数码管位选段显示
extern u32 Hxe8_data; //传递数码管显示数据
//----------------脉冲参数变量的----------------//
extern u32 Seg_pul_frequent;
extern u32 Seg_pul_frequent_mid;
extern u32 Seg_pul_accel;
extern u32 Seg_Pul_accel_mid;
extern u16 Pul_frequent[2];
extern u16 Pul_accel[2];
u8 Kup; //旋钮锁死标志(1为锁死)
u8 Kt; //存放按键对比值
u16 Cou; //初始锁死计数器
//----------------旋转编码器按钮变量----------------//
static u16 Key_upCnt = 0; //按键弹起后计数值
static u16 Key_holdon_ms = 0; //按下的时长
u16 Key_second_down = 0; //按键再次按下计数标志
u8 Flag_ec11_key_shotclick = 0; //EC11按键短按动作标志
u8 Flag_ec11_key_longclick = 0; //EC11按键长按动作标志
u8 Flag_ec11_key_doubleclick = 0; //EC11按键双击动作标志
u8 Key_fall_flag = 0; //外部中断按键按下标志
u16 Key_up_flag = 0; //按键弹起标志
//----------------当前显示的界面参数----------------//
u8 EC11_NUM_SW = 0; //记录处于在哪个设置界面中,目前只有一个界面
u8 Ec11_set_she = 0; //进入设置参数,目前尚未添加此功能
u8 Flash_refreshclick = 0; //数据有改动标志
/**
* 函数功能: EC11旋转编码器初始化
* 输入参数: 无
* 返 回 值: 无
* 说 明:以外部触发上升沿中断方式检测
*/
void Encoder_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //外部中断,需要使能AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
GPIO_InitStructure.GPIO_Pin = ENCODER_CLK; //当做旋钮的时钟 输入外部触发中断
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //选择IO接口工作方式上拉电阻
GPIO_Init(ENCODER_GPIO, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = ENCODER_DT;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //选择IO接口工作方式上拉电阻
GPIO_Init(ENCODER_GPIO, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = ENCODER_KEY; //当做旋钮的时钟 输入外部触发中断
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //选择IO接口工作方式上拉电阻
GPIO_Init(ENCODER_KEY_GPIO, &GPIO_InitStructure);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn; //使能按键所在的外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; //子优先级1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);
//GPIOA3 中断线以及中断初始化配置 旋转转动
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource3);
EXTI_InitStructure.EXTI_Line=EXTI_Line3; //链接到外部中断3组
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling ; //上升沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; //使能按键所在的外部中断通道 进一步确认是否优先级搞事情
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x03; //抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03; //子优先级2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);
//GPIOC14 中断线以及中断初始化配置 按键按钮按下 HH 这个是确认按钮 GG在使用中会卡死状态屏蔽掉没事
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);
EXTI_InitStructure.EXTI_Line = EXTI_Line1; //链接到外部中断14组
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
GPIO_SetBits(ENCODER_GPIO, ENCODER_CLK);
GPIO_SetBits(ENCODER_GPIO, ENCODER_DT);
GPIO_SetBits(ENCODER_KEY_GPIO, ENCODER_KEY);
}
定时器2初始化
/**
* 函数功能: 配置TIM2定时器初始化
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
void Tim2_init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/*-------------------中断优先级配置初始化-------------------*/ //中断优先级会影响
/* 设置中断组为0 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
/* 设置中断来源 */
NVIC_InitStructure.NVIC_IRQChannel = ADVANCE_TIM2_IRQ;
/* 设置主优先级为 0 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
/* 设置抢占优先级为3 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/*-------------------TIM2配置初始化-------------------*/
/* 开启TIM2_CLK即内部时钟CK_INT=72M */
RCC_APB1PeriphClockCmd(ADVANCE_TIM2_CLK, ENABLE);
/* 自动重装载寄存器周的值(计数值) */
TIM_TimeBaseStructure.TIM_Period=ADVANCE_TIM2_PERIOD;
/* 累计 TIM_Period个频率后产生一个更新或者中断
时钟预分频数为71,则驱动计数器的时钟CK_CNT = CK_INT / (71+1)=1M */
TIM_TimeBaseStructure.TIM_Prescaler= ADVANCE_TIM2_PRESCALER;
/* 计数器计数模式,基本定时器TIM8向上计数 */
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
/* 初始化定时器TIM2 */
TIM_TimeBaseInit(ADVANCE_TIM2, &TIM_TimeBaseStructure);
/* 清除计数器中断标志位 */
TIM_ClearITPendingBit(ADVANCE_TIM2, TIM_IT_Update);
/* 使能计数器 */
TIM_Cmd(ADVANCE_TIM2, ENABLE);
/* 开启计数器中断 */
TIM_ITConfig(ADVANCE_TIM2,TIM_IT_Update,ENABLE);
}
旋转编码器。中断服务函数触发,因为编码器的特性,扭动一步 AB必定产生电平的变化也导致中断的产生。
后判断 AB相,哪个脉冲先运行即可判断出顺时针或逆时针并做好标记
/**
* 函数功能: 外部EXTI3中断服务函数
对应关系是PB3-编码器的A相
ENCODER_DT = PB4-编码器的B相
* 输入参数: 无
* 返 回 值: 无
* 说 明:Encoder_status变量状态:
* -------------------------------- 0:无动作;
* -------------------------------- 1:正转;
* -------------------------------- -1:反转;
* -------------------------------- 3:按着按键正转;
* -------------------------------- -3:按着按键反;
*/
void EXTI3_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line3) != RESET)
{
Encoder_status = 0;
if(GPIO_ReadInputDataBit(ENCODER_GPIO,ENCODER_CLK)) Kup = 0; //判断旋钮是否解除锁死
if(!GPIO_ReadInputDataBit(ENCODER_GPIO,ENCODER_CLK) && Kup == 0) //判断是否旋转旋钮,同时判断是否有旋钮锁死
{
delay_us(100);
Kt=GPIO_ReadInputDataBit(ENCODER_GPIO,ENCODER_DT); //把旋钮另一端电平状态记录
delay_ms(2); //延时
if(!GPIO_ReadInputDataBit(ENCODER_GPIO,ENCODER_CLK)) //去抖
{
if(Kt!=0)
{ //用另一端判断左或右旋转
Encoder_status = 1; //顺时针转
//--------编码器顺时针动作代码--------//
Clockwise(); //旋转编码器顺时针动作代码
//--------编码器顺时针动作结束区--------//
}
else
{
Encoder_status = -1;//逆时针转转
//--------编码器逆时针动作代码--------//
Anticlockwise();
//--------编码器逆时针动作结束区--------//
}
/* 由于数码管刷新频率存在缺陷将此段进行屏蔽
Cou=0; //初始锁死判断计数器
while(!GPIO_ReadInputDataBit(ENCODER_GPIO,ENCODER_CLK)&&Cou<60000)//等待放开旋钮,同时累加判断锁死
{
Cou++;Kup=1;delay_us(20);
}
*/
}
}
/*
//取消按键按下状态旋转编码器的触发
if(GPIO_ReadInputDataBit(ENCODER_KEY_GPIO,ENCODER_KEY) == 0) //在按键按下状态旋转编码器
{
if(Encoder_status == 1 )
{
Encoder_status = 3;
DIG3= !DIG3;
}
else if(Encoder_status == -1)
{
Encoder_status = -3;
DIG4= !DIG4;
}
}
*/
EXTI_ClearITPendingBit(EXTI_Line3); //清除LINE3上的中断标志位
}
}
按键中断服务函数。
/**
* 函数功能: EXTI_2外部服务中断函数
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
void EXTI1_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line1) != RESET)
{
if(GPIO_ReadInputDataBit(ENCODER_KEY_GPIO, ENCODER_KEY) == 0) //按键 EXTI_2
{
Key_fall_flag = 1; //外部中断按键按下标志
}
EXTI_ClearFlag(EXTI_Line1);
EXTI_ClearITPendingBit(EXTI_Line1); //清除 LINE15 上的中断标志位
}
}
此处为判断按键短按长按双击 通过定时器,累加变量判断各种时间标志。
/**
* 函数功能: Timer2定时器服务中断函数
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)!= RESET) //检查指定的TIM中断发生与否:TIM 中断源
{
TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //清除TIMx的中断待处理位:TIM 中断源
if(Key_fall_flag == 1) //发生按键按下事件
{
if(GPIO_ReadInputDataBit(ENCODER_KEY_GPIO, ENCODER_KEY) == 0) //按键持续按下
{
if(Key_holdon_ms <= 2000) //按下按键时间开始计时
{
Key_holdon_ms++;
}
else if(Key_holdon_ms > 2000) //按键按下到2000ms就判断长按时间成立,生成长按标志
{
Key_holdon_ms = 0; //清除长按动作EC11按键动作计数器
Flag_ec11_key_shotclick = 0; //清除EC11按键短按动作标志
Flag_ec11_key_longclick = 1; //置位EC11按键长按动作标志
Flag_ec11_key_doubleclick = 0; //清除EC11按键双击动作标志
Key_fall_flag = 0; //外部中断按键按下标志
Key_up_flag = 0; //按键弹起标志
Key_upCnt = 0; //按键弹起后计数值
Encoder_status = 5; //旋转编码器当前状态
//--------编码器长按动作代码--------//
Long_press();//旋转编码器长按动作代码
//--------编码器长按作结束区--------//
}
}
else //按键抬起
{
if(Key_holdon_ms > 10 && Flag_ec11_key_longclick == 0 ) //按下时间大于50ms,按键时间到达消抖时间
{
Key_up_flag = 1; //单击抬起按键后,生成按键抬起标志
Key_second_down ++; //按键再次按下计数标志
if(Key_second_down >= 200 && Key_upCnt<200)
{
Key_holdon_ms = 0; //清除长按动作EC11按键动作计数器
Flag_ec11_key_shotclick = 1; //置位EC11按键短按动作标志
Flag_ec11_key_longclick = 0; //清除EC11按键长按动作标志
Flag_ec11_key_doubleclick = 0; //清除EC11按键双击动作标志
Key_fall_flag = 0; //外部中断按键按下标志
Key_second_down = 0; //再次按下标志位
Key_up_flag = 0; //按键弹起标志
Key_upCnt = 0; //按键弹起后计数值
Encoder_status = 4; //旋转编码器当前状态
//--------编码器短按动作代码--------//
Press(); //旋转编码器短按动作代码
//--------编码器短按作结束区--------//
}
else if(Key_upCnt>400) //如果在规定双击时间内超时按下按键
{ //认为按键是双击动作
Key_holdon_ms = 0; //清除长按动作EC11按键动作计数器
Flag_ec11_key_shotclick = 0; //清除EC11按键短按动作标志
Flag_ec11_key_longclick = 0; //清除EC11按键长按动作标志
Flag_ec11_key_doubleclick = 1; //置位EC11按键双击动作标志
Key_fall_flag = 0; //外部中断按键按下标志
Key_second_down = 0; //再次按下标志位
Key_up_flag = 0; //按键弹起标志
Key_upCnt = 0; //按键弹起后计数值
Encoder_status = 2; //旋转编码器当前状态
//--------编码器双击动作代码--------//
Double_click(); //旋转编码器双击动作代码
//--------编码器双击作结束区--------//
}
}
else //按键持续时间小于50ms,忽略
{
Key_holdon_ms = 0; //清除长按动作EC11按键动作计数器
Flag_ec11_key_shotclick = 0; //清除EC11按键短按动作标志
Flag_ec11_key_longclick = 0; //置位EC11按键长按动作标志
Flag_ec11_key_doubleclick = 0; //清除EC11按键双击动作标志
Key_fall_flag = 0; //外部中断按键按下标志
Key_up_flag = 0; //按键弹起标志
Key_upCnt = 0; //按键弹起后计数值
Encoder_status = 0; //旋转编码器当前状态
}
}
}
if(Key_up_flag)//单击抬起后,启动计数,计数到500ms
Key_upCnt++;
if(Key_upCnt > 500)
{
Key_upCnt = 0;
Key_up_flag = 0;//标记为弹起
}
}
}
下面为各种操作状态所要执行的函数。
/**
* 函数功能: 旋转编码器顺时针动作代码
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
void Clockwise (void)
{
}
/**
* 函数功能: 旋转编码器逆时针动作代码
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
void Anticlockwise (void)
{
}
/**
* 函数功能: 旋转编码器长按动作代码
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
void Long_press (void)
{
}
/**
* 函数功能: 旋转编码器双击动作代码
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
void Double_click (void)
{
}
/**
* 函数功能: 旋转编码器短按动作代码
* 输入参数: 无
* 返 回 值: 无
* 说 明:无
*/
void Press (void)
{
}