ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32F103C8T6 + TB6612:手把手教你搞定直流电机PWM调速(附完整代码与避坑指南)

STM32F103C8T6 + TB6612:手把手教你搞定直流电机PWM调速(附完整代码与避坑指南)

STM32F103C8T6与TB6612直流电机PWM调速实战指南

1. 项目概述与硬件选型

在嵌入式开发领域,电机控制一直是极具挑战性的实践课题。STM32F103C8T6作为经典的Cortex-M3内核微控制器,搭配TB6612电机驱动模块,能够构建高性价比的直流电机调速系统。这套组合特别适合需要快速验证电机控制算法的开发者,或是希望深入理解闭环控制原理的电子爱好者。

核心硬件特性对比:

组件关键参数在本项目中的作用
STM32F103C8T672MHz主频, 16KB SRAM, 64KB Flash产生PWM信号、处理编码器反馈、运行控制算法
TB6612FNG1.2A持续电流, 3.2A峰值电流H桥驱动电路,实现电机正反转和调速
直流电机6V-12V工作电压, 带编码器输出被控对象,编码器提供转速反馈

硬件连接时需特别注意:

  • TB6612的VM引脚需接电机电源(建议与MCU电源隔离)
  • STM32的PWM输出引脚应连接到TB6612的PWMA/PWMB
  • 编码器A/B相输出建议接入STM32的定时器编码器接口

提示:为避免电机干扰导致MCU复位,建议在电源输入端添加100μF电解电容和0.1μF陶瓷电容组合滤波。

2. 开发环境配置与基础驱动实现

2.1 CubeMX工程配置

使用STM32CubeMX可以快速完成外设初始化:

  1. 配置时钟树,确保系统时钟为72MHz
  2. 启用TIM3的Channel1作为PWM生成通道
  3. 设置TIM2为编码器接口模式
  4. 初始化TIM4作为控制算法执行定时器

关键PWM参数计算:

// PWM频率 = 72MHz / (900 * 5) = 16kHz TIM3->ARR = 900 - 1; // 自动重装载值 TIM3->PSC = 5 - 1; // 预分频系数

2.2 电机驱动基础代码

实现电机正反转控制的底层函数:

void Motor_SetSpeed(int16_t speed) { if(speed >= 0) { // 正转 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, speed); } else { // 反转 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, -speed); } }

编码器读数处理:

int32_t Encoder_GetCount(void) { static int32_t overflow = 0; static uint16_t last_cnt = 0; uint16_t current_cnt = TIM2->CNT; // 处理定时器溢出 if((current_cnt < 0x800) && (last_cnt > 0xF000)) { overflow++; } else if((current_cnt > 0xF000) && (last_cnt < 0x800)) { overflow--; } last_cnt = current_cnt; return overflow * 65536 + current_cnt; }

3. 控制算法实现与优化

3.1 增量式PID控制器设计

增量式PID相比位置式PID更适合电机控制场景,其核心优势在于:

  • 不需要累加误差,减少积分饱和风险
  • 输出为增量形式,更匹配PWM调节需求
  • 算法实现简单,计算量小

算法实现代码:

typedef struct { float Kp; float Ki; float Kd; float last_error; float prev_error; } PID_Handle; float PID_Update(PID_Handle *pid, float error) { float delta = pid->Kp * (error - pid->last_error) + pid->Ki * error + pid->Kd * (error - 2*pid->last_error + pid->prev_error); pid->prev_error = pid->last_error; pid->last_error = error; return delta; }

3.2 动态滤波算法实现

传统固定系数的滤波算法难以兼顾响应速度和平稳性。我们采用自适应滤波系数策略:

#define FILTER_THRESHOLD 50.0f float DynamicFilter(float raw, float last, float *factor) { float delta = fabs(raw - last); if(delta > FILTER_THRESHOLD) { // 快速变化阶段,增大滤波系数 *factor = 0.3f; } else { // 稳定阶段,减小滤波系数 *factor = 0.01f; } return *factor * raw + (1 - *factor) * last; }

参数调试经验:

  1. 先调P参数,使系统能够快速响应但不过冲
  2. 再调I参数,消除稳态误差
  3. 最后调整滤波系数,平衡平滑性和响应速度
  4. 不同转速下可能需要不同的PID参数组合

4. 系统集成与调试技巧

4.1 匿名地面站数据可视化

将调试数据通过串口发送到匿名地面站可以直观观察系统响应:

void SendToGroundStation(float target, float actual) { uint8_t buf[10]; int16_t target_int = (int16_t)(target * 100); int16_t actual_int = (int16_t)(actual * 100); buf[0] = 0xAA; // 帧头 buf[1] = 0x03; // 功能字 buf[2] = target_int >> 8; buf[3] = target_int & 0xFF; buf[4] = actual_int >> 8; buf[5] = actual_int & 0xFF; HAL_UART_Transmit(&huart1, buf, 6, 100); }

4.2 典型问题排查指南

电机不转的可能原因:

  1. TB6612的STBY引脚未拉高
  2. PWM频率设置超出驱动芯片范围
  3. 电机电源电压不足
  4. 硬件保护电路触发

转速波动大的解决方案:

  1. 检查编码器连接是否可靠
  2. 适当降低P参数
  3. 增加滤波系数
  4. 确保电源有足够容量

系统响应慢的优化方向:

  1. 提高控制算法执行频率
  2. 适当增大P参数
  3. 采用动态滤波策略
  4. 检查机械传动是否顺畅

5. 进阶优化方向

5.1 抗饱和积分设计

为防止积分项饱和导致系统失控,可增加积分限幅:

float PID_Update_AntiWindup(PID_Handle *pid, float error, float max_output) { float delta = /* 常规PID计算 */; // 积分限幅 if(fabs(pid->integral) > max_output) { pid->integral = copysignf(max_output, pid->integral); } return delta; }

5.2 参数自整定策略

实现简单的参数自整定流程:

  1. 施加阶跃信号并记录响应曲线
  2. 根据超调量和调节时间自动调整PID参数
  3. 在不同工作点重复上述过程
  4. 建立参数与工作点的映射关系

5.3 多闭环控制架构

对于更高要求的应用,可以考虑:

  • 电流环:提高动态响应
  • 速度环:保证转速精度
  • 位置环:实现精准定位
void Control_Loop(void) { static float current = 0.0f; static float speed = 0.0f; static float position = 0.0f; // 电流环(最内层) current = GetMotorCurrent(); float current_out = Current_PID_Update(target_current, current); // 速度环 speed = GetMotorSpeed(); target_current = Speed_PID_Update(target_speed, speed); // 位置环(最外层) position = GetMotorPosition(); target_speed = Position_PID_Update(target_position, position); }
返回列表