This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm that you accept these cookies being set.

Pid
#1
Hello everyone,
I hope you're all well. I'm writing to you because I'm having a problem with a script related to a PI controller for a radiant floor heating system. The system consists of a 0/10 valve and a flow sensor. To prevent the controller from becoming too sensitive to temperature variations, a moving average filter was added. However, despite this precaution, the PID remains very lazy and unstable.
I've attached the script and an image of the configured parameters. The setpoints, proportional, and integral values are adjustable via the supervisory system. I feel like I'm a bit lost in the dark, so I'm counting on your expert help to resolve this situation. I forgot about the Pt1000 sensors read by the KNX module with a 2-minute cyclic variation and a value change of 0.2K, but the valves are 0/10 controlled by the KNX module.
Thank you so much for your support and availability.
Michael

Code:
-- ============================================================
-- REGOLATORE PI – MISCELATRICE PAVIMENTO RADIANTE
-- SOLO SONDA MANDATA
-- ============================================================

---------------------------------------------------------------
-- PARAMETRI BASE (persistenti)
---------------------------------------------------------------
local Kp = storage.get('Kp_PINT') or 3.0
local Ki = storage.get('Ki_PINT') or 0.02
local integral = storage.get('integral_PINT') or 0
local last_mode = storage.get('last_mode')

---------------------------------------------------------------
-- CONFIGURAZIONE KNX
---------------------------------------------------------------
local group_mode        = '5/0/0'    -- 1=inverno / 0=estate
local group_in          = '5/2/1'    -- temperatura mandata
local group_out         = '5/0/1'    -- 0–10 V miscelatrice
local group_set_winter  = '5/2/4'
local group_set_summer  = '5/2/5'
local group_kp          = '5/2/6'
local group_ki          = '5/2/7'

---------------------------------------------------------------
-- COSTANTI DI CONTROLLO
---------------------------------------------------------------
local cycle_time = 120          -- secondi
local filter_samples = 3        -- filtro ridotto
local out_min, out_max = 0, 100 -- %
local max_delta = 2             -- base rampa
local deadband = 0.3            -- °C

---------------------------------------------------------------
-- VARIABILI
---------------------------------------------------------------
local readings = {}
local last_output = storage.get('last_output') or 0

---------------------------------------------------------------
-- MEDIA MOBILE TEMPERATURA MANDATA
---------------------------------------------------------------
local function moving_average(v)
  if type(v) ~= 'number' then return nil end
  table.insert(readings, v)
  if #readings > filter_samples then
    table.remove(readings, 1)
  end
  local s = 0
  for _, x in ipairs(readings) do
    s = s + x
  end
  return s / #readings
end

---------------------------------------------------------------
-- CICLO PRINCIPALE
---------------------------------------------------------------
while true do

  -------------------------------------------------------------
  -- Aggiornamento Kp / Ki da KNX
  -------------------------------------------------------------
  local v = grp.getvalue(group_kp)
  if type(v) == 'number' then
    Kp = v
    storage.set('Kp_PINT', Kp)
  end

  v = grp.getvalue(group_ki)
  if type(v) == 'number' then
    Ki = v
    storage.set('Ki_PINT', Ki)
  end

  -------------------------------------------------------------
  -- Modalità estate / inverno
  -------------------------------------------------------------
  local is_winter = grp.getvalue(group_mode) and true or false

  if last_mode ~= is_winter then
    integral = 0
    last_mode = is_winter
    storage.set('last_mode', is_winter)
  end

  -------------------------------------------------------------
  -- Setpoint
  -------------------------------------------------------------
  local set = is_winter
    and grp.getvalue(group_set_winter)
    or  grp.getvalue(group_set_summer)

  if type(set) ~= 'number' then
    set = is_winter and 35 or 25
  end

  -------------------------------------------------------------
  -- Temperatura mandata filtrata
  -------------------------------------------------------------
  local T = grp.getvalue(group_in)
  local Tf = moving_average(T)

  if Tf then

    -----------------------------------------------------------
    -- LIMITI TEMPERATURA MANDATA
    -----------------------------------------------------------
    local Tmax  = set + 2.0
    local Tstop = set + 3.0

    -----------------------------------------------------------
    -- ERRORE
    -----------------------------------------------------------
    local error = set - Tf
    if math.abs(error) < deadband then
      error = 0
    end

    -----------------------------------------------------------
    -- INTEGRALE
    -- (bloccato solo sopra Tmax)
    -----------------------------------------------------------
    if Tf <= Tmax then
      integral = integral + error * Ki * cycle_time
    end

    -----------------------------------------------------------
    -- USCITA PI BASE
    -----------------------------------------------------------
    local output = Kp * error + integral

    -----------------------------------------------------------
    -- AZIONE FORTE SOPRA SETPOINT
    -----------------------------------------------------------
    if Tf > set then
      output = output - Kp * (Tf - set) * 3
    end

    -----------------------------------------------------------
    -- AZIONE PIÙ FORTE SOPRA Tmax
    -----------------------------------------------------------
    if Tf > Tmax then
      output = output - Kp * (Tf - Tmax) * 4
    end

    -----------------------------------------------------------
    -- TAGLIO DI SICUREZZA
    -----------------------------------------------------------
    if Tf >= Tstop then
      output = out_min
      integral = 0
    end

    -----------------------------------------------------------
    -- SATURAZIONE
    -----------------------------------------------------------
    if output > out_max then
      output = out_max
    elseif output < out_min then
      output = out_min
    end

    -----------------------------------------------------------
    -- RAMPA DINAMICA
    -- apertura veloce, chiusura controllata
    -----------------------------------------------------------
    local ramp = max_delta
    if error > 5 then
      ramp = max_delta * 4
    elseif error > 2 then
      ramp = max_delta * 2
    end

    local delta = output - last_output
    if delta > ramp then
      output = last_output + ramp
    elseif delta < -max_delta then
      output = last_output - max_delta
    end

    -----------------------------------------------------------
    -- SCRITTURA KNX
    -----------------------------------------------------------
    if math.abs(output - last_output) >= 0.5 then
      grp.write(group_out, output)
      last_output = output
      storage.set('last_output', output)
      storage.set('integral_PINT', integral)

      log(string.format(
        "[%s] SP %.1f  T %.1f  OUT %.1f%%  ERR %.2f",
        is_winter and "INV" or "EST",
        set, Tf, output, error
      ))
    end
  end

  sleep(cycle_time)
end

Attached Files Thumbnail(s)
   
.txt   Regolatore PI Michael.txt (Size: 6.47 KB / Downloads: 8)
Reply
#2
Most likely you need to tune P/I parameters. Create trend logs for both temperature value and valve output and see how the system behaves with different parameters.
Reply
#3
First of all, thank you for your reply, but it seems that even raising the P/I parameters doesn't improve.
Reply
#4
For tuning the parameters do the following:

1. Use PID script from our KB: https://kb.logicmachine.net/scripting/pid-thermostat/

2. Create a trend log for output value and current temperature

3. Set ki and kd to 0

4. Keep the setpoint constant

5. Adjust kp so the temperature oscillates around the setpoint

6. Let the system run for some time

7. Record the time between each oscillation (Tu)

8. PID parameters can be calculated using the following formula, where Ku is the kp value from the step 5.
kp = 0.6 * Ku
ki = 1.2 * Ku / Tu
kd = 0.075 * Ku * Tu

You can try other coefficients to further tune the system: https://en.wikipedia.org/wiki/Ziegler%E2...ols_method
Reply
#5
Hi, thanks for the valuable advice, now it seems to be starting to work quite well, I need to give it a little more fine tuning, but compared to before this is already an excellent result. I'm posting the logs. Thanks a lot, good work.

Attached Files Thumbnail(s)
       
Reply


Forum Jump: