Central Heating controller using the real time clock, PHY module for internet, 1-wire interface for temperature sensors, a system log and a configuration file

Dependencies:   net 1-wire lpc1768 crypto clock web fram log

/media/uploads/andrewboyson/heating.sch

/media/uploads/andrewboyson/heating.brd

/media/uploads/andrewboyson/eagle.epf

heating/boiler.c

Committer:
andrewboyson
Date:
2021-04-23
Revision:
106:41ed3ea0bbba
Parent:
105:1899f7ed17ec

File content as of revision 106:41ed3ea0bbba:

#include <string.h>
#include <stdint.h>
#include <stdbool.h>

#include "gpio.h"
#include "mstimer.h"
#include "ds18b20.h"
#include "fram.h"
#include "pwm.h"
#include "log.h"

#define BOILER_PUMP_DIR FIO2DIR(4) // P2.4 == p22
#define BOILER_PUMP_PIN FIO2PIN(4)
#define BOILER_PUMP_SET FIO2SET(4)
#define BOILER_PUMP_CLR FIO2CLR(4)

#define BOILER_CALL_DIR FIO2DIR(5) // P2.5 == p21
#define BOILER_CALL_PIN FIO2PIN(5)
#define BOILER_CALL_SET FIO2SET(5)
#define BOILER_CALL_CLR FIO2CLR(5)

#define PUMP_SPEED_CALLING_AUTO_ONLY  -1
#define PUMP_SPEED_CALLING_AUTO_TWEAK -2

#define MAX_SPEED 100

static char*     tankRom; static int iTankRom;
static char*   outputRom; static int iOutputRom;
static char*   returnRom; static int iReturnRom;

static int8_t  fullSpeedSecs;      static int iFullSpeedSecs;

static int8_t  tankSetPoint;       static int iTankSetPoint;
static int16_t tankHysteresis;     static int iTankHysteresis;
static int16_t _runOnDelta16ths; static int iRunOnResidual;
static uint8_t runOnTime2s;        static int iRunOnTime;

static int8_t  boilerTarget;       static int iBoilerTarget;
static int8_t  pumpSpeedCalling;   static int iPumpSpeedCalling;
static int8_t  _rampDownTime;     static int _iRampDownTime;

static int8_t  _minSpeed;             static int _iMinSpeed;
static int8_t  _midSpeedPwm;          static int _iMidSpeedPwm;
static int16_t _fullSpeedDeltaT16ths; static int _iFullSpeedDeltaT;

//Set in main scan
static int16_t _boilerOutput16ths   = DS18B20_ERROR_VALUE_NOT_SET;
static int16_t _boilerReturn16ths   = DS18B20_ERROR_VALUE_NOT_SET;
static int16_t _boilerRtnDel16ths   = DS18B20_ERROR_VALUE_NOT_SET;
static int16_t _boilerDeltaT16ths   = DS18B20_ERROR_VALUE_NOT_SET;
static bool    _boilerDeltaTisValid = false;

int16_t  BoilerGetTankDS18B20Value  () { return DS18B20ValueFromRom(tankRom);   } 
int16_t  BoilerGetOutputDS18B20Value() { return _boilerOutput16ths;    } 
int16_t  BoilerGetReturnDS18B20Value() { return _boilerReturn16ths;    }
int16_t  BoilerGetRtnDelDS18B20Value() { return _boilerRtnDel16ths;    }
int16_t  BoilerGetDeltaTDS18B20Value() { return _boilerDeltaT16ths;    } 
int      BoilerGetFullSpeedSecs     () { return fullSpeedSecs;         }
int      BoilerGetTankSetPoint      () { return tankSetPoint;          }
int      BoilerGetTankHysteresis    () { return tankHysteresis;        } 
int      BoilerGetRunOnDeltaT       () { return _runOnDelta16ths;      }
int      BoilerGetRunOnTime         () { return runOnTime2s << 1;      }
int      BoilerGetPumpSpeedCalling  () { return pumpSpeedCalling;      }
int      BoilerGetRampDownTime      () { return _rampDownTime;        }
int      BoilerGetOutputTarget      () { return boilerTarget;          }
int      BoilerGetMinSpeed          () { return _minSpeed;             }
int      BoilerGetMidSpeedPwm       () { return _midSpeedPwm;          }
int      BoilerGetFullSpeedDeltaT   () { return _fullSpeedDeltaT16ths; }

static void setTankRom          (char* value) { memcpy(tankRom,         value, 8); FramWrite(iTankRom,         8,  tankRom              ); }
static void setOutputRom        (char* value) { memcpy(outputRom,       value, 8); FramWrite(iOutputRom,       8,  outputRom            ); }
static void setReturnRom        (char* value) { memcpy(returnRom,       value, 8); FramWrite(iReturnRom,       8,  returnRom            ); }
void BoilerSetFullSpeedSecs     (int   value) { fullSpeedSecs         = value;     FramWrite(iFullSpeedSecs,   1, &fullSpeedSecs        ); }
void BoilerSetTankSetPoint      (int   value) { tankSetPoint          = value;     FramWrite(iTankSetPoint,    1, &tankSetPoint         ); }
void BoilerSetTankHysteresis    (int   value) { tankHysteresis        = value;     FramWrite(iTankHysteresis,  2, &tankHysteresis       ); }
void BoilerSetRunOnDeltaT       (int   value) { _runOnDelta16ths      = value;     FramWrite(iRunOnResidual,   2, &_runOnDelta16ths     ); }
void BoilerSetRunOnTime         (int   value) { runOnTime2s           = value >> 1;FramWrite(iRunOnTime,       1, &runOnTime2s          ); }
void BoilerSetPumpSpeedCalling  (int   value) { pumpSpeedCalling      = value;     FramWrite(iPumpSpeedCalling,1, &pumpSpeedCalling     ); }
void BoilerSetRampDownTime      (int   value) { _rampDownTime         = value;     FramWrite(_iRampDownTime,   1, &_rampDownTime        ); }
void BoilerSetOutputTarget      (int   value) { boilerTarget          = value;     FramWrite(iBoilerTarget,    1, &boilerTarget         ); }
void BoilerSetMinSpeed          (int   value) { _minSpeed             = value;     FramWrite(_iMinSpeed,       1, &_minSpeed            ); }
void BoilerSetMidSpeedPwm       (int   value) { _midSpeedPwm          = value;     FramWrite(_iMidSpeedPwm,    1, &_midSpeedPwm         ); }
void BoilerSetFullSpeedDeltaT   (int   value) { _fullSpeedDeltaT16ths = value;     FramWrite(_iFullSpeedDeltaT,2, &_fullSpeedDeltaT16ths); }

static int calculateBetweenTwoPoints(int x, int xA, int xB, int yA, int yB)
{
    float m = (float)(yB - yA) / (xB - xA);
    return  yA + m * (x - xA);
}
static int calculateSpeedFromDeltaT(int deltaT16ths)
{
    if (deltaT16ths < _fullSpeedDeltaT16ths) return MAX_SPEED;   //Needed in case deltaT16ths is negative or zero
    int speed = MAX_SPEED * _fullSpeedDeltaT16ths / deltaT16ths; //eg for 20 deg ==> 100 * (10 << 4) / (20 << 4) == 50
    if (speed > MAX_SPEED) speed = MAX_SPEED;
    if (speed < _minSpeed) speed = _minSpeed;
    return speed;
}
static int calculateDeltaTFromSpeed(int speed)
{
    int deltaT16ths = MAX_SPEED * _fullSpeedDeltaT16ths / speed; //eg for speed = 50 ==> 100 * (10 << 4) / 50 == 20 << 4
    return deltaT16ths;
}

int BoilerInit()
{
      tankRom = DS18B20Roms + 8 * DS18B20RomCount;
    DS18B20RomSetters[DS18B20RomCount] = setTankRom;
    DS18B20RomNames[DS18B20RomCount] = "Tank";
    DS18B20RomCount++;
    
    outputRom = DS18B20Roms + 8 * DS18B20RomCount;
    DS18B20RomSetters[DS18B20RomCount] = setOutputRom;
    DS18B20RomNames[DS18B20RomCount] = "BlrOut";
    DS18B20RomCount++;
    
    returnRom = DS18B20Roms + 8 * DS18B20RomCount;
    DS18B20RomSetters[DS18B20RomCount] = setReturnRom;
    DS18B20RomNames[DS18B20RomCount] = "BlrRtn";
    DS18B20RomCount++;
    
    int address;
    uint8_t def1;
    int16_t def2;
    int32_t def4;
                  address = FramLoad( 8,  tankRom,                   0); if (address < 0) return -1; iTankRom          = address;
                  address = FramLoad( 8,  outputRom,                 0); if (address < 0) return -1; iOutputRom        = address;
                  address = FramLoad( 8,  returnRom,                 0); if (address < 0) return -1; iReturnRom        = address;
    def1 =   100; address = FramLoad( 1, &fullSpeedSecs,         &def1); if (address < 0) return -1; iFullSpeedSecs    = address;
    def1 =    65; address = FramLoad( 1, &tankSetPoint,          &def1); if (address < 0) return -1; iTankSetPoint     = address;
    def2 =     5; address = FramLoad( 2, &tankHysteresis,        &def2); if (address < 0) return -1; iTankHysteresis   = address;
    def2 =     2; address = FramLoad( 2, &_runOnDelta16ths,      &def2); if (address < 0) return -1; iRunOnResidual    = address;
    def1 =   180; address = FramLoad( 1, &runOnTime2s,           &def1); if (address < 0) return -1; iRunOnTime        = address;
    def1 =   100; address = FramLoad( 1, &pumpSpeedCalling,      &def1); if (address < 0) return -1; iPumpSpeedCalling = address;
    def1 =    10; address = FramLoad( 1, &_rampDownTime,        &def1); if (address < 0) return -1; _iRampDownTime   = address;
    def1 =    65; address = FramLoad( 1, &boilerTarget,          &def1); if (address < 0) return -1; iBoilerTarget     = address;
    def1 =    50; address = FramLoad( 1, &_minSpeed,             &def1); if (address < 0) return -1; _iMinSpeed        = address;
                            FramAllocate(1);
    def1 =    50; address = FramLoad( 1, &_midSpeedPwm,          &def1); if (address < 0) return -1; _iMidSpeedPwm     = address;
                            FramAllocate(1);
    def2 = 10<<4; address = FramLoad( 2, &_fullSpeedDeltaT16ths, &def2); if (address < 0) return -1; _iFullSpeedDeltaT = address;
    
    BOILER_PUMP_DIR = 1; //Set the direction to 1 == output
    BOILER_CALL_DIR = 1; //Set the direction to 1 == output

    PwmInit(400, 100);

    return 0;
}
bool BoilerCallEnable = true;
bool BoilerCall = false;
static void controlBoilerCall()
{
    if (BoilerCallEnable)
    {
        int tankTemp16ths = DS18B20ValueFromRom(tankRom);
        if (DS18B20IsValidValue(tankTemp16ths)) //Ignore values which are likely to be wrong
        {
            int  tankUpper16ths = tankSetPoint   << 4;
            int hysteresis16ths = tankHysteresis << 4;
            int  tankLower16ths = tankUpper16ths - hysteresis16ths;
        
            if (tankTemp16ths >= tankUpper16ths) BoilerCall = false;
            if (tankTemp16ths <= tankLower16ths) BoilerCall = true;
        }
    }
    else
    {
        BoilerCall = false;
    }
}
bool BoilerPump = false;
static void controlBoilerPump()
{
    static uint32_t msTimerBoilerPumpRunOn = 0;
    if (BoilerCall)
    {
        BoilerPump = true;
        msTimerBoilerPumpRunOn = MsTimerCount;
    }
    else
    {
        if (MsTimerRelative(msTimerBoilerPumpRunOn,    runOnTime2s * 2000)) BoilerPump = false;
        if (_boilerDeltaTisValid && _boilerDeltaT16ths < _runOnDelta16ths ) BoilerPump = false;
    }
}
int BoilerPumpFlow  = MAX_SPEED;
int BoilerPumpSpeed = MAX_SPEED;
int BoilerPumpPwm   = 0;
static int _autoSpeed = 0;
static void calculateAutoSpeed()
{
    if (!DS18B20IsValidValue(_boilerReturn16ths)) return;
    
    int target16ths = (int)boilerTarget << 4;
    int targetRise16ths = target16ths - _boilerReturn16ths; //eg 65 - eg 45 = 20*16 16ths
    
    _autoSpeed = calculateSpeedFromDeltaT(targetRise16ths);
}
static void controlBoilerPumpSpeed()
{
    static uint32_t msTimerReduction = 0;
    calculateAutoSpeed();
    if (BoilerCall)
    {
        if (pumpSpeedCalling < 0) BoilerPumpSpeed = _autoSpeed;        //Auto
        else                      BoilerPumpSpeed = pumpSpeedCalling;  //Manual
        msTimerReduction = MsTimerCount;
    }
    else
    {
        if (BoilerPumpSpeed > _minSpeed)
        {
            int msPerUnit = 1000 * _rampDownTime / (MAX_SPEED - _minSpeed);
            if (MsTimerRepetitive(&msTimerReduction, msPerUnit)) BoilerPumpSpeed--;
        }
        else
        {
            BoilerPumpSpeed = _minSpeed;
        }
    }
    if (BoilerPumpSpeed < _minSpeed) BoilerPumpSpeed = _minSpeed;
    if (BoilerPumpSpeed > MAX_SPEED) BoilerPumpSpeed = MAX_SPEED;
}
static int speedToPwm(int speed)
{
    #define MAX_SPEED_PWM 10
    #define MIN_SPEED_PWM 84
    /*
    PWM input signal [%] Pump status
    ≤ 10 Maximum speed
    > 10 / ≤ 84 Variable speed from minimum to maximum
    speed
    > 84 / ≤ 91 Minimum speed
    > 91/95 Hysteresis area: on/off
    > 95 / ≤ 100 Standby mode: off
    
    Max speed 100 is at fitted = 74; pwm = 10
    Min speed   0 is at fitted =  0; pwm = 84
    */
    if (speed <= _minSpeed) return MIN_SPEED_PWM;
    if (speed >= MAX_SPEED) return MAX_SPEED_PWM;
    int midSpeed = (_minSpeed + MAX_SPEED) / 2;
    if (speed < midSpeed) return calculateBetweenTwoPoints(speed, _minSpeed,  midSpeed, MIN_SPEED_PWM,   _midSpeedPwm);
    else                  return calculateBetweenTwoPoints(speed,  midSpeed, MAX_SPEED,  _midSpeedPwm,  MAX_SPEED_PWM);
    //int pwm = calculateBetweenTwoPoints(BoilerPumpSpeed, _minSpeed, MAX_SPEED, 84, 10);
    //if (pwm < 10) pwm = 10;
    //if (pwm > 84) pwm = 84;
    //BoilerPumpPwm = pwm;
}
#define TIME_BEFORE_TWEAK_SECS 120
static void tweakDeltaTs()
{
    if (pumpSpeedCalling != PUMP_SPEED_CALLING_AUTO_TWEAK) return;
    
    static uint32_t msTimerBoilerHeating = 0;
    if (!BoilerCall) msTimerBoilerHeating = MsTimerCount;
    if (!MsTimerRelative(msTimerBoilerHeating, TIME_BEFORE_TWEAK_SECS * 1000)) return;
    
    if (!_boilerDeltaTisValid) return;
    
    static int speedLastScan = -1;
    
    if (speedLastScan < MAX_SPEED && BoilerPumpSpeed == MAX_SPEED)
    {
        if (_fullSpeedDeltaT16ths > _boilerDeltaT16ths) _fullSpeedDeltaT16ths--;
        if (_fullSpeedDeltaT16ths < _boilerDeltaT16ths) _fullSpeedDeltaT16ths++;
    }
    
    speedLastScan = BoilerPumpSpeed;
}

#define TIME_BEFORE_DELTA_T_ALARM_SECS 300
#define DELTA_T_LIMIT (3 << 4)
static void checkDeltaTs()
{   
    static uint32_t msTimerDeltaTNonConform = 0;
    if (!BoilerCall)
    {
        msTimerDeltaTNonConform = MsTimerCount;
        return;
    }
    
    int expectedDeltaT16ths = calculateDeltaTFromSpeed(BoilerPumpSpeed);
    
    bool deltaTisOk = _boilerDeltaTisValid &&
                      _boilerDeltaT16ths > (expectedDeltaT16ths - DELTA_T_LIMIT) &&
                      _boilerDeltaT16ths < (expectedDeltaT16ths + DELTA_T_LIMIT);
    
    
    static bool deltaTwasOk = true;
    
    /*
    if (deltaTwasOk != deltaTisOk)
    {
        LogTimeF("Boiler delta T ");
        DS18B20Log(_boilerDeltaT16ths);
        if (deltaTisOk)
        {
            Log(" is inside expected value ");
        }
        else
        {
            Log(" is outside expected value ");
        }
        DS18B20Log(expectedDeltaT16ths);
        Log("\r\n");
    }
    
    deltaTwasOk = deltaTisOk;
    */
    
    static bool hadAlarm = false;
    if (deltaTisOk) msTimerDeltaTNonConform = MsTimerCount;
    bool haveAlarm = MsTimerRelative(msTimerDeltaTNonConform, TIME_BEFORE_DELTA_T_ALARM_SECS * 1000);
    if (haveAlarm && !hadAlarm)
    {
        LogTimeF("Boiler delta T would have tripped after not being ok for %d seconds\r\n", TIME_BEFORE_DELTA_T_ALARM_SECS);
    }
    hadAlarm = haveAlarm;
}
#define NUMBER_OF_STEPS 10
static int16_t _returns16ths[NUMBER_OF_STEPS]; //0 is last, 9th is first
static void delayLine()
{
    static uint32_t msTimerDelay = 0;
    if (BoilerPump)
    {
        int msTotal = 1000 * fullSpeedSecs * MAX_SPEED / BoilerPumpSpeed; //speed 10 ==> 10000; speed 100 ==> 1000
        int msPerStep = msTotal / NUMBER_OF_STEPS;
        if (MsTimerRelative(msTimerDelay, msPerStep))
        {
            for (int i = 0; i < NUMBER_OF_STEPS - 1; i++) _returns16ths[i] = _returns16ths[i + 1];
            _returns16ths[NUMBER_OF_STEPS - 1] = _boilerReturn16ths;
            msTimerDelay = MsTimerCount;
            //LogTimeF("Ms per step = %d, delayed boiler return = ", msPerStep);
            //DS18B20Log(_returns16ths[0]);
            //Log("\r\n");
        }
    }
    else
    {
        msTimerDelay = MsTimerCount;
        for (int i = 0; i < NUMBER_OF_STEPS; i++) _returns16ths[i] = DS18B20_ERROR_VALUE_NOT_SET;
    }
}

void BoilerMain()
{
    delayLine();
    _boilerOutput16ths   = DS18B20ValueFromRom(outputRom);
    _boilerReturn16ths   = DS18B20ValueFromRom(returnRom);
    _boilerRtnDel16ths   = _returns16ths[0];
    _boilerDeltaTisValid = DS18B20IsValidValue(_boilerOutput16ths) && DS18B20IsValidValue(_boilerRtnDel16ths);
    if (_boilerDeltaTisValid) _boilerDeltaT16ths   = _boilerOutput16ths - _boilerRtnDel16ths;
    else                      _boilerDeltaT16ths   = DS18B20_ERROR_VALUE_NOT_SET;
    
    controlBoilerCall();
    if (BoilerCall) BOILER_CALL_SET;
    else            BOILER_CALL_CLR;
    
    controlBoilerPump();
    if (BoilerPump) BOILER_PUMP_SET;
    else            BOILER_PUMP_CLR;
    
    controlBoilerPumpSpeed();
    BoilerPumpPwm = speedToPwm(BoilerPumpSpeed);
    PwmSet(BoilerPumpPwm);
    
    tweakDeltaTs();
    checkDeltaTs();
}