
简介一份基于霍尔传感器与STM32的直流电机调速系统课程设计报告适合自动化、电气类本科生及嵌入式初学者参考用于完成电力拖动类课程设计或理解直流电机闭环调速实现思路。资源为PDF格式共1个文件压缩包大小约762KB内容完整涵盖霍尔传感器电路制作、转速采样电路设计、数码管显示电路、STM32微控制器编程以及系统总体方案与测试优化等环节并从控制器选型、电源方案、键盘与显示模块、驱动模块到PWM控制逐一展开提供一套可复现的硬件与软件设计流程。全文基于Cortex-M3内核的STM32平台给出测量范围200rpm-3000rpm、误差±10rpm的设计指标及结构框图便于读者对照学习信号转换、数字转速换算和嵌入式程序编写方法。该资源已有800余人学习适合需要快速掌握课程设计结构、硬件电路搭建思路和STM32调速系统框架的读者作为参考资料。1. 用霍尔传感器给直流电机调速为什么闭环比调占空比更值钱手里有一台直流电机做小型风机、泵、转台或者桌面输送带你会发现一个很现实的问题开环给固定 PWM 占空比电机空载转速和带载转速能差出 20% 甚至更多。负载一变化转速就掉控制不了。这时候最便宜的解法不是换编码器而是用霍尔传感器做转速反馈配合 STM32 实现闭环调速。这套系统的骨架是电机轴上装磁钢霍尔传感器感应转速输出脉冲STM32 用定时器捕获脉冲计算实际转速PID 根据目标转速修正 PWM 占空比。很多人误以为调速系统的核心是 PWM 生成其实真正把精度拉起来的是霍尔传感器那一环。没有转速反馈码盘精度再好的 PWM 也只是开环负载一变化就露馅。这套方案适合正在做嵌入式课程设计、毕设或者产品样机验证的人也适合想把产线上开环电机改成闭环的老工程师。霍尔传感器比光电编码器便宜比测速发电机好安装在粉尘和振动环境里也更扛得住。下面从传感器选型开始一步步把整个系统拆开讲。2. 霍尔测速方案选型磁钢、传感器与信号调理怎么做才稳2.1 开关型与锁存型霍尔两类传感器的选用边界霍尔传感器在有刷直流电机调速系统里绝大多数情况下不是用来换相的而是当作测速反馈元件。它的输出经过内部比较器整形后就是方波STM32 直接捕获上升沿就能数脉冲。市面上常见的霍尔元件分两类开关型和锁存型两者输出行为差别很大选错型号后面会非常难受。开关型霍尔例如常见 A3144 这类输出随着磁场强度的变化而翻转磁场超过工作点输出低电平低于释放点输出高电平。它只响应磁场的「有没有」不区分极性。好处是单极磁钢就能驱动安装自由度大缺点是如果电机轴上有多个磁极且磁场强度变化不够陡峭输出容易抖动。对比项开关型锁存型输出行为磁场强弱决定输出电平靠磁场极性翻转锁存输出状态测速适用单磁钢、低速、安装空间大多极磁环、需要高分辨率抗抖动一般需配合滤波较好输出状态稳定安装要求宽松贴合面即可对气隙和极性方向敏感典型场景入门样机、单脉冲测速高速电机、每转多脉冲需求锁存型霍尔需要磁场方向反转才会翻转输出正好匹配多极磁环N 极来一下输出翻到一端S 极来一下再翻回来。这样每转就能产生 4 个、8 个甚至更多的脉冲测速分辨率明显提升。我一般建议只要能保证磁钢和霍尔的距离稳定优先选锁存型加多极磁环尤其当电机额定转速只有几百转的时候每转一个脉冲会让测速刷新慢到没法看。2.2 磁钢安装与极对数测速分辨率的源头磁钢安装是整个系统里最容易被忽略的玄学环节。霍尔传感器与磁钢之间的气隙直接决定输出信号的幅值和波形质量。气隙太大磁场强度不够开关型霍尔可能根本翻不了转气隙太小磁钢旋转时如果有轻微偏心霍尔输出脉冲宽度会忽宽忽窄测速值跟着跳。磁钢的极对数用 P 表示每转脉冲数等于 2 × 极对数。比如一个 4 对极磁环每转产生 8 个脉冲。P 越大单位转数的脉冲越多相邻脉冲时间间隔越短测速的实时性越好。但 P 不是越大越好磁钢极对数多要求霍尔安装位置对准磁环的圆周任何偏心都会造成脉冲间隔不均匀也就是常说的「占空比漂移」最终体现为测速值周期性跳动。安装磁钢时我习惯先在电机轴端粘一个同轴磁环用千分表打一下径向跳动控制在 0.1mm 以内再固定霍尔支架。霍尔元件贴在磁环切线方向距离 1~3mm 比较稳妥。这个环节不要图省事后面测速跳动排错会节省大量时间。2.3 信号调理从霍尔输出到 STM32 捕获引脚的 3 个环节霍尔元件输出一般需要外部上拉电阻才能形成可靠的数字电平很多开关型霍尔是开漏输出。上拉电阻常见取 4.7kΩ 到 10kΩ取值偏小会增大静态功耗偏大则上升沿变缓。STM32 的捕获引脚建议配成浮空输入因为霍尔输出侧已经有上拉电阻了不要在 MCU 侧再启用内部上拉两个上拉并联会让信号边沿变得很拖沓。第二环节是 RC 滤波。霍尔点在电机轴转动时如果磁钢表面有油污或安装偏心输出会产生毛刺一个上拉电阻硬扛不住。常用做法是加一个 1kΩ 电阻串联加 10nF 电容到地构成低通滤波截止频率大约在 16kHz把几十纳秒的窄脉冲滤掉。注意滤波电容不能太大否则边沿变缓单片机捕获边沿的时机产生抖动测速精度反而下降。第三环节其实是可选的施密特整形。如果霍尔信号边沿在低转速下明显变缓或者线缆比较长引入了噪声可以在霍尔输出和 STM32 引脚之间加一级施密特缓冲器。大多数情况下STM32 定时器的输入滤波器ICFilter就能起到类似作用把捕获引脚内部滤波打开外部就不需要额外整形电路了。这样硬件最简成本最低。3. STM32 定时器测速M 法、T 法的代码实现与切换3.1 输入捕获初始化把霍尔脉冲变成可计算的计数霍尔信号接入 STM32 的定时器输入捕获引脚比如 PA0 复用为 TIM2_CH1。核心思路是配置定时器工作在输入捕获模式每检测到一个上升沿硬件自动把当前计数值锁存到捕获寄存器并触发中断。这样软件只需在中断里读一下捕获值就能知道相邻两次脉冲的时间间隔或者累计脉冲数。下面这段初始化代码基于 HAL 库以 72MHz 系统时钟为例// 定时器输入捕获初始化PA0 - TIM2_CH1 void MX_TIM2_Init(void) { TIM_IC_InitTypeDef sConfigIC {0}; htim2.Instance TIM2; htim2.Init.Period 0xFFFF; // 16位计数器计满回绕 htim2.Init.Prescaler 72 - 1; // 72MHz / 72 1MHz计数周期 1us htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; HAL_TIM_IC_Init(htim2); sConfigIC.ICPrescaler TIM_ICPSC_DIV1; // 捕获不分频每个边沿都捕获 sConfigIC.ICFilter 0x0F; // 输入滤波器滤除窄脉冲毛刺 sConfigIC.Channel TIM_CHANNEL_1; sConfigIC.ICPolarity TIM_INPUTCHANNELPOLARITY_RISING; // 上升沿捕获 HAL_TIM_IC_ConfigChannel(htim2, sConfigIC, TIM_CHANNEL_1); HAL_TIM_IC_Start_IT(htim2, TIM_CHANNEL_1); // 使能捕获中断 }分频系数 72-1 把计数频率降成 1MHz每个计数代表 1 微秒这个口径在 T 法测速时非常方便。Period 设为 0xFFFF 是因为 TIM2 是 16 位定时器计数到 65535 后回绕。ICFilter 取值对应内部数字滤波器采样频率0x0F 大约能滤掉几微秒宽的窄脉冲但又不会影响真实霍尔信号的边沿。如果霍尔线很长、干扰大可以适当再调大一点但注意过大会把低速时的真实脉冲也滤掉。捕获中断回调里做两件事累计脉冲数同时记录相邻两次捕获的计数值差值。这两件事分别服务于 M 法和 T 法代码在同一个中断里维护volatile uint32_t hall_pulse_cnt 0; // 脉冲累计数 volatile uint32_t cap_period_us 0; // 相邻脉冲间隔单位 us volatile uint32_t last_cap 0; // 上次捕获计数值 void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { uint32_t cur HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // 计算与上次捕获之间的计数值注意处理 16 位回绕 if (cur last_cap) { cap_period_us cur - last_cap; } else { cap_period_us cur 0xFFFF - last_cap; } last_cap cur; hall_pulse_cnt; } }回绕处理是必须要写的逻辑。计数器从 65535 回绕到 0 的时候直接做减法会得到一个巨大的错误值必须先判断当前值是否小于上次值。这种边界情况在低转速下特别容易触发因为相邻脉冲间隔可能超过 65535 微秒也就是 65ms。3.2 M 法测速固定闸门内数脉冲适合中高速M 法测速的原理是固定一个时间闸门统计闸门内来了多少个霍尔脉冲然后用脉冲数除以闸门时间再除以每转脉冲数得到转速。闸门时间一般取 50ms 到 200ms 之间太短则低速时脉冲数量少、误差大太长则转速响应迟钝。我常用 100ms兼顾实时性和精度。#define PULSES_PER_REV 8 // 磁环 4 对极每转 8 个脉冲 #define GATE_MS 100 // 闸门 100ms volatile float speed_rpm 0; // 最终转速单位 RPM // 在 1ms 周期任务中调用累计到闸门时间就计算一次转速 void Speed_Task_1ms(void) { static uint32_t cnt_prev 0; static uint16_t gate_cnt 0; if (gate_cnt GATE_MS) { gate_cnt 0; uint32_t delta hall_pulse_cnt - cnt_prev; // 闸门内脉冲数 cnt_prev hall_pulse_cnt; // 60000 60 秒 * 1000 毫秒 speed_rpm (float)delta * 60000.0f / (PULSES_PER_REV * GATE_MS); } }公式里 60000 是把毫秒口径的闸门时间换算成每分钟的脉冲频率。举一个直观的数字如果 delta 是 80 个脉冲PULSES_PER_REV 是 8闸门 100ms转速就是 80 × 60000 / (8 × 100) 6000 RPM。M 法的误差来源在低速时暴露得很明显。转速 60RPM 加上 8 脉冲每转闸门 100ms 内只有 8 个脉冲少一个脉冲就偏差 12.5%这精度没法看。所以在低速区间必须引入 T 法。3.3 T 法测速用相邻脉冲间隔算周期低速救星T 法测速不数脉冲个数而是测量相邻两个脉冲之间的时间间隔用这个间隔反推转速。前面在捕获中断里已经维护了 cap_period_us 变量它就是相邻脉冲间隔。有了它低速测速精度会高很多。// T 法转速计算单位 RPM // cap_period_us 是相邻脉冲间隔单位为微秒 float Speed_Calc_T_Method(uint32_t period_us) { if (period_us 0) { return 0; } // 1 分钟 60,000,000 微秒 // 每转耗时 period_us * PULSES_PER_REV // 转速 60,000,000 / 每转耗时 return 60000000.0f / ((float)period_us * PULSES_PER_REV); }注意 T 法的单位口径每个脉冲对应的机械角度是 1/PULSES_PER_REV 转那么一整转的时间就是脉冲间隔乘以 PULSES_PER_REV 微秒。一分钟是 6000 万微秒除一下就得到 RPM。如果脉冲间隔是 12500 微秒PULSES_PER_REV 是 8每转耗时 100ms转速就是 600 RPM。实际项目中我不会把 M 法和 T 法做成两套独立逻辑而是统一在捕获中断里维护数据在闸门任务里判断闸门时间内脉冲数 delta 大于等于 10 就用 M 法小于 10 就用最近一次记录的 cap_period_us 走 T 法。切换阈值是一个经验值太低则 T 法介入晚太高则中速区间抖动。10 个脉冲对应在 8 脉冲每转时100ms 闸门内转速约 750RPM 以下切 T 法这个切换点实测比较顺滑。4. PWM 驱动与速度闭环增量式 PID 的参数怎么给4.1 PWM 频率选择与 H 桥驱动配置先跑通开环再谈闭环有刷直流电机调速的功率级一般用 H 桥STM32 输出 PWM 控制占空比方向引脚控制正反转。PWM 频率不能拍脑袋定。常见做法是选 10kHz 到 20kHz一方面避开人耳可听的噪声频段另一方面保证电枢电流相对平滑。我一般默认 16kHz如果电机电枢电感很小可以降到 12kHz 提高电流连续度。PWM 初始化时需要关注几个参数计数频率、自动重载值、占空比寄存器以及输出极性。以 72MHz 主频、16kHz PWM 为例预分频选 72-1、自动重载值选 62这样 PWM 频率 72MHz / 72 / 63 ≈ 16kHz。占空比寄存器从 0 到 62对应 0% 到 100%步进约 1.6%对调速来说够用了。H 桥驱动部分常见的集成驱动芯片内部已经包含死区时间软件侧只需要按顺序初始化先把方向和 PWM 引脚拉到确定电平再给驱动芯片使能引脚低电平完成初始化后最后拉高使能。这个顺序很重要否则上电瞬间 GPIO 默认电平不确定H 桥上下桥臂可能同时导通直接烧驱动。开环调通是闭环的前提。先用固定占空比 50% 让电机转起来确认霍尔测速读数正常、正反转方向可控再进入 PID 环节。很多翻车现场都是跳过这一步直接上闭环结果分不清是测速问题还是控制问题。4.2 增量式 PID 实现与参数整定从公式到能用的三组数速度环用增量式 PID 是电机调速最常见的做法。增量式输出的是占空比修正量而不是绝对占空比天然带有记忆效果而且不容易出现积分深度饱和导致的大幅超调。实现代码如下typedef struct { float kp, ki, kd; float prev_error; // 上一次误差 float prev_prev_error; // 上上次误差 float output; // 当前输出即占空比加增量后的值 float out_limit; // 输出限幅对应最大占空比 } PID_Handle_t; // 增量式 PID 计算返回占空比范围 -out_limit ~ out_limit int16_t PID_Calc(PID_Handle_t *pid, int16_t setpoint, int16_t feedback) { float error (float)setpoint - (float)feedback; float delta pid-kp * (error - pid-prev_error) pid-ki * error pid-kd * (error - 2.0f * pid-prev_error pid-prev_prev_error); pid-output delta; // 输出累加形成绝对占空比 if (pid-output pid-out_limit) { pid-output pid-out_limit; } if (pid-output -pid-out_limit) { pid-output -pid-out_limit; } pid-prev_prev_error pid-prev_error; pid-prev_error error; return (int16_t)pid-output; }增量式 PID 有三个值得注意的地方。第一是输出累加而不是直接赋 error 的比例所以占空比有天然的「记忆」负载突变时调整过程比较平滑。第二是 out_limit 必须设置否则电机启动瞬间误差大输出一路累加超过 100%电机全压启动电流冲击很大。第三是微分项 D 在这个系统里作用很小很多情况下甚至可以设 0因为霍尔测速本身带有量化噪声微分项会把噪声放大成抖动的占空比。参数整定顺序和经验值我一般遵循先 P 后 I 再 D 的原则。先给一个保守的 P让系统不振荡然后加 I 消除稳态误差最后根据超调决定要不要加 D。工况kpkikd备注空载起步0.30.050.01先跑通超调允许轻载稳定0.50.020.005追求快速响应重载带波动0.80.080.02负载波动大时加强比例这些值不是万能的伺服系统负载转动惯量变化很大但我见过很多项目从这组初值开始微调都能收敛。4.3 闭环联调从开环占空比到速度闭环的验证路径闭环联调的正确路径是先验证测速方向和控制方向的一致性。转速反馈是正的目标转速也是正的那么误差 目标 - 反馈。如果误差为正说明转速偏低此时占空比增大电机加速系统才会收敛。如果接线接反PID 输出增大后电机反而减速误差进一步扩大形成正反馈几分钟内就能看到转速冲顶或者停转。验证方向的方法很简单给定一个固定占空比手动把目标转速设得比实际转速高一点点观察 PID 输出占空比应该从当前值缓慢上升。如果下降说明反馈方向反了把霍尔脉冲或者方向引脚取反即可。闭环联调阶段建议把 PID 输出和实际转速同时通过串口或者调试器观察。先用空载小惯量做阶跃测试给定一个目标转速观察响应波形记录超调量和稳定时间。这套路径跑通了再带真实负载负载变化导致的问题基本集中在参数调整上很少再出现方向性的问题。5. 霍尔调速系统避坑5 个常见问题与排查记录5.1 测速值周期性跳动先查磁钢与霍尔的气隙和同心度现象转速稳定在目标值附近但串口打印的实时转速呈周期性波动波动频率和电机转速接近不是随机噪声。原因磁钢安装偏心导致霍尔与磁钢的距离在旋转过程中不断变化。距离近磁场强霍尔提前翻转距离远磁场弱翻转滞后。脉冲间隔忽长忽短转速自然跟着跳。解决拆下磁钢重新安装用千分表打径向跳动控制在 0.1mm 以内。同时检查霍尔和磁钢的气隙保持 1~3mm 且不随旋转变化。如果结构上没法调整可以改用锁存型霍尔配多极磁环锁存型对磁场幅度变化更不敏感。5.2 低速测不准M 法脉冲太少误差拉满现象电机转速低于 100RPM 时测速值跳动非常剧烈甚至出现 0 RPM 和 200 RPM 交替跳变。原因M 法在闸门时间内只能数到几个脉冲丢失任何一个脉冲都造成巨大百分比误差。我之前调试一台 4 对极磁环电机目标转速 50RPM闸门 100ms 内理论上只有约 3 个脉冲少一个就是 33% 的误差。解决闸门内脉冲数少于 10 时切换 T 法用相邻脉冲间隔计算转速。如果 T 法也抖动检查是否低速下霍尔输出边沿抖动导致捕获不稳定适当加大 ICFilter 滤波参数。5.3 上电瞬间电机抖动或者驱动芯片发热先查 GPIO 默认态和使能时序现象程序烧录后上电电机在初始化完成前就猛地转了一下或者驱动芯片很快发烫但程序跑到主循环后一切正常。原因STM32 上电到 GPIO 初始化完成这段时间引脚输出状态是默认浮空或者不确定电平H 桥两个输入引脚出现任意组合。如果恰好是高电平加高电平桥臂直通电流飙升。解决H 桥使能引脚接一个下拉电阻默认失能程序初始化第一件事就是把 PWM 和方向引脚全部拉低再初始化定时器最后才拉高使能引脚。驱动芯片发烫严重说明直通已经发生换芯片前先检查这个时序。5.4 PID 整定振荡积分限幅和输出限幅双保险必须有现象闭环投入后电机转速来回波动甚至周期性振荡把 kp 调小也不见明显改善。原因增量式 PID 输出累加后没有限幅启动瞬间误差大输出一路累加超限电机全压加减速来回冲。另一个常见原因是微分项放大了测速量化噪声kd 给得过大。解决代码里的 out_limit 不能省根据电机额定电压和 PWM 分辨率设置一般限制在 80%~90% 占空比。先只调 P找到系统不振荡的临界增益再减半做初始值然后把 ki 从 0 慢慢加上去消除稳态误差。kd 在这类系统里通常不需要除非负载惯量特别大。5.5 高速时频繁中断拖慢主循环用硬件计数减轻中断负担现象电机高速运行时霍尔脉冲频率可能达到每秒几千个每次进入捕获中断做处理主循环任务明显卡顿甚至影响 PWM 更新。原因中断频率过高大部分 CPU 时间花在进出中断的压栈出栈上。我遇到过 8 脉冲每转、6000RPM 的情况每秒脉冲 800 个还能扛但换成更高极对数和更高转速后中断开销就很可观了。解决把捕获中断里最耗时的浮点运算全部移出去中断里只维护计数值和捕获值。进一步优化方案是用定时器的从模式做硬件级联让硬件直接完成计数MCU 只负责周期读取。没有 DMA 或从模式配置经验的情况下先把计算任务挪出中断是最快见效的做法。6. 调 PID 的省力技巧串口转速曲线与斜坡限速调 PID 最怕的就是只能看到最终转速数字看不到动态过程。串口打印一个轻量级的曲线数据出去比盯着调试器变量舒服得多。我一般会做一根简单的数据帧用 sprintf 把目标转速、实际转速、占空比拼成一行波特率 11520010ms 发一次PC 端随便用一个串口绘图工具就能画出三条曲线。// 串口输出转速曲线数据格式目标,实际,占空比 void Speed_Report(uint16_t setpoint, uint16_t actual, int16_t output) { char buf[48]; snprintf(buf, sizeof(buf), %d,%d,%d\r\n, setpoint, actual, output); HAL_UART_Transmit(huart1, (uint8_t*)buf, strlen(buf), 100); }看曲线调参数的逻辑其实很简单超调大就把 kp 往下压稳态偏差大就加 ki波形高频抖动就减 kd 甚至清零。这三条经验能覆盖大多数调参场景比背一整本控制理论管用。另一个很实用的技巧是斜坡限速。直接把目标转速给 PID启动会让电机全压加速电流冲击大机械结构也可能因为扭矩突变而抖动。给目标转速加一个斜坡发生器让给定值逐步爬升到最终目标能明显改善启动品质。代码很简单// 斜坡限速每个周期向目标值逼近 step 步长 // 在主循环里每隔 10ms 调用一次 int16_t Speed_Ramp(int16_t target, int16_t current, int16_t step) { if (target current step) { return current step; } if (target current - step) { return current - step; } return target; }step 取 5~20 RPM 比较合适根据系统需要的响应速度调整。这套系统从方案验证到现在我在 PID 整定上翻车的次数远多于硬件烧毁的次数类比重载启动时先把斜坡 step 调小、再看看曲线振荡周期决定动哪个参数才算真正把霍尔调速这套闭环玩明白。希望帮到你。本文还有配套的精品资源点击获取