旋转编码器驱动程序设计
上一期已经探讨过旋转编码器的时序图,以及进行硬件滤波。
·
上一期已经探讨过旋转编码器的时序图,以及进行硬件滤波。
本期来编写旋转编码器的代码驱动程序
程序功能:
- 判断旋转编码器的旋转方向和旋转角度(精确无抖动)
- 添加旋转编码器按动逻辑:单击,双击,长按。
先看效果:
旋功能转

长按关灯

短按开灯

双击关小绿灯

经过我简单测试,旋转递增(递减)的稳定性极高,目前未发现任何抖动
按键的逻辑只要不短期连续操作多种逻辑,稳定性很高,但是怎么解决连续长按短按双击,就要看各位高手的修正了
以下是驱动代码,基于HAL库编写,其中外部中断触发条件均为双边沿
其中涉及到位操作的函数来源于
/*
* Encoder_SW.c
*
* Created on: Sep 26, 2025
* Author: zr186
*/
#include "main.h"
#include "Battery_Control.h"
#include "State_LED.h"
static uint8_t Coefficient=3;
static int8_t Encoder_Count=0;
static uint16_t ENCODER_A_CountDown=0,ENCODER_B_CountDown=0,ENCODER_K_CountDown=0;
static uint8_t STATE=0;//01,234,5,67
static uint8_t AB_Debouncing=1,SW_Debouncing=10,SW_DoubleClick_Interval=30,SW_PressDown_Interval=120;
static uint16_t time=0;
static void SW_DoubleClick(void);
static void SW_PressDownLong(void);
static void SW_PressDownShort(void);
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if(GPIO_Pin==ENCODER_A_Pin){
HAL_NVIC_DisableIRQ(ENCODER_A_EXTI_IRQn);
__HAL_GPIO_EXTI_CLEAR_IT(ENCODER_A_Pin);
if(HAL_GPIO_ReadPin(ENCODER_A_GPIO_Port,ENCODER_A_Pin)==GPIO_PIN_RESET){
Set_Bit(STATE, 1);
}else{
if(Get_Bit(STATE, 1)==1){
if(Get_Bit(STATE, 0)==1) {
if(Encoder_Count+Coefficient>=127) Encoder_Count=127;
else if(Encoder_Count+Coefficient<=-127) Encoder_Count=-127;
else Encoder_Count=Encoder_Count+Coefficient;
}
else Encoder_Count=Encoder_Count-Coefficient;
Clear_Bit(STATE, 1);
}
}
Set_Bit(STATE,4);
}else if(GPIO_Pin==ENCODER_B_Pin){
HAL_NVIC_DisableIRQ(ENCODER_B_EXTI_IRQn);
__HAL_GPIO_EXTI_CLEAR_IT(ENCODER_B_Pin);
if(HAL_GPIO_ReadPin(ENCODER_B_GPIO_Port,ENCODER_B_Pin)==GPIO_PIN_SET){
Set_Bit(STATE, 0);
}else{
Clear_Bit(STATE, 0);
}
Set_Bit(STATE,3);
}else if(GPIO_Pin==ENCODER_K_Pin){
HAL_NVIC_DisableIRQ(ENCODER_K_EXTI_IRQn);
__HAL_GPIO_EXTI_CLEAR_IT(ENCODER_K_Pin);
if(HAL_GPIO_ReadPin(ENCODER_K_GPIO_Port,ENCODER_K_Pin)==GPIO_PIN_RESET){
if(Get_Bit(STATE, 7)==0 && Get_Bit(STATE, 6)==0 && Get_Bit(STATE,5)==0){
Set_Bit(STATE,6);
Clear_Bit(STATE,7);//第一边沿
time=0;
Set_Bit(STATE,5);//开始计数
}else if(Get_Bit(STATE, 7)==1 && Get_Bit(STATE, 6)==0){
Set_Bit(STATE,6);
Set_Bit(STATE,7);//达到第三边沿
Clear_Bit(STATE,5);//停止计数
time=0;//清除计时
}
}else if(HAL_GPIO_ReadPin(ENCODER_K_GPIO_Port,ENCODER_K_Pin)==GPIO_PIN_SET){
if(Get_Bit(STATE, 7)==0 && Get_Bit(STATE, 6)==1){
Set_Bit(STATE,7);
Clear_Bit(STATE,6);//达到第二边沿
time=0;
}else if(Get_Bit(STATE, 7)==1 && Get_Bit(STATE, 6)==1){
Clear_Bit(STATE,6);
Clear_Bit(STATE,7);//达到第四(0)边沿
Clear_Bit(STATE,5);//停止计数
time=0;//清除计时
}
}
}
Set_Bit(STATE,2);//开启消抖
}
void Encoder_SW_ParemeterSet(uint8_t progressive_Coefficient,
uint8_t AB_Debouncing_Time,
uint8_t SW_Debouncing_Time,
uint16_t DoubleClick_Interval,
uint16_t PressDown_Interval){
Coefficient=progressive_Coefficient;
AB_Debouncing=AB_Debouncing_Time;
SW_Debouncing=SW_Debouncing_Time;
SW_DoubleClick_Interval=(uint8_t)(DoubleClick_Interval/10);
SW_PressDown_Interval=(uint8_t)(PressDown_Interval/10);
}
int8_t Get_Encoder_Count(void){
int8_t data=Encoder_Count;
Encoder_Count=0;
return data;
}
void Encoder_TimeSource(void){
if(Get_Bit(STATE,4)==1){
if(ENCODER_A_CountDown>1){
ENCODER_A_CountDown--;
}else{
Clear_Bit(STATE, 4);
__HAL_GPIO_EXTI_CLEAR_IT(ENCODER_A_Pin);
HAL_NVIC_EnableIRQ(ENCODER_A_EXTI_IRQn);
ENCODER_A_CountDown=AB_Debouncing*10;
}
}
if(Get_Bit(STATE,3)==1){
if(ENCODER_B_CountDown>1){
ENCODER_B_CountDown--;
}else{
Clear_Bit(STATE, 3);
__HAL_GPIO_EXTI_CLEAR_IT(ENCODER_B_Pin);
HAL_NVIC_EnableIRQ(ENCODER_B_EXTI_IRQn);
ENCODER_B_CountDown=AB_Debouncing*10;
}
}
if(Get_Bit(STATE,2)==1){
if(ENCODER_K_CountDown>1){
ENCODER_K_CountDown--;
}else{
Clear_Bit(STATE, 2);
__HAL_GPIO_EXTI_CLEAR_IT(ENCODER_K_Pin);
HAL_NVIC_EnableIRQ(ENCODER_K_EXTI_IRQn);
ENCODER_K_CountDown=SW_Debouncing*10;
}
}
if(Get_Bit(STATE,5)==1){
if(time<60000){
time++;
if(time==SW_DoubleClick_Interval*100 && Get_Bit(STATE,7)==1 && Get_Bit(STATE,6)==0){//经过第2边沿但是没有按时按下双击,判定为单击
Clear_Bit(STATE,5);//停止计数
time=0;//清除计时
}
if(time==SW_PressDown_Interval*100 && Get_Bit(STATE,7)==0 && Get_Bit(STATE,6)==1){//在12边沿之间达到长按判定条件
Clear_Bit(STATE,5);//关闭按下时长计数
time=0;//清除计时
}
}
}
}
void SW_Process(void){
if(Get_Bit(STATE,6)==1 && Get_Bit(STATE,7)==0 && Get_Bit(STATE,5)==0){
SW_PressDownLong();
}else if(Get_Bit(STATE,6)==1 && Get_Bit(STATE,7)==1 && Get_Bit(STATE,5)==0){
SW_DoubleClick();
}else if(Get_Bit(STATE,6)==0 && Get_Bit(STATE,7)==1 && Get_Bit(STATE,5)==0){
SW_PressDownShort();
}
}
static void SW_DoubleClick(void){
Clear_Bit(STATE,6);
Clear_Bit(STATE,7);
PWM_SET(LED0,0);//此处添加双击操作
}
static void SW_PressDownLong(void){
Clear_Bit(STATE,6);
Clear_Bit(STATE,7);
PWM_SET(ALED,0);//此处添加长按操作
LED2_Set(LED2_OFF);
}
static void SW_PressDownShort(void){
Clear_Bit(STATE,6);
Clear_Bit(STATE,7);
PWM_SET(ALED,100);//此处添加短按操作
LED2_Set(LED2_W);
TYPE_C_WKUP();
}
/*
* Encoder_SW.h
*
* Created on: Sep 26, 2025
* Author: zr186
*/
#ifndef ENCODER_SW_H_
#define ENCODER_SW_H_
/*
* 此函数是中断回调函数,不可更改函数名
* */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin);
/*
* 本驱动程序需要与定时器紧密配合,此函数应当放在定时器中断中不断执行,为消抖提供时间来源。
* 默认定时器配置为每秒执行10000次中断程序
* */
void Encoder_TimeSource(void);
/*
* 此函数是旋转编码器按下后的处理函数,务必放在“main.c”的循环函数中,并且不可以对此函数进行更改
* */
void SW_Process(void);
/*
* 获取上次调用该函数到本次调用期间的旋转编码器偏转值,调用之后计数值会清零,
* 建议放到循环中100ms左右执行一次
* */
int8_t Get_Encoder_Count(void);
/*
* 默认时间源0.1ms时,所有参数默认单位1ms
* 配置消抖参数以及单击,双击,长按判定参数
*
* progressive_Coefficient:旋转编码器递增倍数,参数越大,旋转编码器转一次计数越大
*
* AB_Debouncing_Time:这是AB两相消抖的时间,默认1ms
*
* SW_Debouncing_Time:按键消抖时间,默认10ms
*
* DoubleClick_Interval:双击判定时,连续两次按下的时间间隔,默认300ms
*
* PressDown_Interval:长按判定时,低电平的持续时间,默认1200ms,必须小于2.5秒
*
* */
void Encoder_SW_ParemeterSet(uint8_t progressive_Coefficient,
uint8_t AB_Debouncing_Time,
uint8_t SW_Debouncing_Time,
uint16_t DoubleClick_Interval,
uint16_t PressDown_Interval);
#endif /* ENCODER_SW_H_ */
以上代码的运行示例:
1.在定时器中断中添加时间来源

2.在主函数的while循环中添加按键处理函数

3.若要修改按键功能,只需要调整这些函数里的代码

更多推荐



所有评论(0)