鼠标滚轮编码器 | 原理、检测与维修

注:本文为 “鼠标滚轮编码器” 相关合辑。
图片清晰度受引文原图所限。
略作重排,如有内容异常,请看原文。


鼠标滚轮/编码器检测- wheel/encoder detect for mouse

阳雨风 qq125242773 于 2020-10-09 22:36:29 首次发布
已于 2025-02-06 16:37:55 修改

wheel/encoder 原理

鼠标滚轮 wheel/encoder 原理图

wheel/encoder 示波器实测波形

实测波形

wheel/encoder 单片机检测固件

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

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,第二引脚和第三引脚为输出端。

编码器通过相位差判断滑动方向,通过输出低电平的持续时间判断滑动速度;但无论滑动速度和方向如何,各引脚输出的脉冲宽度均保持一致。


鼠标滚轮编码器检测代码性能优化

一、三点

针对嵌入式系统的特性(资源有限、实时性要求高),代码性能主要围绕以下三点展开:

  1. 执行效率:减少单次状态处理的时间开销,提升代码运行速度
  2. 抗干扰能力:降低外部噪声与机械抖动对信号检测的影响,提高稳定性
  3. 资源占用:内存使用与 CPU 占用率,适配嵌入式系统有限的硬件资源

二、方案

1. 中断驱动替代轮询(CPU 资源)

问题:传统主循环轮询方式需持续检测编码器状态,导致 CPU 资源被无意义占用,尤其在多任务系统中影响其他功能响应速度。

将编码器的 Z1、Z2 引脚配置为双边沿触发中断(上升沿与下降沿均触发),仅在电平状态变化时执行处理逻辑。示例代码如下(以 STM32 为例):

// 中断服务程序(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 电平组合)抽象为有限状态机,通过“当前状态-上一状态”的组合查表直接获取转动方向,替代分支判断。示例代码如下:

// 状态转换表(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),滤除高频噪声。
  • 软件防抖:通过多次采样并采用“多数表决”机制确认电平状态,避免单次抖动影响。示例代码如下:
// 带软件防抖的引脚读取函数
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修饰共享变量:防止编译器对频繁访问的变量进行优化(如缓存到寄存器),确保每次读取均来自内存。

    typedef struct {
        volatile int8_t value;  // 转动计数(共享变量)
        volatile uint8_t state; // 状态标记(共享变量)
    } WheelData;
    
  • 原子操作保护数据更新:在读取并清零计数等关键操作中,通过关闭中断实现原子操作,避免并发修改导致的数据错误。

    int8_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寄存器)读取电平状态,减少中间环节。示例代码如下:

// 宏定义:直接读取 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. 实现(优化后)

结合中断驱动、查表法、防抖与寄存器直接操作的优化方案,示例代码如下:

/* 中断入口:仅标记状态变化,避免 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 占用周期:

// 启用 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()函数的执行时间:

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_tsirq_entry_ts×106
其中,irq_entry_ts为中断入口时刻的时间戳,proc_done_ts为状态处理完成时刻的时间戳,通过 DWT 计数器记录。

5. 综合对比(示例数据)

指标优化前(轮询)优化后(综合方案)提升幅度
CPU 占用率30 %5 %↓ 83 %
单次执行时间20 μs8 μs↓ 60 %
误触发率8 %0.5 %↓ 94 %
实时响应性(端到端)150 μs30 μs↓ 80 %

注:实际数据受 MCU 型号、时钟频率、编码器特性及环境噪声影响,以上为参考示例。

6. 工具与脚本

  • CPU/任务统计:Keil µVision(System Viewer)、STM32CubeIDE(Profiler)、FreeRTOS Trace
  • 周期计数:Cortex-M 的 DWT Cycle Counter(代码级时间测量)
  • 误触发统计:Python + pySerial(读取串口日志并自动计算)
  • 延迟波形分析:示波器(如 Tektronix TBS1000)、逻辑分析仪(如 Saleae Logic)

四、注意事项

  1. 中断服务程序(ISR)需保持简洁,仅执行状态标记等轻量操作,避免长时间占用 CPU 影响系统响应。
  2. 状态转换表需根据编码器的实际相位特性调整,不同型号编码器的状态映射可能存在差异。
  3. 防抖参数(采样次数、间隔时间)需结合硬件特性调试,平衡抗干扰能力与响应速度。
  4. 对于高速编码器(如高分辨率电竞鼠标),建议采用更高效的寄存器操作与中断优先级配置,确保信号无漏检。

鼠标滚轮编码器:原理、故障与维修指南

一、编码器基础认知

1. 类型与特性

img

  • 光栅式:早期广泛应用,依赖红外线收发信号,无物理接触,稳定性强、寿命较长,但光源会自然衰减,对主控编程有特定要求,现仅少数厂商使用。

  • 机械式:目前主流类型,结构简单、主控编程便捷,具备清晰机械刻度手感与精准定位特性,使用寿命已从 10 万圈提升至 200 万圈,适配多数鼠标。

img

2. 常见故障及成因

故障表现
  • 滚轮无规律上下跳动、失灵;
  • 滚动方向异常(如下滑触发上滑)或滚动过度;
  • 游戏场景中视野无规律缩放、无法聚焦;
  • 滚动信号丢失,反应时有时无或完全失效。
故障成因
  • 零部件精度不足,使用中产生误差;
  • 编码器触点材质较差,长期使用易磨损导致接触不良;
  • 鼠标内部卷入异物,阻碍机械结构运行。

3. 编码器选型三项关键参数:

  1. 安装高度(如 TTC 11 mm 规格);
  2. 编码器类型(光栅式/机械式);
  3. 结构细节(六边形方孔朝向、是否为高端鼠标定制款)。

img

二、编码器高度测量

结构

img

参数

Hole Size

在这里插入图片描述

Torque Field

在这里插入图片描述

Height Options

在这里插入图片描述

安装高度测量

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

img

img

安装尺寸

img

三、编码器常见类型

img
103 系列(通用型)

img
123 系列(部分带线款)

img

四、维修流程

1. 准备工具与配件

img

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

img

img

2. 鼠标拆解步骤

  1. 移除鼠标底部脚贴/贴纸,露出隐藏螺丝;

    img

  2. 拧下螺丝,沿卡扣缝隙缓慢分离上盖,避免损坏内部排线;

  3. 取出主板,定位滚轮下方圆柱形编码器组件(焊接固定或插头连接);

    img

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

    img

3. 编码器更换操作

  1. 用橡皮擦清洁滚轮金属触点,去除氧化层;

    img

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

    img

    img

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

    img

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

    img

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

    img

4. 维修注意事项

  • 具备基础动手能力与电子设备认知者可自行操作,无维修经验建议寻求专业支持;
  • 鼠标在保修期内时,优先联系制造商咨询免费维修/更换服务,避免自行拆解影响保修。

via:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值