大家好,我是板哥,最近社区上线了【硬核玩电·创意DIY】活动,欢迎各位电子界朋友们,以电子为笔,以创意为墨来社区交个朋友。
本次活动参加即有奖励。更有开发板大礼包(多款)+京东自营购物金等您来拿!详见文末
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;
};
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, 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)
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
点击阅读原文,了解活动详情!