简介本资源是一套面向电气自动化、嵌入式系统初学者与课程实验者的电机测速与调速实践项目聚焦直流电机转速的实时采集、处理与LED可视化显示解决机电控制中速度反馈与闭环调节的核心教学难点。压缩包共3个文件含Keil工程文件.Uv2、主控程序源码.c及L298驱动芯片数据手册.pdf分别用于工程配置、算法实现与硬件接口参考整体仅122KB轻量易部署。已有235人学习下载适合单片机课程设计、电赛备赛或课设快速上手。读者可直接导入Keil编译运行获取完整测速逻辑如脉冲计数法或定时器捕获、LED数码管动态刷新代码、L298驱动电路连接说明及关键注释尤其zong.c中已标注信号采集、滤波、换算与显示全流程大幅降低调试门槛。1. 电机测速不是读个转速值那么简单从编码器信号到实时速度闭环为什么“shiyan.rar”里那个测速实验总卡在3000rpm上很多刚接触电机控制的工程师拿到“shiyan.rar”这类实验压缩包时第一反应是解压、打开main.c、烧录、串口看数据——结果发现电机一转串口打印的“speed: 1245”跳得毫无规律或者干脆卡死在某个整数比如标题里反复出现的3000rpm再换负载、调电源、换线缆都没用。这不是硬件坏了而是没搞清“电机测速”这件事的本质它不是对一个物理量做单次采样而是在嵌入式系统里构建一套时间敏感型信号链路——从光电编码器的A/B相脉冲边沿捕获到定时器计数窗口的同步配置再到速度单位换算中的浮点精度陷阱最后还要过滤掉机械抖动和电气噪声引入的虚假跳变。本文面向已能点亮LED、写过GPIO驱动的嵌入式开发者不讲PID理论不画框图只拆解“shiyan.rar”中那个看似简单的测速功能如何在STM32F103或GD32F303上真正跑稳。你会看到为什么用SysTick测速必翻车、为什么TIM2的编码器接口模式比外部中断可靠10倍、以及那个被藏在注释里的#define SPEED_SCALE 65536到底在补偿什么。2. 编码器信号到数字速度值必须选对定时器工作模式否则脉冲丢一半都察觉不到电机测速的核心输入源是增量式编码器输出的A/B相正交脉冲。它的本质是两路方波相位差90°每转产生固定数量的脉冲周期如1000PPR。但直接用GPIO外部中断去数A相上升沿会因中断响应延迟和抖动导致严重丢脉冲——尤其在3000rpm时1000PPR编码器每秒输出50,000个边沿中断服务函数执行时间若超20μs丢脉冲率就超10%。所以工业级方案从不用外部中断做主测速通道。2.1 STM32/GD32的编码器接口模式才是正解硬件自动计数方向识别STM32F103和GD32F303的通用定时器TIM2/TIM3/TIM4均支持编码器接口模式Encoder Interface Mode。该模式下定时器将A/B相分别接入TI1/TI2引脚内部逻辑自动完成三件事根据A/B相电平组合判断旋转方向正转/反转在每个有效边沿A或B的上升/下降沿触发计数器加减计数器值实时反映相对位置且无软件干预延迟。启用该模式只需四行寄存器配置以TIM2为例// 1. 使能TIM2时钟 RCC-APB1ENR | RCC_APB1ENR_TIM2EN; // 2. 配置PA0(TI1)和PA1(TI2)为复用推挽对应编码器A/B GPIOA-CRL ~(0xFF 0); GPIOA-CRL | (0x88 0); // PA0/PA1设为AF_PP // 3. 设置编码器模式非反相滤波器采样频率CK_INT/8死区0 TIM2-SMCR TIM_SMCR_SMS_3; // 编码器模式3TI1FP1和TI2FP2都有效 TIM2-CCMR1 (TIM_CCMR1_IC1F_1 | TIM_CCMR1_IC1F_0) | // TI1滤波8个CK_INT周期 (TIM_CCMR1_IC2F_1 | TIM_CCMR1_IC2F_0); // TI2滤波同上 TIM2-CCER TIM_CCER_CC1E | TIM_CCER_CC2E; // 使能TI1/TI2捕获 // 4. 启动计数器 TIM2-CR1 TIM_CR1_CEN;提示TIM_SMCR_SMS_3是关键——它让定时器同时监听A/B相的全部4个边沿A↑、A↓、B↑、B↓理论上将分辨率提升至原始PPR的4倍。例如1000PPR编码器在此模式下每转产生4000个计数脉冲大幅提高低速段测速精度。2.2 计数窗口选择为什么用定时器溢出中断比SysTick更可靠得到计数值后需将其转换为转速rpm。公式为rpm (count_in_window × 60) / (PPR × window_seconds)问题在于window_seconds怎么定常见错误是用SysTick每100ms触发一次读取TIM2-CNT但SysTick是软件中断实际触发时间受其他中断抢占影响误差可达±5ms——在3000rpm时这会导致±15rpm误差。正确做法是用另一个定时器如TIM3作为精确时间基准触发TIM2计数器捕获并清零。// TIM3配置为100ms周期更新中断无PWM纯计时 TIM3-PSC 7200 - 1; // 72MHz APB1 / 7200 10kHz TIM3-ARR 1000 - 1; // 10kHz / 1000 100Hz → 10ms? 错要100ms → ARR1000 TIM3-DIER TIM_DIER_UIE; TIM3-CR1 TIM_CR1_CEN; // 在TIM3中断中 void TIM3_IRQHandler(void) { static uint16_t last_count 0; uint16_t current_count TIM2-CNT; uint16_t delta current_count - last_count; last_count current_count; // 换算rpm假设编码器1000PPR窗口100ms0.1s int32_t rpm (delta * 60) / (1000 * 0.1); // 即 delta * 0.6 // 清零TIM2计数器避免溢出 TIM2-CNT 0; TIM3-SR 0; // 清中断标志 }注意TIM2-CNT是16位寄存器最大值65535。若电机转速达3000rpm且PPR1000则每秒计数50,000100ms内达5000远未溢出。但若PPR仅1003000rpm时每秒50,000×10500,000100ms即50,000——接近溢出阈值。此时必须缩短窗口如20ms或改用32位定时器TIM1/TIM8。3. 从原始计数到可用速度值单位换算、滤波与抗干扰的三道硬门槛即使硬件计数准确delta * 0.6这种裸计算仍会输出大量毛刺。实测中电机空载启动瞬间常出现rpm12456这样的离群值这是机械振动导致编码器A/B相短暂失步产生的虚假边沿。必须叠加软件层处理。3.1 浮点运算陷阱为什么#define SPEED_SCALE 65536出现在shiyan.rar的头文件里查看“shiyan.rar”中的motor.h你会发现类似定义#define PPR 1000 #define SPEED_SCALE 65536 // 2^16 #define RPM_TO_CNT (PPR * 100 / 60) // 每rpm对应多少cnt/100ms?这并非随意设定。其本质是用定点数替代浮点除法避免MCU无FPU时的性能损失。推导如下目标rpm (delta × 60) / (PPR × window_sec)设window_sec 0.1则rpm delta × 0.6。但0.6无法用二进制精确表示浮点乘法耗时长。改为rpm_fixed (delta × 65536 × 0.6) 16 (delta × 39321) 16其中39321 round(0.6 × 65536)。最终代码为int32_t rpm_fixed (delta * 39321) 16; // 等效于 delta * 0.6但纯整数运算SPEED_SCALE就是这个缩放因子它把小数运算转化为移位操作执行时间从数十微秒降至1μs内。3.2 两级滤波先中值滤波剔野值再滑动平均稳读数单次delta值易受干扰需至少两级滤波中值滤波Median Filter缓存最近3次delta值取中间值。对脉冲式干扰如电刷火花效果极佳。滑动平均Moving Average对中值后的序列做N点平均N4~8。避免低通滤波器引入的相位滞后。实现示例环形缓冲区#define FILTER_DEPTH 4 static uint16_t delta_buf[FILTER_DEPTH] {0}; static uint8_t buf_idx 0; void add_delta_to_filter(uint16_t delta) { delta_buf[buf_idx] delta; buf_idx (buf_idx 1) % FILTER_DEPTH; } uint16_t get_filtered_delta(void) { // 中值滤波冒泡排序取中间 uint16_t temp[FILTER_DEPTH]; for(int i0; iFILTER_DEPTH; i) temp[i] delta_buf[i]; for(int i0; iFILTER_DEPTH-1; i) { for(int j0; jFILTER_DEPTH-1-i; j) { if(temp[j] temp[j1]) { uint16_t t temp[j]; temp[j] temp[j1]; temp[j1] t; } } } uint16_t median temp[FILTER_DEPTH/2]; // 滑动平均累加后除深度 static uint32_t sum 0; static uint16_t last_median 0; sum sum - last_median median; last_median median; return sum / FILTER_DEPTH; }提示FILTER_DEPTH不宜过大。实测中DEPTH4时响应延迟约400ms4×100ms足够抑制抖动又不拖慢调速响应若设为8延迟达800ms在闭环控制中会引起振荡。4. 实验验证与典型故障排查为什么你的测速值总在3000rpm卡死“shiyan.rar”中测速卡在3000rpm是高频故障根源不在代码逻辑而在三个物理层细节。以下排查步骤按优先级排列每步耗时不超过2分钟。4.1 检查编码器供电与信号质量示波器不是可选项用示波器探头直连编码器A相输出非单片机引脚观察波形正常干净方波幅值≈5VTTL或3.3VLVTTL边沿陡峭上升/下降时间100ns异常1卡3000rpm波形顶部塌陷呈“馒头波”说明编码器供电不足电流20mA时LDO压降过大异常2波形叠加高频噪声1MHz说明地线未共接或电机驱动电源未隔离。解决方案编码器单独用5V LDO供电如AMS1117-5.0禁用单片机3.3V引脚直接供电编码器GND、电机驱动GND、单片机GND在一点用粗铜线短接A/B相线上各串一个100Ω电阻靠近单片机端抑制高频振铃。4.2 验证定时器计数器是否真实溢出用调试器抓TIM2-CNT快照若怀疑计数器溢出导致卡值暂停程序手动读取TIM2-CNT寄存器运行中读值持续增长 → 计数正常读值恒为655350xFFFF→URS0且ARR未设计数器自由运行溢出后归零但CNT锁在最大值读值在0~100间跳变 → 编码器接线错误A/B相接反或仅接一相。修正方法确保TIM2-ARR 0xFFFF不限制计数上限检查TIM2-CR1中URS位更新请求源是否为0允许溢出更新用万用表通断档确认编码器A/B相与单片机引脚连通且无虚焊。4.3 交叉验证法用另一套独立系统测同一电机最可靠的验证不是看代码而是用第三方工具比对手机APP下载“Tachometer”类应用用手机摄像头对准电机轴上贴的反光标记测得真实转速数字万用表带频率测量功能的万用表如UNI-T UT61E将表笔接编码器A相读取Hz值再换算rpm Hz × 60 / PPR逻辑分析仪抓取A/B相原始波形用Saleae软件测量周期手动计算rpm。若第三方工具测得3200rpm而你的系统显示3000rpm则误差源在PPR定义错误如实际1200PPR却定义为1000或window_sec计算偏差如误将100ms当作0.01s。5. 进阶技巧用DMA搬运计数器值释放CPU资源做更复杂的控制当系统需同时处理CAN通信、ADC采样、多路PWM时定时器中断会成为瓶颈。此时应将TIM2-CNT的读取和清零操作从中断中剥离改用DMA自动搬运。5.1 DMA通道配置让硬件自己搬数据CPU只管算STM32F103的TIM2支持DMA请求TRGO事件可在每次更新事件UEV时触发DMA传输。配置步骤开启TIM2的DMA更新请求TIM2-DIER | TIM_DIER_UDE;配置DMA1通道7TIM2_CH1对应DMA1_Channel7DMA1_CPAR7 (uint32_t)TIM2-CNT;// 外设地址DMA1_CMAR7 (uint32_t)cnt_buffer;// 内存地址uint16_t cnt_buffer[100]DMA1_CNDTR7 100;// 传输次数DMA1_CCR7 DMA_CCR_EN | DMA_CCR_DIR | DMA_CCR_MINC | DMA_CCR_PSIZE_1 | DMA_CCR_MSIZE_1;这样TIM2每发生一次更新即每100msDMA自动将TIM2-CNT值复制到cnt_buffer无需CPU干预。主循环中只需// 主循环中 if(dma_transfer_complete_flag) { uint16_t delta cnt_buffer[idx] - cnt_buffer[(idx-1100)%100]; rpm calculate_rpm(delta); // 调用前述滤波函数 dma_transfer_complete_flag 0; }5.2 关键参数表不同场景下的推荐配置组合场景编码器PPR时间窗口滤波深度定点缩放因子典型误差带伺服电机精调100rpm250020ms865536±0.5rpm直流有刷电机监控0~5000rpm1000100ms465536±3rpm步进电机开环验证仅测速20050ms265536±10rpm高速无刷电机10000rpm50010ms465536±15rpm注意窗口越短响应越快但噪声越大滤波深度越大读数越稳但延迟越高。实际项目中先用100ms窗口深度4跑通再根据控制环路带宽逐步缩短窗口而非盲目追求高刷新率。电机测速的终极检验不是看串口打印是否连续而是当电机负载突变时你的速度曲线能否在200ms内收敛到设定值±1%以内——这要求从编码器选型、硬件布线、寄存器配置到滤波算法每一环都经得起推敲。现在打开你的“shiyan.rar”删掉所有printf把TIM2-CNT的读取换成DMA再用示波器盯住A相波形3000rpm的卡顿就会消失。本文还有配套的精品资源点击获取