## 一阶惯性系统
所谓一阶惯性系统,就是指程序控制的变量,实际并不能直接作用于结果(误差)上。
举个例子:
往水池注水,目标水位是10,当前水位是5,那么误差是10-5=5,此时通过程序控制一个水阀往池中注水,设定水阀的出水量 = 误差。当程序所控制的水阀关闭的瞬间,误差会立刻停止改变。也就是程序控制的变量会立刻马上直接的响应到误差上,此为非惯性系统。
飞船当前位置是10,需要飞往100位置,误差是90.此时通过程序控制飞船的推进器出力,设定推力 = 误差。当程序所控制的推进器关闭推进的瞬间,误差不会立刻停止改变,因为推力产生加速度,加速度产生速度,而速度才产生位移。程序控制的变量不是直接作用于误差,此为惯性系统。
<br>
为了测试之前FCS算法,特地设置了一个测试动态误差的装置。该装置匀速旋转,让FCS锁定之,如果有动态误差,锁定瞄准点会总是落后于目标。
![](https://box.kancloud.cn/cd7b8de062f447902b2edb033cdcf222_1341x503.png)
转动后,可见动态误差如下:
![](https://box.kancloud.cn/b7fd7bd741e727895302b697b1bf26a4_761x599.png)
<br>
首先分析震荡问题
陀螺仪出力范围是-60~60,误差范围-1~1,如果直接采用误差 x 60反馈给陀螺仪,90度误差内也没有震荡。仅在超过90度以后才可能震荡。
如果误差 x 240,相当于4倍时,才产生明显震荡,此时采用
P(err + D*derr)
当D较大(10以上)时,能减少震荡次数。
但当D过大(30以上)时,也能减少更多的震荡次数,但会产生在瞄准后发生微小抖动的情况。
<br>
结论:
使用:P(err + D*derr)
当P设为120时,D设为20时,效果最好。
此时若采用直接误差反馈,P为120,约30度内会产生1次震荡。但采用上述算法不会产生震荡,且抵达时间相同。
## 尝试转换为0阶响应系统
之前在做推进器控制导航时采用过一个算法:
其实这个算法就是把误差从距离转换为速度,所以推进器推速度的控制降阶为0阶。这个算法首先要求取当前所需的速度。
开始测试:
陀螺仪指向问题可以转化为Yaw和Pitch的二维问题
首先设法找到目标相对自己的两个角度,再把问题归纳为一维问题。
```
void AimTarget(Vector3D position){
MatrixD refLookAtMatrix = MatrixD.CreateLookAt(new Vector3D(), cp.WorldMatrix.Forward, cp.WorldMatrix.Up);
Vector3D PositionToMe = Vector3D.TransformNormal(position - cp.Position, refLookAtMatrix);
//需要求出目标基于自己坐标系的相对角度
double TargetYawAngle = Caculate.TargetAngleToMe(PositionToMe, "Yaw");
double TargetPitchAngle = Caculate.TargetAngleToMe(PositionToMe, "Pitch");
//求出自己当前角速度相对自己的值,这是一套弧度值,其中最大值是0.45,最小值是0.0005,x表示俯仰Pitch(上+下-),y表示偏航Yaw(左+右-),z表示滚转(顺时针-逆时针+)
Vector3D MeAngleVelocityToMe = Vector3D.TransformNormal(cp.AngleVelocity, refLookAtMatrix);
double MeYawAngleVelocity = MeAngleVelocityToMe.Y*180/Math.PI;
double MePitchAngleVelocity = MeAngleVelocityToMe.X*180/Math.PI;
Echo(TargetPitchAngle.ToString());
Echo(MePitchAngleVelocity.ToString());
// gys.SetOverride(false);
}
//计算目标坐标点相对自己的角度
//传入目标相当于自己坐标系的坐标,传入一个方向: Yaw、Pitch
//当传入Yaw返回目标相对自己左右的角度(左-右+),传入Pitch返回目标相对自己上下的角度(上-下+)
public static double TargetAngleToMe(Vector3D target_to_me, string direction){
target_to_me = Vector3D.Normalize(target_to_me);
Vector3D meForward = new Vector3D(0,0,-1);
if(direction == "Yaw"){
//把目标投影到xz平面
Vector3D targetYawVector = new Vector3D(target_to_me.X, 0, target_to_me.Z);
double pn = target_to_me.X >= 0 ? 1 : -1;
return Math.Acos(targetYawVector.Dot(meForward)/(targetYawVector.Length() * meForward.Length()))*180/Math.PI * pn;
}else if(direction == "Pitch"){
//把目标投影到yz平面
Vector3D targetPitchVector = new Vector3D(0, target_to_me.Y, target_to_me.Z);
double pn = target_to_me.Y <= 0 ? 1 : -1;
return Math.Acos(targetPitchVector.Dot(meForward)/(targetPitchVector.Length() * meForward.Length()))*180/Math.PI * pn;
}
return 0;
}
```
这样可以获取到目标基于自己坐标系的Yaw Pitch方位角以及自己角速度的Yaw Pitch分量。
但要精确求解还需要知道自己飞船在Yaw和Pitch方向的最大角加速度。
可以通过如下方法获得:
```
Vector2D MaxAngleAcceleration; //其中 X表示Yaw,Y表示Pitch
Vector2D InitAngleVelocity; //其中 X表示Yaw,Y表示Pitch
bool initGyros(){
if(MaxAngleAcceleration.Length() != 0) return true;
//记录当前帧的Yaw角速度
MatrixD refLookAtMatrix = MatrixD.CreateLookAt(new Vector3D(), cp.WorldMatrix.Forward, cp.WorldMatrix.Up);
Vector3D MeAngleVelocityToMe = Vector3D.TransformNormal(cp.AngleVelocity, refLookAtMatrix);
double MeYawAngleVelocity = MeAngleVelocityToMe.Y*180/Math.PI; //当前角速度
double MePitchAngleVelocity = MeAngleVelocityToMe.X*180/Math.PI;
if(InitAngleVelocity.Length() == 0){
//记录当前角速度
InitAngleVelocity = new Vector2D(MeYawAngleVelocity, MePitchAngleVelocity);
//开启陀螺仪全力
gys.SetOnOff(true);
gys.SetOverride(true);
gys.Yaw = gys.Pitch = 60;
}else{
//下一帧,记录新的Yaw角速度,计算最大角加速度
MaxAngleAcceleration = new Vector2D((MeYawAngleVelocity - InitAngleVelocity.X)*60, (MePitchAngleVelocity - InitAngleVelocity.Y)*60) ; //每秒60帧,所以乘以60
gys.SetOverride(false);
gys.Yaw = gys.Pitch = 0;
}
return false;
}
```
获取到最大角加速度后,验证其正确性完成。
**同时发现了一个特性,游戏中物体的角速度会自然衰减,衰减速度大约在2~3度/s**
**同时发现了最大角加速度也有限制,无论陀螺仪数量多少,最大角加速度在任意方向都是170度/s**
<br>
## 新的方法
经过一系列探索,得到了如下两个算法
```
//第一版算法,不考虑目标速度和加速度
//当速度与目标反向,全力减速。当速度与目标同向,通过计算减速时间和当前速度到达时间判断全力加速或减速
// 这个方法能精确抵达目标,完全消除震荡,但不能有效消除动态误差
double AimTargetRatio = 0.2; //瞄准精度,角度
List<Vector2D> AimTarget_Data = new List<Vector2D>();
void AimTarget(Vector3D position){
double yaw = 0;
double pitch = 0;
MatrixD refLookAtMatrix = MatrixD.CreateLookAt(new Vector3D(), cp.WorldMatrix.Forward, cp.WorldMatrix.Up);
Vector3D PositionToMe = Vector3D.TransformNormal(position - cp.Position, refLookAtMatrix);
//需要求出目标基于自己坐标系的相对角度
double TargetYawAngle = Caculate.TargetAngleToMe(PositionToMe, "Yaw");
double TargetPitchAngle = Caculate.TargetAngleToMe(PositionToMe, "Pitch");
//储存误差数据
while(AimTarget_Data.Count > 30){
AimTarget_Data.RemoveAt(0);
}
while(AimTarget_Data.Count < 30){
AimTarget_Data.Add(new Vector2D(TargetYawAngle, TargetPitchAngle));
}
AimTarget_Data.RemoveAt(0);
AimTarget_Data.Add(new Vector2D(TargetYawAngle, TargetPitchAngle));
//求出自己当前角速度相对自己的值,这是一套弧度值,其中最大值是0.45,最小值是0.0005,x表示俯仰Pitch(上+下-),y表示偏航Yaw(左+右-),z表示滚转(顺时针-逆时针+)
double MeYawAngleVelocity = cp.YawVelocity;
double MePitchAngleVelocity = cp.PitchVelocity;
//将自己速度与目标角速度算合速度,对误差积分
// double YawErrSum = 0;
// double PitchErrSum = 0;
// foreach(Vector2D err in AimTarget_Data){
// YawErrSum += err.X;
// PitchErrSum += err.Y;
// }
// MeYawAngleVelocity = MeYawAngleVelocity + YawErrSum/10;
// MePitchAngleVelocity = MePitchAngleVelocity + PitchErrSum/10;
//由于物体自带角速度削减属性,需要一个参数修正加速度
double AccelerationFixedRatio = 5;
if(MeYawAngleVelocity*TargetYawAngle < 0){
if(Math.Abs(TargetYawAngle) > AimTargetRatio){
//当前速度正朝着目标过去
//刹车所需时间
double StopTime = Math.Abs(MeYawAngleVelocity / (MaxAngleAcceleration.X+AccelerationFixedRatio));
//当前速度抵达目标所需时间
double ArriveTime = Math.Abs(TargetYawAngle / MeYawAngleVelocity);
//比较两个时间大小决定减速或加速
if(StopTime <= ArriveTime){
//如果刹车时间足够,全力推进
yaw = 60 * Math.Abs(TargetYawAngle) / TargetYawAngle;
}else{
yaw = -60 * Math.Abs(TargetYawAngle) / TargetYawAngle;
}
}else{
yaw = TargetYawAngle*60/180;
}
}else{
//当前速度正背离目标
yaw = 60 * Math.Abs(TargetYawAngle) / TargetYawAngle;
}
if(MePitchAngleVelocity*TargetPitchAngle < 0){
if(Math.Abs(TargetPitchAngle) > AimTargetRatio){
//当前速度正朝着目标过去
//刹车所需时间
double StopTime = Math.Abs(MePitchAngleVelocity / (MaxAngleAcceleration.Y+AccelerationFixedRatio));
//当前速度抵达目标所需时间
double ArriveTime = Math.Abs(TargetPitchAngle / MePitchAngleVelocity);
//比较两个时间大小决定减速或加速
if(StopTime <= ArriveTime){
//如果刹车时间足够,全力推进
pitch = -60 * Math.Abs(TargetPitchAngle) / TargetPitchAngle;
}else{
pitch = 60 * Math.Abs(TargetPitchAngle) / TargetPitchAngle;
}
}else{
pitch = -TargetPitchAngle*60/180;
}
}else{
//当前速度正背离目标
pitch = -60 * Math.Abs(TargetPitchAngle) / TargetPitchAngle;
}
gys.Yaw = yaw;
gys.Pitch = pitch;
gys.Roll = -cp.InputRoll*40;
gys.SetOverride(true);
}
```
上述的算法完全无震荡,对于静止的目标,可以达到最高的响应效率。因为是用自己的速度和加速度做判断,当自己速度与目标角度误差反向时,直接反向最大推进。当自己速度与目标角度误差同向时,如果未抵达刹车距离就最大推进,如果已经抵达就最大反向推进。
特性如下:
1. 绝不会错过目标
2. 获得了抵达目标的时间最优解,即在不错过的情况下,用时最短
3. 精确无震荡
4. 存在动态误差,而且在圆周运动时候,动态误差会逐渐扩大到无法控制的程度
<br>
第二个算法:
```
double AimTargetRatio = 1; //瞄准精度,角度
List<Vector2D> AimTarget_Data = new List<Vector2D>();
void AimTarget(Vector3D position){
double yaw = 0;
double pitch = 0;
MatrixD refLookAtMatrix = MatrixD.CreateLookAt(new Vector3D(), cp.WorldMatrix.Forward, cp.WorldMatrix.Up);
Vector3D PositionToMe = Vector3D.TransformNormal(position - cp.Position, refLookAtMatrix);
//需要求出目标基于自己坐标系的相对角度
double TargetYawAngle = Caculate.TargetAngleToMe(PositionToMe, "Yaw");
double TargetPitchAngle = Caculate.TargetAngleToMe(PositionToMe, "Pitch");
//储存误差数据
while(AimTarget_Data.Count > 30){
AimTarget_Data.RemoveAt(0);
}
while(AimTarget_Data.Count < 30){
AimTarget_Data.Add(new Vector2D(TargetYawAngle, TargetPitchAngle));
}
AimTarget_Data.RemoveAt(0);
AimTarget_Data.Add(new Vector2D(TargetYawAngle, TargetPitchAngle));
//求出自己当前角速度相对自己的值,这是一套弧度值,其中最大值是0.45,最小值是0.0005,x表示俯仰Pitch(上+下-),y表示偏航Yaw(左+右-),z表示滚转(顺时针-逆时针+)
double MeYawAngleVelocity = cp.YawVelocity;
double MePitchAngleVelocity = cp.PitchVelocity;
//求解合速度(相当于目标相对自己的角速度与自己角速度的合值),使用合速度判定没有动态误差,但有静态震荡
double EffectYawVelocity = (AimTarget_Data[AimTarget_Data.Count - 1].X - AimTarget_Data[AimTarget_Data.Count - 2].X)*60;
double EffectPitchVelocity = (AimTarget_Data[AimTarget_Data.Count - 1].Y - AimTarget_Data[AimTarget_Data.Count - 2].Y)*60;
if(EffectYawVelocity*TargetYawAngle < 0){
if(Math.Abs(TargetYawAngle) > AimTargetRatio){
//当前速度正朝着目标过去
//刹车所需时间
double StopTime = Math.Abs(EffectYawVelocity / MaxAngleAcceleration.X);
//当前速度抵达目标所需时间
double ArriveTime = Math.Abs(TargetYawAngle / EffectYawVelocity);
//比较两个时间大小决定减速或加速
if(StopTime <= ArriveTime){
//如果刹车时间足够,全力推进
yaw = 60 * Math.Abs(TargetYawAngle) / TargetYawAngle;
}else{
yaw = -60 * Math.Abs(TargetYawAngle) / TargetYawAngle;
}
}else{
yaw = TargetYawAngle*60/180;
}
}else{
//当前速度正背离目标
yaw = 60 * Math.Abs(TargetYawAngle) / TargetYawAngle;
}
if(EffectPitchVelocity*TargetPitchAngle < 0){
if(Math.Abs(TargetPitchAngle) > AimTargetRatio){
//当前速度正朝着目标过去
//刹车所需时间
double StopTime = Math.Abs(EffectPitchVelocity / MaxAngleAcceleration.Y);
//当前速度抵达目标所需时间
double ArriveTime = Math.Abs(TargetPitchAngle / EffectPitchVelocity);
//比较两个时间大小决定减速或加速
if(StopTime <= ArriveTime){
//如果刹车时间足够,全力推进
pitch = -60 * Math.Abs(TargetPitchAngle) / TargetPitchAngle;
}else{
pitch = 60 * Math.Abs(TargetPitchAngle) / TargetPitchAngle;
}
}else{
pitch = -TargetPitchAngle*60/180;
}
}else{
//当前速度正背离目标
pitch = -60 * Math.Abs(TargetPitchAngle) / TargetPitchAngle;
}
gys.Yaw = yaw;
gys.Pitch = pitch;
gys.Roll = -cp.InputRoll*40;
gys.SetOverride(true);
}
```
上述算法区别在于不是采用自己的角速度来判断刹车距离,而是采用合速度。
合速度可以直接根据上一帧和这一帧的目标方位角的差求出
使用合速度来判断,不会产生动态误差,但会在小角度上震荡(震荡也不算太严重)
<br>
## 总结
在精确求解的算法下,实际上做了如下几个重要判断
1. 判断 **速度.a** 是否与目标方位角同向,即这个速度是在扩大与目标的方位角还是减小。如果是扩大,就立刻全力反向出力,如果是减小,那就做第二步判断
2. 用 **速度.b** 和最大加速度判断刹车时间,再用 **速度.c** 与方位角的差判断抵达时间。
3. 比较刹车时间和抵达时间,如果刹车时间更大,说明刹车距离已经不够,此时全力反向出力,以避免超越。如果刹车时间小,说明还可以继续全力正向出力。
<br>
系统可以获取3种速度:
1. 自己的角速度
2. 合角速度
3. 目标的绝对角速度
使用这三种速度分别代入上述两个判断的不同步骤中测试结果
1. 全部采用自己的速度:无震荡,有无法控制的自增动态误差
2. 全部采用合速度:有细微震荡,完全无动态误差
3. a使用自己的速度,bc使用合速度:无细微震荡,有可控的动态误差,但动态误差情况下会出现大幅度震荡
4. a使用合速度,b使用自己的速度,c采用合速度:结果和1相似,几乎没区别
5. a使用合速度,b使用合速度,c采用自己的速度:结果和1相似
- 序言
- 写在前面的话
- 太空工程师
- MEA小组
- 一、入门
- 1.1 基础概念
- 1.2 编程工具
- 1.3 变量
- 1.4 函数 Function
- 1.5 基本语法
- 1.5.1 运算符
- 1.5.2 if
- 1.5.3 for
- 1.5.4 其他语法
- 1.3 类 Class
- 二、编程块
- 2.1 方块的概念
- 2.2 List<T>结构
- 2.3 获取方块
- 2.4 方块的使用
- 三、Ship 类
- 3.1 简介
- Ship v0.5
- 代码
- 手册(待更新)
- 例子(待更新)
- Ship v1.0
- 代码
- 例子
- 文档
- 实例化
- 内置变量
- 内置方法
- Target类
- 四、运动控制算法在SE中的应用
- 4.1 运动控制介绍
- 4.2 过程控制
- 4.3 震荡和动态误差
- 4.4 误差累加方案
- 4.5 PID算法
- 4.6 对PID算法的一点点简化
- 4.7 一阶惯性系统的PID算法优化的研究
- 五、MEA方块类
- 5.0 核心代码目录
- v1.0核心代码
- v1.1 核心代码
- v2.0 核心代码
- 5.1 类的概念
- 5.2 MEA的方块类(Block)
- 5.3 方块类文档
- 5.4 方块类2.0 全教程
- 5.4.1 安装和使用
- 5.4.2 方块类(Block)
- 5.4.3 显示屏类(DisplayScreen)
- 5.4.4 LCD类(LCD)
- 5.4.5 主控座椅类(Cockpit)
- 六、疯猴的编程笔记
- 第一个程序
- 获取和控制其他块
- 物流与生产
- 界面与通信
- 运动与姿态
- 侦测与导航
- 七、SteamZhou的笔记
- 有趣而花里胡哨的IDEA
- 八、质子对撞炮的笔记
- 属性 Property
- 接口 interface