1. 项目缘起为什么用Arduino Uno做测功机测功机或者叫测力计、马力机在工业、汽车维修、甚至DIY爱好者的车库里都是一个听起来很“硬核”的设备。它的核心任务是精确测量旋转机械比如电机、发动机输出的扭矩和转速从而计算出功率。传统上这要么依赖昂贵的商用设备要么需要复杂的模拟电路和数据处理单元门槛不低。几年前我在一个改造小型汽油发动机的项目中就卡在了动力性能验证这一步。租用商用测功机成本太高自己从头搭建模拟电路又太费时。当时手边正好有几块闲置的Arduino Uno一个大胆的想法就冒了出来能不能用这块入门级的8位AVR单片机配合一些基础的传感器实现一个够用的数字测功机并且把数据处理DSP的活儿也干了这个想法听起来有点“疯狂”。Arduino Uno的ATMega328P主频只有16MHzRAM仅2KBFlash 32KB处理复杂的浮点运算和实时信号分析似乎是“小马拉大车”。但正是这种限制逼着我去思考如何用最精简的算法和架构在有限的资源内完成核心任务。最终这个基于Arduino Uno和数字信号处理DSP思路的测功机项目不仅跑通了精度还超出了我的预期。今天我就把这个从构思到实现的完整过程包括所有的坑和技巧分享出来。2. 核心需求解析与系统架构设计一个测功机系统无论多么简化都必须完成几个核心物理量的测量与计算扭矩测量被测机械输出的旋转力。通常通过一个“负载”来吸收这个力并测量这个力的大小。在DIY场景常用“涡流制动”或“磁粉制动”作为负载通过测量制动器上的力臂受到的力来反推扭矩。转速测量单位时间内的转数。这是计算功率的另一个关键参数。功率计算功率瓦特 扭矩牛顿·米 × 角速度弧度/秒。角速度由转速转换而来。数据采集与处理实时、稳定地采集扭矩和转速传感器的模拟/数字信号进行滤波、校准和计算。结果显示与记录将计算出的扭矩、转速、功率实时显示出来并可能记录成曲线或表格。对于Arduino Uno来说挑战在于模拟信号精度扭矩传感器如应变片电桥输出是微弱的模拟电压毫伏级Uno的ADC只有10位分辨率且易受噪声干扰。转速测量实时性需要精确捕捉脉冲频率不能错过任何一次计数。计算负担功率计算涉及浮点乘法在8位机上较慢如果要做实时滤波如移动平均、IIR滤波计算量更大。资源限制程序空间和内存需要精打细算。因此我的系统架构设计遵循“各司其职软硬结合”的原则硬件层主控Arduino Uno R3。负责协调所有外设、运行核心DSP算法、通信。扭矩测量采用S型拉压力传感器量程例如50kg配合仪表放大器如INA125P或HX711。传感器安装在制动器的力臂上将力转换为电压信号。这里的关键是INA125P它能将传感器输出的毫伏级差分信号放大到Arduino ADC可以较好分辨的范围0-5V并抑制共模噪声。转速测量采用霍尔传感器或光电编码器。在被测轴上加装一个磁铁或光栅每转一圈产生一个脉冲。使用Arduino的**外部中断引脚如D2**来捕获这个脉冲利用micros()函数实现高精度频率测量。负载与制动采用一个小型涡流制动器。通过一个MOSFET模块如IRF520由Arduino的PWM引脚控制其励磁电流从而控制负载大小。这是实现“可编程负载”的关键。人机交互一个16x2字符LCD屏I2C接口节省引脚用于实时显示。一个旋转编码器用于菜单操作和参数设置。软件DSP层 这才是项目的精髓。我并没有使用传统的、在PC上运行的DSP库而是在Arduino上实现了一套轻量级的数字信号处理流程ADC采样与缓冲以固定频率如500Hz对放大后的扭矩信号进行ADC采样。使用一个循环缓冲区存储原始数据。数字滤波在循环缓冲区上实施软件滤波。我选择了移动平均滤波简单RAM占用小和一阶低通IIR滤波效果好计算量适中的组合。先进行移动平均初步平滑再用IIR滤波抑制特定频段的噪声。所有滤波算法都采用定点数或整数运算优化避免浮点。转速计算在外部中断服务程序ISR中记录两次脉冲之间的时间间隔micros()差值。在主循环中计算瞬时转速RPM 60,000,000 / 间隔微秒数。为了防止脉冲丢失或干扰造成的野值同样对转速进行移动平均滤波。校准与换算在程序初始化时或通过菜单进行扭矩传感器的“零点”和“满量程”两点校准。将滤波后的ADC读数通过线性插值公式转换为力值N再根据力臂长度m换算为扭矩Nm。功率计算功率 扭矩 × (转速 × 2π / 60)。这个计算是浮点的但频率不高每秒10-20次Uno可以胜任。计算结果会再次经过一个简单的低通滤波使显示更稳定。控制环路进阶为了实现恒转速或恒扭矩测试可以引入一个简单的PID控制器。根据设定的转速或扭矩目标与实际值的偏差动态调整PWM输出以改变制动器负载。这需要仔细调整PID参数并确保控制周期稳定。这个架构把Arduino的潜力压榨到了极致硬件中断处理实时脉冲ADC定时采样主循环专注进行DSP滤波、计算和显示更新。3. 硬件搭建详解从传感器到信号调理纸上谈兵结束我们来动手搭建。这部分是精度和稳定性的基础任何一个环节的疏漏都会在数据上被放大。3.1 扭矩测量链INA125P是关键我选择的是常见的S型称重传感器输出灵敏度一般为2.0mV/V。如果用5V激励电压满量程输出也就10mV。Arduino的ADC在5V参考下最小分辨率为5V/1024≈4.9mV直接测量10mV信号连3个数字都跳不完毫无精度可言。注意千万不要试图用Arduino的ADC直接读取未经放大的应变片或拉压力传感器信号结果会非常不稳定且毫无意义。所以仪表放大器INA是必须的。我选用INA125P因为它内置了精准的参考电压和可编程增益放大器PGA增益通过一个外部电阻设置非常方便。接线与配置传感器将S型传感器的E、E-接5V和GND激励电压。S、S-为信号输出。INA125P传感器S、S-分别接INA125P的Vin和Vin-。V接5VV-接GND单电源供电。最关键的是增益电阻Rg。增益公式 G 4 (60kΩ / Rg)。假设我们希望将10mV满量程信号放大到接近5V需要增益约500倍。计算得 Rg ≈ 60kΩ / (500 - 4) ≈ 121Ω。我选择了一个120Ω的精密电阻。这样满量程输出约为10mV * 500 5V正好匹配ADC量程。INA125P的Vout接Arduino的A0引脚。Ref引脚接一个可调电阻到5V和GND用于微调输出零点我通常直接接GND单极性输出0-5V。电源去耦在INA125P的电源引脚附近务必并联一个0.1uF的陶瓷电容到GND以滤除电源噪声。这是保证放大信号干净的基础。3.2 转速测量利用外部中断的妙处转速测量要求不丢失脉冲。如果用pulseIn()函数在主循环里查询在高转速或主循环繁忙时极易漏掉脉冲。正确做法是使用硬件外部中断将霍尔传感器的信号线集电极开路输出需上拉接到Arduino Uno的D2引脚对应中断0。在setup()中配置中断attachInterrupt(digitalPinToInterrupt(2), rpm_irq, RISING);即在上升沿触发中断函数rpm_irq。在rpm_irq()函数中只做最必要的事记录当前时间currentMicros micros()并计算与上一次中断的时间间隔interval currentMicros - lastMicros然后更新lastMicros currentMicros。绝对不要在中断服务程序中进行复杂的计算、打印或浮点运算这些工作留给主循环。主循环中检查interval是否有效例如在1秒到1分钟之间对应转速范围然后计算rpm 60000000.0 / interval。这里用浮点数计算但每秒只算几次没问题。3.3 涡流制动器驱动PWM与MOSFET涡流制动器本质上是一个线圈。控制通过线圈的电流就能控制制动力矩。我们用PWM来控制平均电流。将Arduino的一个PWM引脚如D9连接到MOSFET驱动模块如IRF520模块的输入。模块的输出接涡流制动器的线圈。线圈另一端接电源12V。注意线圈是感性负载必须在两端并联一个续流二极管通常模块已集成防止关断时产生的高压反电动势击穿MOSFET。在程序中使用analogWrite(pwmPin, value)来设置负载大小value从0到255。重要经验制动器的力矩与电流并非完全线性尤其是在低PWM占空比时由于MOSFET导通电压和线圈电阻的影响可能有一个“死区”。需要实际测试建立PWM值-制动力矩的对应关系表用于后续的PID控制或开环控制。4. 软件DSP核心在8位机上实现信号处理硬件收集来的是粗糙的、充满噪声的原始数据。DSP的任务就是“去伪存真”。下面是我的软件处理流水线。4.1 定时采样与循环缓冲区稳定的采样是DSP的基础。我使用Timer1中断来产生固定的采样时钟如500Hz而不是在loop()中用delay()。// Timer1 初始化设置500Hz采样率16MHz / (31250 * 1) 500Hz void setupADCtimer() { noInterrupts(); TCCR1A 0; TCCR1B 0; TCNT1 0; OCR1A 31249; // 比较匹配值 TCCR1B | (1 WGM12); // CTC模式 TCCR1B | (1 CS10); // 无预分频 TIMSK1 | (1 OCIE1A); // 使能定时器比较中断 interrupts(); } ISR(TIMER1_COMPA_vect) { // 采样标志置位在主循环中处理 adcSampleReady true; }在主循环中检测到adcSampleReady后读取ADC值并存入一个固定长度的循环数组如rawDataBuffer[256]中同时更新写指针。这个缓冲区是后续所有滤波操作的“原料池”。4.2 数字滤波算法的实现与优化移动平均滤波这是最简单有效的去噪方法。计算缓冲区中最近N个点的平均值。N越大越平滑但延迟也越大。对于扭矩信号N16或32是个不错的起点。直接求和再除以N会涉及除法在8位机上较慢。一个优化技巧是使用递推平均new_avg old_avg (new_sample - old_sample) / N。这样每次更新只需要一次加法和一次除法N最好是2的幂可用移位代替除法。#define FILTER_N 16 // 2的幂 int movingAverage(int newVal) { static int buffer[FILTER_N]; static int index 0; static long sum 0; sum - buffer[index]; // 减去最旧的值 buffer[index] newVal; // 存入新值 sum newVal; // 加上新值 index (index 1) % FILTER_N; // 更新索引 return (int)(sum / FILTER_N); // 返回平均值 }一阶IIR低通滤波其离散公式为y[n] α * x[n] (1-α) * y[n-1]。其中α是滤波系数0α1决定了截止频率。α越大带宽越宽响应越快α越小滤波越强但延迟越大。这个公式本身是浮点的。为了在Arduino上快速运行我使用了定点数运算。将α放大为整数例如 Q 1024则 α_fixed α * Q。滤波计算变为y_fixed (α_fixed * x (Q - α_fixed) * y_prev_fixed) / Q。 这里乘法和除法都是整数运算速度快得多。最后y y_fixed / Q如果需要还原为物理值。#define ALPHA_FIXED 26 // 对应 α ≈ 0.025 (26/1024) #define Q 1024 int iirLowPass(int newSample) { static long y_prev_fixed 0; long y_fixed; y_fixed (ALPHA_FIXED * newSample (Q - ALPHA_FIXED) * y_prev_fixed) / Q; y_prev_fixed y_fixed; return (int)(y_fixed); // 返回的是放大了Q倍的值注意后续换算 }我的处理链是ADC原始值 - 移动平均去毛刺- IIR低通平滑- 校准换算为扭矩值。4.3 转速处理的抗干扰策略转速信号是数字脉冲本应干净但实际可能因传感器抖动、电源噪声产生毛刺脉冲。我的策略是“软硬结合”硬件在霍尔传感器信号线到Arduino引脚之间加入一个RC低通滤波器例如1kΩ电阻和0.1uF电容滤除高频噪声。同时在Arduino代码中启用引脚内部上拉电阻。软件在中断服务程序中加入消抖逻辑。记录中断触发时间后立即短暂禁用该中断detachInterrupt然后延时1-2毫秒用delayMicroseconds再读取引脚状态。如果仍然是高电平则认为是有效脉冲否则是噪声忽略。确认有效后再重新启用中断。这能有效滤除短于1ms的干扰脉冲。4.4 校准流程两点定标法测力传感器和放大电路存在零点和增益误差。必须在程序中进行软件校准。零点校准在传感器完全无负载的状态下读取滤波后的ADC值作为adc_zero。满量程校准施加一个已知的、精确的力如挂上一个标准砝码读取此时的ADC值作为adc_cal。已知力值为F_cal单位N。实时换算对于任何时刻滤波后的ADC读数adc_val计算力值force (adc_val - adc_zero) * (F_cal / (adc_cal - adc_zero))。扭矩计算torque force * arm_length力臂长度单位米。这个校准过程可以做成菜单选项引导用户完成并将adc_zero和scale_factorF_cal / (adc_cal - adc_zero)保存到EEPROM中下次上电自动加载。5. 系统集成、测试与性能评估当硬件连好代码写完真正的挑战才刚刚开始——让整个系统稳定、精确地跑起来。5.1 整合与调试步骤分模块测试不要一次性写完所有代码。先写一个简单的程序只测试INA125P的放大功能用万用表测量其输出电压是否随负载线性变化并用串口打印ADC值观察。确保扭矩测量链路正常。单独测试转速用手转动被测轴观察串口打印的转速值是否连续、合理。测试高速和低速下的表现。整合主循环将ADC定时采样、滤波算法、转速计算、功率计算整合到主循环中。注意安排好各任务的时序。我的经验是在loop()中首先检查并处理ADC采样如果标志置位进行滤波和换算然后检查距离上次显示更新时间是否已超过100ms避免刷新太快LCD跟不上如果是则读取最新的转速值注意关中断读取间隔值避免正在更新时读取到半截数据计算功率更新LCD显示。引入控制最后加入PID控制部分。先开环测试手动设置不同的PWM值观察扭矩和转速的变化了解系统的响应特性。然后转入闭环先只使用P比例控制慢慢增加I积分和D微分。切记在8位机上PID计算也要尽量使用整数运算。误差、积分项、微分项都可以用放大一定倍数的整数来表示。5.2 实测性能与精度分析我使用一个已知转速和扭矩特性的小型直流电机有厂家参数作为被测对象来评估我的Arduino测功机。扭矩精度在量程范围内与参考标准对比最大误差在±2% FS满量程以内。主要误差来源INA125P的增益电阻精度、ADC的非线性、传感器的非线性与迟滞。对于大多数DIY和教学应用这个精度完全可以接受。转速精度在100-5000 RPM范围内误差小于±1 RPM。精度主要取决于micros()函数的1微秒分辨率和中断响应时间在这个转速范围内完全够用。动态响应由于采用了500Hz的采样率和轻量级IIR滤波系统能较好地跟踪扭矩的快速变化如电机启动过程没有明显的延迟感。系统稳定性连续运行数小时零点和读数漂移很小证明了硬件设计和软件滤波的有效性。5.3 遇到的坑与解决方案ADC读数跳动大最初未给INA125P电源加去耦电容也未对传感器信号线进行屏蔽。ADC读数最后两位不停跳动。解决加上0.1uF陶瓷电容并将传感器信号线改用屏蔽线屏蔽层单端接地接Arduino的GND跳动立即减小。高转速时转速测量值偏低转速很高时计算出的RPM比预期低。原因中断服务程序rpm_irq虽然很短但仍需执行时间几微秒。在极高转速下脉冲间隔仅几十微秒中断处理时间占用了相当一部分导致micros()记录的时间间隔偏大计算出的RPM偏小。解决对于极高转速测量这不是最佳方案。可以考虑使用硬件计数器如Timer2的输入捕捉功能或者换用更快的MCU。对于中低速测量10000 RPM这个误差可以忽略或通过软件补偿。内存不足Out of Memory当增加了LCD菜单、多个滤波缓冲区、PID参数存储后编译时提示内存不足。解决优化数据结构将一些int改为byte或uint16_t减少全局变量使用局部变量和static将固定的字符串常量存放到Flash中用F()宏包裹仔细审查滤波缓冲区大小在满足性能前提下尽可能减小。PID控制振荡初次加入PID控制负载时系统在目标值附近剧烈振荡。原因采样周期和控制周期不稳定导致微分项计算混乱PID参数尤其是Kd过大。解决确保控制周期是严格定时的同样用定时器中断触发控制计算。将PID参数初始化设小先调P让系统能快速接近目标再加入I消除静差最后谨慎加入D改善动态性能但系数一定要小。6. 项目总结与扩展思考这个基于Arduino Uno的测功机项目本质上是一次“在资源受限环境下实现特定功能”的工程实践。它证明了即使是用最入门的8位单片机只要充分理解问题本质合理设计硬件架构并精心编写软件算法完全可以完成一些看似需要更强处理器的任务。我个人最深的体会是在嵌入式开发中“够用就好”的哲学和“软硬件协同优化”的能力远比单纯追求硬件性能更重要。选择INA125P解决了小信号放大的难题利用外部中断和定时器中断解放了主循环使用定点数运算让滤波算法得以实时运行——每一个选择都是针对Arduino Uno这块特定“土壤”的定制化方案。这个项目还有很大的扩展空间数据记录可以添加一个SD卡模块将扭矩-转速-功率曲线以CSV格式记录下来方便后续在PC上用Excel或Python分析。无线传输增加一个蓝牙模块如HC-05或Wi-Fi模块如ESP-01将实时数据发送到手机APP或PC上位机实现远程监控。图形化显示如果升级主控到带有OLED屏幕的板子如Arduino Due或ESP32可以实时绘制扭矩-转速曲线直观性大大增强。负载升级涡流制动器适合中高速低速时制动力矩小。可以研究磁粉制动器或者更简单的“水涡流”制动以获得不同的负载特性。最后我想说这个项目的价值不仅仅在于做出了一个可用的测功机更在于整个过程——从物理原理到电路设计从传感器选型到信号调理从算法理论到嵌入式实现——提供了一个完整的、跨学科的实践案例。无论你是电子爱好者、机械专业学生还是从事工控的工程师希望这个详细的拆解能给你带来一些切实的启发和帮助。