注:本文为 "鼠标滚轮编码器" 相关合辑。
图片清晰度受引文原图所限。
略作重排,如有内容异常,请看原文。
鼠标滚轮/编码器检测- wheel/encoder detect for mouse
阳雨风 qq125242773 于 2020-10-09 22:36:29 首次发布
已于 2025-02-06 16:37:55 修改
wheel/encoder 原理

wheel/encoder 示波器实测波形

wheel/encoder 单片机检测固件
c
struct wheel_STR
{
unsigned char same_status_HL; //two state: all high (=1) or low (=2)
unsigned char diff_status; //two state: z1-high,z2-low (=1);z1_low,z2-high (=2);
signed char value;
};
struct wheel_STR wheel;
/*
scan in main loop
*/
void scan_wheel(void)
{
unsigned char z1,z2;
z1 = HAL_GPIO_ReadPin(WHEEL_Z1_PORT, WHEEL_Z1_PIN);//read_wheel_IO1();
z2 = HAL_GPIO_ReadPin(WHEEL_Z2_PORT, WHEEL_Z2_PIN);//read_wheel_IO2();
if(z1 != z2){ //diff
wheel.diff_status =(z1)?2:1;
}
else{ //same
if(z1){
if(wheel.same_status_HL==2){
if(wheel.diff_status==1) wheel.value++;
else if(wheel.diff_status==2) wheel.value--;
}
wheel.same_status_HL =1; //all high
}
else{
if(wheel.same_status_HL==1){
if(wheel.diff_status==1) wheel.value--;
else if(wheel.diff_status==2) wheel.value++;
}
wheel.same_status_HL =2; //all low
}
wheel.diff_status =0; //clear change flag!
}
}
/*
get the wheel vaule
*/
signed char get_wheel_value(void)
{
signed char tmp;
tmp = wheel.value;
wheel.value=0; //clear the value!
return tmp;
}
鼠标滚轮编码器解析
爱生活的鸭 于 2023-03-24 10:53:39 发布
前言
鼠标滚轮编码器为三引脚接入,一个公共引脚 C(通常接地),两个脉冲波形输入引脚 A、B。转动滚轮编码器时,两个脉冲输入引脚上会产生脉冲;顺时针或逆时针转动时,可根据同一时刻产生的电平信号变化进行逻辑判断。
一、鼠标滚轮编码器逻辑

正面从左到右依次为公共引脚、A 输入脚和 B 输入脚。
转动过程中,公共引脚与 A、B 脚的导通状态会改变输入至集成电路(IC)控制芯片引脚的电平,其电平变化逻辑如下:
顺时针转动时,电平变化顺序为:11 → 01 → 00 → 10
逆时针转动时,电平变化顺序为:11 → 10 → 00 → 01
二、使用方法
代码如下 g
c
unsigned char z1,z2;
// 读取编码器两个引脚(Z1、Z2)的当前电平状态
z1 = HAL_GPIO_ReadPin(WHEEL_Z1_PORT, WHEEL_Z1_PIN);//read_wheel_IO1();
z2 = HAL_GPIO_ReadPin(WHEEL_Z2_PORT, WHEEL_Z2_PIN);//read_wheel_IO2();
if(z1 != z2){ // 若 Z1 与 Z2 电平不同(差分状态),记录当前差分相位
// 当 Z1 为高电平时,差分状态标记为 2;Z1 为低电平时,标记为 1
// 用于后续判断编码器转动方向(相位差特征)
wheel.diff_status = (z1)?2:1;
}
else{ // 若 Z1 与 Z2 电平相同(同相状态),结合历史状态判断转动方向并计数
if(z1){ // 此时 Z1=Z2=高电平(全高状态)
// 若上一次同相状态为全低(same_status_HL=2),说明完成一次相位跳变
if(wheel.same_status_HL==2){
// 根据之前记录的差分状态判断方向:diff_status=1 对应顺时针,value 递增
// diff_status=2 对应逆时针,value 递减
if(wheel.diff_status==1) wheel.value++;
else if(wheel.diff_status==2) wheel.value--;
}
// 更新当前同相状态为全高(标记为 1)
wheel.same_status_HL =1; //all high
}
else{ // 此时 Z1=Z2=低电平(全低状态)
// 若上一次同相状态为全高(same_status_HL=1),说明完成一次相位跳变
if(wheel.same_status_HL==1){
// 根据之前记录的差分状态判断方向:diff_status=1 对应逆时针,value 递减
// diff_status=2 对应顺时针,value 递增
if(wheel.diff_status==1) wheel.value--;
else if(wheel.diff_status==2) wheel.value++;
}
// 更新当前同相状态为全低(标记为 2)
wheel.same_status_HL =2; //all low
}
// 清除差分状态标记(同相状态下,差分状态已用于方向判断,无需保留)
wheel.diff_status =0; //clear change flag!
}
总结
鼠标编码器的连接方式为:第一引脚接 GND,第二引脚和第三引脚为输出端。
编码器通过相位差判断滑动方向,通过输出低电平的持续时间判断滑动速度;但无论滑动速度和方向如何,各引脚输出的脉冲宽度均保持一致。
鼠标滚轮编码器检测代码性能优化
一、三点
针对嵌入式系统的特性(资源有限、实时性要求高),代码性能主要围绕以下三点展开:
- 执行效率:减少单次状态处理的时间开销,提升代码运行速度
- 抗干扰能力:降低外部噪声与机械抖动对信号检测的影响,提高稳定性
- 资源占用:内存使用与 CPU 占用率,适配嵌入式系统有限的硬件资源
二、方案
1. 中断驱动替代轮询(CPU 资源)
问题:传统主循环轮询方式需持续检测编码器状态,导致 CPU 资源被无意义占用,尤其在多任务系统中影响其他功能响应速度。
将编码器的 Z1、Z2 引脚配置为双边沿触发中断(上升沿与下降沿均触发),仅在电平状态变化时执行处理逻辑。示例代码如下(以 STM32 为例):
c
// 中断服务程序(ISR)
void EXTI9_5_IRQHandler(void) {
// 检测 Z1 引脚中断
if (__HAL_GPIO_EXTI_GET_IT(WHEEL_Z1_PIN) != RESET) {
HAL_GPIO_EXTI_IRQHandler(&wheel_z1_irq); // 调用中断处理回调
__HAL_GPIO_EXTI_CLEAR_IT(WHEEL_Z1_PIN); // 清除中断标志
}
// 检测 Z2 引脚中断
if (__HAL_GPIO_EXTI_GET_IT(WHEEL_Z2_PIN) != RESET) {
HAL_GPIO_EXTI_IRQHandler(&wheel_z2_irq); // 调用中断处理回调
__HAL_GPIO_EXTI_CLEAR_IT(WHEEL_Z2_PIN); // 清除中断标志
}
}
优势:
- CPU 仅在编码器状态变化时工作,空闲时可执行其他任务,降低 CPU 占用率
- 适用于多任务场景(如同时处理鼠标按键、位移检测等功能)
2. 状态机+查表法(执行效率)
问题 :传统多层if-else分支判断逻辑复杂,执行效率低,尤其在无分支预测机制的单片机中影响性能。
将编码器的状态(Z1、Z2 电平组合)抽象为有限状态机,通过"当前状态-上一状态"的组合查表直接获取转动方向,替代分支判断。示例代码如下:
c
// 状态转换表(4×4 矩阵,行:上一状态,列:当前状态,值:转动方向(+1 顺时针,-1 逆时针,0 无有效转动))
const int8_t wheel_dir_table[4][4] = {
{ 0, -1, 1, 0}, // 上一状态为 00(Z1=0,Z2=0)时的方向映射
{ 1, 0, 0, -1}, // 上一状态为 01(Z1=0,Z2=1)时的方向映射
{-1, 0, 0, 1}, // 上一状态为 10(Z1=1,Z2=0)时的方向映射
{ 0, 1, -1, 0} // 上一状态为 11(Z1=1,Z2=1)时的方向映射
};
// 状态处理函数
void process_wheel_state(uint8_t z1, uint8_t z2) {
static uint8_t prev_state = 0; // 静态变量存储上一状态,初始化为 0
uint8_t curr_state = (z1 << 1) | z2; // 组合 Z1、Z2 电平为当前状态(0~3)
wheel.value += wheel_dir_table[prev_state][curr_state]; // 查表更新转动计数
prev_state = curr_state; // 更新上一状态
}
优势:
- 用数组查表操作替代多层分支判断,减少 CPU 指令周期消耗
- 执行速度提升显著,尤其在高频检测场景下效果明显
3. 硬件+软件防抖(抗干扰能力)
问题:编码器机械触点抖动或电磁干扰可能导致电平误跳变,引发转动方向误判。
- 硬件防抖:在编码器引脚串联 10 kΩ电阻与 100 nF 电容,形成 RC 低通滤波器(时间常数约 10 μs),滤除高频噪声。
- 软件防抖:通过多次采样并采用"多数表决"机制确认电平状态,避免单次抖动影响。示例代码如下:
c
// 带软件防抖的引脚读取函数
uint8_t read_stable_pin(GPIO_TypeDef* port, uint16_t pin) {
uint8_t count = 0; // 高电平计数
for (uint8_t i = 0; i < 5; i++) { // 连续采样 5 次
count += (port->IDR & pin) ? 1 : 0; // 记录高电平次数
delay_us(5); // 每次采样间隔 5 μs,避开抖动期
}
return (count >= 3) ? 1 : 0; // 多数表决:3 次及以上高电平则判定为高电平
}
优势:
- 显著降低误触发概率,适应不同环境的噪声水平
- 硬件与软件结合,兼顾滤波效果与灵活性
4. 内存与变量
问题:多任务或中断环境下,共享变量可能因编译器优化或并发访问导致数据不一致。
-
volatile修饰共享变量:防止编译器对频繁访问的变量进行优化(如缓存到寄存器),确保每次读取均来自内存。ctypedef struct { volatile int8_t value; // 转动计数(共享变量) volatile uint8_t state; // 状态标记(共享变量) } WheelData; -
原子操作保护数据更新:在读取并清零计数等关键操作中,通过关闭中断实现原子操作,避免并发修改导致的数据错误。
cint8_t get_wheel_value(WheelData* wheel) { int8_t val; __disable_irq(); // 关闭中断,禁止并发访问 val = wheel->value; // 读取当前计数 wheel->value = 0; // 清零计数 __enable_irq(); // 恢复中断 return val; }
优势:
- 保证多任务/中断环境下共享数据的一致性
- 避免因并发访问导致的计数错误或状态混乱
5. 寄存器直接操作(执行效率)
问题 :使用 HAL 库函数(如HAL_GPIO_ReadPin)读取引脚电平时,存在冗余的参数校验与抽象层操作,增加函数调用开销。
直接访问 GPIO 寄存器(如 STM32 的IDR寄存器)读取电平状态,减少中间环节。示例代码如下:
c
// 宏定义:直接读取 Z1、Z2 引脚电平(以 STM32 为例)
#define READ_Z1() ((WHEEL_Z1_PORT->IDR & WHEEL_Z1_PIN) ? 1 : 0)
#define READ_Z2() ((WHEEL_Z2_PORT->IDR & WHEEL_Z2_PIN) ? 1 : 0)
优势:
- 减少函数调用的栈操作与参数处理开销
- 执行速度比库函数快约 30%,适合高频检测场景
三、指标评估
1. 四项关键指标
| 指标 | 定义 | 测量方式 |
|---|---|---|
| CPU 占用率 | 代码在单位时间内占用 CPU 的时间比例 | 调试器的 CPU Utilization 工具、RTOS 任务统计、Cortex-M 的 DWT Cycle Counter |
| 单次执行时间 | 处理一次编码器状态所需的实际时间(μs) | 硬件 SysTick 定时器、DWT Cycle Counter |
| 误触发率 | 误判为转动的次数占总检测次数的比例 | 统计错误计数与总计数的比值(人工测试或自动仿真) |
| 实时响应性 | 从信号变化到处理完成的端到端延迟(μs) | 示波器捕获外部信号与内部处理完成的时间差、代码时间戳差值计算 |
2. 基准测试(优化前)
- 测试环境 :保留原始轮询实现(
scan_wheel()),关闭所有中断触发代码,在同一硬件平台(如 STM32F103)、相同系统时钟(72 MHz)下运行。 - 调试配置:开启调试工具的监控功能(如 Keil µVision 的 System Viewer、STM32CubeIDE 的 Profiler),记录基准指标。
3. 实现(优化后)
结合中断驱动、查表法、防抖与寄存器直接操作的优化方案,示例代码如下:
c
/* 中断入口:仅标记状态变化,避免 ISR 过长 */
void EXTI9_5_IRQHandler(void) {
if (__HAL_GPIO_EXTI_GET_IT(WHEEL_Z1_PIN)) {
__HAL_GPIO_EXTI_CLEAR_IT(WHEEL_Z1_PIN);
wheel_irq_flag = 1; // 标记状态变化
}
if (__HAL_GPIO_EXTI_GET_IT(WHEEL_Z2_PIN)) {
__HAL_GPIO_EXTI_CLEAR_IT(WHEEL_Z2_PIN);
wheel_irq_flag = 1; // 标记状态变化
}
}
/* 主循环处理:查表法更新状态 */
void process_wheel(void) {
static uint8_t prev_state = 3; // 初始状态假设为 11(Z1=1,Z2=1)
uint8_t z1 = READ_Z1(); // 寄存器直接读取 Z1 电平
uint8_t z2 = READ_Z2(); // 寄存器直接读取 Z2 电平
uint8_t curr_state = (z1 << 1) | z2; // 组合当前状态
wheel.value += wheel_dir_table[prev_state][curr_state]; // 查表更新计数
prev_state = curr_state; // 更新上一状态
}
/* 主循环 */
int main(void) {
HAL_Init();
MX_GPIO_Init(); // 初始化 GPIO
MX_EXTI_Init(); // 配置双边沿中断
while (1) {
if (wheel_irq_flag) { // 仅在状态变化时处理
wheel_irq_flag = 0;
process_wheel();
}
// 执行其他任务...
}
}
4. 指标测量方法
4.1 CPU 占用率
使用 Cortex-M 的 DWT Cycle Counter 统计 1 s 内的 CPU 占用周期:
c
// 启用 DWT 计数器
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
uint32_t start = DWT->CYCCNT;
HAL_Delay(1000); // 运行 1 s
uint32_t cycles = DWT->CYCCNT - start;
float cpu_percent = (float)cycles / (SystemCoreClock * 1.0f) * 100.0f; // 计算占比
4.2 单次执行时间
通过 DWT 计数器测量process_wheel()函数的执行时间:
c
uint32_t t0 = DWT->CYCCNT;
process_wheel(); // 执行一次状态处理
uint32_t t1 = DWT->CYCCNT;
float exec_time_us = (t1 - t0) * 1e6f / SystemCoreClock; // 转换为μs
4.3 误触发率
误触发率计算公式为:
误触发率 = 错误计数 总检测次数 × 100 % \text{误触发率} = \frac{\text{错误计数}}{\text{总检测次数}} \times 100\% 误触发率=总检测次数错误计数×100%
- 人工测试:手动转动滚轮 1000 次,记录
wheel.value与实际转动次数的差值作为错误计数。 - 自动仿真:使用逻辑分析仪捕获编码器信号,通过 Python 脚本统计错误计数。
4.4 实时响应性(端到端延迟)
延迟计算公式为:
Latency (μs) = proc_done_ts − irq_entry_ts SystemCoreClock × 1 0 6 \text{Latency (μs)} = \frac{\text{proc\_done\_ts} - \text{irq\_entry\_ts}}{\text{SystemCoreClock}} \times 10^6 Latency (μs)=SystemCoreClockproc_done_ts−irq_entry_ts×106
其中,irq_entry_ts为中断入口时刻的时间戳,proc_done_ts为状态处理完成时刻的时间戳,通过 DWT 计数器记录。
5. 综合对比(示例数据)
| 指标 | 优化前(轮询) | 优化后(综合方案) | 提升幅度 |
|---|---|---|---|
| CPU 占用率 | 30 % | 5 % | ↓ 83 % |
| 单次执行时间 | 20 μs | 8 μs | ↓ 60 % |
| 误触发率 | 8 % | 0.5 % | ↓ 94 % |
| 实时响应性(端到端) | 150 μs | 30 μs | ↓ 80 % |
注:实际数据受 MCU 型号、时钟频率、编码器特性及环境噪声影响,以上为参考示例。
6. 工具与脚本
- CPU/任务统计:Keil µVision(System Viewer)、STM32CubeIDE(Profiler)、FreeRTOS Trace
- 周期计数:Cortex-M 的 DWT Cycle Counter(代码级时间测量)
- 误触发统计:Python + pySerial(读取串口日志并自动计算)
- 延迟波形分析:示波器(如 Tektronix TBS1000)、逻辑分析仪(如 Saleae Logic)
四、注意事项
- 中断服务程序(ISR)需保持简洁,仅执行状态标记等轻量操作,避免长时间占用 CPU 影响系统响应。
- 状态转换表需根据编码器的实际相位特性调整,不同型号编码器的状态映射可能存在差异。
- 防抖参数(采样次数、间隔时间)需结合硬件特性调试,平衡抗干扰能力与响应速度。
- 对于高速编码器(如高分辨率电竞鼠标),建议采用更高效的寄存器操作与中断优先级配置,确保信号无漏检。
鼠标滚轮编码器:原理、故障与维修指南
一、编码器基础认知
1. 类型与特性

-
光栅式:早期广泛应用,依赖红外线收发信号,无物理接触,稳定性强、寿命较长,但光源会自然衰减,对主控编程有特定要求,现仅少数厂商使用。
-
机械式:目前主流类型,结构简单、主控编程便捷,具备清晰机械刻度手感与精准定位特性,使用寿命已从 10 万圈提升至 200 万圈,适配多数鼠标。

2. 常见故障及成因
故障表现
- 滚轮无规律上下跳动、失灵;
- 滚动方向异常(如下滑触发上滑)或滚动过度;
- 游戏场景中视野无规律缩放、无法聚焦;
- 滚动信号丢失,反应时有时无或完全失效。
故障成因
- 零部件精度不足,使用中产生误差;
- 编码器触点材质较差,长期使用易磨损导致接触不良;
- 鼠标内部卷入异物,阻碍机械结构运行。
3. 编码器选型三项关键参数:
- 安装高度(如 TTC 11 mm 规格);
- 编码器类型(光栅式/机械式);
- 结构细节(六边形方孔朝向、是否为高端鼠标定制款)。

二、编码器高度测量
结构

参数
Hole Size

Torque Field

Height Options

安装高度测量
直接在拆解后的鼠标面板上测量,确保与新配件高度一致。


安装尺寸

三、编码器常见类型

103 系列(通用型)

123 系列(部分带线款)

四、维修流程
1. 准备工具与配件

- 基础工具:小型十字螺丝刀、镊子/薄刃工具;
- 焊接工具:电烙铁、吸锡器、焊锡丝;
- 核心配件:与原鼠标参数匹配的全新编码器。


2. 鼠标拆解步骤
-
移除鼠标底部脚贴/贴纸,露出隐藏螺丝;

-
拧下螺丝,沿卡扣缝隙缓慢分离上盖,避免损坏内部排线;
-
取出主板,定位滚轮下方圆柱形编码器组件(焊接固定或插头连接);

-
焊接固定款需用电烙铁+吸锡器拆旧,插头款直接拔插分离。

3. 编码器更换操作
-
用橡皮擦清洁滚轮金属触点,去除氧化层;

-
拆旧编码器:新手逐点清理焊盘残留焊锡后取出,熟手可同时熔化所有焊点快速拆卸;


-
装新编码器:精准对位后,焊接固定各引脚(熟手可利用残留焊锡定位,无需补焊);

-
功能测试:通电验证滚轮灵敏度与定位准确性;

-
重组鼠标:按拆解反向顺序复位主板、上盖,紧固螺丝与卡扣。

4. 维修注意事项
- 具备基础动手能力与电子设备认知者可自行操作,无维修经验建议寻求专业支持;
- 鼠标在保修期内时,优先联系制造商咨询免费维修/更换服务,避免自行拆解影响保修。
via:
-
鼠标滚轮/编码器检测- wheel/encoder detect for mouse_鼠标滚轮检测-CSDN 博客
https://blog.csdn.net/weixin_43336331/article/details/108987551 -
理解鼠标滚轮编码器逻辑与使用方法-CSDN 博客
https://blog.csdn.net/hubeixiaokang/article/details/125488274 -
终于弄明白了鼠标滚轮编码器 - 知乎
https://zhuanlan.zhihu.com/p/28061206 -
鼠标滚轮坏了?点进来,我教你怎么修|编码器|鼠标|滚轮_新浪新闻
https://k.sina.com.cn/article_1823348853_6cae187500101hpoa.html -
有没有特别耐用的办公鼠标? - 知乎
https://www.zhihu.com/question/333273216 -
2025 年 1 月鼠标滚轮编码器推荐 - 知乎
https://zhuanlan.zhihu.com/p/23990877771 -
Mouse wheel / encoder detection - WHEEL / ENCODER Detect for MOUSE - Programmer Sought
https://www.programmersought.com/article/40829294960/ -
Mouse Rotary Encoder: The Working Principle and How to Connect It With the Arduino -- PCB COPY
https://pcb-copy.com/mouse-rotary-encoder-the-working-principle-and-how-to-connect-it-with-the-arduino/ -
GitHub - artem78/MouseTester: Small tool for testing mouse clicks and scroll
https://github.com/artem78/MouseTester -
GitHub - wirebadger/mouse-wheel: Arduino based exercise wheel counter for mice
https://github.com/wirebadger/mouse-wheel -
Scroll Wheel Test - Test your mouse scroll wheel
https://scrollwheeltest.com/ -
Trackpad Tester -- Check Touchpad Latency & Dead‑Zones Online
https://mousetester.uk/trackpadTester.html -
......