PID学习太痛苦?这个开源库让你效率翻倍!
适用于需精准控制的嵌入式系统,如:电机调速、温度控制、平衡车姿态控制等。:通过串口输出误差曲线,观察响应速度与稳定性,逐步优化参数。以浮点运算实现的 PID 控制库,有浮点运算单元的单片机会比较舒服。:定时中断中调用PID计算,周期需固定(例如1ms)。现在公众号改了推送机制,记得关注并星标,才能不迷路。大家好,我是麦鸽,今天推荐一个PID开源库。:先调P,再加I消除静差,最后加D抑制振荡。:累积
大家好,我是麦鸽,今天推荐一个PID开源库。
PID控制原理
PID(比例-积分-微分)是一种闭环控制算法,通过误差信号动态调整输出:
-
比例(P):实时误差的比例放大,快速响应但易振荡。
-
积分(I):累积历史误差,消除静态偏差,但可能过调。
-
微分(D):预测误差变化趋势,抑制超调和振荡。
单片机应用场景
适用于需精准控制的嵌入式系统,如:电机调速、温度控制、平衡车姿态控制等。传感器采集实时数据,单片机计算PID输出并驱动执行器(如PWM信号控制加热器或电机)。
关键要点
-
采样周期:定时中断中调用PID计算,周期需固定(例如1ms)。
-
参数整定:先调P,再加I消除静差,最后加D抑制振荡。
-
抗积分饱和:限制integral范围,避免系统过调。
-
输出限幅:将返回值约束到执行器有效范围(如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
最后
预告一下,下期准备抽奖送书,具体如下:

前几期送书,有小伙伴反馈没有及时收到消息反馈;
现在公众号改了推送机制,记得关注并星标,才能不迷路。

更多推荐


所有评论(0)