目录
PID控制是什么:
PID控制算法(比例-积分-微分控制)是一种广泛应用于工业控制系统的反馈控制机制。它通过比例(P)、积分(I)和微分(D)三个环节的组合来调节系统输出,使其尽可能接近设定值。
PID控制的公式及其理解:
想象一下,你正在开车去一个目的地。你的目标是让车子稳稳地停在指定的位置。这时候,PID控制器就像是你的大脑,它会根据车子当前的位置、速度以及你离目标的距离,来决定怎么打方向盘、踩油门或者刹车。下面我们把这个过程拆开,看看PID的三个部分是怎么工作的。
1. 比例控制(P)——看距离,快速调整
场景:你离目的地还有一段距离,比如100米。这时候,你肯定会加大油门,让车子快点靠近目标。
作用:比例控制就是根据你离目标的距离来决定怎么操作。距离越远,你踩油门的力度就越大;距离越近,你踩油门的力度就越小。
问题:如果你只靠这个方式,车子可能会在快到目的地时突然减速,甚至停不下来,导致车子在目标位置附近来回晃悠,或者干脆停不到准确的位置。
2. 积分控制(I)——耐心调整,消除误差
场景:你离目的地已经很近了,比如只有1米,但车子就是停不到准确的位置。这时候,你会慢慢调整油门,一点一点地让车子往前挪,直到完全停在目标点。
作用:积分控制就是关注你长时间没停到目标位置的情况。它会慢慢积累误差,然后一点点调整,直到车子完全停在目标位置。
问题:如果调整得太慢,车子可能会花很长时间才停好;如果调整得太快,车子可能会冲过目标点,然后又得倒回来。
3. 微分控制(D)——预判趋势,防止冲过头
场景:你离目的地越来越近,车速也很快。这时候,你会提前松油门或者轻踩刹车,防止车子冲过目标点。
作用:微分控制就是根据车子的速度变化来预判未来的趋势。如果车子正在快速接近目标,它会提前减少油门或者踩刹车,避免车子冲过头。
问题:如果微分控制太敏感,路上有一点小颠簸或者风吹草动,它可能会反应过度,导致车子开得不稳。
把这三个部分结合起来,PID控制器就像是一个经验丰富的司机,能够:
快速接近目标:通过比例控制,迅速缩小距离。
精准停车:通过积分控制,确保车子最终停在准确的位置。
平稳操作:通过微分控制,避免车子冲过头或者来回晃悠。
PID控制的实现:
以stm32控制编码器电机以及姿态传感器实现一个自平衡小车为例
1.初始化外设:
首先,需要初始化 STM32 的各个外设,包括 GPIO、定时器、串口、I2C 等
#include "stm32f1xx_hal.h"
// 定义全局变量
TIM_HandleTypeDef htim3;
I2C_HandleTypeDef hi2c1;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM3_Init(void);
static void MX_I2C1_Init(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM3_Init();
MX_I2C1_Init();
while (1)
{
// 主循环代码
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
static void MX_TIM3_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 0;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 65535;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
}
static void MX_I2C1_Init(void)
{
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000;
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c1) != HAL_OK)
{
Error_Handler();
}
}
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
/*Configure GPIO pin : PA5 */
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
2.读取姿态传感器数据:
使用 I2C 接口读取 MPU6050 的加速度和角速度数据,并通过互补滤波或卡尔曼滤波算法计算出小车的姿态角度。
#include "mpu6050.h"
float angle;
void read_mpu6050(void)
{
int16_t ax, ay, az;
int16_t gx, gy, gz;
MPU6050_ReadAccel(&ax, &ay, &az);
MPU6050_ReadGyro(&gx, &gy, &gz);
// 这里可以添加互补滤波或卡尔曼滤波算法
// 计算出小车的姿态角度
angle = ...;
}
3.实现 PID 控制算法
PID 控制算法是自平衡小车的核心,根据小车的实际姿态角度和目标角度计算出控制量,以调整电机的转速。以下是一个简单的 PID 控制函数:
typedef struct {
float Kp;
float Ki;
float Kd;
float setpoint;
float integral;
float previous_error;
} PID_Controller;
float pid_control(PID_Controller *pid, float current_value)
{
float error = pid->setpoint - current_value;
pid->integral += error;
float derivative = error - pid->previous_error;
float output = pid->Kp * error + pid->Ki * pid->integral + pid->Kd * derivative;
pid->previous_error = error;
return output;
}
4.控制编码器电机
根据 PID 控制算法计算出的控制量,通过 PWM 信号控制电机的转速和方向。以下是一个简单的电机控制函数:
void motor_control(float control_value)
{
if (control_value > 0)
{
// 正转
HAL_GPIO_WritePin(MOTOR_DIR_GPIO_Port, MOTOR_DIR_Pin, GPIO_PIN_SET);
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, (uint32_t)control_value);
}
else
{
// 反转
HAL_GPIO_WritePin(MOTOR_DIR_GPIO_Port, MOTOR_DIR_Pin, GPIO_PIN_RESET);
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, (uint32_t)(-control_value));
}
}
5.主循环
在主循环中,不断读取姿态传感器数据,调用 PID 控制算法计算控制量,然后控制电机转动。
PID_Controller pid;
pid.Kp = 1.0;
pid.Ki = 0.1;
pid.Kd = 0.01;
pid.setpoint = 0;
pid.integral = 0;
pid.previous_error = 0;
while (1)
{
read_mpu6050();
float control_value = pid_control(&pid, angle);
motor_control(control_value);
HAL_Delay(10);
}
PID控制参数的常用调节方法:
积分限幅:
在 PID 控制器中,积分项是对误差在一段时间内的累积。积分限幅指的是对积分项的值进行限制,使其不超过预先设定的上限和下限范围。这是为了避免积分项在某些情况下过度累积,从而对系统的控制性能产生不利影响。
防止积分饱和:当系统存在较大的误差,且持续时间较长时,积分项会不断累积,导致积分项的值变得非常大。这种情况被称为积分饱和。积分饱和会使系统在误差消除后,仍然输出一个较大的控制量,从而导致系统出现超调、振荡甚至不稳定的现象。通过积分限幅,可以限制积分项的最大值,避免积分饱和的发生。
改善系统动态响应:合理的积分限幅可以减少系统的超调,加快系统的响应速度,提高系统的动态性能。例如,在一些需要快速响应的控制系统中,通过积分限幅可以避免积分项的过度累积,使系统能够更快地跟踪设定值。
增强系统稳定性:积分限幅可以使系统在受到干扰或设定值变化时,保持稳定的控制输出。当系统受到干扰时,积分项可能会迅速增大,如果不进行限幅,可能会导致系统失控。通过积分限幅,可以限制积分项的变化范围,增强系统的抗干扰能力和稳定性。
积分限幅实现方式:
积分限幅的实现方式比较简单,通常在计算积分项时,对积分项的值进行判断,如果超出了设定的上限或下限,则将其限制在该范围内。具体步骤如下:在每次计算积分项时,先将当前误差乘以积分系数 Ki,并累加到积分项上。检查积分项的值是否超出了设定的上限或下限。如果积分项的值超出了上限,则将其设置为上限值;如果积分项的值低于下限,则将其设置为下限值。
积分分离:
在传统的 PID 控制中,积分环节的作用是消除系统的稳态误差。然而,在系统偏差较大时,积分作用会使得积分项快速累积,可能导致系统出现较大的超调,甚至引起系统不稳定。积分分离的基本思想是:当系统误差绝对值 ∣e(t)∣ 大于某个预先设定的阈值 β 时,取消积分作用,即令积分项为零,此时控制器相当于一个 PD 控制器,利用比例和微分作用快速响应误差,以减少超调;当系统误差绝对值 ∣e(t)∣ 小于等于阈值 β 时,引入积分作用,以消除系统的稳态误差,使系统能够准确地跟踪设定值。
积分分离的实现方式:
设定阈值 β:根据系统的特性和控制要求,确定一个合适的误差阈值 β。这个阈值需要通过实验或经验来确定,一般要综合考虑系统的响应速度和超调量。
计算误差 e(t):在每个控制周期内,计算设定值 r(t) 与实际输出值 y(t) 之间的误差 e(t)=r(t)−y(t)。
判断误差绝对值与阈值的大小关系
输出控制量:将计算得到的控制量 u(t) 输出到执行机构,对系统进行控制。
微分先行:
在传统的 PID 控制中,微分项是对误差信号进行微分运算,误差信号是设定值与实际输出值的差值。当设定值发生突变时,误差信号也会瞬间产生较大变化,经过微分运算后会使微分项输出一个很大的冲击值,这可能导致系统输出出现较大的超调,甚至使系统不稳定。微分先行的基本思想是将微分作用仅施加于系统的实际输出信号上,而不作用于设定值。这样,当设定值发生变化时,不会因为设定值的突变而使微分项产生过大的输出,从而减少了系统的超调,改善了系统的动态性能。
微分先行的实现方式:
输出微分先行:只对系统的实际输出进行微分运算,而对设定值不进行微分。此时,PID 控制器的输出表达式为:
其中,u(t) 是控制器的输出,e(t)=r(t)−y(t) 是误差信号,r(t) 是设定值,y(t) 是系统的实际输出,Kp、Ki、Kd 分别是比例、积分、微分系数。
给定微分先行:对设定值进行微分处理,但通常会经过一定的滤波等操作来避免突变影响。不过在实际应用中输出微分先行更为常见。