Плавное управление сетевым освещением + PID. Вопрос разработчикам.

Тема в разделе "Iskra JS, Espruino, Йодо", создана пользователем ИгорьК, 24 окт 2016.

  1. ИгорьК

    ИгорьК Гуру

    Коллеги! В продолжение этой темы, пытался управлять сетевой нагрузкой не абстрактно, а более менее осмысленно.
    1. Исходя из задачи, модифицировал библиотеку PID таким образом, чтобы она на выходе давала "обратные" значения: когда "далеко" от целевой точки - значение минимальное, когда близко - максимальное.
    Вот мой вариант библиотеки. Изменения выделил. Они минимальные, не думаю что нужен комментарий.
    Код (Javascript):
    var Pid = function (opts) {
        opts = opts || {};
        this._target = opts.target || 0;
        this._kp = opts.kp || 0;
        this._ki = opts.ki || 0;
        this._kd = opts.kd || 0;
        this._outputMin = opts.outputMin || 0;
        this._outputMax = (opts.outputMax === undefined) ? 1 : opts.outputMax;
        this._lastTime = null;
        this._intervalId = null;
        /*-------------*/
        this._direction = (opts.direction !== false) ? true : false;
        /*-------------*/
    };

    Pid.prototype._clearErrors = function () {
        this._sumError = 0;
        this._lastError = 0;
        this._lastTime = null;
    };

    Pid.prototype.setup = function (opts) {
        this._target = (opts.target === undefined) ? this._target : opts.target;
        this._kp = (opts.kp === undefined) ? this._kp : opts.kp;
        this._ki = (opts.ki === undefined) ? this._ki : opts.ki;
        this._kd = (opts.kd === undefined) ? this._kd : opts.kd;
        this._outputMin = (opts.outputMin === undefined) ? this._outputMin : opts.outputMin;
        this._outputMax = (opts.outputMax === undefined) ? this._outputMax : opts.outputMax;
        /*-------------*/
        this._direction = (opts.direction !== false) ? this._direction : false;
        /*-------------*/
        this._clearErrors();
    };

    Pid.prototype.tune = function (opts) {
        this._kp += opts.kp || 0;
        this._ki += opts.ki || 0;
        this._kd += opts.kd || 0;
        this._clearErrors();
    };

    Pid.prototype.update = function (input) {
        var dt = getTime() - this._lastTime;
        var error = this._target - input;
        var dError = 0;
        var integralNormalized = 0;
        var differential = 0;

        if (this._lastTime) {
            dError = error - this._lastError;
            this._sumError += error;
            integralNormalized = this._ki * this._sumError * dt;
            differential = this._kd * dError / dt;
            integralNormalized = E.clip(
                    integralNormalized,
                    this._outputMin,
                    this._outputMax);
        } else {
            this._clearErrors();
        }
        var output = this._kp * error + integralNormalized - differential;
        output = E.clip(output, this._outputMin, this._outputMax);
        this._lastError = error;
        this._lastTime = getTime();
        if (this._direction) {
            return output;
        /*-------------*/
        } else {
            return (this._outputMax - output + this._outputMin);
        }
        /*-------------*/
    };

    Pid.prototype.run = function (repeat, interval) {
        if (!this._intervalID) {
            this._intervalID = setInterval(
                    repeat, interval * 1000);
            this._clearErrors();
        }
    };

    Pid.prototype.stop = function () {
        if (this._intervalID) {
            clearInterval(this._intervalID);
        }
        this._intervalID = null;
    };

    exports.create = function (opts) {
        return new Pid(opts);
    };

    2. Управлял сетевым освещением следующим кодом:
    Код (Javascript):
    var cl = console.log;
    var myControl = require('pid_my').create({
      target: 30,           // требуемая величина выходного значения
       kp: 3,               // пропорциональная составляющая
       ki: 0.9,             // интегральная составляющая
       kd: 0.1,
    // kp: 5,              // пропорциональная составляющая
    // ki: -0.3,           // интегральная составляющая
    // kd: 0.2,            // дифференциальная составляющая
      outputMin: 0.2,       // минимальное значение выхода
      outputMax: 9,         // максимальное значение выхода
      direction: false        // обратная выдача управляющего сигнала
    });
    /*
    myControl.setup({target: 35});  // задаём новую уставку
    myControl.tune({kp: -0.05});  // Уменьшим пропорциональный коэффициент на 0,05
    myControl.stop();  // остановим ПИД-регулятор
    */


    // A0 - input AC detection, A1 - output to Lamp
    var pinWatchAC = A0;
    var pinOut = A1;

    // Значение текущей температуры
    var inputTemperature = 29;

    // Принимаем расчет от PID для фазового управления,
    // это время в мс от 0.2 до 9, причем 0.2 - полностью включено,
    // 9 - полностью выключено
    var runOutput = 9;

    setWatch(function(e) {
      digitalPulse(pinOut, 0, runOutput);
    }, pinWatchAC, {
      repeat: true,
      edge: 'rising',
    });

    myControl.run(function() {
      runOutput = myControl.update(inputTemperature);
    }, 0.05);

    cl(runOutput);
    setInterval(function(){
      cl(runOutput);
    },2000);

    Переменную inputTemperature менял вручную, чтобы наблюдать за работой PID.
    PID - работает.
    Фазовое управление по ссылке - работает.
    А вот вместе - работает плохо. Хаотично происходит срыв выхода ноги фазового управления. Некоторое время все нормально, некоторое - выход бьется в конвульсиях.
    Видео начинается с конвульсий, устаканивается к самому концу. При этом выход PID не изменялся, как не изменялось и значение управляющей переменной. Вот так:


    Вопрос разработчикам: где может скрываться враг? У меня кривые руки? Плата не заточена под два прерывания одновременно, особенно с учетом того, что планируется добавить пару UART и I2C память?
     
    Последнее редактирование: 24 окт 2016
  2. alp69

    alp69 Форумчанин

    Игорь, а на чистой синусоиде 220 вольт то же самое происходит? Не в качестве электричества дело?
     
  3. ИгорьК

    ИгорьК Гуру

    Нет. Осциллограф работает с восходящим фронтом детектора сети и показывает устойчивую картинку в этой части.
    Посмотрите внимательно осциллограмму.
     
  4. acos

    acos Официальный гик Администратор

    Как поняли, что выход не изменялся?
    У вас таргет стоит 30, а inputTemperature =29. При этом от значения выхода разница между уставкой и инпутом не меняется. По идее, от этого интегральная составляющая должна начать медленно расти, до тех пор, пока не пробьёт потолок, т.е. не станет равна output max. Но, по коду библиотеки, _sumError всегда растёт. Надо бы её занулить где-то...
    Давайте эксперимент проведём - что будет если убрать интегральную составляющую?
     
  5. ИгорьК

    ИгорьК Гуру

    Возможно я плохо обьяснил проблему. Дело не в алгоритме PID. И значения могут не меняться при разнице: сразу после загрузки скрипта PID ждет первого изменеия, выставив выход наполовину возможного. Именно этот момент на видео. Я загрузил скрипт и ничего не менял. Лампочка светит в полнакала и одновременно идет срыв выхода по факту - это видно на осциллографе.
    На скрипте в это время стабильно стоит что-то около 4 на выходе.
    Правильная реакция должна выглядеть так (это ардуино с прерываниями):



    В общем, работа PID (как программы) здесь не причем, проблема в запускающем ее таймере, который, судя по всему, так взаимодействует с setWatch, что вбивает последнюю из колеи.
     
  6. acos

    acos Официальный гик Администратор

    Пришла идея в голову. Попробуйте использовать какой-нибудь выход, который имеет хардварный PWM для pinOut
     
  7. Konkery

    Konkery Гик

    К сожалению нет времени проанализировать логику работы библиотеки и конечной системы в целом, но т.к. в свое время много порешал таких задач, а именно фазовое и не только управление подачей энергии на нагрузку (необходимо было стабилизировать температуру внутри хроматографа, штука такая разбирает компонентный состав вещества) так как порой точность стабилизации температуры требовалась 0.01 С, то приходилось зачастую решать задачу не в "лоб", а обходными путями. В качестве источника энергии (таково ТЗ) была не стабилизированная сетевая синусоида на промышленном предприятии, со всеми вытекающими (сильные помехи при переходе через ноль, коммутационные всплески с большим емкостью энергии и т.д). В таких случаях мы просто уходили от фазового регулирования, т.к. им реализовать точное управление (к тому же с нелинейной характеристикой) не удавалось. Один из способов перейти на регулировку методом пропуска периода/полупериода в зависимости от типа нагрузки и требуемой динамики процесса. Ну а самое точное управление мы получали переходя только на подготовленное точно стабилизированное постоянное напряжение, а дальше собственно ПИД который выставлял температуру в рабочих блоках. Возможно ваша задача элегантнее и надежнее решается по по другому ?
     
    9xA59kK и ИгорьК нравится это.