大家好,我是麦鸽,今天推荐一个PID开源库。

PID控制原理

PID(比例-积分-微分)是一种闭环控制算法,通过误差信号动态调整输出:

  • 比例(P):实时误差的比例放大,快速响应但易振荡。

  • 积分(I):累积历史误差,消除静态偏差,但可能过调。

  • 微分(D):预测误差变化趋势,抑制超调和振荡。

PID
PID

单片机应用场景

适用于需精准控制的嵌入式系统,如:电机调速、温度控制、平衡车姿态控制等。传感器采集实时数据,单片机计算PID输出并驱动执行器(如PWM信号控制加热器或电机)。

关键要点

  1. 采样周期:定时中断中调用PID计算,周期需固定(例如1ms)。

  2. 参数整定:先调P,再加I消除静差,最后加D抑制振荡。

  3. 抗积分饱和:限制integral范围,避免系统过调。

  4. 输出限幅:将返回值约束到执行器有效范围(如PWM占空比0-100%)。

调试建议:通过串口输出误差曲线,观察响应速度与稳定性,逐步优化参数。实际应用需考虑噪声滤波、离散化精度等问题。

PID 库

项目地址:https://github.com/geekfactory/PID

项目首页
项目首页

以浮点运算实现的 PID 控制库,有浮点运算单元的单片机会比较舒服。

以下代码描述了库的基本用法,输入和输出功能应由最终用户提供。

#include "PID.h"

// Structure to strore PID data and pointer to PID structure
struct pid_controller ctrldata;
pid_t pid;

// Control loop input,output and setpoint variables
float input = 0, output = 0;
float setpoint = 15;

// Control loop gains
float kp = 2.5, ki = 1.0, kd = 1.0;

void main()
{
 // Prepare PID controller for operation
 pid = pid_create(&ctrldata, &input, &output, &setpoint, kp, ki, kd);
 // Set controler output limits from 0 to 200
 pid_limits(pid, 0, 200);
 // Allow PID to compute and change output
 pid_auto(pid);

 // MAIN CONTROL LOOP
 for (;;) {
  // Check if need to compute PID
  if (pid_need_compute(pid)) {
   // Read process feedback
   input = process_input();
   // Compute new PID output value
   pid_compute(pid);
   //Change actuator value
   process_output(output);
  }
 }
}

PID.H

/* Floating point PID control loop for Microcontrollers
 Copyright (C) 2014 Jesus Ruben Santa Anna Zamudio.

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.

 Author website: http://www.geekfactory.mx
 Author e-mail: ruben at geekfactory dot mx
 */
#ifndef PID_H
#define PID_H
/*-------------------------------------------------------------*/
/*  Includes and dependencies   */
/*-------------------------------------------------------------*/
#include "Tick/Tick.h"
#include <stdbool.h>
#include <stdint.h>

/*-------------------------------------------------------------*/
/*  Macros and definitions    */
/*-------------------------------------------------------------*/

/*-------------------------------------------------------------*/
/*  Typedefs enums & structs   */
/*-------------------------------------------------------------*/

/**
 * Defines if the controler is direct or reverse
 */
enum pid_control_directions {
 E_PID_DIRECT,
 E_PID_REVERSE,
};

/**
 * Structure that holds PID all the PID controller data, multiple instances are
 * posible using different structures for each controller
 */
struct pid_controller {
 // Input, output and setpoint
 float * input; //!< Current Process Value
 float * output; //!< Corrective Output from PID Controller
 float * setpoint; //!< Controller Setpoint
 // Tuning parameters
 float Kp; //!< Stores the gain for the Proportional term
 float Ki; //!< Stores the gain for the Integral term
 float Kd; //!< Stores the gain for the Derivative term
 // Output minimum and maximum values
 float omin; //!< Maximum value allowed at the output
 float omax; //!< Minimum value allowed at the output
 // Variables for PID algorithm
 float iterm; //!< Accumulator for integral term
 float lastin; //!< Last input value for differential term
 // Time related
 uint32_t lasttime; //!< Stores the time when the control loop ran last time
 uint32_t sampletime; //!< Defines the PID sample time
 // Operation mode
 uint8_t automode; //!< Defines if the PID controller is enabled or disabled
 enum pid_control_directions direction;
};

typedef struct pid_controller * pid_t;

/*-------------------------------------------------------------*/
/*  Function prototypes    */
/*-------------------------------------------------------------*/
#ifdef __cplusplus
extern "C" {
#endif
 /**
  * @brief Creates a new PID controller
  *
  * Creates a new pid controller and initializes its input, output and internal
  * variables. Also we set the tuning parameters
  *
  * @param pid A pointer to a pid_controller structure
  * @param in Pointer to float value for the process input
  * @param out Poiter to put the controller output value
  * @param set Pointer float with the process setpoint value
  * @param kp Proportional gain
  * @param ki Integral gain
  * @param kd Diferential gain
  *
  * @return returns a pid_t controller handle
  */
 pid_t pid_create(pid_t pid, float* in, float* out, float* set, float kp, float ki, float kd);

 /**
  * @brief Check if PID loop needs to run
  *
  * Determines if the PID control algorithm should compute a new output value,
  * if this returs true, the user should read process feedback (sensors) and
  * place the reading in the input variable, then call the pid_compute() function.
  *
  * @return return Return true if PID control algorithm is required to run
  */
 bool pid_need_compute(pid_t pid);

 /**
  * @brief Computes the output of the PID control
  *
  * This function computes the PID output based on the parameters, setpoint and
  * current system input.
  *
  * @param pid The PID controller instance which will be used for computation
  */
 void pid_compute(pid_t pid);

 /**
  * @brief Sets new PID tuning parameters
  *
  * Sets the gain for the Proportional (Kp), Integral (Ki) and Derivative (Kd)
  * terms.
  *
  * @param pid The PID controller instance to modify
  * @param kp Proportional gain
  * @param ki Integral gain
  * @param kd Derivative gain
  */
 void pid_tune(pid_t pid, float kp, float ki, float kd);

 /**
  * @brief Sets the pid algorithm period
  *
  * Changes the between PID control loop computations.
  *
  * @param pid The PID controller instance to modify
  * @param time The time in milliseconds between computations
  */
 void pid_sample(pid_t pid, uint32_t time);

 /**
  * @brief Sets the limits for the PID controller output
  *
  * @param pid The PID controller instance to modify
  * @param min The minimum output value for the PID controller
  * @param max The maximum output value for the PID controller
  */
 void pid_limits(pid_t pid, float min, float max);

 /**
  * @brief Enables automatic control using PID
  *
  * Enables the PID control loop. If manual output adjustment is needed you can
  * disable the PID control loop using pid_manual(). This function enables PID
  * automatic control at program start or after calling pid_manual()
  *
  * @param pid The PID controller instance to enable
  */
 void pid_auto(pid_t pid);

 /**
  * @brief Disables automatic process control
  *
  * Disables the PID control loop. User can modify the value of the output
  * variable and the controller will not overwrite it.
  *
  * @param pid The PID controller instance to disable
  */
 void pid_manual(pid_t pid);

 /**
  * @brief Configures the PID controller direction
  *
  * Sets the direction of the PID controller. The direction is "DIRECT" when a
  * increase of the output will cause a increase on the measured value and
  * "REVERSE" when a increase on the controller output will cause a decrease on
  * the measured value.
  *
  * @param pid The PID controller instance to modify
  * @param direction The new direction of the PID controller
  */
 void pid_direction(pid_t pid, enum pid_control_directions dir);

#ifdef __cplusplus
}
#endif

#endif
// End of Header file

下面我们可以看一下PID.C,是如何实现的?

#include "PID.h"

pid_t pid_create(pid_t pid, float* in, float* out, float* set, float kp, float ki, float kd)
{
 pid->input = in;
 pid->output = out;
 pid->setpoint = set;
 pid->automode = false;

 pid_limits(pid, 0, 255);

 // Set default sample time to 100 ms
 pid->sampletime = 100 * (TICK_SECOND / 1000);

 pid_direction(pid, E_PID_DIRECT);
 pid_tune(pid, kp, ki, kd);

 pid->lasttime = tick_get() - pid->sampletime;

 return pid;
}

bool pid_need_compute(pid_t pid)
{
 // Check if the PID period has elapsed
 return(tick_get() - pid->lasttime >= pid->sampletime) ? true : false;
}

void pid_compute(pid_t pid)
{
 // Check if control is enabled
 if (!pid->automode)
  return false;
 
 float in = *(pid->input);
 // Compute error
 float error = (*(pid->setpoint)) - in;
 // Compute integral
 pid->iterm += (pid->Ki * error);
 if (pid->iterm > pid->omax)
  pid->iterm = pid->omax;
 else if (pid->iterm < pid->omin)
  pid->iterm = pid->omin;
 // Compute differential on input
 float dinput = in - pid->lastin;
 // Compute PID output
 float out = pid->Kp * error + pid->iterm - pid->Kd * dinput;
 // Apply limit to output value
 if (out > pid->omax)
  out = pid->omax;
 else if (out < pid->omin)
  out = pid->omin;
 // Output to pointed variable
 (*pid->output) = out;
 // Keep track of some variables for next execution
 pid->lastin = in;
 pid->lasttime = tick_get();;
}

void pid_tune(pid_t pid, float kp, float ki, float kd)
{
 // Check for validity
 if (kp < 0 || ki < 0 || kd < 0)
  return;
 
 //Compute sample time in seconds
 float ssec = ((float) pid->sampletime) / ((float) TICK_SECOND);

 pid->Kp = kp;
 pid->Ki = ki * ssec;
 pid->Kd = kd / ssec;

 if (pid->direction == E_PID_REVERSE) {
  pid->Kp = 0 - pid->Kp;
  pid->Ki = 0 - pid->Ki;
  pid->Kd = 0 - pid->Kd;
 }
}

void pid_sample(pid_t pid, uint32_t time)
{
 if (time > 0) {
  float ratio = (float) (time * (TICK_SECOND / 1000)) / (float) pid->sampletime;
  pid->Ki *= ratio;
  pid->Kd /= ratio;
  pid->sampletime = time * (TICK_SECOND / 1000);
 }
}

void pid_limits(pid_t pid, float min, float max)
{
 if (min >= max) return;
 pid->omin = min;
 pid->omax = max;
 //Adjust output to new limits
 if (pid->automode) {
  if (*(pid->output) > pid->omax)
   *(pid->output) = pid->omax;
  else if (*(pid->output) < pid->omin)
   *(pid->output) = pid->omin;

  if (pid->iterm > pid->omax)
   pid->iterm = pid->omax;
  else if (pid->iterm < pid->omin)
   pid->iterm = pid->omin;
 }
}

void pid_auto(pid_t pid)
{
 // If going from manual to auto
 if (!pid->automode) {
  pid->iterm = *(pid->output);
  pid->lastin = *(pid->input);
  if (pid->iterm > pid->omax)
   pid->iterm = pid->omax;
  else if (pid->iterm < pid->omin)
   pid->iterm = pid->omin;
  pid->automode = true;
 }
}

void pid_manual(pid_t pid)
{
 pid->automode = false;
}

void pid_direction(pid_t pid, enum pid_control_directions dir)
{
 if (pid->automode && pid->direction != dir) {
  pid->Kp = (0 - pid->Kp);
  pid->Ki = (0 - pid->Ki);
  pid->Kd = (0 - pid->Kd);
 }
 pid->direction = dir;
}

代码中有个宏定义是引用了其他的git模块,可以同步参考一下代码; github.com/geekfactory/Tick

最后

预告一下,下期准备抽奖送书,具体如下:

书籍封面

前几期送书,有小伙伴反馈没有及时收到消息反馈;

FPGA的书
FPGA的书
无人机书籍
无人机书籍
Linux书
Linux书

现在公众号改了推送机制,记得关注并星标,才能不迷路。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐