现在试想以下场景,当机器人正在下浅定深度在某个值
static float depth = 0 //深度为 0
static float v =0 //速度为 0
static float a =0 //加速度为 0
static float m=10 //机器人的质量为 10
static float kv = 1 //在水下受到与速度成正比的比例系数
static float f = 1 //水中的静态阻力
a = (F 推力– kv*v-f)/m //根据牛顿定律求出加速度的值
v +=a //加速度的积分为速度
depth += v //速度积分为深度
使用 pid 算法输入深度值的差,输出为推力的值,不断调试 pid 的参数值 使用单片机的串口(usart)重定向到上位机实时观察 depth 深度的曲线,可以使用 vofa+上位 机软件,实现深度从 0 开始一直闭环到深度 20 的值
以下为vofa+仿真的效果
pid深度仿真
用keil5进行编译,选用vofa+的firewater协议进行数据打印。但是要注意要对“printf”函数进行重定义,强烈建议去看一下小破站江科大自动化科协stm32教程有关于串口数据收发的教程,非常详细易懂。
VOFA+有三种协议设置
- rawdata:和普通的串口助手一样,发送什么显示什么。
- firewater:最简单在VOFA+里绘制波形的协议,建议仅在通道数量不多、发送频率不高的时候使用。要注意想要打印出数据在“printf”函数中在最后要用“\n”进行换行
- justfloat:有一定的数据格式,此协议非常适合用在通道数量多、发送频率高的时候。//设个本菜鸡还没有学
PID部分本站的佬们讲的都很好,可以找一些教程进行学习。
我对PID的理解是
首先要设定一个目标值target,每轮执行程序都要用target-current_depth来计算出error;
1.P比例环节 Pout=P*error P为我们设定的比例系数,由式子可以看出距离越远Pout越大,就像向一个水桶里注水一样,刚开始error大于0注水会用较大的水流注入,当距离我们的目标值越近所用的水流就越小;但当error小于0时就需要从桶中舀出水原理与注水时相同。P的大小可影响Pout的大小,P越大越快,但不宜太大,因为这样会产生过大的波动,影不利于控制。
2.D微分环节 仅仅靠比例环节很难趋近于目标值,因为其产生的是较大的波动,像刹车一样,很难立刻就停下来,所以要依靠微分环节。 Dout=D*(derror/dt),由此得到Dout的大小由误差的变化性速度有关,方向与速度变化方向有关,而且总是与速度方向相反,因此始终会有一个阻尼作用,最终会趋近于目标值。
3.I积分环节 用P和D环节就基本可以趋近于目标值了,那为什们还要用积分环节呢?试想一下,像一个漏水的水桶里注水,漏水就相当于一个静态误差,如果每次像桶内加的水和漏掉的水相等,那水位将不再变化,而加入积分环节,每次都会将误差进行累加,当出现上述情况时,积分环节会加大进水量,从而超过水位不动时的水位线,进而趋近于目标。
这部分我知到写的很烂5555~也借鉴了一些佬的思想,表达能力太差,写错的地方请帮忙指正哈
以下为main函数
#include "stm32f10x.h"
#include "Serial.h"
#include <stdio.h>
typedef struct
{
float Kp; // 比例系数
float Ki; // 积分系数
float Kd; // 微分系数
float target; // 设定值
float integral; // 积分项
float last_error; // 上一次的误差
} PID;
PID pid;
// 初始化pid控制器
void PID_Init(PID *pid, float Kp, float Ki, float Kd, float target)
{
pid->Kp = Kp;
pid->Ki = Ki;
pid->Kd = Kd;
pid->target = target;
pid->integral = 0;
pid->last_error = 0;
}
//计算P I D
float PID_Calc(PID *pid, float feedback)
{
float error = pid->target - feedback;//计算误差
float P = pid->Kp * error;//比例项
pid->integral += error;//积分项
float I = pid->Ki * pid->integral;
float derivative = error - pid->last_error;//微分项
float D = pid->Kd * derivative;
pid->last_error = error;//上一次的误差
float output = P + I+ D;
return output;
}
int main(void)
{
float target = 20;
float current_depth = 0;
float m = 10;
float kv = 1;
float f = 1;
float a = 0;
float v = 0;
PID pid;
Serial_Init();
PID_Init(&pid, 5, 0.5, 0.1, target);
while (1)
{
float F = PID_Calc(&pid, current_depth);//推力大小
a = (F - kv * v - f) / m;//加速度
v += a ;//速度
current_depth += v ;//深度
Serial_Printf("%f\n",current_depth);
}
}
学习过程中遇到了不少麻烦,比如c语言不扎实,对代码的书写能力较弱,所以扎实的c语言功底是十分重要的,还有就是对PID的理解还不够深刻,还需继续学习。
新人写稿 如有错误 欢迎指正