资讯动态

STM32霍尔FOC速度模式实战:从换向信号到连续电角度的关键设计

发布时间:2026/9/10 9:16:50 来源:尧图企业网站定制
简介面向电机控制与嵌入式开发的STM32直流无刷电机FOC矢量控制项目采用霍尔传感器获取转子位置并结合实时操作系统完成多任务调度覆盖电流采样、Clarke/Park坐标变换、PWM调制及速度闭环控制等完整实现链路。压缩包共1192个文件以c/h源码为主辅以s汇编、icf链接脚本、uvprojx工程文件及hex固件可在STM32开发环境中直接编译、烧录与调试。资源整体约31.42MB适用于工业自动化、机器人、无人机等领域的电机驱动开发也适合有一定单片机基础、希望深入理解FOC算法工程落地的开发者。目前已有674人学习下载。项目包含FOC核心算法、霍尔驱动逻辑、FreeRTOS任务划分、电机参数整定和异常处理机制源码结构清晰可依据实际电机参数进行移植或作为课程设计与毕业设计的参考资料。1. 霍尔FOC速度模式为什么比六步换向难写第一次把霍尔BLDC跑起来时我踩的最疼的一个坑是六步换向能转切到FOC速度模式就飞车。电机能转只是错觉真正的问题是霍尔传感器每60度电角度才跳变一次而FOC电流环需要每50到100微秒知道转子角度这个误差直接让Park变换解算出的q轴电流变成噪声。这个工程就是围绕这个问题展开的——STM32做FOC矢量控制速度模式霍尔驱动跑在RTOS上。适合需要把直流无刷电机用在伺服、机器人关节或小型无人机电调上的工程师也适合刚接触FOC想从源码而不是仿真入手的人。工程里带着Cortex-M4F的CMSIS-DSP静态库libarm_cortexM4l_math.a、libarm_cortexM4lf_math.a说明计算部分已经替你把sin/cos、三角函数优化过了。下面写的内容和这个工程相互印证霍尔信号怎么变成电角度电流环怎么和速度环级联RTOS下任务怎么切最后给一个验证角度平滑度的调试技巧。2. 霍尔换向信号怎么变成连续的转子电角度2.1 霍尔传感器输出与扇区查表霍尔传感器是开关型输出三路信号A/B/C按120度电角度安装。转子转过一圈三路输出会组合出6种状态每种状态对应60度电角度的扇区。查表是FOC的第一步也是最容易抄错的一步不同电机绕线相序不同霍尔安装位置不同同一个二进制组合对应的扇区可能不一样。#define HALL_STATE_1 0x01 #define HALL_STATE_2 0x02 /* 按电机实际相序维护状态值即 GPIO_IDR 读取到的低三位 */ static const uint8_t hall_sector_table[8] { 0, /* 0b000 无效 */ 3, /* 0b001 */ 1, /* 0b010 */ 5, /* 0b011 */ 0, /* 0b100 */ 4, /* 0b101 */ 2, /* 0b110 */ 6 /* 0b111 无效 */ };查表逻辑是把GPIO引脚读到的电平组合当作数组下标直接拿到扇区号1到6。工程里驱动霍尔传感器的方式是用STM32的GPIO配置成开漏上拉输入霍尔器件输出引脚自己拉低不需要额外电平转换。上拉电阻一般在4.7k到10k之间转速高时上拉电阻过大容易导致边沿变缓输入捕获的误差会变大。注意不要在换向瞬间直接读取霍尔状态。霍尔信号跳变沿附近可能因机械安装偏差产生抖动我一般会在GPIO EXTI中断里加一个3微秒的软件去抖或者用定时器的输入滤波寄存器避免在边沿抖动时把错误扇区写入电机控制变量。2.2 速度计算用输入捕获测边沿周期速度模式必须要有速度反馈。常见做法是霍尔信号接到STM32的定时器输入捕获通道每个边沿触发一次捕获中断读两次边沿之间的时间差。霍尔边沿在60度电角度产生一次电机极对数为p时机械转速计算公式可以写成volatile float hall_speed_rpm; void HALL_TIM_CaptureCallback(TIM_HandleTypeDef *htim) { static uint32_t last_count 0; uint32_t current_count __HAL_TIM_GET_COUNTER(HALL_TIM); uint32_t dt current_count - last_count; /* 定时器计数差 */ last_count current_count; if (dt 0) { float freq_hz (float)APB1_TIMER_CLOCK_MHZ * 1000000.0f / (float)dt; /* 每个沿对应60度电角度转一圈要6个沿除以极对数得机械转速 */ hall_speed_rpm freq_hz / 6.0f / (float)MOTOR_POLE_PAIRS * 60.0f; } }代码里的APB1_TIMER_CLOCK_MHZ是定时器实际时钟具体数值取决于RCC时钟树和预分频器设置。这个公式的前提是霍尔信号每个沿都代表转过60度电角度如果极对数为2转子转一圈有6 * 2 12个沿所以要先除极对数再乘60。参数MOTOR_POLE_PAIRS不是极数是极对数工程里电机标称“14极”时这里填7。转速计算放在捕获中断里做低频时两次沿之间时间很长速度更新率会降到几十赫兹。速度环在1kHz执行时中间大部分周期拿不到新速度需要用上一拍的速度值或者做软滤波。这个工程里速度环周期是1ms我一般会在捕获中断里直接更新速度并同步清掉速度环的任务事件让RTOS任务立刻读取而不是等下一次调度。2.3 扇区内的连续电角度外推直接查表得到的电角度是一个阶梯波每个扇区维持60度然后突变。FOC的Park变换要求电角度连续变化否则d/q轴电流会剧烈抖动量。霍尔FOC工程里的核心技巧是用速度在扇区内积分把角度从跳变表变成连续值。float hall_angle_continuous; /* 连续电角度单位rad */ float motor_elec_speed; /* 电角速度rad/s */ uint32_t last_edge_time_us; void HALL_OnEdge(uint16_t sector) { uint32_t now_us hall_timestamp_us(); float dt (float)(now_us - last_edge_time_us) * 1e-6f; if (dt 1e-5f) { /* 60度电角度 PI/3 rad */ motor_elec_speed (float)MATH_PI / (3.0f * dt); } last_edge_time_us now_us; } void HALL_UpdateAngleContinuous(uint32_t now_us) { float dt (float)(now_us - last_edge_time_us) * 1e-6f; hall_angle_continuous motor_elec_speed * dt; if (hall_angle_continuous (float)MATH_2PI) { hall_angle_continuous - (float)MATH_2PI; } }这个做法的核心思想是边沿到达时更新一次角速度之后在电流环里按匀速外推角度。霍尔边沿之间机械位置发生变化是近似匀速的速度变化越快外推误差越大。工程里电流环中断是20kHz即每50微秒更新一次角度速度突变时误差还来不及积累效果可以接受。要注意扇区跳变时角度必须对齐到真实60度分界否则长时间运行会产生累积相位漂移。项目六步换向霍尔FOC角度分辨率60度电角度理论上连续低速力矩脉动大平稳依赖速度估计不需要需要低速时难对霍尔安装误差不敏感敏感需校准霍尔FOC在低速时比六步换向的优势明显但对霍尔边沿的准确性也更敏感。霍尔安装偏差会造成边沿时间误差进而让速度估算出现跳变表现为电流波形上出现固定的毛刺。3. 电流环与速度环Clarke变换到PID参数整定3.1 为什么电流采样只需要两相FOC需要得到三相定子电流在αβ轴上的分量但实际采样只采两相。电机星形连接且中心点不引出时根据基尔霍夫电流定律三相电流之和为零。实际工程中采样A相和B相C相通过计算得到。void Clarke_Transform(float ia, float ib, float *i_alpha, float *i_beta) { *i_alpha ia; *i_beta (ia 2.0f * ib) * 0.57735027f; /* 1/sqrt(3) */ }代码里0.57735027f是等幅值Clarke变换系数。等幅值变换下αβ坐标系的电压幅值与三相相电压幅值一致这样SVPWM计算时不需要额外缩放。注意这里采样的两相不限于A相B相也可以采A相C相关键是在PWM中心对齐时刻采样此时电流等于一个PWM周期的平均电流。工程里ADC触发信号一般由定时器更新事件产生采样窗口避开上下桥臂开通瞬间。参数ia和ib是经过偏置校准后的电流值。霍尔电流传感器的零点偏移会导致Clarke变换结果偏大我一般上电时采样100次求平均作为零点运行中再做一次动态校准否则低速时q轴电流会出现固定偏移电机会往一个方向微颤。3.2 帕克变换和电角度的作用Clarke变换把三相静止坐标变成两相静止坐标但αβ轴上的电流仍然是交流量。Park变换以转子电角度为旋转基准把交流量变成直流量d轴对应励磁分量q轴对应转矩分量。#include arm_math.h void Park_Transform(float i_alpha, float i_beta, float theta, float *i_d, float *i_q) { float sin_t arm_sin_f32(theta); float cos_t arm_cos_f32(theta); *i_d i_alpha * cos_t i_beta * sin_t; *i_q -i_alpha * sin_t i_beta * cos_t; }这里的theta就是连续电角度来自上一章的角度外推。使用CMSIS-DSP库的arm_sin_f32而不是编译器自带的sinf是因为这个库针对Cortex-M4F做过周期数优化在20kHz电流环里能节省几十微秒的CPU占用。工程里带的libarm_cortexM4lf_math.a文件名中的f表示带FPU的M4l表示小端模式链接时选择与芯片匹配的那个静态库。角度误差对FOC的影响比一般人想的大。电角度偏5度q轴电流解算会损失约0.4%的转矩偏30度时转矩输出只剩86%同时d轴会出现去磁电流。霍尔安装偏差容易导致固定的角度偏置工程里常见做法是让电机空载以恒定速度旋转观测q轴电流波形调整角度偏置让q轴电流脉动最小。3.3 速度环与电流环的级联和PID初值速度模式通常用速度外环加电流内环的双闭环结构。速度环输出是q轴电流参考值d轴电流参考值在表贴式电机上直接设为0。电流环应比速度环快5到10倍一般电流环10kHz到20kHz速度环1kHz。控制环执行频率常用初值整定方向电流环P10-20kHz0.1-0.5先调比例再调积分电流环I10-20kHz10-50消除稳态误差过大会振荡速度环P1kHz0.5-2.0增大响应过大会有超调速度环I1kHz5-20抗负载扰动过大会低频抖动速度环的PID实现要注意积分饱和。速度给定时电机还没到达目标速度之前积分项会一直积累等到接近目标时积分已经超出执行器上限导致速度回摆。工程里一般把积分输出限制在电流限幅以内并在输出饱和时停止积分也就是抗积分饱和。typedef struct { float Kp; float Ki; float Kd; float integral; float prev_error; float out_max; } pid_t; float PID_Update(pid_t *pid, float error) { float out; pid-integral error; out pid-Kp * error; out pid-Ki * pid-integral; out pid-Kd * (error - pid-prev_error); pid-prev_error error; if (out pid-out_max) { out pid-out_max; /* 输出饱和时回退积分避免积分饱和 */ pid-integral (out - pid-Kp * error - pid-Kd * (error - pid-prev_error)) / pid-Ki; } else if (out -pid-out_max) { out -pid-out_max; pid-integral (-pid-out_max - pid-Kp * error - pid-Kd * (error - pid-prev_error)) / pid-Ki; } return out; }这个实现里有两个容易忽略的参数out_max是电流限幅通常设成堵转电流的一半微分项对噪声非常敏感霍尔速度反馈本身有量化噪声速度环的微分项建议直接置0。工程里速度环只用PI电流环也可以用PI微分项在电机控制里主要用于补偿大惯量负载但参数难整定新手经常越调越振。另外FOC运行时的电流环中断频率很高PID计算里的浮点运算在Cortex-M4F上很快但要注意编译时开启硬件FPU并链接libarm_cortexM4lf这个单精度浮点库否则全部走软浮点20kHz中断容易占用超过50%的CPU时间。4. 引入OS后的FOC任务划分与中断优先级设计4.1 电流环必须放在PWM更新中断里工程名里带OS工程里用的是FreeRTOS。很多人一上来就把电流环放进RTOS任务周期设到100微秒实际跑起来发现波形一团糟。原因很简单RTOS任务调度可能有几微秒到几十微秒的抖动电流环对时间确定性要求极高一点点调度抖动都会变成电流噪声。正确做法是电流环完全放在PWM定时器更新中断里利用PWM中心对齐触发ADC采样采样完成再触发中断执行FOC计算。void TIM1_UP_IRQHandler(void) { if (__HAL_TIM_GET_FLAG(htim1, TIM_FLAG_UPDATE) ! RESET) { __HAL_TIM_CLEAR_FLAG(htim1, TIM_FLAG_UPDATE); Read_PhaseCurrents(); Hall_UpdateAngleContinuous(now_us()); Clarke_Transform(ia, ib, i_alpha, i_beta); Park_Transform(i_alpha, i_beta, hall_angle_continuous, i_d, i_q); PID_Update(current_pid_d, d_ref - i_d); PID_Update(current_pid_q, q_ref - i_q); Inverse_Park_Transform(v_d, v_q, hall_angle_continuous, v_alpha, v_beta); SVPWM_Update(v_alpha, v_beta); } }中断里的代码顺序很清楚先读电流和角度再做坐标变换PID计算反变换最后更新SVPWM占空比。中断频率等于PWM频率工程里设置在16kHz到20kHz之间。Read_PhaseCurrents()读到的电流必须和PWM占空比更新在同一周期内有效否则一拍延迟会让电流环的相位裕度明显下降。4.2 RTOS里放什么任务速度指令、监控、通信RTOS在FOC工程里主要管理非实时部分比如速度指令接收、状态监控、上位机通信、堵转保护逻辑。这些任务不需要微秒级响应放到RTOS里反而清爽。void vTaskSpeedCmd(void *pvParameters) { int16_t speed_cmd; BaseType_t ret; for (;;) { ret xQueueReceive(speed_cmd_queue, speed_cmd, portMAX_DELAY); if (ret pdPASS) { /* 限幅后写入速度环参考值PWM中断里只做读取 */ speed_ref_latched (float)speed_cmd; } } } void vTaskMonitor(void *pvParameters) { uint32_t hall_delta_t; for (;;) { vTaskDelay(pdMS_TO_TICKS(100)); hall_delta_t get_hall_last_edge_interval_us(); if (hall_delta_t ROTOR_STALL_US) { /* 霍尔长时间无新边沿判定堵转 */ fault_code | FAULT_ROTOR_STALL; } } }速度指令队列用FreeRTOS队列传递好处是PWM中断里始终只读普通全局变量speed_ref_latched而RTOS任务写入这个变量前先做一个临界区保护。监控任务每100毫秒运行一次检查霍尔边沿时间差超过预设值就报堵转故障。这种写法把硬实时计算留在中断把容错和交互放在低优先级任务系统整体更稳定。4.3 任务优先级和临界区保护的边界FreeRTOS里任务优先级需要结合中断优先级统一规划。电流环中断优先级应设为高于任何RTOS可屏蔽中断这样无论RTOS在做什么PWM更新中断都能抢到CPU。FreeRTOS运行在BASEPRI寄存器掩码之下不能阻止真正高优先级的中断。优先级模块说明最高PWM更新中断电流环FOC20kHz高霍尔边沿中断更新扇区角度和速度微秒级中速度环定时器任务1kHz可接受少量抖动低通信/监控任务100Hz允许阻塞霍尔边沿中断需要尽快处理但它的时间不确定性主要来自机械转速。低速时两个边沿间隔可能长达几十毫秒此时中断频率很低不占用CPU高速时边沿间隔变短最坏情况下20kHz电流环和霍尔中断会竞争。工程里霍尔边沿中断内只更新扇区、时间戳和电角速度不要做任何滤波计算滤波放到电流环中断里读。共享变量保护方面速度环和电流环都可能访问hall_angle_continuous。我一般用__disable_irq()和__enable_irq()包裹RTOS任务里的短临界区或者用taskENTER_CRITICAL()。绝不要在FreeRTOS任务里长时间关中断否则PWM中断的计算会丢失电机立刻失控。这里的一个可用原则是PWM中断里不调用任何RTOS API只更新全局变量RTOS任务读取这些变量前加临界区写入速度指令时用队列或者带原子访问的全局变量。4.4 为什么霍尔FOC比无感FOC更容易跑OS无感FOC需要反电动势观测器或磁链观测器这些算法在低速时很难收敛而且需要占用大量CPU做矩阵运算留给RTOS的时间很少。霍尔FOC有霍尔传感器提供绝对位置不需要复杂观测器所以PWM中断里只有坐标变换和PID运算量小CPU有更多余量跑通信和监控任务。这也是这个工程可以在带OS的情况下稳定运行的原因。如果换成无感FOC建议先优化中断里的浮点计算再把通讯任务优先级降低否则偶发的调度延迟会让无感观测器发散。5. 把电角度“看”出来的调试技巧5.1 用DAC输出角度曲线霍尔FOC最麻烦的问题是电角度不连续。肉眼看不到内部变量只看电流波形很难判断是角度外推错误还是PID参数问题。工程里低成本做法是用STM32的DAC输出电角度把0到2π映射到0V到3.3V然后用示波器观察。void Debug_OutputAngle(float angle_rad) { uint16_t dac_value (uint16_t)(angle_rad * DHR12R_FULL_RANGE / 6.28318f); __HAL_DAC_SET_VALUE(hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, dac_value); }正常匀速运行时DAC输出应该是一条锯齿波每个霍尔扇区一次锯齿回复。如果看到扇区内角度往回跳、增速不匀、或者扇区边界出现毛刺说明外推角度没有和霍尔边沿对齐。代码里的DHR12R_FULL_RANGE是DAC 12位分辨率时的满量程值STM32F4系列是4095映射比例可根据DAC参考电压微调。5.2 扇区突变时的环绕补偿角度外推到0和2π边界时一小步计算误差就可能让角度从359度跳回1度造成速度和电流突变。调试时尤其容易遇到这种情况。float Angle_AddWithWrap(float angle, float delta) { angle delta; if (angle (float)MATH_2PI) { angle - (float)MATH_2PI; } else if (angle 0.0f) { angle (float)MATH_2PI; } return angle; }单独看这个函数很简单但实际工程里霍尔扇区更新和角度外推是两个不同中断一旦时间戳错乱角度可能连续两次回绕。我调试时会在DAC输出上加一个方向标志正转时角度递增反转时递减反转过零时DAC波形从接近0V跳变到接近3.3V这样在示波器上很容易区分正常回绕和异常抖动。另一个常用做法是记录每个扇区的进入时间与角度外推速度做交叉校验如果同一扇区停留时间明显短于边沿周期就说明角度更新逻辑里有重复计数。5.3 低速堵转检测的关键参数霍尔FOC在低速时边沿间隔很长堵转时完全没有边沿。堵转检测不能靠速度值本身而要看边沿间隔是否小于堵转阈值。把这个判断放在RTOS监控任务里100毫秒采样一次比放在霍尔中断里更合理。阈值建议设为电机额定转速对应边沿间隔的3到5倍。比如额定3000转电机、极对数为4时额定转速下的霍尔沿频率约300Hz堵转阈值可以设在100Hz对应时间。如果环境振动大可以再加一次确认连续两次检测到超时再报故障避免启动瞬间误报。调试霍尔FOC不能只盯转速波形要把电角度、d/q轴电流、速度环输出同时录下来。这一套工程里最值得抄的就是角度外推和任务划分把实时计算留在中断把逻辑判断交给RTOS整个系统才有余量去处理通信和故障。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价