Как интегратор убирает статическую ошибку

Вместо введения

Системы автоматического управления (САУ) предназначены для автоматического изменения одного или нескольких параметров объекта управления с целью установления требуемого режима его работы. САУ обеспечивает поддержание постоянства заданных значений регулируемых параметров или их изменение по заданному закону либо оптимизирует определенные критерии качества управления. Например, к таким системам относятся:

  • системы стабилизации,
  • системы программного управления,
  • следящие системы

Это достаточно широкий класс систем, которые можно найти где угодно. Но какое это отношение имеет к Unity3D и вероятно к играм в частности? В принципе прямое: в любой игре так или иначе использующей симуляцию как элемент геймплея реализуются САУ, к таким играм относятся, например, Kerbal Space Programm, Digital Combat Simulator (бывший Lock On), Strike Suit Zero и т.д. (кто знает еще примеры — пишите в комментариях). В принципе любая игра, моделирующая реальные физические процессы, в том числе и просто кинематику с динамикой движения, может реализовывать те или иные САУ — этот подход проще, естественнее, а у разработчика уже есть есть набор готовых инструментов, предоставленных всякими Вышнеградскими, Ляпуновыми, Калманами, Чебышевами и прочими Коломогоровами, поэтому можно обойтись без изобретения велосипеда, т.к. его уже изобрели, да так, что получилась отдельная наука: Теория автоматического управления. Главное тут не переусердствовать. Одна тут только проблема: рассказывают про ТАУ не везде, не всем, зачастую мало и не очень понятно.

Немножко теории

Классическая система автоматического управления представленная на следующем рисунке:

image

Ключевым элементом любой САУ является регулятор представляющий из себя устройство, которое следит за состоянием объекта управления и обеспечивает требуемый закон управления. Процесс управления включает в себя: вычисление ошибки управления или сигнала рассогласования e(t) как разницы между желаемой уставкой (set point или SP) и текущей величиной процесса (process value или PV), после чего регулятор вырабатывает управляющие сигналы (manipulated value или MV).

Одной из разновидностью регуляторов является пропорционально-интегрально-дифференцирующий (ПИД) регулятор, который формирует управляющий сигнал, являющийся суммой трёх слагаемых: пропорционального, интегрального и дифференциального.

image

Где, $e(t)$ ошибка рассогласования, а также, $ P = K_p cdot e(t)$ — пропорциональная, $ I = K_i cdot int_0^t e(tau)dtau$ — интегральная, $D = K_d cdot frac{de(t)}{dt}$ — дифференциальная составляющие (термы) закона управления, который в итоговом виде описывается следующими формулами

$ e(t) = SP(t) - PV(t), $

$ MV(t) = underbrace{K_p cdot e(t)}_{P} + underbrace{K_i cdot int_0^t e(tau)dtau}_{I} + underbrace{K_d cdot frac{de(t)}{dt}}_{D}, $

Пропорциональная составляющая P — отвечает за т.н. пропорциональное управление, смысл которого в том, что выходной сигнал регулятора, противодействует отклонению регулируемой величины (ошибки рассогласования или еще это называют невязкой) от заданного значения. Чем больше ошибка рассогласования, тем больше командное отклонение регулятора. Это самый простой и очевидный закон управления. Недостаток пропорционального закона управления заключается в том, что регулятор никогда не стабилизируется в заданном значении, а увеличение коэффициента пропорциональности всегда приводит к автоколебаниям. Именно поэтому в довесок к пропорциональному закону управления приходиться использовать интегральный и дифференциальный.

Интегральная составляющая I накапливает (интегрирует) ошибку регулирования, что позволяет ПИД-регулятору устранять статическую ошибку (установившуюся ошибку, остаточное рассогласование). Или другими словами: интегральное звено всегда вносит некоторое смещение и если система подвержена некоторыми постоянным ошибкам, то оно их компенсирует (за счет своего смещения). А вот если же этих ошибок нет или они пренебрежительно малы, то эффект будет обратным — интегральная составляющая сама будет вносить ошибку смещения. Именно по этой причине её не используют, например, в задачах сверхточного позиционирования. Ключевым недостатком интегрального закона управления является эффект насыщения интегратора (Integrator windup).

Дифференциальная составляющая D пропорциональна темпу изменения отклонения регулируемой величины и предназначена для противодействия отклонениям от целевого значения, которые прогнозируются в будущем. Примечательно то, что дифференциальная компонента устраняет затухающие колебания. Дифференциальное регулирование особенно эффективно для процессов, которые имеют большие запаздывания. Недостатком дифференциального закона управления является его неустойчивость к воздействую шумов (Differentiation noise).

Таким образом, в зависимости от ситуации могут применятся П-, ПД-, ПИ- и ПИД-регуляторы, но основным законом управления в основном является пропорциональный (хотя в некоторых специфических задачах и могут использоваться исключительно только звенья дифференциаторов и интеграторов).

Казалось бы, вопрос реализации ПИД-регуляторов уже давно избит и здесь на Хабре есть парочка неплохих статей на эту тему в том числе и на Unity3D, также есть неплохая статья PID Without a PhD (перевод) и цикл статей в журнале «Современные технологии автоматизации» в двух частях: первая и вторая. Также к вашим услугам статья на Википедии (наиболее полную читайте в английском варианте). А на форумах коммьюнити Unity3D нет-нет, да и всплывет PID controller как и на gamedev.stackexchange

При вопрос по реализации ПИД-регуляторов несколько глубже чем и кажется. Настолько, что юных самоделкиных, решивших, реализовать такую схему регулирования ждет немало открытий чудных, а тема актуальная. Так что надеюсь сей опус, кому-нибудь да пригодиться, поэтому приступим.

Попытка номер раз

В качестве примера попытаемся реализовать схему регулирования на примере управления поворотом в простенькой космической 2D-аркаде, по шагам, начиная с самого начала (не забыли, что это туториал?).

Почему не 3D? Потому что реализация не измениться, за исключением того, что придется воротить ПИД-регулятор для контроля тангажа, рысканья и крена. Хотя вопрос корректного применения ПИД-регулирования вместе с кватернионами действительно интересный, возможно в будущем его и освящу, но даже в NASA предпочитают углы Эйлера вместо кватернионов, так что обойдемся простенькой моделью на двухмерной плоскости.

Для начала создадим сам объект игровой объект космического корабля, который будет состоять из собственно самого объекта корабля на верхнем уровне иерархии, прикрепим к нему дочерний объект Engine (чисто спецэффектов ради). Вот как это выглядит у меня:

image

А на сам объект космического корабля накидаем в инспекторе всяческих компонент. Забегая вперед, приведу скрин того, как он будет выглядеть в конце:

image
Но это потом, а пока в нем еще нет никаких скриптов, только стандартный джентльменский набор: Sprite Render, RigidBody2D, Polygon Collider, Audio Source (зачем?).

Собственно физика у нас сейчас самое главное и управление будет осуществляться исключительно через неё, в противном случае, применение ПИД-регулятора потеряло бы смысл. Масса нашего космического корабля оставим также в 1 кг, а все коэффициенты трения и гравитации равны нулю — в космосе же.

Т.к. помимо самого космического корабля есть куча других, менее умных космических объектов, то сначала опишем родительский класс BaseBody, который в себе будет содержать ссылки на на наши компоненты, методы инициализации и уничтожения, а также ряд дополнительных полей и методов, например для реализации небесной механики:

BaseBody.cs

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

namespace Assets.Scripts.SpaceShooter.Bodies
{
    [RequireComponent(typeof(SpriteRenderer))]
    [RequireComponent(typeof(AudioSource))]
    [RequireComponent(typeof(Rigidbody2D))]
    [RequireComponent(typeof(Collider2D))]

    public class BaseBody : MonoBehaviour
    {
        readonly float _deafultTimeDelay = 0.05f;

[HideInInspector]
        public static List<BaseBody> _bodies = new List<BaseBody>();

        #region RigidBody

        [HideInInspector]
        public Rigidbody2D _rb2d;

        [HideInInspector]
        public Collider2D[] _c2d;

        #endregion

        #region References

        [HideInInspector]
        public Transform _myTransform;

        [HideInInspector]
        public GameObject _myObject;

        /// <summary>
        /// Объект, который появляется при уничтожении
        /// </summary>
        public GameObject _explodePrefab;

        #endregion

        #region  Audio

        public AudioSource _audioSource;

        /// <summary>
        /// Звуки, которые проигрываются при получении повреждения
        /// </summary>
        public AudioClip[] _hitSounds;

        /// <summary>
        /// Звуки, которые проигрываются при появлении объекта
        /// </summary>
        public AudioClip[] _awakeSounds;

        /// <summary>
        /// Звуки, которые воспроизводятся перед смертью
        /// </summary>
        public AudioClip[] _deadSounds;

        #endregion

        #region External Force Variables
        /// <summary>
        /// Внешние силы воздйствующие на объект
        /// </summary>
        [HideInInspector]
        public Vector2 _ExternalForces = new Vector2();

        /// <summary>
        /// Текущий вектор скорости
        /// </summary>
        [HideInInspector]
        public Vector2 _V = new Vector2();

        /// <summary>
        /// Текущий вектор силы гравитации
        /// </summary>
        [HideInInspector]
        public Vector2 _G = new Vector2();
        #endregion

        public virtual void Awake()
        {
            Init();
        }

        public virtual void Start()
        {

        }

        public virtual void Init()
        {
            _myTransform = this.transform;
            _myObject = gameObject;

            _rb2d = GetComponent<Rigidbody2D>();
            _c2d = GetComponentsInChildren<Collider2D>();
            _audioSource = GetComponent<AudioSource>();

            PlayRandomSound(_awakeSounds);

            BaseBody bb = GetComponent<BaseBody>();
            _bodies.Add(bb);
        }

        /// <summary>
        /// Уничтожение персонажа
        /// </summary>
        public virtual void Destroy()
        {
            _bodies.Remove(this);
            for (int i = 0; i < _c2d.Length; i++)
            {
                _c2d[i].enabled = false;
            }
            float _t = PlayRandomSound(_deadSounds);
            StartCoroutine(WaitAndDestroy(_t));
        }

        /// <summary>
        /// Ждем некоторое время перед уничтожением
        /// </summary>
        /// <param name="waitTime">Время ожидания</param>
        /// <returns></returns>
        public IEnumerator WaitAndDestroy(float waitTime)
        {
            yield return new WaitForSeconds(waitTime);

            if (_explodePrefab)
            {
                Instantiate(_explodePrefab, transform.position, Quaternion.identity);
            }

            Destroy(gameObject, _deafultTimeDelay);
        }

        /// <summary>
        /// Проигрывание случайного звука
        /// </summary>
        /// <param name="audioClip">Массив звуков</param>
        /// <returns>Длительность проигрываемого звука</returns>
        public float PlayRandomSound(AudioClip[] audioClip)
        {
            float _t = 0;
            if (audioClip.Length > 0)
            {
                int _i = UnityEngine.Random.Range(0, audioClip.Length - 1);
                AudioClip _audioClip = audioClip[_i];
                _t = _audioClip.length;
                _audioSource.PlayOneShot(_audioClip);
            }
            return _t;
        }

        /// <summary>
        /// Получение урона
        /// </summary>
        /// <param name="damage">Уровень урона</param>
        public virtual void Damage(float damage)
        {
            PlayRandomSound(_hitSounds);
        }

    }
}

Вроде описали все что надо, даже больше чем нужно (в рамках этой статьи). Теперь отнаследуем от него класс корабля Ship, который должен уметь двигаться и поворачивать:

SpaceShip.cs

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

namespace Assets.Scripts.SpaceShooter.Bodies
{
    public class Ship : BaseBody
    {
        public Vector2 _movement = new Vector2();
        public Vector2 _target = new Vector2();
        public float _rotation = 0f;

        public void FixedUpdate()
        {
            float torque = ControlRotate(_rotation);
            Vector2 force = ControlForce(_movement);

            _rb2d.AddTorque(torque);
            _rb2d.AddRelativeForce(force);
        }

        public float ControlRotate(Vector2 rotate)
        {
            float result = 0f;

            return result;
        }

        public Vector2 ControlForce(Vector2 movement)
        {
            Vector2 result = new Vector2();

            return result;

        }
    }
}

Пока в нем нет ничего интересно, на текущий момент это просто класс-заглушка.

Также опишем базовый(абстрактный) класс для всех контроллеров ввода BaseInputController:

BaseInputController.cs

using UnityEngine;
using Assets.Scripts.SpaceShooter.Bodies;

namespace Assets.Scripts.SpaceShooter.InputController
{
    public enum eSpriteRotation
    {
        Rigth = 0,
        Up = -90,
        Left = -180,
        Down = -270
    }

    public abstract class BaseInputController : MonoBehaviour
    {
        public GameObject _agentObject;
        public Ship _agentBody; // Ссылка на компонент логики корабля
        public eSpriteRotation _spriteOrientation = eSpriteRotation.Up; //Это связано с нестандартной 
                                                                           // ориентации спрайта "вверх" вместо "вправо"

        public abstract void ControlRotate(float dt);
        public abstract void ControlForce(float dt);

        public virtual void Start()
        {
            _agentObject = gameObject;
            _agentBody = gameObject.GetComponent<Ship>();
        }

        public virtual void FixedUpdate()
        {
            float dt = Time.fixedDeltaTime;
            ControlRotate(dt);
            ControlForce(dt);
        }

        public virtual void Update()
        {
            //TO DO
        }
    }
}

И наконец, класс контроллера игрока PlayerFigtherInput:

PlayerInput.cs

using UnityEngine;
using Assets.Scripts.SpaceShooter.Bodies;

namespace Assets.Scripts.SpaceShooter.InputController
{
    public class PlayerFigtherInput : BaseInputController
    {
        public override void ControlRotate(float dt)
        {
            // Определяем позицию мыши относительно игрока
            Vector3 worldPos = Input.mousePosition;
            worldPos = Camera.main.ScreenToWorldPoint(worldPos);

            // Сохраняем координаты указателя мыши
            float dx = -this.transform.position.x + worldPos.x;
            float dy = -this.transform.position.y + worldPos.y;

            //Передаем направление
            Vector2 target = new Vector2(dx, dy);
            _agentBody._target = target;

            //Вычисляем поворот в соответствии с нажатием клавиш
            float targetAngle = Mathf.Atan2(dy, dx) * Mathf.Rad2Deg;
            _agentBody._targetAngle = targetAngle + (float)_spriteOrientation;
        }

        public override void ControlForce(float dt)
        {
            //Передаем movement
            _agentBody._movement = Input.GetAxis("Vertical") * Vector2.up 
                + Input.GetAxis("Horizontal") * Vector2.right;
        }
    }
}

Вроде бы закончили, теперь наконец можно перейти к тому, ради чего все это затевалось, т.е. ПИД-регуляторам (не забыли надеюсь?). Его реализация кажется простой до безобразия:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Assets.Scripts.Regulator
{
    [System.Serializable] // Этот атрибут необходим для того что бы поля регулятора 
                                   // отображались в инспекторе и сериализовывались
    public class SimplePID
    {
        public float Kp, Ki, Kd;

        private float lastError;
        private float P, I, D;

        public SimplePID()
        {
            Kp = 1f;
            Ki = 0;
            Kd = 0.2f;
        }

        public SimplePID(float pFactor, float iFactor, float dFactor)
        {
            this.Kp = pFactor;
            this.Ki = iFactor;
            this.Kd = dFactor;
        }

        public float Update(float error, float dt)
        {
            P = error;
            I += error * dt;
            D = (error - lastError) / dt;
            lastError = error;

            float CO = P * Kp + I * Ki + D * Kd;

            return CO;
        }
    }
}

Значения коэффициентов по умолчанию возьмем с потолка: это будет тривиальный единичный коэффициент пропорционального закона управления Kp = 1, небольшое значение коэффициента для дифференциального закона управления Kd = 0.2, который должен устранить ожидаемые колебания и нулевое значение для Ki, которое выбрано потому, что в нашей программной модели нет никаких статичных ошибок (но вы всегда можете их внести, а потом героически побороться с помощью интегратора).

Теперь вернемся к нашему классу SpaceShip и попробуем заюзать наше творение в качестве регулятора поворота космического корабля в методе ControlRotate:

 public float ControlRotate(Vector2 rotate)
 {
      float MV = 0f;
      float dt = Time.fixedDeltaTime;

      //Вычисляем ошибку
      float angleError = Mathf.DeltaAngle(_myTransform.eulerAngles.z, targetAngle);

      //Получаем корректирующее ускорение
      MV = _angleController.Update(angleError, dt);

      return MV;
 }

ПИД-регулятор будет осуществлять точное угловое позиционировая космического корабля только за счет крутящего момента. Все честно, физика и САУ, почти как в реальной жизни.

И без этих ваших Quaternion.Lerp

 if (!_rb2d.freezeRotation)
     rb2d.freezeRotation = true;

 float deltaAngle = Mathf.DeltaAngle(_myTransform.eulerAngles.z, targetAngle);
 float T = dt *  Mathf.Abs( _rotationSpeed / deltaAngle);

 // Трансформируем угол в вектор
Quaternion rot = Quaternion.Lerp(
                _myTransform.rotation,
                Quaternion.Euler(new Vector3(0, 0, targetAngle)),
                T);

 // Изменяем поворот объекта
 _myTransform.rotation = rot;

Получившейся исходный код Ship.cs под спойлером

using UnityEngine;
using Assets.Scripts.Regulator;

namespace Assets.Scripts.SpaceShooter.Bodies
{
    public class Ship : BaseBody
    {
        public GameObject _flame;

        public Vector2 _movement = new Vector2();
        public Vector2 _target = new Vector2();

        public float _targetAngle = 0f;
        public float _angle = 0f;

        [Header("PID")]
        public SimplePID _angleController = new SimplePID();

        public void FixedUpdate()
        {
            float torque = ControlRotate(_targetAngle);
            Vector2 force = ControlForce(_movement);

            _rb2d.AddTorque(torque);
            _rb2d.AddRelativeForce(force);
        }

        public float ControlRotate(float rotate)
        {
            float MV = 0f;
            float dt = Time.fixedDeltaTime;

            _angle = _myTransform.eulerAngles.z;

            //Вычисляем ошибку
            float angleError = Mathf.DeltaAngle(_angle, rotate);

            //Получаем корректирующее ускорение
            MV = _angleController.Update(angleError, dt);

            return MV;
        }

        public Vector2 ControlForce(Vector2 movement)
        {
            Vector2 MV = new Vector2();

            //Кусок кода спецэффекта работающего двигателя ради
            if (movement != Vector2.zero)
            {
                if (_flame != null)
                {
                    _flame.SetActive(true);
                }
            }
            else
            {
                if (_flame != null)
                {
                    _flame.SetActive(false);
                }
            }

            MV = movement;
            return MV;
        }
    }
}

Все? Расходимся по домам?

WTF! Что происходит? Почему корабль поворачивается как-то странно? И почему он так резко отскакивает от других объектов? Неужели этот глупый ПИД-регулятор не работает?

Без паники! Давайте попробуем разобраться что происходит.

В момент получения нового значения SP, происходит резкий (ступенчатый) скачок рассогласования ошибки, которая, как мы помним, вычисляется вот так: $e(t) = SP(t) - PV(t), $ соответственно происходит резкий скачок производной ошибки $frac{de(t)}{dt}$, которую мы вычисляем в этой строчке кода:

D = (error - lastError) / dt;

Можно, конечно, попробовать другие схемы дифференцирования, например, трехточечную, или пятиточечную, или… но все равно это не поможет. Ну вот не любят производные резких скачков — в таких точках функция не является дифференцируемой. Однако поэкспериментировать с разными схемами дифференцирования и интегрирования стоит, но потом и не в этой статье.

Думаю что настал момент построить графики переходного процесса: ступенчатое воздействие от S(t) = 0 в SP(t) = 90 градусов для тела массой в 1 кг, длинной плеча силы в 1 метр и шагом сетки дифференцирования 0.02 с — прям как в нашем примере на Unity3D (на самом деле не совсем, при построении этих графиков не учитывалось, что момент инерции зависит от геометрии твердого тела, поэтому переходный процесс будет немножко другой, но все же достаточно похожий для демонстрации). Все величены на грифике приведены в абсолютных значениях:
image
Хм, что здесь происходит? Куда улетел отклик ПИД-регулятора?

Поздравляю, мы только что столкнулись с таким явлением как «удар» (kick). Очевидно, что в момент времени, когда процесс еще PV = 0, а уставка уже SP = 90, то при численном дифференцировании получим значение производной порядка 4500, которое умножится на Kd=0.2 и сложится с пропорциональным теромом, так что на выходе мы получим значение углового ускорения 990, а это уже форменное надругательство над физической моделью Unity3D (угловые скорости будут достигать 18000 град/с… я думаю это предельное значение угловой скорости для RigidBody2D).

  • Может стоит подобрать коэффициенты ручками, так чтобы скачок был не таким сильным?
  • Нет! Самое лучше чего мы таким образом сможем добиться — небольшая амплитуда скачка производной, однако сам скачок как был так и останется, при этом можно докрутиться до полной неэффективности дифференциальной составляющей.

Впрочем можете поэкспериментировать.

Попытка номер два. Сатурация

Логично, что привод (в нашем случае виртуальные маневровые двигатели SpaceShip), не может отрабатывать сколько угодно большие значения которые может выдать наш безумный регулятор. Так что первое что мы сделаем — сатурируем выход регулятора:

public float ControlRotate(Vector2 rotate, float thrust)
{
    float CO = 0f;
    float MV = 0f;
    float dt = Time.fixedDeltaTime;

    //Вычисляем ошибку
    float angleError = Mathf.DeltaAngle(_myTransform.eulerAngles.z, targetAngle);

    //Получаем корректирующее ускорение
    CO = _angleController.Update(angleError, dt);

    //Сатурируем
    MV = CO;
    if (MV > thrust) MV = thrust;
    if (MV< -thrust) MV = -thrust;

    return MV;
}

А очередной раз переписанный класс Ship полностью выглядит так

namespace Assets.Scripts.SpaceShooter.Bodies
{
    public class Ship : BaseBody
    {
        public GameObject _flame;

        public Vector2 _movement = new Vector2();
        public Vector2 _target = new Vector2();

        public float _targetAngle = 0f;
        public float _angle = 0f;

        public float _thrust = 1f;

        [Header("PID")]
        public SimplePID _angleController = new SimplePID(0.1f,0f,0.05f);

        public void FixedUpdate()
        {
            _torque = ControlRotate(_targetAngle, _thrust);
            _force = ControlForce(_movement);

            _rb2d.AddTorque(_torque);
            _rb2d.AddRelativeForce(_force);
        }

        public float ControlRotate(float targetAngle, float thrust)
        {
            float CO = 0f;
            float MV = 0f;
            float dt = Time.fixedDeltaTime;

            //Вычисляем ошибку
            float angleError = Mathf.DeltaAngle(_myTransform.eulerAngles.z, targetAngle);

            //Получаем корректирующее ускорение
            CO = _angleController.Update(angleError, dt);

            //Сатурируем
            MV = CO;
            if (MV > thrust) MV = thrust;
            if (MV< -thrust) MV = -thrust;

            return MV;
        }

        public Vector2 ControlForce(Vector2 movement)
        {
            Vector2 MV = new Vector2();

            if (movement != Vector2.zero)
            {
                if (_flame != null)
                {
                    _flame.SetActive(true);
                }
            }
            else
            {
                if (_flame != null)
                {
                    _flame.SetActive(false);
                }
            }

            MV = movement * _thrust;

            return MV;
        }

        public void Update()
        {

        }        
    }
}

Итоговая схема нашего САУ тогда станет уже вот такой
image

При этом уже становится понятно, что выход контроллера CO(t) немного не одно и тоже, что управляемая величина процесса MV(t).

Собственно с этого места можно уже добавлять новую игровую сущность — привод, через которую и будет осуществляться управление процессом, логика работы которой может быть более сложной, чем просто Mathf.Clamp(), например, можно ввести дискретизацию значений (дабы не перегружать игровую физику величинами идущими шестыми после запятой), мертвую зону (опять таки не имеет смысл перегружать физику сверхмалыми реакциями), ввести задержку в упраление и нелинейность (например, сигмоиду) привода, после чего посмотреть, что из этого получится.

Запустив игру, мы обнаружим, что космический корабль стал наконец управляемым:

Если построить графики, то можно увидеть, что реакция контроллера стала уже вот такой:
image
Здесь уже используются нормированные величены, углы поделены на значение SP, а выход контроллера отнормирован относительно максимального значения на котором уже происходит сатурация.

Теперь на графике видно наличие ошибки перерегулирования (overshooting) и затухающие колебания. Уменьшая Kp и увеличивая Kd можно добиться уменьшения колебаний, но зато увеличится время реакции контроллера (скорость поворота корабля). И наоборот, увеличивая Kp и уменьшая Kd — можно добиться увеличения скорости реакции контроллера, но появятся паразитные колебания, которые при определенных (критических) значениях, перестанут быть затухающими.

Ниже приведена известна таблица влияния увеличения параметров ПИД-регулятора (как уменьшить шрифт, а то таблица безе переносов не лезет?):

А общий алгоритм ручной настройки ПИД-регулятора следующий:

  1. Подбираем пропорциональный коэффициенты при отключенных дифференциальных и интегральных звеньях до тех пор пока не начнутся автоколебания.
  2. Постепенно увеличивая дифференциальную составляющую избавляемся от автоколебаний
  3. Если наблюдается остаточная ошибка регулирования (смещение), то устраняем её за счет интегральной составляющей.

Каких-то общих значений параметров ПИД-регулятора нет: конкретные значения зависят исключительно от параметров процесса (его передаточной характеристики): ПИД-регулятор отлично работающий с одним объектом управления окажется неработоспособным с другим. Более того, коэффициенты при пропорциональной, интегральной и дифференциальной составляющих еще и взаимозависимы.

В общем не будем о грустном, дальше нас ждет самое интересное…

Попытка номер три. Еще раз производные

Приделав костыль в виде ограничения значений выхода контроллера мы так и не решили самую главную проблему нашего регулятора — дифференциальная составляющая плохо себя чувствует при ступенчатом изменении ошибки на входе регуляторе. На самом деле есть множество других костылей, например, в момент скачкообразного изменения SP «отключать» дифференциальную составляющую или же поставить фильтры нижних частот между SP(t) и операцией $SP(t)-PV(t)$ за счет которого будет происходить плавное нарастание ошибки, а можно совсем развернуться и впендюрить самый настоящий фильтр Калмана для сглаживания входных данных. В общем костылей много, и добавить наблюдателя конечно хотелось бы, но не в этот раз.

Поэтому снова вернемся к производной ошибки рассогласования и внимательно на неё посмотрим:

$ frac{de(t)}{dt} = frac{d(SP(t)-PV(t))}{dt} = frac{dSP(t)}{dt} - frac{dPV(t)}{dt}, $

Ничего не заметили? Если хорошенько присмотреться, то можно обнаружить, что вообще-то SP(t), не меняется во времени (за исключением моментов ступенчатого изменения, когда регулятор получает новую команду), т.е. её производная равна нулю:

$ frac{dSP(t)}{dt} = 0, $

тогда

$ frac{de(t)}{dt} = - frac{dPV(t)}{dt}, $

Иными словами, вместо производной ошибки, которая дифференцируема не везде мы можем использовать производную от процесса, который в мире классической механики как правило непрерывен и дифференцируем везде, а схема нашей САУ уже приобретет следующий вид:
image

$ e(t) = SP(t) - PV(t), $

$ CO(t) = underbrace{K_p cdot e(t)}_{P} + underbrace{K_i cdot int_0^t e(tau)dtau}_{I} - underbrace{K_d cdot frac{dPV(t)}{dt}}_{D}, $

Модифицируем код регулятора:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Assets.Scripts.Regulator
{
    [System.Serializable]
    public class SimplePID
    {
        public float Kp, Ki, Kd;
        private float P, I, D;

        private float lastPV = 0f;   

        public SimplePID()
        {
            Kp = 1f;
            Ki = 0f;
            Kd = 0.2f;
        }

        public SimplePID(float pFactor, float iFactor, float dFactor)
        {
            this.Kp = pFactor;
            this.Ki = iFactor;
            this.Kd = dFactor;
        }

        public float Update(float error, float PV, float dt)
        {
            P = error;
            I += error * dt;
            D = -(PV - lastPV) / dt;

            lastPV = PV;

            float CO = Kp * P + Ki * I + Kd * D;

            return CO;
        }
    }
}

И немного изменим метод ControlRotate:

public float ControlRotate(Vector2 rotate, float thrust)
{
     float CO = 0f;
     float MV = 0f;
     float dt = Time.fixedDeltaTime;

     //Вычисляем ошибку
     float angleError = Mathf.DeltaAngle(_myTransform.eulerAngles.z, targetAngle);

     //Получаем корректирующее ускорение
     CO = _angleController.Update(angleError, _myTransform.eulerAngles.z, dt);

     //Сатурируем
     MV = CO;
     if (CO > thrust) MV = thrust;
     if (CO < -thrust) MV = -thrust;

     return MV;
}

И-и-и-и… если запустить игру, то обнаружиться, что на самом деле ничего ничего не изменилось с последней попытки, что и требовалось доказать. Однако, если убрать сатурацию, то график реакции регулятора будет выглядеть вот так:
image
Скачок CO(t) по прежнему присутствует, однако он уже не такой большой как был в самом начале, а самое главное — он стал предсказуемым, т.к. обеспечивается исключительно пропорциональной составляющей, и ограничен максимально возможной ошибкой рассогласования и пропорциональным коэффициентом ПИД-регулятора (а это уже намекает на то, что Kp имеет смысл выбрать все же меньше единицы, например, 1/90f), но не зависит от шага сетки дифференцирования (т.е. dt). В общем, я настоятельно рекомендую использовать именно производную процесса, а не ошибки.

Думаю теперь никого не удивит, но таким же макаром можно заменить $K_p cdot e(t)$ на $-K_p cdot PV(t)$, однако останавливаться на этом мы не будем, можете сами поэкспериментировать и рассказать в комментариях, что из этого получилось (самому интересно)

Попытка номер четыре. Альтернативные реализации ПИД-регулятор

Помимо описанного выше идеального представления ПИД-регулятора, на практике часто применяется стандартная форма, без коэффициентов Ki и Kd, вместо которых используются временные постоянные.

Такой подход связан с тем, что ряд методик настройки ПИД-регулятора основан на частотных характеристиках ПИД-регулятора и процесса. Собственно вся ТАУ и крутится вокруг частотных характеристик процессов, поэтому для желающих углубиться, и, внезапно, столкнувшихся с альтернативной номенклатурой, приведу пример т.н. стандартной формы ПИД-регулятора:

$ e(t) = SP(t) - PV(t), $

$ CO(t) =CO_{bias} + K_p cdot Bigl(e(t) + frac{1}{T_i} cdot int_0^t e(tau)dtau - T_d cdot frac{dPV(t)}{dt} Bigl), $

где, $T_d= frac{K_d}{K_p}$ — постоянная дифференцирования, влияющая на прогнозирование состояния системы регулятором,
$T_i = frac{K_p}{K_i}$ — постоянная интегрирования, влияющая на интервал усреднения ошибки интегральным звеном.

Основные принципы настройки ПИД-регулятора в стандартной форме аналогичны идеализированному ПИД-регулятору:

  • увеличение пропорционального коэффициента увеличивает быстродействие и снижает запас устойчивости;
  • с уменьшением интегральной составляющей ошибка регулирования с течением времени уменьшается быстрее;
  • уменьшение постоянной интегрирования уменьшает запас устойчивости;
  • увеличение дифференциальной составляющей увеличивает запас устойчивости и быстродействие

Исходный код стандартной формы, вы можете найти под спойлером

namespace Assets.Scripts.Regulator
{
    [System.Serializable]    
    public class StandartPID
    {
        public float Kp, Ti, Td;
        public float error, CO;
        public float P, I, D;

        private float lastPV = 0f;

        public StandartPID()
        {
            Kp = 0.1f;
            Ti = 10000f;
            Td = 0.5f;
            bias = 0f;
        }

        public StandartPID(float Kp, float Ti, float Td)
        {
            this.Kp = Kp;
            this.Ti = Ti;
            this.Td = Td;
        }

        public float Update(float error, float PV, float dt)
        {
            this.error = error;
            P = error;
            I += (1 / Ti) * error * dt;
            D = -Td * (PV - lastPV) / dt;

            CO = Kp * (P + I + D);
            lastPV = PV;

            return CO;
        }
    }
}

В качестве значений по умолчанию, выбраны Kp = 0.01, Ti = 10000, Td = 0.5 — при таких значениях корабль поворачивается достаточно быстро и обладает некоторым запасом устойчивости.

Помимо такой формы ПИД-регулятора, часто используется т.н. реккурентная форма:

$ CO(t_k)=CO(t_{k-1})+K_pleft[left(1+dfrac{Delta t}{T_i}+dfrac{T_d}{Delta t}right)e(t_k)+left(-1-dfrac{2T_d}{Delta t}right)e(t_{k-1})+dfrac{T_d}{Delta t}e(t_{k-2})right] $

Не будем на ней останавливаться, т.к. она актуальна прежде всего для хардверных программистов, работающих с FPGA и микроконтроллерами, где такая реализация значительно удобнее и эффективнее. В нашем же случае — давайте что-нибудь сваям на Unity3D — это просто еще одна реализация ПИД-контроллера, которая ни чем не лучше других и даже менее понятная, так что еще раз дружно порадуемся как хорошо программировать в уютненьком C#, а не в жутком и страшном VHDL, например.

Вместо заключения. Куда бы еще присобачить ПИД-регулятор

Теперь попробуем немного усложнить управление корабля используя двухконтурное управление: один ПИД-регулятор, уже знакомый нам _angleController, отвечает по прежнему за угловое позиционирование, а вот второй — новый, _angularVelocityController — контролирует скорость поворота:

public float ControlRotate(float targetAngle, float thrust)
{
    float CO = 0f;
    float MV = 0f;
    float dt = Time.fixedDeltaTime;

    _angle = _myTransform.eulerAngles.z;

    //Контроллер угла поворота
    float angleError = Mathf.DeltaAngle(_angle, targetAngle);
    float torqueCorrectionForAngle = 
    _angleController.Update(angleError, _angle, dt);

    //Контроллер стабилизации скорости
    float angularVelocityError = -_rb2d.angularVelocity;
    float torqueCorrectionForAngularVelocity = 
        _angularVelocityController.Update(angularVelocityError, -angularVelocityError, dt);

    //Суммарный выход контроллера
    CO = torqueCorrectionForAngle + torqueCorrectionForAngularVelocity;

    //Дискретизируем с шагом 100            
    CO = Mathf.Round(100f * CO) / 100f;

    //Сатурируем
    MV = CO;
    if (CO > thrust) MV = thrust;
    if (CO < -thrust) MV = -thrust;

    return MV;
}

Назначение второго регулятора — гашение избыточных угловых скоростей, за счет изменения крутящего момента — это сродни наличию углового трения, которое мы отключили еще при создании игрового объекта. Такая схема управления [возможно] позволит получить более стабильное поведение корабля, и даже обойтись только пропорциональными коэффициентами управления — второй регулятор будет гасить все колебания, выполняя функцию, аналогичную дифференциальной составляющей первого регулятора.

Помимо этого, добавим новый класс ввода игрока — PlayerInputCorvette, в котором повороты буду осуществляться уже за счет нажатия клавиш «вправо-влево», а целеуказание с помощью мыши мы оставим для чего-нибудь более полезного, например, для управления турелью. Заодно у нас теперь появился такой параметр как _turnRate — отвечающий за скорость/отзывчивость поворота (не понятно только куда его поместить лучше в InputCOntroller или все же Ship).

public class PlayerCorvetteInput : BaseInputController
{
     public float _turnSpeed = 90f;

     public override void ControlRotate()
     {
         // Находим указатель мыши
         Vector3 worldPos = Input.mousePosition;
         worldPos = Camera.main.ScreenToWorldPoint(worldPos);

         // Сохраняем относительные координаты указателя мыши
         float dx = -this.transform.position.x + worldPos.x;
         float dy = -this.transform.position.y + worldPos.y;

         //Передаем направление указателя мыши
         Vector2 target = new Vector2(dx, dy);
         _agentBody._target = target;

         //Вычисляем поворот в соответствии с нажатием клавиш
         _agentBody._rotation -= Input.GetAxis("Horizontal") * _turnSpeed * Time.deltaTime;
    }

    public override void ControlForce()
    {            
         //Передаем movement
         _agentBody._movement = Input.GetAxis("Vertical") * Vector2.up;
    }
}

Также для наглядности накидаем на коленках скрипт для отображения отладочной информации

namespace Assets.Scripts.SpaceShooter.UI
{
    [RequireComponent(typeof(Ship))]
    [RequireComponent(typeof(BaseInputController))]
    public class Debugger : MonoBehaviour
    {
        Ship _ship;
        BaseInputController _controller;
        List<SimplePID> _pids = new List<SimplePID>();
        List<string> _names = new List<string>();

        Vector2 _orientation = new Vector2();

        // Use this for initialization
        void Start()
        {
            _ship = GetComponent<Ship>();
            _controller = GetComponent<BaseInputController>();

            _pids.Add(_ship._angleController);
            _names.Add("Angle controller");

            _pids.Add(_ship._angularVelocityController);
            _names.Add("Angular velocity controller");

        }

        // Update is called once per frame
        void Update()
        {
            DrawDebug();
        }

        Vector3 GetDiretion(eSpriteRotation spriteRotation)
        {
            switch (_controller._spriteOrientation)
            {
                case eSpriteRotation.Rigth:
                    return transform.right;
                case eSpriteRotation.Up:
                    return transform.up;
                case eSpriteRotation.Left:
                    return -transform.right;
                case eSpriteRotation.Down:
                    return -transform.up;
            }
            return Vector3.zero;
        }

        void DrawDebug()
        {
            //Направление поворота
            Vector3 vectorToTarget = transform.position 
                + 5f * new Vector3(-Mathf.Sin(_ship._targetAngle * Mathf.Deg2Rad), 
                    Mathf.Cos(_ship._targetAngle * Mathf.Deg2Rad), 0f);

            // Текущее направление
            Vector3 heading = transform.position + 4f * GetDiretion(_controller._spriteOrientation);

            //Угловое ускорение
            Vector3 torque = heading - transform.right * _ship._Torque;

            Debug.DrawLine(transform.position, vectorToTarget, Color.white);
            Debug.DrawLine(transform.position, heading, Color.green);
            Debug.DrawLine(heading, torque, Color.red);
        }

        void OnGUI()
        {
            float x0 = 10;
            float y0 = 100;

            float dx = 200;
            float dy = 40;

            float SliderKpMax = 1;
            float SliderKpMin = 0;
            float SliderKiMax = .5f;
            float SliderKiMin = -.5f;
            float SliderKdMax = .5f;
            float SliderKdMin = 0;

            int i = 0;
            foreach (SimplePID pid in _pids)
            {
                y0 += 2 * dy;

                GUI.Box(new Rect(25 + x0, 5 + y0, dx, dy), "");

                pid.Kp = GUI.HorizontalSlider(new Rect(25 + x0, 5 + y0, 200, 10), 
                    pid.Kp, 
                    SliderKpMin, 
                    SliderKpMax);
                pid.Ki = GUI.HorizontalSlider(new Rect(25 + x0, 20 + y0, 200, 10), 
                    pid.Ki, 
                    SliderKiMin, 
                    SliderKiMax);
                pid.Kd = GUI.HorizontalSlider(new Rect(25 + x0, 35 + y0, 200, 10), 
                    pid.Kd, 
                    SliderKdMin, 
                    SliderKdMax);

                GUIStyle style1 = new GUIStyle();
                style1.alignment = TextAnchor.MiddleRight;
                style1.fontStyle = FontStyle.Bold;
                style1.normal.textColor = Color.yellow;
                style1.fontSize = 9;

                GUI.Label(new Rect(0 + x0, 5 + y0, 20, 10), "Kp", style1);
                GUI.Label(new Rect(0 + x0, 20 + y0, 20, 10), "Ki", style1);
                GUI.Label(new Rect(0 + x0, 35 + y0, 20, 10), "Kd", style1);

                GUIStyle style2 = new GUIStyle();
                style2.alignment = TextAnchor.MiddleLeft;
                style2.fontStyle = FontStyle.Bold;
                style2.normal.textColor = Color.yellow;
                style2.fontSize = 9;

                GUI.TextField(new Rect(235 + x0, 5 + y0, 60, 10), pid.Kp.ToString(), style2);
                GUI.TextField(new Rect(235 + x0, 20 + y0, 60, 10), pid.Ki.ToString(), style2);
                GUI.TextField(new Rect(235 + x0, 35 + y0, 60, 10), pid.Kd.ToString(), style2);

                GUI.Label(new Rect(0 + x0, -8 + y0, 200, 10), _names[i++], style2);
            }
        }
    }
}

Класс Ship также претерпел необратимые мутации и теперь должен выглядеть вот так:

namespace Assets.Scripts.SpaceShooter.Bodies
{
    public class Ship : BaseBody
    {
        public GameObject _flame;

        public Vector2 _movement = new Vector2();
        public Vector2 _target = new Vector2();

        public float _targetAngle = 0f;
        public float _angle = 0f;

        public float _thrust = 1f;

        [Header("PID")]
        public SimplePID _angleController = new SimplePID(0.1f,0f,0.05f);
        public SimplePID _angularVelocityController = new SimplePID(0f,0f,0f);

        private float _torque = 0f;
        public float _Torque
        {
            get
            {
                return _torque;
            }
        }

        private Vector2 _force = new Vector2();
        public Vector2 _Force
        {
            get
            {
                return _force;
            }
        }

        public void FixedUpdate()
        {
            _torque = ControlRotate(_targetAngle, _thrust);
            _force = ControlForce(_movement, _thrust);

            _rb2d.AddTorque(_torque);
            _rb2d.AddRelativeForce(_force);
        }

        public float ControlRotate(float targetAngle, float thrust)
        {
            float CO = 0f;
            float MV = 0f;
            float dt = Time.fixedDeltaTime;

            _angle = _myTransform.eulerAngles.z;

            //Контроллер угла поворота
            float angleError = Mathf.DeltaAngle(_angle, targetAngle);
            float torqueCorrectionForAngle = 
                _angleController.Update(angleError, _angle, dt);

            //Контроллер стабилизации скорости
            float angularVelocityError = -_rb2d.angularVelocity;
            float torqueCorrectionForAngularVelocity = 
                _angularVelocityController.Update(angularVelocityError, -angularVelocityError, dt);

            //Суммарный выход контроллера
            CO = torqueCorrectionForAngle + torqueCorrectionForAngularVelocity;

            //Дискретизируем с шагом 100            
            CO = Mathf.Round(100f * CO) / 100f;

            //Сатурируем
            MV = CO;
            if (CO > thrust) MV = thrust;
            if (CO < -thrust) MV = -thrust;

            return MV;
        }

        public Vector2 ControlForce(Vector2 movement, float thrust)
        {
            Vector2 MV = new Vector2();

            if (movement != Vector2.zero)
            {
                if (_flame != null)
                {
                    _flame.SetActive(true);
                }
            }
            else
            {
                if (_flame != null)
                {
                    _flame.SetActive(false);
                }
            }

            MV = movement * thrust;

            return MV;
        }

        public void Update()
        {

        }        
    }
}

А вот, собственно заключительное видео того, что должно получиться:

К сожалению получилось охватить не все, что хотелось бы, в частности почти не затронут вопрос настройки ПИД-регулятора и практически не освящена интегральная составляющая — фактически приведен пример только для ПД-регулятора. Собственно изначально планировалось несколько больше примеров (круиз-контроль, вращение турели и компенсация вращательного момента), но статья итак уже разбухла, да и вообще:
image

Немного ссылок

  1. Годная статья на английской вики
  2. PID tutorial
  3. ПИД-регуляторы: вопросы реализации. Часть 1
  4. ПИД-регуляторы: вопросы реализации. Часть 2
  5. PID Without a PhD
  6. PID Without a PhD. Перевод
  7. Derivative Action and PID Control
  8. Control System Lab: PID
  9. ПИД-регулятор своими руками
  10. Корректная реализация разностной схемы ПИД регулятора
  11. Программируем квадрокоптер на Arduino (часть 1)
  12. Виртуальный квадрокоптер на Unity + OpenCV (Часть 1)
  13. Поляков К.Ю. Теория автоматического управления для чайников
  14. PID control system analysis, design, and technology
  15. Aidan O’Dwyer. Handbook of PI and PID Controller Tuning Rules (3rd ed.)
  16. PID process control, a “Cruise Control” example
  17. https://www.mathworks.com/discovery/pid-control.html
  18. http://scilab.ninja/study-modules/scilab-control-engineering-basics/module-4-pid-control/
  19. https://sourceforge.net/p/octave/control/ci/default/tree/inst/optiPID.m

Еще немного ссылок на другие примеры
http://luminaryapps.com/blog/use-a-pid-loop-to-control-unity-game-objects/
http://www.habrador.com/tutorials/pid-controller/3-stabilize-quadcopter/
https://www.gamedev.net/articles/programming/math-and-physics/pid-control-of-physics-bodies-r3885/
https://ksp-kos.github.io/KOS/tutorials/pidloops.html

7.1. Статическая ошибка по управлению и возмущению

Установившийся
режим работы системы автоматического
управления характеризуется окончанием
переходного процесса. В этом случае
выходные переменные или являются
постоянными величинами (статический
режим работы, рис. 7.1), или изменяются с
постоянной ошибкой в соответствии с
изменением входного сигнала (динамический
постоянный процесс, рис. 7.2).

Рисунок
7.1 — Статический режим работы

Рисунок
7.2 — Динамический постоянный процесс

Основной
характеристикой установившегося режима
является статическая ошибка.

При наличии
единичной обратной связи, ошибка
определяется как разность между входным
воздействиеми выходной переменнойв установившемся режиме (рис. 7.1, 7.2).
Различают статическую ошибку по
управлению и по возмущению. Статическая
ошибка по управлению характеризует
ошибку при отработке управляющего
сигнала, а ошибка по возмущению —
отклонение управляемого параметра в
установившемся режиме под действием
возмущения.

Статическую ошибку
можно определить из передаточной функции
системы, используя свойство преобразования
Лапласа. Если в оригинале время
,
то в выражении изображения.

Рассмотрим
вычисление статической ошибки по
управлению для системы, структурная
схема которой представлена на рис. 7.3.

Рисунок
7.3 — Структурная схема замкнутой системы
регулирования

Статическая ошибка
равняется:

.

(7.1)

Будем полагать,
что
— звено регулятора, а— звено объекта управления.

Для примера возьмем
следующие передаточные функции:

,

(7.2)

,

(7.3)

Передаточная
функция замкнутой системы регулирования
по управлению:

(7.4)

Если приравнять
,
то передаточная функция будет иметь
вид:

.

(7.5)

Статическая ошибка
по управлению (рис. 7.4):

Рисунок
7.4 — График переходных процессов в системе
при отсутствии интегратора в регуляторе
и в объекте регулирования

.

(7.6)

Из формулы (7.6)
видим, что чем больше коэффициент
усиления регулятора, тем меньше ошибка
по управлению.

Определим статическую
ошибку по возмущению, используя
структурную схему на рис. 7.5.

Рисунок
7.5 — Структурная схема замкнутой системы
регулирования с возмущающим воздействием

Передаточная
функция замкнутой системы регулирования
по возмущению:

.

(7.7)

Учитывая то, что
,,
получим:

(7.8)

При
передаточная функция равняется:

.

(7.9)

Статическая ошибка
по возмущению (рис. 7.4):

.

(7.10)

Из формулы (7.10)
видно, что чем больше коэффициент
усиления регулятора, тем меньше влияет
возмущение на отклонение управляемого
параметра
.

Таким образом, при
наличии пропорционального регулятора,
замкнутая система регулирования
(регулятор и объект регулирования не
содержат интегратора) имеет статическую
ошибку по управлению и по возмущению.

Рассмотрим в
качестве регулятора интегрирующее
звено:

.

(7.11)

При интегральном
регуляторе передаточная функция
замкнутого контура по управлению примет
вид:

(7.12)

При
.

Ошибка по управлению
в соответствии с уравнением (7.1) равна
нулю (рис. 7.6):

.

(7.13)

Рисунок
7.6 — График переходных процессов в системе
при наличии интегратора в регуляторе.

Эквивалентная
передаточная функция по возмущению при
интегральном регуляторе имеет вид:

(7.14)

При
.
Таким образом, статическая ошибка по
возмущению, при наличии интегратора в
составе регулятора, равна нулю (рис. 7.6):

.

(7.15)

Таким образом,
применение регулятора в виде интегрирующего
звена приводит к устранению статической
ошибки по управлению и возмущению.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #

Уменьшение — статическая ошибка

Cтраница 3

Эта ошибка возрастает также, если в следящей САУ имеются упругие механические звенья. Уменьшения статической ошибки можно добиться, применяя в САУ специальные методы коррекции, а также путем сочетания принципа регулирования по отклонению с принципом регулирования по возмущению.
 [31]

Из рисунка видно, что в первый период внезапного отклонения величины от заданного значения решающее влияние на перемещение исполнительного механизма оказывает пропорциональная составляющая, которая в состоянии вернуть регулируемую величину к заданному значению с некоторой статической ошибкой. Уменьшение статической ошибки осуществляется в результате воздействия астатической части, которая в основном и служит для ликвидации остаточного отклонения.
 [32]

Эта ошибка возрастает также, если в следящей САУ имеются упругие механические звенья. Уменьшения статической ошибки можно добиться, применяя в САУ специальные методы коррекции, а также путем сочетания принципа регулирования по отклонению с принципом регулирования по возмущению.
 [33]

Введем последовательную коррекцию в систему, заменив статическое звено с коэффициентом передачи K ( s) 0 15 звеном апериодическим. Для уменьшения статической ошибки дополнительно включим интегратор, действие которого прекращается при повышении частоты с помощью дифференцирующего звена.
 [34]

Q не должен превышать значения, при котором запас устойчивости системы по фазе будет меньше допустимого. Усиление сигнала ошибки ывых ( /) по мощности и возможность уменьшения статической ошибки в соответствии с формулой АЭДОП является принципиальным отличием позиционной следящей системы с измерительной схемой СД-СТ от индикаторной схемы СД-СП.
 [36]

Соотношение (5.88) показывает, что при отсутствии в системе интегрирующих звеньев ( разомкнутая система — статическая) постоянные воздействия ga и / 0 вызывают постоянную установившуюся ошибку е0 которую называют статической. Эта ошибка будет тем меньше, чем больше коэффициент усиления / С системы, причем для уменьшения статической ошибки, вызываемой возмущающим воздействием, следует для увеличения К увеличивать коэффициент Къ регулятора, а не / Ci объекта.
 [37]

Одной из причин постепенных отказов регуляторов является дрейф нуля, который имеет место у ряда устройств и в первую очередь у электронных и полупроводниковых усилителей постоянного тока. Будем предполагать, что до наступления постепенного отказа обслуживающий персонал не вмешивается в работу регулятора с целью уменьшения статической ошибки путем компенсации сигнала дрейфа.
 [39]

Это обстоятельство позволяет косвенным образом улучшить и статические показатели качества регулирования. Действительно, если увеличение коэффициента усиления регулятора было ограничено недостаточной устойчивостью переходного процесса, то введение производной, повышающей демпфирование системы, позволит увеличить и значение статического коэффициента усиления регулятора, что приведет к уменьшению статической ошибки системы.
 [40]

Квантование сигналов по времени и по уровню сказывается по-разному на эффективности действия трех основных компонент ШД-закона регулирования. При этом для повышения быстродействия системы, определяемого дифференциальной и пропорциональной составляющими, следует сокращать интервал между моментами передачи управляющего воздействия; в то же время этот интервал практически не влЕяет на свойства интегральной составляющей, однако для уменьшения статической ошибки, как показано выше, его следовало бы увеличить.
 [41]

Пропорционально-дифференциальные регуляторы более сложны по структуре, чем пропорциональные и интегральные. Наличие двух параметров настройки усложняет методику выбора регулятора. Применение прпорционально-дифференциалъных регуляторов дает возможность увеличивать общее усиление при сохранении устойчивости системы, что резко улучшает качество протекания переходных процессов. Кроме того, пропорционально-дифференциальные регуляторы пригодны для стабилизации регулируемых систем с двумя нейтральными звеньями или с одним нейтральным и одним неустойчивым звеном. Повышение общего коэффициента усиления цепи регулирования приводит к уменьшению статических ошибок.
 [42]

Эта система применена фирмой Торр ( США) для привода координатно-расточных станков повышенной точности. Поворот вала ШД приводит к смещению сердечника дифференциального трансформатора. Возникающий сигнал рассогласования поступает на обмотку управления ОУ / электромашннного усилителя. От ЭМУ питается двигатель постоянного тока, перемещающий механизм. При этом одновременно с леремещением механизма втулка сердечника дифференциального трансформатора, кинематически связанная с силовым валом механизма, возвращается в исходное состояние. Сигнал рассогласования становится равным нулю. Для уменьшения динамических и статических ошибок а системе предусматриваются задание и обратная связь по скорости. Задание скорости производится по частоте импульсов, управляющих ШД.
 [43]

Описанная схема усилителя особенно эффективна для управления небольшими двигателями, работающими на частоте 400 гц. Хотя в собственно усилителе потери всегда малы, наличие проводимости дросселей при нулевом сигнале на выходе является причиной потерь мощности на нагрев обмоток двигателя, когда он не работает. Однако эта мощность не является полностью потерянной, так как в силу тормозящего действия постоянного тока в управляющей обмотке двигателя она выполняет очень полезную функцию стабилизации системы. Для двигателей — большей мощности это увеличение потерь может в значительной мере определять нагрев двигателя и привести к снижению его номинальной мощности. Последнее может послужить препятствием применению данной схемы. На частоте 60 гц такой усилитель может быть использован и для управления двигателями очень малой мощности, однако колебания вала с двойной частотой, вызванные взаимодействием постоянных и переменных полей в машине, могут нарушить точность работы двигателя при нуле. Амплитуда этих колебаний зависит от инерции ротора и частоты колебаний. В устройствах, работающих на частоте 400 гц, амплитуда колебаний достаточно мала. Здесь эти колебания могут быть использованы для устранения возможных механических заеданий в системе и уменьшения статической ошибки.
 [44]

Страницы:  

   1

   2

   3

3. Пропорционально-интегральное регулирование (ПИ-закон)

Чтобы убрать статическую ошибку в установившемся режиме, в регулятор вводят интегральный канал с коэффициентом усиления $K_I$, так что

$$C(s)=K+frac{K_I}{s},$$
$$u(t)=Ke(t)+K_I ∫_0^t e(t)mathrm{d}t.$$

Такой регулятор называется пропорционально-интегральным или ПИ-регулятором. Интегратор выдает сигнал, пропорциональный накопленной ошибке, поэтому переходный процесс несколько замедляется. Однако за счет интегрального канала обеспечивается нулевая ошибка в установившемся состоянии при ступенчатом возмущении и ступенчатом изменении задающего сигнала-уставки.

На рисунке ниже показаны переходные характеристики замкнутой системы с И-регулятором и объектом второго порядка вида

Реакция на скачок замкнутой системы с объектом 2-го порядка с И-регулятором.

Реакция на скачок замкнутой системы с объектом 2-го порядка с И-регулятором.

При больших постоянных интегрирования переходная характеристика имеет вид, сходный с характеристикой апериодического звена. С уменьшением постоянных интегрирования растет усиление регулятора и когда петлевое усиление контура с обратной связью приближается к 1, в системе появляются колебания.

ПИ-регулятор имеет два существенных положительных отличия от И-регулятора: во-первых, его усиление на всех частотах не может стать меньше $K$, следовательно, увеличивается динамическая точность регулирования, во-вторых, по сравнению с И-регулятором, он вносит дополнительный сдвиг фаз только в области низких частот, что увеличивает запас устойчивости замкнутой системы. Оба фактора дают дополнительные степени свободы для оптимизации качества регулирования. В то же время, как и в И-регуляторе, модуль коэффициента передачи регулятора с уменьшением частоты стремится к бесконечности, обеспечивая тем самым нулевую ошибку в установившемся режиме. Отсутствие сдвига фаз на высоких частотах позволяет увеличить скорость нарастания управляемой переменной (по сравнению с И-регулятором) без снижения запаса устойчивости. Однако это справедливо до тех пор, пока пропорциональный коэффициент не станет настолько большой, что увеличит усиление контура до единицы.

Переходный процесс в ПИ-регуляторе показан на рисунке ниже.

Реакция замкнутой системы с ПИ регулятором на скачок .

Реакция замкнутой системы с ПИ регулятором на скачок .

С ростом пропорционального коэффициента появляется дополнительная ошибка во время переходного процесса, которая уменьшается с ростом $K$, однако при этом снижается запас устойчивости системы, поскольку с ростом $K$ увеличивается усиление на частоте . Это приводит к появлению затухающих колебаний в начале переходного процесса. Когда величина становится достаточно большой для компенсации ослабления сигнала в объекте, в системе появляются незатухающие колебания.

Следует отметить, что в отличие от П-регулятора, в котором ошибка остается в установившемся режиме, наличие интегрального члена в ПИ-регуляторе сводит эту ошибку в идеальном регуляторе до нуля, как в И-регуляторе.

Однако появление пропорционального коэффициента приводит к затягиванию переходного процесса по сравнению с И-регулятором. Объясняется это тем, что в ПИ-регуляторе сигнал ошибки, поступающий на вход интегратора, меньше, чем в И-регуляторе (он уменьшается благодаря пропорциональному коэффициенту), поэтому сигнал, компенсирующий ошибку нарастает медленнее, чем в И-регуляторе.

При работе прибора в режиме ПИ-регулятора величина выходного сигнала $Y_i$ зависит как от величины отклонения $E_i$, так и от суммы предыдущих рассогласований:

$$Y_i=frac{1}{X_p}·left(E_i+frac{1}{τ_и}·sum_{i=0}^n E_i·∆t_{изм}right)·100%.$$

где $X_p$ – полоса пропорциональности; $E_i$ – рассогласование; $τ_и$ – постоянная времени интегрирования; $sum_{i=0}^n E_i·∆t_{изм}$ – накопленная в i-й момент времени сумма рассогласований (интегральная сумма).

Из рисунка видно, что в первый момент времени, когда нет отклонения ($E_i=0$), нет и выходного сигнала ($Y_i=0$). С появлением отклонения $E_i$ появляются импульсы, длительность которых постепенно увеличивается. В импульсах присутствует пропорциональная составляющая, которая зависит от величины $E$ (незаштрихованная часть импульсов) и интегральная составляющая (заштрихованная часть). Увеличение длительности импульсов происходит за счет роста интегральной составляющей, которая зависит от рассогласования $E_i$ и коэффициента $τ_и$.

Выходной сигнал ПИ-регулятора и длительность управляющих импульсов при различных значениях $τ_и$ и $E=10$.

Выходной сигнал ПИ-регулятора и длительность управляющих импульсов при различных значениях $τ_и$ и $E=10$.

Устройство относится к области средств автоматизации и может использоваться в системах управления технологическими процессами и объектами в химической промышленности, теплотехнике, энергетике. Технический результат — повышение точности управления в системах стабилизации с предлагаемым устройством в условиях действия как сигнальных, так и параметрических возмущающих воздействий на объект. Устройство содержит сумматор, интегратор и усилитель. Устройство автоматически устраняет статическую ошибку при его использовании в системах стабилизации динамических объектов путем масштабирования задающего воздействия. Пример конкретного выполнения регулятора реализован на пневматических элементах УСЭППА. 4 ил.

Устройство относится к области средств автоматизации и может применяться в системах автоматического управления динамическими объектами различной природы (в химической промышленности, тепло- и электроэнергетике).

Качество работы системы автоматического управления в установившемся режиме работы оценивается по величине статической ошибки, равной разности между требуемым и действительным значениями регулируемой величины в установившемся режиме работы системы. В системах стабилизации с линейным объектом ошибка системы может быть найдена по известному выражению [см., например, кн. Теория автоматического управления. 4.1. Под ред А.В.Нетушила. М.: Высшая школа. — 1967. — С.198., Бесекерский В.А., Попов Е.П. Теория систем автоматического регулирования. — М.: Наука, 1972. — С.203.]:

где x0(s) и F(s) — изображения требуемого значения регулируемой величины (уставки) и возмущающего воздействия соответственно, W(s) — передаточная функция разомкнутой системы, Fk(s) и Wk(s) — возмущение и соответствующая ему передаточная функция по возмущению. Применительно к выражению (1) передаточные функции разомкнутой системы и по возмущению дают возможность в символической или операторной форме записать дифференциальное уравнение, связывающее ошибку с входными воздействиями:

где

— алгебраический оператор дифференцирования.

Статическая ошибка в соответствии с теоремой о предельном переходе в системах стабилизации при условии, что и возмущения fk(t)=dk0=const, будет иметь следующий вид:

Первое слагаемое этого выражения представляет собой составляющую ошибки, определяемую задающим воздействием. Эта составляющая ошибки может быть отличной от нуля в системах стабилизации динамических объектов без астатизма с пропорциональным регулятором. В этом случае W(p)=k представляет собой общий коэффициент усиления по разомкнутой цепи и первое слагаемое в выражении (3) может быть представлено в виде

Эта составляющая ошибки практически может быть уменьшена путем увеличения коэффициента k и сведена к нулю при астатическом регулировании, когда W(0)→∞. Вторая составляющая никогда не обращается в нуль, если возмущающее воздействие приложено до интегрирующего звена.

Существуют устройства, позволяющие устранить статическую ошибку без использования интегрирующих элементов. Достигается это путем использования неединичной обратной связи в замкнутых системах стабилизации (см. Бесекерский В.А., Попов Е.П. Теория систем автоматического регулирования. — М.: Наука, 1972. — С.261) либо путем масштабирования входного воздействия x0 или выходной величины x (там же, с.262).

Наиболее близким по своей сущности к заявляемому устройству является устройство, представленное в книге Бесекерский В.А., Попов Е.П. Теория систем автоматического регулирования. — М.: Наука, 1972. — С.261, рис.9.16. Недостаток представленного устройства состоит в том, что он оказывается малопригоден для практического использования в условиях неопределенности объекта и среды, при действии параметрических и меняющихся сигнальных возмущений.

Техническим результатом изобретения является повышение точности управления в системах стабилизации с предлагаемым устройством в условиях действия как сигнальных, так и параметрических возмущающих воздействий на объект.

Для этого в заявляемое устройство введен сумматор, первый вход которого соединен с первым входом устройства и первым входом интегратора, выход которого связан со вторым входом сумматора, выход которого соединен с первым входом усилителя, второй вход которого связан со вторым входом устройства, выход которого подключен к выходу устройства.

Устройство изображено на фиг.1, где представлена его блок-схема, фиг.2 иллюстрирует пример конкретного выполнения регулятора на пневматических элементах УСЭППА, на фиг.3 и 4 представлены результаты исследования процессов в системе с предложенным устройством методом цифрового моделирования.

Устройство содержит (фиг.1) сумматор 1, усилитель разности двух сигналов 2, интегратор 3, x0 — первый вход устройства (сигнал задания), x — второй вход устройства (переменная), U — выходной сигнал устройства.

Первый вход устройства x0 подключен к первому входу сумматора 1 и первому входу интегратора 3. Второй вход устройства (переменная — x) соединен со вторыми входами усилителя 2 и интегратора 3. Выход интегратора 3 соединен со вторым входом сумматора 1, выход которого связан с первым входом усилителя 2, выход которого подключен к выходу устройства U.

Подобное соединение элементов позволяет реализовать в устройстве следующий закон управления:

где

k1, k2 и k3 — постоянные коэффициенты.

Рассмотрим работу устройства в целом и его отдельных элементов.

Сумматор — это или дроссельный (пневматический), или резисторный сумматор, или программный продукт. Усилитель и интегратор — также стандартные элементы. Пример их конкретного исполнения представлен на фиг.2.

Интегратор 3 (фиг.2) содержит повторитель 8, усилитель 9, дроссель 7 и пневмоемкость 6. Входами его являются сигналы х0 и х. Это типовое устройство, построенное на усилителе 9, охваченном инерционной положительной обратной связью, и формирующее на своем выходе линейно нарастающий сигнал, пропорциональный разности двух сигналов, действующих на его входах.

Усилитель 2 помимо элемента 4 содержит дроссельный сумматор 5, работающий по принципу делителя, входом которого является сигнал с выхода усилителя 2, а выход, соединенный с камерой элемента 4, реализует неединичную обратную связь. На входы усилителя 4 также поступают два сигнала: на прямой вход — с сумматора 1, а на второй — с первого входа устройства x. Это стандартный элемент.

Сумматор 1 построен по схеме дроссельного сумматора и реализует операцию по формуле k1·x0+(1-k1)·xи, где коэффициент k1 меньше единицы, его значение определяется настройкой переменного дросселя в сумматоре, а xи — выходной сигнал интегратора, подключенный ко второму входу сумматора.

Работу устройства рассмотрим по фиг.2 и 3 в предположении, что его выходной сигнал U подключен к входу объекта управления, а выходной сигнал объекта подключен ко второму входу устройства. Тогда в момент включения устройства в его выходной линии U формируется сигнал управления с выхода усилителя 2, вызванный задающим сигналом (уставкой) x0. Этот сигнал проходит через сумматор 1 на прямой вход усилителя 2. Под воздействием выходного сигнала усилителя 2 на втором входе устройства x появляется сигнал с выхода объекта, который сравнивается в интеграторе 3 с заданным x0. Одновременно сигнал x поступает на инверсный вход усилителя 3, где сравнивается с сигналом, который формируется в сумматоре 2. Сигнал на выходе сумматора 2 складывается из суммы двух сигналов: масштабированного входного сигнала х0 и выходного сигнала интегратора 3. Выходной сигнал интегратора 3 изменяется до тех пор, пока разность между сигналом x и заданным его значением x0 не станет равной нулю, что и является свидетельством отсутствия статической ошибки в системе. Сигнал х0 при этом сдвигается в ту или иную сторону на величину статической ошибки. Статическая ошибка в системе всегда будет равна нулю, независимо от того, вызвана ли она сигнальными или параметрическими возмущающими воздействиями на объект.

Следует также обратить внимание на то, что в прямой цепи управления объектом нет интегрирующих звеньев (только усилитель, выполняющий роль пропорционального регулятора). Как известно из теории автоматического управления, наличие в прямой цепи управления интегрирующего звена снижает при прочих равных условиях запас устойчивости системы управления объектом, делает ее менее надежной и ухудшает показатели качества управления в условиях неопределенности параметров объекта и среды.

На фиг.3 и фиг.4 приведены процессы в системе с заявляемым устройством, полученные методом цифрового моделирования. В качестве объекта исследовалась модель с передаточной функцией W(s)=1-5/(4.76·s+1)(1.98·s+1)(0.32·s+1).

На фиг.3 приведены процессы изменения выходной координаты в условиях, когда интегратор 3 был отключен. В результате статическая ошибка в системе имеется. На фиг.4 представлены те же процессы (при прочих равных условиях) с подключенным интегратором 3. Статическая ошибка в системе отсутствует. Здесь x0(t)=k1·x0+(1-k1)·a(t). Результаты исследований в условиях воздействия постоянно действующих параметрических и сигнальных возмущений на объект при необходимости могут быть представлены дополнительно. Но они мало чем отличаются от результатов, представленных на фиг.4.

Таким образом, данное устройство решает задачу обеспечения равенства регулируемой координаты в установившемся режиме работы системы его заданному значению путем автоматического масштабирования сигнала x0 (в сторону увеличения или уменьшения) на заранее неизвестную величину, которая определяется параметрами объекта и среды, которые могут быть известны не точно.

Самонастраивающееся устройство для устранения статической ошибки в автоматических системах стабилизации динамических объектов, содержащее интегратор, первый и второй входы которого соединены с первым и вторым входами устройства, и усилитель разности двух сигналов, инверсный вход которого подключен ко второму входу устройства и второму входу интегратора, а выход — к выходу устройства, отличающееся тем, что в него дополнительно включен сумматор, первый вход которого соединен с первым входом устройства, второй вход — с выходом интегратора, а выход — с прямым входом усилителя.

  • Как изменить эту ошибку
  • Как изменить состояние принтера ошибка
  • Как избежать ошибок при строительстве беседки
  • Как изменить ошибку при регистрации
  • Как избежать ошибок при составлении бизнес плана