如何用Arduino读取4500RPM以上高速旋转编码器数据(手动跑步机场景)
设备清单
- Arduino ATmega2560
- 旋转编码器
- 弧形手动跑步机
项目背景
计划用Arduino采集手动跑步机的距离、速度数据,实现方案为将旋转编码器与滑板轮联动,跑步机跑带运动时带动轮组同比例旋转。
经测试,轮组每旋转1圈,计数变量累计约2000个脉冲;轮组直径6cm,周长18.8495559215cm,换算得每行进1厘米对应约106个脉冲(2000 pulses/18.8495559215 cm ≈ 106 pulses/cm)。
测试代码
(参考开源方案修改,因跑步机仅单向运行,将原代码中两处counter--改为counter++,解决了中低速计数递减问题,但高速运行仍异常)
volatile long temp, counter = 0; //该变量随编码器旋转增减 void setup() { Serial.begin (9600); pinMode(2, INPUT_PULLUP); // 2号引脚设为内部上拉输入 pinMode(3, INPUT_PULLUP); // 3号引脚设为内部上拉输入 // 配置中断:编码器A相上升沿触发ai0中断,2号引脚对应外部中断0 attachInterrupt(0, ai0, RISING); // 配置中断:编码器B相上升沿触发ai1中断,3号引脚对应外部中断1 attachInterrupt(1, ai1, RISING); } void loop() { // 计数器每累计1厘米对应脉冲数时打印数值 if( counter != temp && counter % 106 == 0 ){ Serial.println (counter); temp = counter; } } void ai0() { // 2号引脚电平从低变高时触发该中断 if(digitalRead(3)==LOW) { counter++; }else{ counter++; } } void ai1() { // 3号引脚电平从低变高时触发该中断 if(digitalRead(2)==LOW) { counter++; }else{ counter++; } }
当前问题
速度计算逻辑已验证可行,可通过millis()函数搭配时间变量实现,暂未写入代码。
当前代码在跑步机速度低于5km/h时运行正常,超过5km/h后会出现串口监视器周期性卡顿、数据不准的问题;需求是支持最高50km/h的速度检测,对应轮组转速超过4500RPM(75RPS),按2000P/R计算每秒需处理150000个脉冲。
待解答疑问:
- 当前编码器接在2、3号引脚,是否需要调整?
- 编码器有一根橙色Z线,现有方案仅用到A、B线,接入Z线是否能解决高速读取问题?
- 编码器目前使用Arduino的5V供电,参数显示最高支持26V供电,使用外部高压供电是否能提升性能?是否需要搭配继电器使用?
- 编码器支持多档分辨率可选,包括100、200、2000、5000P/R等,当前默认是2000P/R,调整到更高分辨率是否有助于解决问题?如何调整?
需求总结
需实现手动跑步机最高50km/h时速下的速度、距离数据采集,优先基于现有硬件给出解决高速读取卡顿、数据不准的方案;若现有旋转编码器方案不可行,也可接受其他测速测距方案建议,可额外采购必要硬件。

疑问解答
- 2、3号引脚为ATmega2560的标准外部中断引脚,硬件连接本身无需调整,当前性能瓶颈来自软件逻辑和脉冲处理压力,和引脚无关。
- Z线为编码器零位信号,每旋转一圈输出一个脉冲,仅用于累计误差校准,无法提升高速读取性能,额外接入还会增加中断处理开销,反而加剧卡顿问题。
- 编码器供电电压仅和输出电平兼容性有关,和脉冲响应性能无关,5V供电完全满足使用需求,外部高压供电无任何增益,也不需要搭配继电器。
- 更高分辨率会增加单位时间的脉冲数量,进一步加大CPU处理压力,反而会加剧问题。需往低调整分辨率,推荐降到100P/R,调整方式参考编码器说明书,一般通过本体上的拨码开关或配置引脚接高低电平切换。
现有硬件优化方案
按以下步骤修改即可满足50km/h检测需求:
- 降低编码器分辨率到100P/R:调整后每秒仅需处理7500个脉冲,CPU负载降至原来的5%,完全无处理压力。换算后每厘米对应约5.3个脉冲,测距精度可满足跑步机使用需求。
- 简化中断逻辑:因设备仅单向运行,无需接B相,仅保留A相连接2号引脚,中断服务函数删除所有冗余判断,直接累加计数器,大幅降低中断执行耗时。
- 优化串口配置:将波特率从9600调整到115200,修改打印逻辑为固定间隔(如每100ms)打印一次数据,避免频繁触发串口打印占用CPU资源。
- 增加变量读取保护:读取计数器变量时临时关闭中断,避免读取过程中变量被中断修改导致数据错误。
优化后代码示例
volatile unsigned long counter = 0; unsigned long lastPrint = 0; unsigned long lastCounter = 0; const float pulsePerCm = 100.0 / 18.8496; // 100P/R下每厘米对应脉冲数 void setup() { Serial.begin(115200); pinMode(2, INPUT_PULLUP); attachInterrupt(0, countPulse, RISING); } void loop() { unsigned long now = millis(); // 每100ms打印一次距离和速度 if (now - lastPrint >= 100) { noInterrupts(); unsigned long currentCount = counter; interrupts(); float distance = currentCount / pulsePerCm / 100.0; // 单位:米 float deltaCount = currentCount - lastCounter; float speed = (deltaCount / pulsePerCm / 100.0) * 36; // 单位:km/h,0.1s走的距离*360=km/h Serial.print("距离: "); Serial.print(distance); Serial.print("m, 速度: "); Serial.print(speed); Serial.println("km/h"); lastCounter = currentCount; lastPrint = now; } } void countPulse() { counter++; }
备选升级方案
若需要保留2000P/R的高分辨率,可额外采购PCF8583脉冲计数模块,该模块为专用硬件计数器,最高支持10MHz脉冲计数,通过I2C和Arduino通信,Arduino无需处理中断,仅需定时读取计数数值即可,可完全满足150k脉冲每秒的处理需求。
若不想使用编码器方案,可改用霍尔传感器+跑带贴磁铁的方案,每圈输出一个脉冲,结构简单稳定,完全满足跑步机测速测距需求。
内容的提问来源于stack exchange,提问作者Josh

