Brew heater controller. Programmed to mash grain and to boil wort

Dependencies:   mbed QEI UniGraphic

Revision:
0:c673d397e9dc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Fri Jan 07 12:08:12 2022 +0000
@@ -0,0 +1,771 @@
+/**
+
+ Spice modal
+ .SUBCKT NTCLE203E3103SB0 RN Rp  PARAMS: TOLR=0 TOLB=0
+X64 Rn Rp NTC_BASE Params:
++ w=-14.6571976
++ x=4798.842
++ y=-115334
++ z=-3730535
++ gth=0.0032 gth1 = 0.0000167
++ cth=0.032
++ a=-14.65719769
++ r25=10000
++ b=4798.842
++ c=-115334
++ d=-3730535
++ T0=273.15
++ TR={1+TOLR/100}
++ TB={1+TOLB/100}
+.ENDS
+
+ */
+
+
+/* Includes ------------------------------------------------------------------*/
+
+/* mbed specific header files. */
+/*
+GPIO_InitTypeDef GPIO_InitStruct;
+//Configure GPIO pin : PB14
+GPIO_InitStruct.Pin = GPIO_PIN_14;
+GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;     // digital Output
+GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
+HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
+*/
+#include "mbed.h"
+#include "SSD1306.h"
+#include "string"
+
+#include "MashProfile.h"
+#include "MashProfileFull.h"
+#include "Boil1.h"
+#include "Boil2.h"
+#include "Boil3.h"
+#ifndef PROFILES
+#define PROFILES
+#include "Profiles.h"
+#endif //Profiles
+
+#define Height 32
+#define Width 132
+
+
+
+#include "QEI.h"
+
+//Firing delay in microseconds
+#define SyncToZeroCrossingDelayRise 115
+#define SyncToZeroCrossingDelayFall 115
+
+#define TriacPulseLength 250
+// Blinking rate in milliseconds
+#define BLINKING_RATE_MS  500
+#define Bias_Current 0.261  // Thermistor current in mA
+/*
+
+FIR filter designed with
+http://t-filter.appspot.com
+
+sampling frequency: 2000 Hz
+
+* 0 Hz - 100 Hz
+  gain = 1
+  desired ripple = 5 dB
+  actual ripple = 1.0675530446538501 dB
+
+* 600 Hz - 1000 Hz
+  gain = 0
+  desired attenuation = -60 dB
+  actual attenuation = -71.5928467318133 dB
+
+*/
+
+#define NumberOfTaps 9
+
+static double filter_taps[NumberOfTaps] = {
+    0.009246906411528302,
+    0.0484176527692072,
+    0.12764201806827455,
+    0.21665898750162413,
+    0.25663867508629506,
+    0.21665898750162413,
+    0.12764201806827455,
+    0.0484176527692072,
+    0.009246906411528302
+};
+//float PotBuf[NumberOfTaps];
+float TempBuf[NumberOfTaps];
+int CycleCount=0,StartT=0;
+bool FireTriac,Boiling,DisplayFlag;
+/*------- inputs ---------*/
+InterruptIn FallingEdge(D12);
+InterruptIn RisingEdge(D13);
+//AnalogIn Potentiometer(A0);
+// Thermistor (ntc) with a constant current flowing through it
+AnalogIn Thermistor(A1);
+//Rotary Encoder for entering values and navigation
+QEI wheel (D9, D8, NC, 20);
+DigitalIn Button(PC_9);
+InterruptIn ButtonPressed(PC_9);
+/*------- inputs -end-----*/
+
+/*------- outputs ---------*/
+//DigitalOut led(LED1);
+// Output to switch on the LED in the opto to fire the triac
+DigitalOut Triac(D10),OledReset(D6);
+// Test output to see timings and states on a scope
+DigitalOut Test(D15);
+// The screen
+SSD1306 MyOled(SPI_8, 10000000, D4, D5, D3, D2, D6, D7,"MyOled", Width, Height); // Spi 8bit, 10MHz, mosi, miso, sclk, cs, reset, dc
+// fixme clock may need to be 5MHz
+// 5000000 gives a clock of 3.125MHz (genius)
+// 10000000 gives 6.25
+// 12800000 gives 12.8Mhz still works
+// 20000000 gives 12.5Mhz
+// the nearest i can find to 8MHz is 10000000
+
+/*------- outputs -end-----*/
+
+
+char Buff[3][20];
+Timeout SyncDelay,TriacPulseTimeout,ReadTemperature;
+
+//float Pot;
+int FireCount;
+int Demand;
+float valT,LastTemp=0,LastError=0,Integrated=0;
+bool Rise,MainsOn;
+time_t now;
+Timer ElapsedTime;
+uint64_t LastTime,TempTime,DebounceTime,StartTime,DemandTime,CheckTime,KnobTime,CycleTime;
+struct Controls {
+    float Temperature;
+    int8_t PercentPower;
+    int16_t TimeSeconds;
+    int8_t ProfileNumber;
+    int8_t ElementNumber;
+    uint8_t MessageNo;
+    goal Goal;
+    char Message[20];
+
+};
+struct Controls MyControls= {25.0,50,0};
+struct Profile *ProfileArray[5]= { &MashProfile,&MashProfileFull,&Boil1,&Boil2,&Boil3};
+enum VariableType { Index, TempC, PwrW, TimeS };
+struct MenuType {
+    int8_t IndexVal;  //The current thing pointed to by the cursor
+    int8_t Number;
+    int8_t MaxIndex;
+    int8_t Offset;
+    VariableType CurrentType;
+    void EncoderHandler( bool Increment, VariableType ChangeType);
+    struct Controls *Cntrl; // points to the main loop controls
+    char *MenuText[8]; //8 lines of menus to start with
+    void (*NextMenu[8])();
+    bool Switch;
+};
+
+Serial Mypc(USBTX, USBRX, 115200);  //for debugging
+int64_t DebugValue;
+void FiringCounter(int Power);
+void PulseTriac();
+void SyncFall();
+void SyncRise();
+//char *Menu[2]={"Manual","Programmed"};
+
+int string_length(char* given_string)
+{
+    // variable to store the
+    // length of the string
+    int length = 0;
+    while (*given_string != '\0') {
+        length++;
+        given_string++;
+    }
+
+    return length;
+}
+void MenuType::EncoderHandler( bool Increment, VariableType ChangeType)
+{
+    int Multiplier=1,t;
+    t=ElapsedTime.read_ms()-KnobTime;
+    if (t<50) Multiplier=10;
+    else if (t<100) Multiplier=5;
+    else if (t<150) Multiplier=2;
+    KnobTime=ElapsedTime.read_ms();
+    switch (ChangeType) {
+        case Index:
+            if (Increment) IndexVal++;
+            else IndexVal--;
+            if (IndexVal>MaxIndex) IndexVal=0;
+            if (IndexVal<0) IndexVal=MaxIndex;
+            if ((IndexVal-Offset)>2) Offset=IndexVal-2;
+            if ((IndexVal-Offset)<0) Offset=IndexVal;
+            break;
+        case TempC:
+            if (Increment) Cntrl->Temperature=Cntrl->Temperature+(float)0.1*(float)Multiplier;
+            else Cntrl->Temperature=Cntrl->Temperature-(float)0.1*(float)Multiplier;
+            if (Cntrl->Temperature>120)Cntrl->Temperature=120;
+            if (Cntrl->Temperature<25)Cntrl->Temperature=25;
+            break;
+        case PwrW:
+            if (Increment) Cntrl->PercentPower+=Multiplier;
+            else Cntrl->PercentPower-=Multiplier;
+            if (Cntrl->PercentPower>100)Cntrl->PercentPower=100;
+            if (Cntrl->PercentPower<=0)Cntrl->PercentPower=1;
+            break;
+        case TimeS:
+            Multiplier=Multiplier*Multiplier;
+            if (Increment) Cntrl->TimeSeconds+=Multiplier;
+            else Cntrl->TimeSeconds-=Multiplier;
+            if (Cntrl->TimeSeconds>10800)Cntrl->TimeSeconds=10800;
+            if (Cntrl->TimeSeconds<0)Cntrl->TimeSeconds=0;
+            break;
+    }
+}
+
+struct MenuType MainMenu;
+void SetTopLevelMenu();
+void SetProgrammedMenu();
+void SetManualMenu();
+void ShowProgramMenu();
+void StartProfileMenu();
+void DoNothing();
+void AlterDataMenu();
+void LoadProfile(int Prof,int Ele);
+void SetProfile();
+void ProfileMenu();
+void GetTemperature();
+void DisplayNow();
+void DisplayMenu();
+void SetOutputSpeed();
+void PulseTriac()
+{
+    if (Triac==1) {
+        Triac=0;
+        Test=0; //fixme
+    } else {
+        Triac=1;// Fire the triac
+        Test=1;
+        TriacPulseTimeout.attach_us(&PulseTriac,TriacPulseLength);
+    }
+}
+void MenuSetup()
+{
+    MainMenu.Offset=0;
+    MainMenu.IndexVal=0;
+    MainMenu.Cntrl=&MyControls;
+    SetTopLevelMenu();
+}
+void SyncFall()
+{
+    int64_t t;
+    /* After many attempts to get this right t should be a uint64_t BUT
+    whan you do a comparison if(uint64_t> 99) it all goes to crap.
+    I think you end up with an overflow in the math used to evaluate it.
+    So only do comparicons with signed numbers int or float.
+    */
+    if (!Rise) return;
+    Rise=false;
+    t=ElapsedTime.read_us()-CycleTime;
+    DebugValue=t;
+    CycleTime=ElapsedTime.read_us();
+    if (DisplayFlag) return;
+    if ((t>20400)||(t<19600)) {
+        CycleCount=0;
+        MainsOn=false;
+    }
+    if (CycleCount<10) {
+        CycleCount++;
+        return;
+    }
+    MainsOn=true;
+    if (FireTriac) SyncDelay.attach_us(&PulseTriac,SyncToZeroCrossingDelayFall);
+    FiringCounter(Demand);
+    ReadTemperature.attach_us(&GetTemperature,9000); //read temp if mains on synced
+    // the noise generated on triac firing upsets the temperature measurment
+}
+void SyncRise()
+{
+    int64_t t;
+    Test=1;
+    if (Rise) return;
+    Rise=true;
+    t=ElapsedTime.read_us()-CycleTime;
+    if (DisplayFlag) return;
+    if ((t>10200)||(t<9800)) {
+        CycleCount=0;
+        MainsOn=false;
+    }
+    if (CycleCount<10) {
+        return;
+    }
+    Test=0;
+    if (FireTriac) SyncDelay.attach_us(&PulseTriac,SyncToZeroCrossingDelayRise);
+}
+
+float ReadResistance() // Read voltage across thermistor
+{
+
+    int i,j;
+    float tmp=0;
+    j=StartT;
+    TempBuf[StartT]=Thermistor;
+    for (i=0; i<NumberOfTaps; i++) {
+        if (j<0) j+=NumberOfTaps;
+        tmp+=TempBuf[j]*filter_taps[i];
+        j--;
+        if (j<0) j=NumberOfTaps-1;
+    }
+    StartT--;
+    if (StartT<0) StartT=NumberOfTaps-1;
+    /* tmp is the fraction 3.3V across thermistor.
+    Resistance = V/I
+    Resistance = (tmp*3.3V)/Bias_Current */
+    tmp=(tmp*(float)3.3)/(float)Bias_Current;  // Answer is in Kohm because current is in mA
+    return tmp;
+}
+float ResistanceToTemp(float Res)
+{
+    /* T0 = 25C = 298K
+    beta =3977
+    R0=10000 ohm     */
+    Res=Res+(float)0.0001;  //Res can't be zero
+    float temp;  // temp here is both temperary and temperature
+    temp=log(Res/(float)10)/(float)3977.0;  //beta from datasheet
+    temp=(float)1/(temp+(float)0.003354);  // 1/298.15
+    //temp=temp-(float)273.15 ;//kelvin to c conversion
+    /* ok so the temp is miles off due to things like the sensor being pressed
+    against the outside of the barrel.  Loosing heat to ambient etc.
+    So lets compensate for that with an offset and a scale factor. */
+    temp=(temp*(float)1.09)-(float)22;
+    temp=temp-(float)273.15 ;//kelvin to c conversion
+    return temp;
+}
+void GetTemperature()
+{
+    if (DisplayFlag) return; // The noise generated by the comms to the display causes 0.5C error
+    valT=ResistanceToTemp(ReadResistance());
+}
+void Pressed()
+{
+    int t;
+    t=ElapsedTime.read_ms()-DebounceTime;
+    DebounceTime=ElapsedTime.read_ms();
+    if (t<250) return;
+    //Mypc.printf("Index %d \n",MainMenu.IndexVal);
+    MainMenu.Number=MainMenu.IndexVal;
+    MainMenu.NextMenu[MainMenu.IndexVal]();
+}
+void setup()
+{
+    set_time(1256729737);  // Set RTC time to Wed, 28 Oct 2009 11:35:37
+    now=time(NULL);
+    // Mypc.printf("Setup");
+    Triac=0;  // Low to fire
+    FallingEdge.fall(&SyncFall); //falling mains voltage
+    RisingEdge.fall(&SyncRise);  //rising mains voltage
+    FireCount=0;
+    ButtonPressed.fall(&Pressed);
+    //SetOutputSpeed();
+    MyOled.BusEnable(true);//Chip select
+    MyOled.FastWindow(true);
+    MyOled.set_orientation(3);
+    MyOled.locate(0,0);
+    MyOled.set_font((unsigned char*) Terminal6x8,32,127,false);
+    MyOled.cls();
+    MyOled.BusEnable(false);//Chip select
+    ButtonPressed.fall(&Pressed);
+    ElapsedTime.start();
+    LastTime=ElapsedTime.read_ms();
+    DebounceTime=LastTime;
+    CycleTime=ElapsedTime.read_us();
+    TempTime=LastTime;
+    CheckTime=LastTime;
+    DemandTime=LastTime;
+    KnobTime=LastTime;
+    MenuSetup();
+}
+void SetOutputSpeed()
+{
+    GPIO_InitTypeDef GPIO_InitStruct;
+//Configure GPIO pin : PB_3 D3
+    GPIO_InitStruct.Pin = GPIO_PIN_3;
+    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;     // digital Output
+    GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
+    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
+}
+
+void FiringCounter(int Power)
+{
+    int x;
+    x=Power;
+    if (x>100) x=100;
+    if (x<0) x=0;
+    if (FireCount<0) {
+        FireCount+=100-x;
+        FireTriac=true;
+    } else {
+        FireCount-=x;
+        FireTriac=false;
+    }
+
+}
+void SetTopLevelMenu()
+{
+
+    MainMenu.IndexVal=0;
+    MainMenu.MaxIndex=2;
+    MainMenu.Offset=0;
+    MainMenu.MenuText[0]=TopLevelMenu[0];
+    MainMenu.MenuText[1]=TopLevelMenu[1];
+    MainMenu.MenuText[2]=TopLevelMenu[2];
+    MainMenu.CurrentType=Index;
+    MainMenu.NextMenu[0]=&SetTopLevelMenu;
+    MainMenu.NextMenu[1]=&SetManualMenu;
+    MainMenu.NextMenu[2]=&SetProgrammedMenu;
+    MainMenu.NextMenu[3]=&SetTopLevelMenu;//Set it to something that is valid
+    MainMenu.NextMenu[4]=&SetTopLevelMenu;
+    MainMenu.NextMenu[5]=&SetTopLevelMenu;
+    MyControls.Temperature= 25.0;
+    MyControls.PercentPower=1;
+    MyControls.TimeSeconds=0;
+    MainMenu.Switch=true;
+}
+void SetProgrammedMenu()
+{
+    MainMenu.Offset=0;
+    MainMenu.IndexVal=0;
+    MainMenu.MaxIndex=5;//12345678901234567890
+    MainMenu.MenuText[0]="Previous Menu      ";
+    MainMenu.MenuText[1]=MashProfile.Name;
+    MainMenu.MenuText[2]=MashProfileFull.Name;
+    MainMenu.MenuText[3]=Boil1.Name;
+    MainMenu.MenuText[4]=Boil2.Name;
+    MainMenu.MenuText[5]=Boil3.Name;
+
+    MainMenu.NextMenu[0]=&SetTopLevelMenu;
+    MainMenu.NextMenu[1]=&SetProfile;
+    MainMenu.NextMenu[2]=&SetProfile;
+    MainMenu.NextMenu[3]=&SetProfile;
+    MainMenu.NextMenu[4]=&SetProfile;
+    MainMenu.NextMenu[5]=&SetProfile;
+
+    MainMenu.CurrentType=Index;
+    MainMenu.Switch=true;
+}
+void SetManualMenu()
+{
+    MainMenu.IndexVal=0;
+    MainMenu.MaxIndex=3;
+    MainMenu.Offset=0; // 1234567890123456789
+    MainMenu.MenuText[0]="Manual Menu        ";
+    MainMenu.MenuText[1]="Temperature";
+    MainMenu.MenuText[2]="Power Level";
+    MainMenu.MenuText[3]="Time       ";
+    MainMenu.NextMenu[0]=&SetTopLevelMenu;
+    MainMenu.NextMenu[1]=&AlterDataMenu;
+    MainMenu.NextMenu[2]=&AlterDataMenu;
+    MainMenu.NextMenu[3]=&AlterDataMenu;
+    MainMenu.NextMenu[4]=&DoNothing;
+    MainMenu.Switch=false;
+}
+void ShowProgramMenu()
+{
+    MainMenu.Offset=0;
+    MainMenu.IndexVal=0;
+    MainMenu.MaxIndex=2;
+    MainMenu.MenuText[0]="Previous Menu      ";
+    MainMenu.MenuText[1]=ProfileArray[MyControls.ProfileNumber]->Name;
+    MainMenu.MenuText[2]="Start              ";
+
+    if (MyControls.Goal==Null) {
+        MainMenu.MenuText[3]="";
+        MainMenu.NextMenu[3]=&SetProgrammedMenu;
+    } else {//profile loaded
+        MainMenu.MenuText[3]="Current Profile    ";
+        //                    1234567890123456789
+        MainMenu.NextMenu[3]=&ProfileMenu;
+        MainMenu.MaxIndex=3;
+    }
+    MainMenu.MenuText[4]="";
+    MainMenu.MenuText[5]="";
+    MainMenu.NextMenu[0]=&SetProgrammedMenu;
+    MainMenu.NextMenu[1]=&DoNothing;
+    MainMenu.NextMenu[2]=&StartProfileMenu;
+
+    MainMenu.NextMenu[4]=&SetProgrammedMenu;
+    MainMenu.NextMenu[5]=&SetProgrammedMenu;
+    MainMenu.CurrentType=Index;
+    MainMenu.Switch=true;
+
+}
+void StartProfileMenu()
+{
+    LoadProfile(MyControls.ProfileNumber,0); //load the first element of the selected profile
+    ProfileMenu();
+}
+void ProfileMenu()
+{
+    MainMenu.IndexVal=0;
+    MainMenu.MaxIndex=3;
+    MainMenu.Offset=0;                       //
+    MainMenu.MenuText[0]=MyControls.Message;
+    MainMenu.MenuText[1]="Temperature";
+    MainMenu.MenuText[2]="Power Level";
+    MainMenu.MenuText[3]="Time       ";
+    MainMenu.NextMenu[0]=&ShowProgramMenu;
+    MainMenu.NextMenu[1]=&AlterDataMenu;
+    MainMenu.NextMenu[2]=&AlterDataMenu;
+    MainMenu.NextMenu[3]=&AlterDataMenu;
+    MainMenu.NextMenu[4]=&DoNothing;
+    MainMenu.Switch=false;
+}
+void AlterDataMenu()
+{
+    if (MainMenu.CurrentType==Index) switch (MainMenu.IndexVal) {
+            case 1:
+                MainMenu.CurrentType=TempC;
+                break;
+            case 2:
+                MainMenu.CurrentType=PwrW;
+                break;
+            case 3:
+                MainMenu.CurrentType=TimeS;
+                break;
+            default:
+                MainMenu.CurrentType=Index;
+        } else MainMenu.CurrentType=Index;
+}
+void DoNothing() //The purpose of this function is assign a function pointer to it rather than something wrong or a crash
+{
+}
+void SetProfile()
+{
+    int i=MainMenu.Number-1;
+    if (i<0)i=0;
+    if (i>4)i=4;//sanitize
+    MyControls.ProfileNumber=i;
+    strncpy(MyControls.Message,ProfileArray[i]->Name,20);
+    MyControls.Goal=Null;
+    ShowProgramMenu();
+
+
+}
+void LoadProfile(int Prof,int Ele)
+{
+    MyControls.Temperature= ProfileArray[Prof]->Element[Ele].Temp;
+    MyControls.PercentPower=ProfileArray[Prof]->Element[Ele].Power;
+    MyControls.TimeSeconds=ProfileArray[Prof]->Element[Ele].Seconds;
+    MyControls.Goal=ProfileArray[Prof]->Element[Ele].GoalType;
+    MyControls.ProfileNumber=Prof;
+    MyControls.ElementNumber=Ele;
+    MyControls.MessageNo=ProfileArray[Prof]->Element[Ele].MessageNo;
+    StartTime=ElapsedTime.read();
+    /*Mypc.printf("T %.1f P %d Sec %d Prof %d Ele %d\n",
+               MyControls.Temperature,
+                MyControls.PercentPower,
+                MyControls.TimeSeconds,
+                MyControls.ProfileNumber,
+                MyControls.ElementNumber);
+    */
+}
+
+void DisplayMenu()
+{
+    DisplayFlag=true; //This flag is used to stop generating new interupts
+    wait_us(SyncToZeroCrossingDelayRise+TriacPulseLength+25); //wait for interupts to finish
+    MyOled.BusEnable(true);//Chip select
+    bool Highlight=false;
+    //We have a 32 pixecl height and 9 pixcel font.  3 lines 10 spacing
+    if (MainMenu.Switch) {
+        for (int i=0; i<3 ; i++) {
+            MyOled.locate(6,(i*10)+1);
+            if (MainMenu.IndexVal==(i+MainMenu.Offset)) {
+                MyOled.background(White);
+                MyOled.foreground(Black);
+            } else {
+                MyOled.background(Black);
+                MyOled.foreground(White);
+            }
+            MyOled.printf(MainMenu.MenuText[i+MainMenu.Offset]);
+            MyOled.background(Black);
+            MyOled.foreground(White);
+        }
+    } else  {
+        time_t t;
+        if (MainMenu.CurrentType==TempC)sprintf(Buff[0],"  %6.1f ",MyControls.Temperature);
+        else sprintf(Buff[0],"  %6.1f ",valT);
+        if (MainMenu.CurrentType==PwrW)sprintf(Buff[1],"  %5d%c ",MyControls.PercentPower,38);
+        else sprintf(Buff[1],"  %5d%c ",Demand,38);
+        if (MainMenu.CurrentType==TimeS) {
+            t=MyControls.TimeSeconds;
+            strftime(Buff[2],10,"%T ",localtime(&t));
+        } else {
+            t=(int)ElapsedTime.read()-StartTime;
+            strftime(Buff[2],10,"%T ",localtime(&t));
+        }
+        for (int i=0; i<3 ; i++) {
+            MyOled.locate(6,(i*10)+1);
+            if (MainMenu.IndexVal==(i+MainMenu.Offset)) {
+                Highlight=true;
+            } else {
+                Highlight=false;
+            }
+            if (Highlight&&MainMenu.CurrentType==Index) {
+                MyOled.background(White);
+                MyOled.foreground(Black);
+                MyOled.printf(MainMenu.MenuText[i+MainMenu.Offset]);
+            } else {
+                MyOled.background(Black);
+                MyOled.foreground(White);
+                MyOled.printf(MainMenu.MenuText[i+MainMenu.Offset]);
+            }
+            if (Highlight&&MainMenu.CurrentType!=Index) {
+                MyOled.background(White);
+                MyOled.foreground(Black);
+                if ((i+MainMenu.Offset>=1)&&(i+MainMenu.Offset<=3))MyOled.printf(Buff[i+MainMenu.Offset-1]);
+            } else {
+                MyOled.background(Black);
+                MyOled.foreground(White);
+                if ((i+MainMenu.Offset>=1)&&(i+MainMenu.Offset<=3))MyOled.printf(Buff[i+MainMenu.Offset-1]);
+            }
+        }
+    }
+    MyOled.BusEnable(false);//Chip select
+    DisplayFlag=false;
+}
+int CalcDemand() //return percent power
+{
+    float ErrorSignal, Differential;
+    int power;
+    ErrorSignal=MyControls.Temperature-valT;
+    if (abs(ErrorSignal)<1)Integrated+=ErrorSignal;
+    else Integrated=0;
+    if (Integrated>20)Integrated=(float)20;
+    if (Integrated<-1)Integrated=(float)-1;
+    Differential=ErrorSignal-LastError;
+    LastError=ErrorSignal;
+    power=(int)(((float)70*ErrorSignal)+((float)0.6*Integrated)+(45*Differential));
+    // Mypc.printf("Error %.2f Int %.2f Dif %.2f\n\r", ErrorSignal,Integrated,Differential);
+    MainsOn ? Mypc.printf("Mains On\n\r") : Mypc.printf("Mains Off\n\r");
+    Mypc.printf("DebugValue %d\n\r",DebugValue);
+    Mypc.printf("Elapsed time %d\n\r",ElapsedTime.read_us());
+
+    if (power<0)power=0;
+    if (power>MyControls.PercentPower)power=MyControls.PercentPower;
+    return power;
+}
+void DisplayMessage()
+{
+    int x,y,z;
+    z=MyControls.MessageNo; //convert to int before compare
+    if (z>7)MyControls.MessageNo=0;
+    if (z==0) {
+        strncpy(MyControls.Message,ProfileArray[MyControls.ProfileNumber]->Name,20);
+    } else {
+        y=string_length(Message[MyControls.MessageNo]) ;
+        for (x=0; x<18; x++) {
+            if (x<y)MainMenu.MenuText[0][x]=Message[MyControls.MessageNo][x];
+            else MainMenu.MenuText[0][x]=' ';
+        }
+        MainMenu.MenuText[0][19]='\0';
+    }
+}
+int main()
+{
+    Mypc.printf("Starting Main\n\r");
+    setup();
+    int Position=wheel.getPulses();
+    int UpdateTime=750;
+    uint64_t TempTime=0;
+    int64_t t;// time is uint64_t but int64_t used for comparison in if statement
+    while (true) { //main loop
+        if (Position!=wheel.getPulses()) {
+            MainMenu.EncoderHandler( Position<wheel.getPulses(), MainMenu.CurrentType);
+            Position=wheel.getPulses();
+            UpdateTime=250; //Quick display refresh when knob moved
+            //Mypc.printf("Pos %d \n",Position);
+            //DisplayMenu();
+        }
+        /*Had a problem where mains detection was dropping out here.
+        The sum ElapsedTime.read_ms()-CycleTime was sometimes =0.
+        But 0>75 is true. eg if ((ElapsedTime.read_ms()-CycleTime)>75) would be true
+        if the sum result was 0. Doing the sum first was ok.  Had the same problem with Debounce.
+        */
+        t=ElapsedTime.read_us()-CycleTime;
+        if (t>75000) { //mains not proven. Read temp.  When mains is on this is done synced to mains
+            CycleCount=0;
+            MainsOn=false;
+            GetTemperature();
+        }
+        t=ElapsedTime.read_ms()-LastTime;
+        if (t>UpdateTime) {
+            LastTime=ElapsedTime.read_ms();
+            UpdateTime=750;
+            DisplayMenu();
+        }
+        t=ElapsedTime.read_ms()-TempTime;
+        if (t>15000) { //upped to 15s which will give a smaller temp rise by averaging over a longer period
+            if (valT>99.0f) {
+                if (valT-LastTemp<0.15f)Boiling=true;
+                else    Boiling=false; // still rising
+            } else  Boiling=false;  //too cold
+            LastTemp=valT;
+            TempTime=ElapsedTime.read_ms();
+        }
+        t=ElapsedTime.read_ms()-DemandTime;
+        if (t>3000) {
+            Demand=CalcDemand();
+            DemandTime=ElapsedTime.read_ms();
+        }
+        t=ElapsedTime.read_ms()-CheckTime;
+        if (t>3000) {
+            CheckTime=ElapsedTime.read_ms();
+            switch (MyControls.Goal) {
+                //enum goal { Null,Boil, Temp, Time};
+                case Boil:
+                    if (Boiling ) {
+                        DisplayMessage();
+                        if (MyControls.ElementNumber<5) {
+                            MyControls.ElementNumber++;
+                            LoadProfile(MyControls.ProfileNumber,MyControls.ElementNumber);
+                        } else {
+                            MyControls.Temperature=25.0;
+                            MyControls.PercentPower=0;
+                            MyControls.TimeSeconds=0;
+                        }
+                    }
+                    break;
+                case Temp:
+                    if (abs(valT-MyControls.Temperature)<(float)0.25) {
+                        DisplayMessage();
+                        if  (MyControls.ElementNumber<5) {
+                            MyControls.ElementNumber++;
+                            LoadProfile(MyControls.ProfileNumber,MyControls.ElementNumber);
+                        } else {
+                            MyControls.Temperature=25.0;
+                            MyControls.PercentPower=0;
+                            MyControls.TimeSeconds=0;
+                        }
+                    }
+                    break;
+                case Time:
+                    t=ElapsedTime.read()-StartTime;
+                    if (t>MyControls.TimeSeconds) {
+                        DisplayMessage();
+                        if(MyControls.ElementNumber<5) {
+                            MyControls.ElementNumber++;
+                            LoadProfile(MyControls.ProfileNumber,MyControls.ElementNumber);
+                        } else {
+                            MyControls.Temperature=25.0;
+                            MyControls.PercentPower=0;
+                            MyControls.TimeSeconds=0;
+                        }
+                    }
+                    break;
+            }
+        }//goal test
+    }
+}