上一期已经探讨过旋转编码器的时序图,以及进行硬件滤波。

本期来编写旋转编码器的代码驱动程序

程序功能:

  1. 判断旋转编码器的旋转方向和旋转角度(精确无抖动)
  2. 添加旋转编码器按动逻辑:单击,双击,长按。

先看效果:

旋功能转

长按关灯

短按开灯

双击关小绿灯

经过我简单测试,旋转递增(递减)的稳定性极高,目前未发现任何抖动

按键的逻辑只要不短期连续操作多种逻辑,稳定性很高,但是怎么解决连续长按短按双击,就要看各位高手的修正了

以下是驱动代码,基于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.若要修改按键功能,只需要调整这些函数里的代码

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐