1. 项目概述
今天要分享的是基于STM32F103C8T6的无刷直流电机(BLDC)控制系统实现,包含有传感器和无传感器两种方案。这两种方案都采用了速度电流双闭环控制结构,核心控制算法都是PI调节器,但在转子位置检测方式上存在本质区别。
无传感器方案通过检测反电动势过零点来估算转子位置,而有传感器方案则直接使用霍尔传感器获取位置信息。两种方案各有优劣:无传感器方案节省了硬件成本但算法复杂度高,有传感器方案实现简单但增加了硬件复杂度和故障点。
2. 硬件设计解析
2.1 主控芯片选型
我们选用STM32F103C8T6作为主控芯片,这款Cortex-M3内核的MCU具有以下优势:
- 72MHz主频,足够处理电机控制算法
- 丰富的外设资源:高级定时器TIM1可用于PWM生成,TIM3可用于捕获反电动势信号
- 内置ADC支持注入通道模式,可实现精准的电流采样时机控制
- 价格低廉,性价比高
2.2 功率驱动电路
功率驱动部分采用三相全桥拓扑结构,每相使用一对N沟道MOSFET作为开关器件。关键设计要点包括:
- 栅极驱动电路:使用专用栅极驱动芯片如IR2104,提供足够的驱动电流
- 死区时间设置:必须配置合适的死区时间(通常300-500ns)防止上下管直通
- 电流检测:在直流母线负极串联小阻值采样电阻(通常0.01-0.1Ω)
注意:死区时间不足会导致MOS管直通炸管,建议先用示波器验证实际死区时间是否符合预期
3. 无传感器方案实现
3.1 反电动势过零点检测
无传感器方案的核心是检测悬浮相的反电动势过零点。实现要点:
- 电机运行时,总有一相处于不导通状态(悬浮相)
- 通过分压电阻网络将三相电压引入MCU的ADC或比较器
- 检测悬浮相电压与虚拟中性点电压的交点
关键代码实现:
c复制// 反电动势过零点检测中断服务
void BEMF_Zero_Cross_IRQHandler(void)
{
uint8_t phase_state = GPIOA->IDR & 0x07; // 读取三相电压状态
if((phase_state == 0x02) && (commutation_step == 1))
{
// 计算下次换相时机
uint16_t new_commutation_time = TIM3->CCR1 + (BEMF_DETECTION_DELAY / 2);
TIM3->CCR1 = new_commutation_time % PWM_PERIOD;
commutation_step = (commutation_step + 1) % 6;
}
}
3.2 三步启动法
无传感器方案启动时需要特殊处理,因为静止时无反电动势信号。常用三步启动法:
- 定位阶段:给固定两相通电,将转子拉到确定位置
- 加速阶段:按照预设顺序换相,使转子加速旋转
- 闭环切换:当转速足够产生可检测的反电动势后,切换到闭环控制
c复制void Motor_Startup(void)
{
// 第一阶段:强制定位
PWM_SetDuty(U_PHASE, 70); // U相70%占空比
PWM_SetDuty(V_PHASE, 0);
PWM_SetDuty(W_PHASE, 0);
HAL_Delay(100); // 保持100ms
// 第二阶段:加速旋转
const uint8_t startup_sequence[] = {0x05, 0x01, 0x03, 0x02, 0x06, 0x04};
for(int i=0; i<12; i++) {
Motor_Commutation(startup_sequence[i%6]);
HAL_Delay(20 - i); // 延时逐渐减小,实现加速
}
// 第三阶段:切入闭环
BEMF_Detection_Enable();
}
4. 有传感器方案实现
4.1 霍尔传感器接口
霍尔传感器通常输出3路数字信号,组合起来可表示6种转子位置状态。接口设计要点:
- 使用GPIO外部中断检测霍尔信号变化
- 添加RC滤波电路抑制噪声干扰
- 必要时使用施密特触发器整形信号
c复制// 霍尔传感器中断处理
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == (HALL_U_Pin|HALL_V_Pin|HALL_W_Pin))
{
uint8_t hall_state = (HAL_GPIO_ReadPin(HALL_U_GPIO_Port, HALL_U_Pin) << 2)
| (HAL_GPIO_ReadPin(HALL_V_GPIO_Port, HALL_V_Pin) << 1)
| HAL_GPIO_ReadPin(HALL_W_GPIO_Port, HALL_W_Pin);
// 霍尔状态到换相步骤的映射表
static const uint8_t hall_to_commutation[] = {0, 3, 1, 5, 4, 2};
Motor_Commutation(hall_to_commutation[hall_state]);
}
}
4.2 霍尔传感器安装校准
霍尔传感器的安装位置直接影响控制性能:
- 理想情况下,霍尔信号跳变应与反电动势过零点对齐
- 实际安装可能存在30°左右的偏差
- 可通过软件补偿修正安装误差
校准方法:
- 缓慢旋转电机转子,记录霍尔信号跳变时的反电动势过零点
- 计算平均偏差角度
- 在换相逻辑中加入补偿量
5. 双闭环控制实现
5.1 PI控制器设计
速度环和电流环都采用PI控制器,关键设计参数:
- 比例系数Kp:决定系统响应速度
- 积分系数Ki:消除稳态误差
- 输出限幅:防止积分饱和
c复制typedef struct {
float Kp;
float Ki;
float integral;
float limit;
float Ts; // 采样周期
} PI_Controller;
float PI_Update(PI_Controller *pi, float error)
{
// 积分项更新
pi->integral += error * pi->Ts;
// 计算输出
float output = pi->Kp * error + pi->Ki * pi->integral;
// 抗饱和处理
if(output > pi->limit) {
output = pi->limit;
pi->integral -= error * pi->Ts; // 回退积分
} else if(output < -pi->limit) {
output = -pi->limit;
pi->integral -= error * pi->Ts;
}
return output;
}
5.2 速度测量方法
速度测量常用方法:
- 霍尔传感器方案:计算两个霍尔信号跳变的时间间隔
- 无传感器方案:计算两次换相的时间间隔
- M法测速:固定时间内计数脉冲数
c复制// 基于霍尔信号的测速实现
float Get_Speed(void)
{
static uint32_t last_tick = 0;
uint32_t current_tick = HAL_GetTick();
uint32_t delta_t = current_tick - last_tick;
last_tick = current_tick;
// 每转6个霍尔边沿,计算RPM
float speed_rpm = 60000.0f / (delta_t * 6);
return speed_rpm;
}
6. 调试技巧与经验分享
6.1 PI参数整定方法
PI参数调试是电机控制的关键,推荐调试步骤:
-
先调电流环:
- 将速度环输出限幅设为较小值
- 设置Ki=0,逐渐增大Kp直到电流响应快速但不过冲
- 然后逐渐增加Ki消除稳态误差
-
再调速度环:
- 使用阶跃速度指令测试
- 同样先调Kp再调Ki
- 注意观察转速超调量和稳定时间
实用技巧:可以先在有传感器模式下调试PI参数,然后再移植到无传感器方案
6.2 常见问题排查
-
电机不启动:
- 检查电源电压是否足够
- 验证PWM信号是否正常输出
- 检查霍尔传感器信号(有传感器方案)
-
电机振动或噪音大:
- 检查反电动势检测电路(无传感器方案)
- 调整PI参数,通常Kp过大导致
- 验证换相时序是否正确
-
转速不稳定:
- 检查速度测量是否准确
- 适当增加速度环积分时间
- 检查机械连接是否牢固
6.3 优化建议
-
无传感器方案在低速时性能较差,可以考虑:
- 高频注入法提升低速性能
- 切换到有传感器模式启动,然后平滑过渡到无传感器模式
-
提高系统可靠性:
- 增加过流、过压、欠压保护
- 实现故障自动检测和恢复
- 添加温度监测功能
-
性能优化方向:
- 采用FOC(磁场定向控制)替代方波控制
- 使用STM32的硬件加速功能(如Cordic协处理器)
- 实现参数自动整定功能
7. 关键代码解析
7.1 PWM生成配置
c复制void PWM_Init(void)
{
TIM_HandleTypeDef htim1;
TIM_OC_InitTypeDef sConfigOC;
htim1.Instance = TIM1;
htim1.Init.Prescaler = 0;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = PWM_PERIOD - 1;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
HAL_TIM_PWM_Init(&htim1);
// 死区时间配置
__HAL_TIM_SET_DEADTIME(&htim1, DEAD_TIME);
// PWM通道配置
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_2);
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
// 启动PWM输出
__HAL_TIM_MOE_ENABLE(&htim1);
}
7.2 电流采样实现
c复制#define CURRENT_OFFSET 2048 // 12位ADC中间值
float Get_Phase_Current(void)
{
// 配置ADC注入通道
ADC_InjectionConfTypeDef sConfigInjected;
sConfigInjected.InjectedChannel = ADC_CHANNEL_5;
sConfigInjected.InjectedRank = 1;
sConfigInjected.InjectedSamplingTime = ADC_SAMPLETIME_28CYCLES;
sConfigInjected.InjectedOffset = 0;
sConfigInjected.InjectedNbrOfConversion = 1;
sConfigInjected.ExternalTrigInjecConv = ADC_EXTERNALTRIGINJECCONV_T1_TRGO;
sConfigInjected.ExternalTrigInjecConvEdge = ADC_EXTERNALTRIGINJECCONVEDGE_RISING;
sConfigInjected.AutoInjectedConv = DISABLE;
sConfigInjected.InjectedDiscontinuousConvMode = DISABLE;
HAL_ADCEx_InjectedConfigChannel(&hadc1, &sConfigInjected);
// 触发ADC采样
HAL_ADCEx_InjectedStart(&hadc1);
HAL_ADCEx_InjectedPollForConversion(&hadc1, 10);
uint16_t adc_value = HAL_ADCEx_InjectedGetValue(&hadc1, ADC_INJECTED_RANK_1);
// 转换为实际电流值(A)
float current = ((int32_t)adc_value - CURRENT_OFFSET) * 3.3f / 4096.0f / 0.1f;
return current;
}
在实际项目中,无刷电机控制是一个系统工程,需要综合考虑硬件设计、软件算法和实际调试经验。建议先从有传感器方案入手,掌握基本原理后再挑战无传感器方案。调试过程中,示波器是必不可少的工具,可以观察PWM波形、反电动势信号和电流波形,帮助分析问题。