去年接手了一个工业风扇改造项目,需要将老旧的交流电机替换为无刷直流电机(BLDC)。市面上现成的控制器要么功能过剩价格昂贵,要么调节精度不够。索性自己用STM32开发了一套带调速和方向控制的解决方案,实测转速控制误差小于±2%,成本不到50元。这个项目最让我自豪的是在资源有限的STM32F103C8T6上实现了平滑调速、实时显示和PID闭环控制,代码量控制在8KB以内。
BLDC电机相比传统有刷电机,具有效率高、寿命长、噪音低等优势,但控制复杂度也呈指数级上升。核心难点在于需要精确控制三相绕组的通电顺序和时间,同时还要处理反电动势、死区补偿等技术细节。我的方案采用六步换向法(Six-step Commutation),配合霍尔传感器反馈,在保证性能的前提下最大限度降低了实现难度。
选择STM32F103C8T6主要基于三点考量:首先它具备高级定时器TIM1,支持互补PWM输出和死区插入;其次72MHz主频足够处理换向逻辑和PID运算;最后8KB RAM和64KB Flash的存储配置刚好满足需求。这个芯片还有个隐藏优势——它的ADC支持定时器触发采样,这对实现调速同步非常关键。
驱动芯片选用ST的L6234而非常见的IR2104方案,主要看中其三点特性:
硬件设计警示:L6234的续流二极管参数直接影响电机运行稳定性。实测发现当二极管正向压降低于0.5V时,电机高速运转会产生明显震荡。建议在PCB布局时预留可调电阻位置,方便后期优化。
调速电路采用10kΩ多圈电位器分压,通过100nF电容滤波后接入PA1(ADC1_IN1)。这里有个细节处理——在ADC输入端串联200Ω电阻,可有效抑制高频干扰。显示部分使用LCD1602的4线模式,节省了4个GPIO口。
霍尔传感器接口设计需特别注意:

BLDC电机运转本质上是按特定顺序给三相绕组通电。通过霍尔传感器输出的3位二进制值(共6种状态),可以精确确定转子位置。在STM32中,我用TIM1的三个通道(CH1/CH2/CH3)生成带死区的互补PWM,配合以下换相表实现控制:
| 霍尔状态 | 导通相 | TIM1输出模式 |
|---|---|---|
| 0x05 | A+B- | CH1高, CH2N高, CH3关闭 |
| 0x01 | A+C- | CH1高, CH3N高, CH2关闭 |
| 0x03 | B+C- | CH2高, CH3N高, CH1关闭 |
| 0x02 | B+A- | CH2高, CH1N高, CH3关闭 |
| 0x06 | C+A- | CH3高, CH1N高, CH2关闭 |
| 0x04 | C+B- | CH3高, CH2N高, CH1关闭 |
实现代码的关键片段:
c复制void Commutation(uint8_t hall_state) {
switch(hall_state) {
case 0x05: // AB导通
TIM_CCxCmd(TIM1, TIM_Channel_1, TIM_CCx_Enable);
TIM_CCxNCmd(TIM1, TIM_Channel_2, TIM_CCxN_Enable);
TIM_CCxCmd(TIM1, TIM_Channel_3, TIM_CCx_Disable);
break;
// 其他状态处理类似...
}
}
传统测速方法需要频繁进入中断计数,在高速场景下会占用大量CPU资源。我的方案利用TIM2的输入捕获功能,通过测量霍尔信号周期来计算转速:
转速计算公式推导:
code复制转速(RPM) = 60 / (脉冲周期 × 极对数)
对于7对极电机:转速 = 60 / (周期 × 7)
考虑到6步换向:实际转速 = 10^6 / (周期 × 7 × 6)
代码实现中的巧妙之处在于处理计数器溢出:
c复制uint32_t Get_RPM(void) {
static uint32_t last_capture = 0;
uint32_t current_capture = TIM_GetCapture2(TIM2);
uint32_t period = (current_capture > last_capture) ?
(current_capture - last_capture) :
(0xFFFFFFFF - last_capture + current_capture);
last_capture = current_capture;
return (uint32_t)(60000000.0 / (period * 6 * 7));
}
调速电位器的ADC采样看似简单,但要做好需要处理三个问题:
我的解决方案是:
ADC_Value = 0.8*ADC_Value + 0.2*new_sample关键配置代码:
c复制void ADC_Config(void) {
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
// DMA配置(循环模式,半字传输)
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_Value;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 1;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
// 定时器触发ADC采样
TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update);
ADC_ExternalTrigConvCmd(ADC1, ENABLE);
}
项目中采用位置式PID算法,参数整定过程总结出以下经验:
实测最优参数组合:
c复制#define KP 0.8f
#define KI 0.05f
#define KD 0.01f
float PID_Update(float setpoint, float actual) {
static float integral = 0, last_error = 0;
float error = setpoint - actual;
integral += error;
if(integral > 1000) integral = 1000; // 抗积分饱和
float derivative = error - last_error;
last_error = error;
return KP*error + KI*integral + KD*derivative;
}
调试技巧:先用纯P控制让电机转起来,然后逐步加入I和D。观察转速波形时,建议用串口输出数据到上位机绘图,比直接看LCD更直观。
现象:上电后电机抖动但不旋转
排查步骤:
根本原因:90%的情况是霍尔传感器安装相位偏移,可通过调整换相表解决。
现象:转速超过15000RPM时出现周期性震荡
优化方案:
常见错误:
模型参数建议:
code复制.model D1 D(Is=1e-15 Rs=0.1 N=0.1 Cjo=10p Tt=100n)
.model Q1 NMOS(Level=3 Gamma=0 Delta=0 Eta=0 Theta=0)
这套系统在实际运行三个月后,我又做了几项重要改进:
最实用的改进是增加了"软启动"功能——电机从0加速到设定转速需要3秒时间,有效避免了启动电流冲击。实现代码非常简单:
c复制void Soft_Start(uint16_t target_rpm) {
for(uint16_t i=0; i<target_rpm; i+=100) {
Set_PWM(i);
delay_ms(30);
}
Set_PWM(target_rpm);
}
这个项目让我深刻体会到,好的嵌入式设计不在于用了多高级的芯片,而在于每个细节的精心打磨。比如那个用TIM1触发ADC采样的"骚操作",看似只是加了一行代码,却让调速响应速度提升了20%以上。