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

Dependencies:   mbed QEI UniGraphic

main.cpp

Committer:
dswood
Date:
2022-01-07
Revision:
0:c673d397e9dc

File content as of revision 0:c673d397e9dc:

/**

 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
    }
}