STM32F103三环PID双电机伺服控制实战:从原理到代码实现

STM32F103三环PID控制双电机协同
于 2026-07-08 05:12:15 修改
·本内容遵循CC 4.0 BY-SA版权协议

在嵌入式电机控制项目中,双电机协同工作一直是工程师面临的挑战之一。传统单电机控制方案难以满足精密传动、机器人关节等场景对同步精度的要求,而基于STM32F103的三环PID控制架构能有效解决这一问题。本文将完整呈现从HAL库环境搭建到双电机伺服控制的全流程实战,包含可复用的代码模块与参数整定技巧,适合有一定STM32基础的开发者深入掌握工业级电机控制技术。

1. 伺服控制核心概念解析

1.1 什么是伺服控制系统

伺服控制系统是一种能够精确跟随输入指令的自动控制系统,其核心特征在于闭环反馈机制。系统通过持续检测被控对象(如电机)的实际状态,与期望目标值进行比较,进而实时调整控制输出以消除偏差。在电机控制领域,伺服系统通常包含位置环、速度环和电流环三个闭环控制层级,形成经典的三环控制结构。

1.2 PID控制原理与三环架构

PID控制器由比例(P)、积分(I)、微分(D)三个环节组成,分别对应系统偏差的当前状态、历史累积和未来趋势。三环控制架构将三个PID控制器串联使用,内环响应速度最快,外环控制精度最高:

  • 电流环(最内环):响应时间在微秒级,负责控制电机转矩,保证快速动态响应
  • 速度环(中间环):建立在电流环基础上,控制电机转速,抑制负载扰动
  • 位置环(最外环):响应最慢但精度最高,实现最终的位置定位控制

1.3 STM32F103在电机控制中的优势

STM32F103C8T6作为经典的Cortex-M3内核微控制器,具备72MHz主频、硬件乘除法器和丰富的外设资源,特别适合电机控制应用:

  • 内置高级定时器(TIM1/TIM8)支持互补PWM输出和死区控制
  • 12位ADC可实时采样电机电流和位置反馈
  • 编码器接口模式直接读取光电编码器信号
  • HAL库提供统一的硬件抽象层,简化开发流程

2. 开发环境搭建与工程配置

2.1 软件工具准备

本项目开发需要以下软件环境,建议使用相同版本以避免兼容性问题:

  • IDE:STM32CubeIDE v1.11.0(集成STM32CubeMX代码生成功能)
  • 固件库:STM32F1 HAL库 v1.8.4
  • 编译工具链:ARM GCC embedded 10.3-2021.10
  • 调试工具:ST-LINK V2编程器/调试器

2.2 新建STM32CubeIDE工程

打开STM32CubeIDE,按照以下步骤创建基础工程框架:

  1. 选择File → New → STM32 Project
  2. 在MCU/MPU Selector中输入STM32F103C8,选择STM32F103C8Tx
  3. 设置项目名称:Dual_Motor_PID_Control
  4. 选择工程存储路径,确保路径不含中文和特殊字符
  5. 选择IDE为STM32CubeIDE,工具链为STM32Cube

关键配置完成后,STM32CubeMX图形化界面将自动打开,进入外设配置阶段。

2.3 时钟树配置

时钟配置直接影响PWM频率和ADC采样精度,按照以下参数设置:

C
// 系统时钟配置
HSE频率:8MHz
PLL倍频系数:9
系统时钟频率:72MHz
AHB预分频器:1
APB1预分频器:2(36MHz)
APB2预分频器:1(72MHz)
 
// 定时器时钟
TIM1(APB2):72MHz
TIM2(APB1):72MHz(因APB1预分频器≠1,定时器时钟×2)
TIM3(APB1):72MHz
TIM4(APB1):72MHz

在Clock Configuration标签页中,依次设置以上参数,确保系统时钟树显示为绿色(无配置冲突)。

2.4 关键外设初始化

通过STM32CubeMX图形界面配置以下外设:

GPIO配置:

  • 电机1 PWM:PA8(TIM1_CH1),PA9(TIM1_CH2),PA10(TIM1_CH3)
  • 电机2 PWM:PB6(TIM4_CH1),PB7(TIM4_CH2),PB8(TIM4_CH3)
  • 编码器接口:PA0(TIM2_CH1),PA1(TIM2_CH2)用于电机1
  • 电流采样:PA4(ADC1_IN4),PA5(ADC1_IN5)用于双电机电流检测

定时器配置:

C
// TIM1配置(电机1 PWM)
时钟源:内部时钟
通道1/2/3:PWM Generation CH1/CH2/CH3
预分频器:71(1MHz计数频率)
自动重载值:999(1kHz PWM频率)
Pulse初始值:0(占空比0%)
 
// TIM2配置(电机1编码器)
编码器模式:Encoder Mode TI1 and TI2
预分频器:0(72MHz计数频率)
自动重载值:65535(16位最大值)
 
// TIM4配置(电机2 PWM)
配置同TIM1,使用不同GPIO引脚

ADC配置:

  • 模式:独立模式
  • 扫描转换模式:启用
  • 连续转换模式:启用
  • 触发源:定时器触发(TIM1_TRGO)
  • 采样时间:239.5周期(提高采样精度)

完成图形化配置后,点击Generate Code生成完整的HAL库工程框架。

3. PID控制算法实现

3.1 增量式PID算法设计

与传统位置式PID相比,增量式算法更适合电机控制,避免积分饱和问题:

C
// pid_controller.h 头文件定义
# ifndef __PID_CONTROLLER_H
# define __PID_CONTROLLER_H
 
typedef struct {
float Kp; // 比例系数
float Ki; // 积分系数
float Kd; // 微分系数
float integral; // 积分项累计
float prev_error; // 上一次误差
float output; // 控制器输出
float output_max; // 输出上限
float output_min; // 输出下限
} PID_Controller;
 
void PID_Init(PID_Controller* pid, float kp, float ki, float kd, float max, float min);
float PID_Calculate(PID_Controller* pid, float setpoint, float feedback);
 
# endif

3.2 PID核心算法实现

C
// pid_controller.c 源文件实现
# include "pid_controller.h"
 
void PID_Init(PID_Controller* pid, float kp, float ki, float kd, float max, float min) {
pid->Kp = kp;
pid->Ki = ki;
pid->Kd = kd;
pid->integral = 0.0f;
pid->prev_error = 0.0f;
pid->output = 0.0f;
pid->output_max = max;
pid->output_min = min;
}
 
float PID_Calculate(PID_Controller* pid, float setpoint, float feedback) {
float error = setpoint - feedback;
// 比例项
float proportional = pid->Kp * error;
// 积分项(带抗饱和处理)
pid->integral += error;
if (pid->integral > pid->output_max) pid->integral = pid->output_max;
if (pid->integral < pid->output_min) pid->integral = pid->output_min;
float integral_term = pid->Ki * pid->integral;
// 微分项
float derivative = pid->Kd * (error - pid->prev_error);
pid->prev_error = error;
// 输出计算和限幅
pid->output = proportional + integral_term + derivative;
if (pid->output > pid->output_max) pid->output = pid->output_max;
if (pid->output < pid->output_min) pid->output = pid->output_min;
return pid->output;
}

3.3 三环控制器结构体设计

为双电机系统设计完整的三环控制结构:

C
// motor_control.h
typedef struct {
// 三个PID控制器
PID_Controller current_pid; // 电流环
PID_Controller speed_pid; // 速度环
PID_Controller position_pid; // 位置环
// 电机状态反馈
float current_feedback; // 电流反馈值
float speed_feedback; // 速度反馈值
float position_feedback; // 位置反馈值
// 控制目标
float current_target; // 电流目标
float speed_target; // 速度目标
float position_target; // 位置目标
// PWM输出相关
uint32_t pwm_channel[3]; // 三相PWM通道
TIM_HandleTypeDef* pwm_tim; // PWM定时器句柄
} Motor_Controller;
 
// 双电机控制器实例
extern Motor_Controller motor1, motor2;

4. 双电机控制完整实现

4.1 电机控制初始化函数

在main.c中实现完整的初始化流程:

C
// 全局变量定义
Motor_Controller motor1, motor2;
 
void Motor_Control_Init(void) {
// 电机1三环PID参数初始化
PID_Init(&motor1.current_pid, 1.5f, 0.2f, 0.01f, 1000.0f, -1000.0f);
PID_Init(&motor1.speed_pid, 0.8f, 0.1f, 0.05f, 500.0f, -500.0f);
PID_Init(&motor1.position_pid, 2.0f, 0.05f, 0.1f, 100.0f, -100.0f);
// 电机2三环PID参数初始化
PID_Init(&motor2.current_pid, 1.5f, 0.2f, 0.01f, 1000.0f, -1000.0f);
PID_Init(&motor2.speed_pid, 0.8f, 0.1f, 0.05f, 500.0f, -500.0f);
PID_Init(&motor2.position_pid, 2.0f, 0.05f, 0.1f, 100.0f, -100.0f);
// 关联PWM定时器
motor1.pwm_tim = &htim1;
motor2.pwm_tim = &htim4;
// 启动PWM和编码器定时器
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_3);
HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);
HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
}

4.2 实时控制任务函数

在定时器中断中执行核心控制算法:

C
// 1kHz控制频率的中断服务函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM6) { // 基础定时器TIM6产生1kHz中断
// 电机1三环控制计算
Motor_Control_Update(&motor1);
// 电机2三环控制计算
Motor_Control_Update(&motor2);
}
}
 
// 单电机控制更新函数
void Motor_Control_Update(Motor_Controller* motor) {
// 位置环计算(最外环)
motor->speed_target = PID_Calculate(&motor->position_pid,
motor->position_target,
motor->position_feedback);
// 速度环计算(中间环)
motor->current_target = PID_Calculate(&motor->speed_pid,
motor->speed_target,
motor->speed_feedback);
// 电流环计算(最内环)
float pwm_output = PID_Calculate(&motor->current_pid,
motor->current_target,
motor->current_feedback);
// PWM输出限幅和设置
if (pwm_output > 1000) pwm_output = 1000;
if (pwm_output < -1000) pwm_output = -1000;
// 根据正负值设置PWM占空比方向
if (pwm_output >= 0) {
// 正转:CH1和CH2互补输出,CH3关闭
__HAL_TIM_SET_COMPARE(motor->pwm_tim, TIM_CHANNEL_1, (uint32_t)pwm_output);
__HAL_TIM_SET_COMPARE(motor->pwm_tim, TIM_CHANNEL_2, 0);
__HAL_TIM_SET_COMPARE(motor->pwm_tim, TIM_CHANNEL_3, 0);
} else {
// 反转:CH2和CH3互补输出,CH1关闭
__HAL_TIM_SET_COMPARE(motor->pwm_tim, TIM_CHANNEL_1, 0);
__HAL_TIM_SET_COMPARE(motor->pwm_tim, TIM_CHANNEL_2, (uint32_t)(-pwm_output));
__HAL_TIM_SET_COMPARE(motor->pwm_tim, TIM_CHANNEL_3, 0);
}
}

4.3 反馈信号处理

编码器位置和电流采样需要实时处理:

C
// 编码器位置读取函数
int32_t Read_Encoder(TIM_HandleTypeDef* htim) {
int32_t count = (int32_t)__HAL_TIM_GET_COUNTER(htim);
__HAL_TIM_SET_COUNTER(htim, 0); // 读取后清零
return count;
}
 
// 速度计算函数(在定时中断中调用)
void Speed_Calculate(Motor_Controller* motor, TIM_HandleTypeDef* encoder_tim) {
static int32_t last_position[2] = {0, 0};
static uint8_t motor_index = 0;
int32_t current_position = Read_Encoder(encoder_tim);
int32_t position_delta = current_position - last_position[motor_index];
// 速度计算:编码器脉冲/采样周期
motor->speed_feedback = (float)position_delta / 0.001f; // 1kHz采样频率
last_position[motor_index] = current_position;
motor->position_feedback += position_delta; // 累计位置
motor_index = (motor_index + 1) % 2;
}
 
// ADC电流采样处理
void Current_Sampling_Handler(void) {
// ADC DMA传输完成中断中处理
uint16_t adc_value1 = adc_buffer[0]; // 电机1电流采样
uint16_t adc_value2 = adc_buffer[1]; // 电机2电流采样
// ADC值转换为实际电流值(根据采样电阻和运放增益)
motor1.current_feedback = (float)(adc_value1 - 2048) * 3.3f / 4096.0f / 0.1f; // 0.1Ω采样电阻
motor2.current_feedback = (float)(adc_value2 - 2048) * 3.3f / 4096.0f / 0.1f;
}

5. PID参数整定与系统调试

5.1 三环PID参数整定顺序

伺服系统参数整定必须从内环到外环逐级进行:

  1. 电流环整定:断开速度环和位置环,让电机堵转,调节P参数使电流响应快速无超调
  2. 速度环整定:固定电流环参数,给定阶跃速度信号,调节P参数使转速平稳跟踪
  3. 位置环整定:最后整定位置环,通常P参数较小,避免超调和振荡

5.2 实际整定示例参数

根据实验经验,以下参数可作为初始调试参考:

C
// 直流有刷电机典型参数(需根据实际电机调整)
// 电流环:响应最快,P大I小
float current_kp = 1.5f, current_ki = 0.2f, current_kd = 0.01f;
 
// 速度环:中等响应,P适中I适中
float speed_kp = 0.8f, speed_ki = 0.1f, speed_kd = 0.05f;
 
// 位置环:响应最慢,P小I小
float position_kp = 2.0f, position_ki = 0.05f, position_kd = 0.1f;

5.3 调试技巧与注意事项

  • 使用STM32CubeIDE的实时变量观察功能监控关键参数
  • 通过串口打印实时数据,使用Python matplotlib绘制响应曲线
  • 先单电机调试,稳定后再进行双电机同步控制
  • 注意PWM死区时间设置,防止上下桥臂直通
  • 电流采样需要硬件滤波,软件可增加移动平均滤波

6. 常见问题与解决方案

6.1 电机控制异常排查

问题现象 可能原因 解决方案
电机不转,无反应 PWM输出配置错误 检查定时器时钟使能、PWM通道配置
电机振动严重 PID参数不匹配 从内环开始重新整定参数,降低P值
只能单向转动 方向逻辑错误 检查PWM互补输出逻辑和死区设置
转速不稳定 编码器干扰 增加编码器硬件滤波,检查接线屏蔽
电流采样异常 ADC基准电压不稳 检查电源质量,增加参考电压滤波

6.2 HAL库使用常见问题

问题1:定时器中断不触发

C
// 解决方案:检查中断优先级配置
HAL_NVIC_SetPriority(TIM6_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM6_IRQn);

问题2:ADC采样值跳动大

C
// 解决方案:增加软件滤波
# define FILTER_DEPTH 8
uint16_t adc_filter_buffer[FILTER_DEPTH];
uint16_t ADC_Filter(uint16_t new_value) {
static uint8_t index = 0;
adc_filter_buffer[index] = new_value;
index = (index + 1) % FILTER_DEPTH;
uint32_t sum = 0;
for(int i = 0; i < FILTER_DEPTH; i++) {
sum += adc_filter_buffer[i];
}
return sum / FILTER_DEPTH;
}

问题3:双电机同步精度差

  • 检查两个编码器的安装同心度
  • 确保两个电机的机械负载特性一致
  • 分别整定两套PID参数,考虑电机个体差异

7. 性能优化与进阶功能

7.1 抗积分饱和改进

标准PID在长期偏差时会出现积分饱和,改进算法:

C
// 抗积分饱和PID实现
float PID_Calculate_AntiWindup(PID_Controller* pid, float setpoint, float feedback) {
float error = setpoint - feedback;
// 条件积分:仅当输出未饱和时进行积分
float output_temp = pid->Kp * error + pid->integral;
if (output_temp < pid->output_max && output_temp > pid->output_min) {
pid->integral += pid->Ki * error;
}
// 微分项和输出计算
float derivative = pid->Kd * (error - pid->prev_error);
pid->prev_error = error;
pid->output = pid->Kp * error + pid->integral + derivative;
// 输出限幅
if (pid->output > pid->output_max) pid->output = pid->output_max;
if (pid->output < pid->output_min) pid->output = pid->output_min;
return pid->output;
}

7.2 前馈控制增强

在PID基础上加入前馈控制,提高响应速度:

C
// 前馈+PID复合控制
typedef struct {
PID_Controller pid;
float Kf; // 前馈系数
} FeedForward_PID_Controller;
 
float FeedForward_PID_Calculate(FeedForward_PID_Controller* ff_pid,
float setpoint, float feedback, float derivative_target) {
// 前馈项:目标变化率的预测补偿
float feedforward = ff_pid->Kf * derivative_target;
// PID反馈项
float pid_output = PID_Calculate(&ff_pid->pid, setpoint, feedback);
return feedforward + pid_output;
}

7.3 自适应PID参数

根据系统运行状态自动调整PID参数:

C
// 模糊自适应PID简化实现
void Adaptive_PID_Tuning(PID_Controller* pid, float error, float error_change) {
// 根据误差和误差变化率调整参数
if (fabs(error) > 10.0f) {
// 大误差区间:强P弱I
pid->Kp = 2.0f;
pid->Ki = 0.01f;
} else if (fabs(error) > 1.0f) {
// 中等误差:适中参数
pid->Kp = 1.0f;
pid->Ki = 0.1f;
} else {
// 小误差:弱P强I提高精度
pid->Kp = 0.5f;
pid->Ki = 0.2f;
}
}

本实验完整实现了基于STM32F103 HAL库的双电机三环PID伺服控制系统,涵盖了从硬件配置到算法优化的全流程。实际应用中可根据具体电机特性调整PID参数,结合机械系统特性进一步优化控制性能。这种架构可扩展至四轴机器人、CNC机床等需要多轴协同的精密控制场景。