PID学习太痛苦?这个开源库让你效率翻倍!

面包板社区 2025-08-08 16:59

大家好,我是板哥,最近社区上线了【硬核玩电·创意DIY】活动,欢迎各位电子界朋友们,以电子为笔,以创意为墨来社区交个朋友。

本次活动参加即有奖励。更有开发板大礼包(多款)+京东自营购物金等您来拿!详见文末

本篇正文


PID控制原理

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

  • 比例(P):实时误差的比例放大,快速响应但易振荡。
  • 积分(I):累积历史误差,消除静态偏差,但可能过调。
  • 微分(D):预测误差变化趋势,抑制超调和振荡。
资讯配图
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, 0200);
// 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;
};

typedefstruct 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, floatsetfloat 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, floatsetfloat kp, float ki, float kd)
{
 pid->input = in;
 pid->output = out;
 pid->setpoint = set;
 pid->automode = false;

 pid_limits(pid, 0255);

// 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)
returnfalse;

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;
elseif (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;
elseif (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;
elseif (*(pid->output) < pid->omin)
   *(pid->output) = pid->omin;

if (pid->iterm > pid->omax)
   pid->iterm = pid->omax;
elseif (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;
elseif (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

硬核玩电/DIY!赢开发板大礼包!

亲爱的电子工程师、硬件极客、电子爱好者、社区的家人们:

这个夏天,以电子为笔,以创意为墨——来面包板社区造点会"跳动"的电子DIY吧!我们给大家准备了开发板大礼包+京东自营购物礼金!等您来拿哦!


资讯配图基础福利:所有参与者可领取2000 E币(可在面包板社区兑换商城使用)。


资讯配图活动奖项:

  • 硬核奖(1名):开发板大礼包(知名品牌开发板2块【如芯驿、STM32等】+其他开发板1块,市场价不低于1500元) +1000元京东自营商城购物金。

  • 创意奖(1名):开发板礼包(品牌开发板2块【STM32、灵动微等】,市场价不低于500元);+500元京东自营商城购物金。

  • 人气奖(1名):开发板礼包(品牌开发板2块【STM32、灵动微等】,市场价不低于500元)+500元京东自营商城购物金。

  • 达人奖(5名):奖励200元京东自营购物金。

  • 优秀作品奖:内容最生动、故事性最强的作品在面包板社区微信公众号阅读量过万的内容,每篇奖励1000E币,不限篇数。

注:更多详情请访问https://mbb.eet-china.com/forum/topic/153762_1_1.html

资讯配图点击阅读原文,了解活动详情!


声明:内容取材于网络,仅代表作者观点,如有内容违规问题,请联系处理。 
开源
more
让OpenAI只领先5天,百川发布推理新模型,掀翻医疗垂域开源天花板
[8.19 杭州]诚邀参会:芯片测试、LabVIEW+全新开源大模型、前沿射频测试等
[8.19 杭州]诚邀参会:NI测试测量技术研讨会 | 晶圆/光电器件/芯片测试、LabVIEW+全新开源大模型等
41个榜单SOTA!智谱最新开源GLM-4.5V实测:看图猜地址、视频秒变代码
机器人上下文协议首次开源:阿里达摩院一口气放出具身智能「三大件」
Qwen紧追OpenAI开源4B端侧大模型,AIME25得分超越Claude 4 Opus
DeepMind 没舍得开源的 Genie 3,被昆仑万维放出来了
一张截图就能生成前端页面?MMLab实验室推出SCREENCODER大模型 | 开源
AI做了个“GTA5”?国产开源世界模型硬刚谷歌,实时交互、分钟级生成
o3 Gemini 都翻车?首个可验证长链 GUI 数据集 VeriGUI 重磅开源,探索通用 Agent 能力边界
Copyright © 2025 成都科技区角科技有限公司
蜀ICP备2025143415号-1
  
川公网安备51015602001305号