PID控制算法

目录

PID控制是什么:

PID控制的公式及其理解:

PID控制的实现:

PID控制参数的常用调节方法:

积分限幅:

积分限幅实现方式:

积分分离:

积分分离的实现方式:

微分先行:

微分先行的实现方式:

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​ 分别是比例、积分、微分系数。

给定微分先行:对设定值进行微分处理,但通常会经过一定的滤波等操作来避免突变影响。不过在实际应用中输出微分先行更为常见。

<think>好的,用户需要关于PID控制器中积分限幅的代码实现示例。首先,我需要回忆PID控制的基本结构和积分限幅的作用。积分限幅主要是为了防止积分项饱和,避免系统出现大的超调或振荡。 接下来,我应该查找相关的引用资料。用户提供了三个引用,其中引用[2]提到了抗积分饱和的代码实现和MATLAB仿真,引用[3]讨论了C语言PID温度控制代码的参数调整和保护措施。这些应该是主要参考。 根据引用[2],Microchip的AN1078文档中的PI框图可能有帮助。通常,积分限幅会在积分项更新时检查是否超过设定的上下限,如果超过则限制积分值。代码中需要维护积分项,并在每次更新时应用限幅。 引用[3]提到调整Ki值影响稳态误差,但积分限幅可能还需要在代码中添加保护措施,比如限制积分累积的最大最小值。结合这些信息,我需要构造一个C语言的示例,包含积分项的限制。 需要注意代码的结构,包括错误计算、积分累积、积分限幅、微分项计算,最后综合输出。同时,输出限幅也是常见的做法,防止执行机构过载。 然后,要确保代码符合用户的要求,使用正确的变量名,并添加注释说明积分限幅的部分。同时,可能需要引用相关的资料来支持实现的正确性。 最后,生成相关问题,帮助用户深入理解积分限幅的应用和优化方法。比如积分分离、抗饱和的其他方法等。</think>### PID控制器积分限幅代码实现示例 在PID控制中,积分限幅主要用于防止积分项持续累积导致系统超调或振荡。以下是C语言实现示例: ```c typedef struct { float Kp, Ki, Kd; // PID参数 float integral; // 积分累积值 float i_max; // 积分上限 float i_min; // 积分下限 float prev_error; // 上一次误差 } PIDController; float PID_Update(PIDController *pid, float setpoint, float measurement) { // 计算误差 float error = setpoint - measurement; // 比例项 float P = pid->Kp * error; // 积分项(带限幅pid->integral += error; if (pid->integral > pid->i_max) { pid->integral = pid->i_max; } else if (pid->integral < pid->i_min) { pid->integral = pid->i_min; } float I = pid->Ki * pid->integral; // 微分项 float D = pid->Kd * (error - pid->prev_error); pid->prev_error = error; // 输出限幅(0-100%) float output = P + I + D; return (output > 100) ? 100 : (output < 0) ? 0 : output; } ``` #### 关键实现要点: 1. **积分累积限制**:通过`i_max`和`i_min`约束积分项的累积范围,避免"积分饱和"现象[^2] 2. **输出限幅**:最终输出限制在0-100%范围,适用于执行机构(如PWM)的物理限制[^3] 3. **微分项处理**:采用后向差分法计算微分,避免直接使用测量值噪声 #### 典型应用场景: - 温度控制系统(加热器功率控制) - 电机转速控制 - 压力/流量调节系统
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vizio<

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值