ПИД-регулятор

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

/**
 * https://github.com/wilberforce/pid-controller/tree/master
 * pid-controller -  A node advanced PID controller based on the Arduino PID library
 * github@wilberforce.co.nz Rhys Williams
 * Based on:
 * Arduino PID Library - Version 1.0.1
 * by Brett Beauregard <br3ttb@gmail.com> brettbeauregard.com
 * 
 * This Library is licensed under a GPL-3.0 License
 */

//"use strict";
var PID = function(Input, Setpoint, Kp, Ki, Kd, ControllerDirection) {
    this.input = Input;
    this.mySetpoint = Setpoint;
    this.inAuto = false;
    this.setOutputLimits(0, 255); // default output limits
    this.SampleTime = 100; // default Controller Sample Time is 0.1 seconds
    this.setTunings(Kp, Ki, Kd);
    this.setControllerDirection(ControllerDirection);
    this.lastTime = this.millis() - this.SampleTime;

    this.ITerm = 0;
    this.myOutput = 0;
};

// Constants for backward compatibility
PID.AUTOMATIC = 1;
PID.MANUAL = 0;
PID.DIRECT = 0;
PID.REVERSE = 1;

PID.prototype.setInput = function(current_value) {
    this.input = current_value;
};

PID.prototype.setPoint = function(current_value) {
    this.mySetpoint = current_value;
};

PID.prototype.millis = function() {
    var d = new Date();
    return d.getTime();
};

/**
 * Compute()
 * This, as they say, is where the magic happens.  this function should be called
 * every time "void loop()" executes.  the function will decide for itself whether a new
 * pid Output needs to be computed.  returns true when the output is computed,
 * false when nothing has been done.
 */
PID.prototype.compute = function() {
    if (!this.inAuto) {
        return false;
    }
    var now = this.millis();
    var timeChange = (now - this.lastTime);
    if (timeChange >= this.SampleTime) {

        // Compute all the working error variables
        var input = this.input;
        var error = this.mySetpoint - input;
        this.ITerm += (this.ki * error);
        var dInput = input - this.lastInput;
        // Compute PID Output
        var output = (this.kp * error + this.ITerm - this.kd * dInput) * this.setDirection;

        if (output > this.outMax) {
            output = this.outMax;
        }
        else if (output < this.outMin) {
            output = this.outMin;
        }
        this.myOutput = output;

        // Remember some variables for next time
        this.lastInput = input;
        this.lastTime = now;
        return true;
    }
    else {
        return false;
    }
};

/**
 * SetTunings(...)
 * This function allows the controller's dynamic performance to be adjusted. 
 * it's called automatically from the constructor, but tunings can also
 * be adjusted on the fly during normal operation
 */
PID.prototype.setTunings = function(Kp, Ki, Kd) {
    if (Kp < 0 || Ki < 0 || Kd < 0) {
        return;
    }

    this.dispKp = Kp;
    this.dispKi = Ki;
    this.dispKd = Kd;

    this.SampleTimeInSec = (this.SampleTime) / 1000;
    this.kp = Kp;
    this.ki = Ki * this.SampleTimeInSec;
    this.kd = Kd / this.SampleTimeInSec;
};

/**
 * SetSampleTime(...)
 * sets the period, in Milliseconds, at which the calculation is performed	
 */
PID.prototype.setSampleTime = function(NewSampleTime) {
    if (NewSampleTime > 0) {
        var ratio = NewSampleTime / (1.0 * this.SampleTime);
        this.ki *= ratio;
        this.kd /= ratio;
        this.SampleTime = Math.round(NewSampleTime);
    }
};

/**
 * SetOutput( )
 * Set output level if in manual mode
 */
PID.prototype.setOutput = function(val) {
    if (val > this.outMax) {
        this.myOutput = val;
    }
    else if (val < this.outMin) {
        val = this.outMin;
    }
    this.myOutput = val;
};

/**
 * SetOutputLimits(...)
 * This function will be used far more often than SetInputLimits.  while
 * the input to the controller will generally be in the 0-1023 range (which is
 * the default already,)  the output will be a little different.  maybe they'll
 * be doing a time window and will need 0-8000 or something.  or maybe they'll
 * want to clamp it from 0-125.  who knows.  at any rate, that can all be done here.
 */
PID.prototype.setOutputLimits = function(Min, Max) {
    if (Min >= Max) {
        return;
    }
    this.outMin = Min;
    this.outMax = Max;

    if (this.inAuto) {
        if (this.myOutput > this.outMax) {
            this.myOutput = this.outMax;
        }
        else if (this.myOutput < this.outMin) {
            this.myOutput = this.outMin;
        }

        if (this.ITerm > this.outMax) {
            this.ITerm = this.outMax;
        }
        else if (this.ITerm < this.outMin) {
            this.ITerm = this.outMin;
        }
    }
};

/**
 * SetMode(...)
 * Allows the controller Mode to be set to manual (0) or Automatic (non-zero)
 * when the transition from manual to auto occurs, the controller is
 * automatically initialized
 */
PID.prototype.setMode = function(Mode) {
    var newAuto;
    if (Mode == PID.AUTOMATIC || Mode.toString().toLowerCase() == 'automatic' || Mode.toString().toLowerCase() == 'auto') {
        newAuto = 1;
    }
    else if (Mode == PID.MANUAL || Mode.toString().toLowerCase() == 'manual') {
        newAuto = 0;
    }
    else {
        throw new Error("Incorrect Mode Chosen");
    }

    if (newAuto == !this.inAuto) { //we just went from manual to auto
        this.initialize();
    }
    this.inAuto = newAuto;
};

/**
 * Initialize()
 * does all the things that need to happen to ensure a bumpless transfer
 * from manual to automatic mode.
 */
PID.prototype.initialize = function() {
    this.ITerm = this.myOutput;
    this.lastInput = this.input;
    if (this.ITerm > this.outMax) {
        this.ITerm = this.outMax;
    }
    else if (this.ITerm < this.outMin) {
        this.ITerm = this.outMin;
    }
};

/**
 * SetControllerDirection(...)
 * The PID will either be connected to a DIRECT acting process (+Output leads 
 * to +Input) or a REVERSE acting process(+Output leads to -Input.)  we need to
 * know which one, because otherwise we may increase the output when we should
 * be decreasing.  This is called from the constructor.
 */
PID.prototype.setControllerDirection = function(ControllerDirection) {
    if (ControllerDirection == 0 || ControllerDirection.toString().toLowerCase() == 'direct') {
        this.setDirection = 1;
    }
    else if (ControllerDirection == 1 || ControllerDirection.toString().toLowerCase() == 'reverse') {
        this.setDirection = -1;
    }
    else {
        throw new Error("Incorrect Controller Direction Chosen");
    }
};

/**
 * Status Functions
 * Just because you set the Kp=-1 doesn't mean it actually happened.  these
 * functions query the internal state of the PID.  they're here for display 
 * purposes.  this are the functions the PID Front-end uses for example
 */
PID.prototype.getKp = function() {
    return this.dispKp;
};

PID.prototype.getKd = function() {
    return this.dispKd;
};

PID.prototype.getKi = function() {
    return this.dispKi;
};

PID.prototype.getMode = function() {
    return this.inAuto ? "Auto" : "Manual";
};

PID.prototype.getDirection = function() {
    return this.controllerDirection;
};

PID.prototype.getOutput = function() {
    return this.myOutput;
};

PID.prototype.getInput = function() {
    return this.input;
};

PID.prototype.getSetPoint = function() {
    return this.mySetpoint;
};

module.exports = PID;

//-----------------------------------------------------------------
// Создаем виртуальное устройство
defineVirtualDevice("PIDTest", {
    title: "PID",
    cells: {
        // Топик температуры
        enabled: {
            type: "range",
            value: 20,
            max: 40,
            min: 10
        },
        // Уставка
        setPoint: {
            type: "range",
            value: 20,
            min: 15,
            max: 40 
        },
        // Вывод ПИД-а
        log1: {
            type: "text",
            value: ''
        },
        // Коэф. пропорциональной составляющей
        kp1: {
            type: "range",
            value: 1,
            min: 1,
            max: 100,
        },
        // Коэф интегральной составляющей
      	ki1: {
            type: "range",
            value: 0,
            min: 0,
            max: 100,
        }
    },
});
//--------------------------------------------------------
// Вывод информации о текущих параметрах ПИД-а
PID.prototype.getAllInfo = function(){
  	log('kp='+this.dispKp+' ki='+this.dispKi+' kd='+this.dispKd+' inp='+this.input+' SP='+this.mySetpoint);
};
// Время вызова
var timeframe = 2000
// Максимальное значение выхода ПИД-а
var maxOutput = 10000;

// Инициализация ПИД-а
var temperature = dev["PIDTest/enabled"];
var temperatureSetpoint =  dev["PIDTest/setPoint"];
var Kp = dev["PIDTest/kp1"],
    Ki = dev["PIDTest/ki1"],
    Kd = 0;
// direct - работа на обогрев, reverse - на охлаждение
var ctr = new PID(temperature, temperatureSetpoint, Kp, Ki, Kd, 'direct');
ctr.setSampleTime(timeframe);
ctr.setOutputLimits(0, maxOutput);
ctr.setMode('auto');

//цикл работы ПИД-а
var myControl = function() {
    // Актуализация (получение) параметров из панели
	var temperature = dev["PIDTest/enabled"]; 
  	var temperatureSetpoint =  dev["PIDTest/setPoint"]; 
  	var Kp = dev["PIDTest/kp1"],
        Ki = dev["PIDTest/ki1"],
    // Запись полученных/обновленных параметров в ПИД 	
  	ctr.setInput(temperature);
  	ctr.setPoint(temperatureSetpoint);
  	ctr.setTunings(Kp, Ki, Kd);
    // Работа ПИД-а
  	ctr.compute();
  	// Получение результата
  	var output = String(ctr.getOutput());
  	log("Output : " + output);

    dev["PIDTest/log1"] = output;
};
setInterval(myControl, timeframe);
1 Like

Добрый день.
Не пробовал еще - но сама реализация не сильно и отличается, немного понятнее, да.

Проверил, работает. Можно использовать.

Эта тема была автоматически закрыта через 7 дней после последнего ответа. В ней больше нельзя отвечать.