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

Dependencies:   mbed QEI UniGraphic

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers main.cpp Source File

main.cpp

00001 /**
00002 
00003  Spice modal
00004  .SUBCKT NTCLE203E3103SB0 RN Rp  PARAMS: TOLR=0 TOLB=0
00005 X64 Rn Rp NTC_BASE Params:
00006 + w=-14.6571976
00007 + x=4798.842
00008 + y=-115334
00009 + z=-3730535
00010 + gth=0.0032 gth1 = 0.0000167
00011 + cth=0.032
00012 + a=-14.65719769
00013 + r25=10000
00014 + b=4798.842
00015 + c=-115334
00016 + d=-3730535
00017 + T0=273.15
00018 + TR={1+TOLR/100}
00019 + TB={1+TOLB/100}
00020 .ENDS
00021 
00022  */
00023 
00024 
00025 /* Includes ------------------------------------------------------------------*/
00026 
00027 /* mbed specific header files. */
00028 /*
00029 GPIO_InitTypeDef GPIO_InitStruct;
00030 //Configure GPIO pin : PB14
00031 GPIO_InitStruct.Pin = GPIO_PIN_14;
00032 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;     // digital Output
00033 GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
00034 HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
00035 */
00036 #include "mbed.h"
00037 #include "SSD1306.h"
00038 #include "string"
00039 
00040 #include "MashProfile.h"
00041 #include "MashProfileFull.h"
00042 #include "Boil1.h"
00043 #include "Boil2.h"
00044 #include "Boil3.h"
00045 #ifndef PROFILES
00046 #define PROFILES
00047 #include "Profiles.h"
00048 #endif //Profiles
00049 
00050 #define Height 32
00051 #define Width 132
00052 
00053 
00054 
00055 #include "QEI.h"
00056 
00057 //Firing delay in microseconds
00058 #define SyncToZeroCrossingDelayRise 115
00059 #define SyncToZeroCrossingDelayFall 115
00060 
00061 #define TriacPulseLength 250
00062 // Blinking rate in milliseconds
00063 #define BLINKING_RATE_MS  500
00064 #define Bias_Current 0.261  // Thermistor current in mA
00065 /*
00066 
00067 FIR filter designed with
00068 http://t-filter.appspot.com
00069 
00070 sampling frequency: 2000 Hz
00071 
00072 * 0 Hz - 100 Hz
00073   gain = 1
00074   desired ripple = 5 dB
00075   actual ripple = 1.0675530446538501 dB
00076 
00077 * 600 Hz - 1000 Hz
00078   gain = 0
00079   desired attenuation = -60 dB
00080   actual attenuation = -71.5928467318133 dB
00081 
00082 */
00083 
00084 #define NumberOfTaps 9
00085 
00086 static double filter_taps[NumberOfTaps] = {
00087     0.009246906411528302,
00088     0.0484176527692072,
00089     0.12764201806827455,
00090     0.21665898750162413,
00091     0.25663867508629506,
00092     0.21665898750162413,
00093     0.12764201806827455,
00094     0.0484176527692072,
00095     0.009246906411528302
00096 };
00097 //float PotBuf[NumberOfTaps];
00098 float TempBuf[NumberOfTaps];
00099 int CycleCount=0,StartT=0;
00100 bool FireTriac,Boiling,DisplayFlag;
00101 /*------- inputs ---------*/
00102 InterruptIn FallingEdge(D12);
00103 InterruptIn RisingEdge(D13);
00104 //AnalogIn Potentiometer(A0);
00105 // Thermistor (ntc) with a constant current flowing through it
00106 AnalogIn Thermistor(A1);
00107 //Rotary Encoder for entering values and navigation
00108 QEI wheel (D9, D8, NC, 20);
00109 DigitalIn Button(PC_9);
00110 InterruptIn ButtonPressed(PC_9);
00111 /*------- inputs -end-----*/
00112 
00113 /*------- outputs ---------*/
00114 //DigitalOut led(LED1);
00115 // Output to switch on the LED in the opto to fire the triac
00116 DigitalOut Triac(D10),OledReset(D6);
00117 // Test output to see timings and states on a scope
00118 DigitalOut Test(D15);
00119 // The screen
00120 SSD1306 MyOled(SPI_8, 10000000, D4, D5, D3, D2, D6, D7,"MyOled", Width, Height); // Spi 8bit, 10MHz, mosi, miso, sclk, cs, reset, dc
00121 // fixme clock may need to be 5MHz
00122 // 5000000 gives a clock of 3.125MHz (genius)
00123 // 10000000 gives 6.25
00124 // 12800000 gives 12.8Mhz still works
00125 // 20000000 gives 12.5Mhz
00126 // the nearest i can find to 8MHz is 10000000
00127 
00128 /*------- outputs -end-----*/
00129 
00130 
00131 char Buff[3][20];
00132 Timeout SyncDelay,TriacPulseTimeout,ReadTemperature;
00133 
00134 //float Pot;
00135 int FireCount;
00136 int Demand;
00137 float valT,LastTemp=0,LastError=0,Integrated=0;
00138 bool Rise,MainsOn;
00139 time_t now;
00140 Timer ElapsedTime;
00141 uint64_t LastTime,TempTime,DebounceTime,StartTime,DemandTime,CheckTime,KnobTime,CycleTime;
00142 struct Controls {
00143     float Temperature;
00144     int8_t PercentPower;
00145     int16_t TimeSeconds;
00146     int8_t ProfileNumber;
00147     int8_t ElementNumber;
00148     uint8_t MessageNo;
00149     goal Goal;
00150     char Message[20];
00151 
00152 };
00153 struct Controls MyControls= {25.0,50,0};
00154 struct Profile *ProfileArray[5]= { &MashProfile,&MashProfileFull,&Boil1,&Boil2,&Boil3};
00155 enum VariableType { Index, TempC, PwrW, TimeS };
00156 struct MenuType {
00157     int8_t IndexVal;  //The current thing pointed to by the cursor
00158     int8_t Number;
00159     int8_t MaxIndex;
00160     int8_t Offset;
00161     VariableType CurrentType;
00162     void EncoderHandler( bool Increment, VariableType ChangeType);
00163     struct Controls *Cntrl; // points to the main loop controls
00164     char *MenuText[8]; //8 lines of menus to start with
00165     void (*NextMenu[8])();
00166     bool Switch;
00167 };
00168 
00169 Serial Mypc(USBTX, USBRX, 115200);  //for debugging
00170 int64_t DebugValue;
00171 void FiringCounter(int Power);
00172 void PulseTriac();
00173 void SyncFall();
00174 void SyncRise();
00175 //char *Menu[2]={"Manual","Programmed"};
00176 
00177 int string_length(char* given_string)
00178 {
00179     // variable to store the
00180     // length of the string
00181     int length = 0;
00182     while (*given_string != '\0') {
00183         length++;
00184         given_string++;
00185     }
00186 
00187     return length;
00188 }
00189 void MenuType::EncoderHandler( bool Increment, VariableType ChangeType)
00190 {
00191     int Multiplier=1,t;
00192     t=ElapsedTime.read_ms()-KnobTime;
00193     if (t<50) Multiplier=10;
00194     else if (t<100) Multiplier=5;
00195     else if (t<150) Multiplier=2;
00196     KnobTime=ElapsedTime.read_ms();
00197     switch (ChangeType) {
00198         case Index:
00199             if (Increment) IndexVal++;
00200             else IndexVal--;
00201             if (IndexVal>MaxIndex) IndexVal=0;
00202             if (IndexVal<0) IndexVal=MaxIndex;
00203             if ((IndexVal-Offset)>2) Offset=IndexVal-2;
00204             if ((IndexVal-Offset)<0) Offset=IndexVal;
00205             break;
00206         case TempC:
00207             if (Increment) Cntrl->Temperature=Cntrl->Temperature+(float)0.1*(float)Multiplier;
00208             else Cntrl->Temperature=Cntrl->Temperature-(float)0.1*(float)Multiplier;
00209             if (Cntrl->Temperature>120)Cntrl->Temperature=120;
00210             if (Cntrl->Temperature<25)Cntrl->Temperature=25;
00211             break;
00212         case PwrW:
00213             if (Increment) Cntrl->PercentPower+=Multiplier;
00214             else Cntrl->PercentPower-=Multiplier;
00215             if (Cntrl->PercentPower>100)Cntrl->PercentPower=100;
00216             if (Cntrl->PercentPower<=0)Cntrl->PercentPower=1;
00217             break;
00218         case TimeS:
00219             Multiplier=Multiplier*Multiplier;
00220             if (Increment) Cntrl->TimeSeconds+=Multiplier;
00221             else Cntrl->TimeSeconds-=Multiplier;
00222             if (Cntrl->TimeSeconds>10800)Cntrl->TimeSeconds=10800;
00223             if (Cntrl->TimeSeconds<0)Cntrl->TimeSeconds=0;
00224             break;
00225     }
00226 }
00227 
00228 struct MenuType MainMenu;
00229 void SetTopLevelMenu();
00230 void SetProgrammedMenu();
00231 void SetManualMenu();
00232 void ShowProgramMenu();
00233 void StartProfileMenu();
00234 void DoNothing();
00235 void AlterDataMenu();
00236 void LoadProfile(int Prof,int Ele);
00237 void SetProfile();
00238 void ProfileMenu();
00239 void GetTemperature();
00240 void DisplayNow();
00241 void DisplayMenu();
00242 void SetOutputSpeed();
00243 void PulseTriac()
00244 {
00245     if (Triac==1) {
00246         Triac=0;
00247         Test=0; //fixme
00248     } else {
00249         Triac=1;// Fire the triac
00250         Test=1;
00251         TriacPulseTimeout.attach_us(&PulseTriac,TriacPulseLength);
00252     }
00253 }
00254 void MenuSetup()
00255 {
00256     MainMenu.Offset=0;
00257     MainMenu.IndexVal=0;
00258     MainMenu.Cntrl=&MyControls;
00259     SetTopLevelMenu();
00260 }
00261 void SyncFall()
00262 {
00263     int64_t t;
00264     /* After many attempts to get this right t should be a uint64_t BUT
00265     whan you do a comparison if(uint64_t> 99) it all goes to crap.
00266     I think you end up with an overflow in the math used to evaluate it.
00267     So only do comparicons with signed numbers int or float.
00268     */
00269     if (!Rise) return;
00270     Rise=false;
00271     t=ElapsedTime.read_us()-CycleTime;
00272     DebugValue=t;
00273     CycleTime=ElapsedTime.read_us();
00274     if (DisplayFlag) return;
00275     if ((t>20400)||(t<19600)) {
00276         CycleCount=0;
00277         MainsOn=false;
00278     }
00279     if (CycleCount<10) {
00280         CycleCount++;
00281         return;
00282     }
00283     MainsOn=true;
00284     if (FireTriac) SyncDelay.attach_us(&PulseTriac,SyncToZeroCrossingDelayFall);
00285     FiringCounter(Demand);
00286     ReadTemperature.attach_us(&GetTemperature,9000); //read temp if mains on synced
00287     // the noise generated on triac firing upsets the temperature measurment
00288 }
00289 void SyncRise()
00290 {
00291     int64_t t;
00292     Test=1;
00293     if (Rise) return;
00294     Rise=true;
00295     t=ElapsedTime.read_us()-CycleTime;
00296     if (DisplayFlag) return;
00297     if ((t>10200)||(t<9800)) {
00298         CycleCount=0;
00299         MainsOn=false;
00300     }
00301     if (CycleCount<10) {
00302         return;
00303     }
00304     Test=0;
00305     if (FireTriac) SyncDelay.attach_us(&PulseTriac,SyncToZeroCrossingDelayRise);
00306 }
00307 
00308 float ReadResistance() // Read voltage across thermistor
00309 {
00310 
00311     int i,j;
00312     float tmp=0;
00313     j=StartT;
00314     TempBuf[StartT]=Thermistor;
00315     for (i=0; i<NumberOfTaps; i++) {
00316         if (j<0) j+=NumberOfTaps;
00317         tmp+=TempBuf[j]*filter_taps[i];
00318         j--;
00319         if (j<0) j=NumberOfTaps-1;
00320     }
00321     StartT--;
00322     if (StartT<0) StartT=NumberOfTaps-1;
00323     /* tmp is the fraction 3.3V across thermistor.
00324     Resistance = V/I
00325     Resistance = (tmp*3.3V)/Bias_Current */
00326     tmp=(tmp*(float)3.3)/(float)Bias_Current;  // Answer is in Kohm because current is in mA
00327     return tmp;
00328 }
00329 float ResistanceToTemp(float Res)
00330 {
00331     /* T0 = 25C = 298K
00332     beta =3977
00333     R0=10000 ohm     */
00334     Res=Res+(float)0.0001;  //Res can't be zero
00335     float temp;  // temp here is both temperary and temperature
00336     temp=log(Res/(float)10)/(float)3977.0;  //beta from datasheet
00337     temp=(float)1/(temp+(float)0.003354);  // 1/298.15
00338     //temp=temp-(float)273.15 ;//kelvin to c conversion
00339     /* ok so the temp is miles off due to things like the sensor being pressed
00340     against the outside of the barrel.  Loosing heat to ambient etc.
00341     So lets compensate for that with an offset and a scale factor. */
00342     temp=(temp*(float)1.09)-(float)22;
00343     temp=temp-(float)273.15 ;//kelvin to c conversion
00344     return temp;
00345 }
00346 void GetTemperature()
00347 {
00348     if (DisplayFlag) return; // The noise generated by the comms to the display causes 0.5C error
00349     valT=ResistanceToTemp(ReadResistance());
00350 }
00351 void Pressed()
00352 {
00353     int t;
00354     t=ElapsedTime.read_ms()-DebounceTime;
00355     DebounceTime=ElapsedTime.read_ms();
00356     if (t<250) return;
00357     //Mypc.printf("Index %d \n",MainMenu.IndexVal);
00358     MainMenu.Number=MainMenu.IndexVal;
00359     MainMenu.NextMenu[MainMenu.IndexVal]();
00360 }
00361 void setup()
00362 {
00363     set_time(1256729737);  // Set RTC time to Wed, 28 Oct 2009 11:35:37
00364     now=time(NULL);
00365     // Mypc.printf("Setup");
00366     Triac=0;  // Low to fire
00367     FallingEdge.fall(&SyncFall); //falling mains voltage
00368     RisingEdge.fall(&SyncRise);  //rising mains voltage
00369     FireCount=0;
00370     ButtonPressed.fall(&Pressed);
00371     //SetOutputSpeed();
00372     MyOled.BusEnable(true);//Chip select
00373     MyOled.FastWindow(true);
00374     MyOled.set_orientation(3);
00375     MyOled.locate(0,0);
00376     MyOled.set_font((unsigned char*) Terminal6x8,32,127,false);
00377     MyOled.cls();
00378     MyOled.BusEnable(false);//Chip select
00379     ButtonPressed.fall(&Pressed);
00380     ElapsedTime.start();
00381     LastTime=ElapsedTime.read_ms();
00382     DebounceTime=LastTime;
00383     CycleTime=ElapsedTime.read_us();
00384     TempTime=LastTime;
00385     CheckTime=LastTime;
00386     DemandTime=LastTime;
00387     KnobTime=LastTime;
00388     MenuSetup();
00389 }
00390 void SetOutputSpeed()
00391 {
00392     GPIO_InitTypeDef GPIO_InitStruct;
00393 //Configure GPIO pin : PB_3 D3
00394     GPIO_InitStruct.Pin = GPIO_PIN_3;
00395     GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;     // digital Output
00396     GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
00397     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
00398 }
00399 
00400 void FiringCounter(int Power)
00401 {
00402     int x;
00403     x=Power;
00404     if (x>100) x=100;
00405     if (x<0) x=0;
00406     if (FireCount<0) {
00407         FireCount+=100-x;
00408         FireTriac=true;
00409     } else {
00410         FireCount-=x;
00411         FireTriac=false;
00412     }
00413 
00414 }
00415 void SetTopLevelMenu()
00416 {
00417 
00418     MainMenu.IndexVal=0;
00419     MainMenu.MaxIndex=2;
00420     MainMenu.Offset=0;
00421     MainMenu.MenuText[0]=TopLevelMenu[0];
00422     MainMenu.MenuText[1]=TopLevelMenu[1];
00423     MainMenu.MenuText[2]=TopLevelMenu[2];
00424     MainMenu.CurrentType=Index;
00425     MainMenu.NextMenu[0]=&SetTopLevelMenu;
00426     MainMenu.NextMenu[1]=&SetManualMenu;
00427     MainMenu.NextMenu[2]=&SetProgrammedMenu;
00428     MainMenu.NextMenu[3]=&SetTopLevelMenu;//Set it to something that is valid
00429     MainMenu.NextMenu[4]=&SetTopLevelMenu;
00430     MainMenu.NextMenu[5]=&SetTopLevelMenu;
00431     MyControls.Temperature= 25.0;
00432     MyControls.PercentPower=1;
00433     MyControls.TimeSeconds=0;
00434     MainMenu.Switch=true;
00435 }
00436 void SetProgrammedMenu()
00437 {
00438     MainMenu.Offset=0;
00439     MainMenu.IndexVal=0;
00440     MainMenu.MaxIndex=5;//12345678901234567890
00441     MainMenu.MenuText[0]="Previous Menu      ";
00442     MainMenu.MenuText[1]=MashProfile.Name;
00443     MainMenu.MenuText[2]=MashProfileFull.Name;
00444     MainMenu.MenuText[3]=Boil1.Name;
00445     MainMenu.MenuText[4]=Boil2.Name;
00446     MainMenu.MenuText[5]=Boil3.Name;
00447 
00448     MainMenu.NextMenu[0]=&SetTopLevelMenu;
00449     MainMenu.NextMenu[1]=&SetProfile;
00450     MainMenu.NextMenu[2]=&SetProfile;
00451     MainMenu.NextMenu[3]=&SetProfile;
00452     MainMenu.NextMenu[4]=&SetProfile;
00453     MainMenu.NextMenu[5]=&SetProfile;
00454 
00455     MainMenu.CurrentType=Index;
00456     MainMenu.Switch=true;
00457 }
00458 void SetManualMenu()
00459 {
00460     MainMenu.IndexVal=0;
00461     MainMenu.MaxIndex=3;
00462     MainMenu.Offset=0; // 1234567890123456789
00463     MainMenu.MenuText[0]="Manual Menu        ";
00464     MainMenu.MenuText[1]="Temperature";
00465     MainMenu.MenuText[2]="Power Level";
00466     MainMenu.MenuText[3]="Time       ";
00467     MainMenu.NextMenu[0]=&SetTopLevelMenu;
00468     MainMenu.NextMenu[1]=&AlterDataMenu;
00469     MainMenu.NextMenu[2]=&AlterDataMenu;
00470     MainMenu.NextMenu[3]=&AlterDataMenu;
00471     MainMenu.NextMenu[4]=&DoNothing;
00472     MainMenu.Switch=false;
00473 }
00474 void ShowProgramMenu()
00475 {
00476     MainMenu.Offset=0;
00477     MainMenu.IndexVal=0;
00478     MainMenu.MaxIndex=2;
00479     MainMenu.MenuText[0]="Previous Menu      ";
00480     MainMenu.MenuText[1]=ProfileArray[MyControls.ProfileNumber]->Name;
00481     MainMenu.MenuText[2]="Start              ";
00482 
00483     if (MyControls.Goal==Null) {
00484         MainMenu.MenuText[3]="";
00485         MainMenu.NextMenu[3]=&SetProgrammedMenu;
00486     } else {//profile loaded
00487         MainMenu.MenuText[3]="Current Profile    ";
00488         //                    1234567890123456789
00489         MainMenu.NextMenu[3]=&ProfileMenu;
00490         MainMenu.MaxIndex=3;
00491     }
00492     MainMenu.MenuText[4]="";
00493     MainMenu.MenuText[5]="";
00494     MainMenu.NextMenu[0]=&SetProgrammedMenu;
00495     MainMenu.NextMenu[1]=&DoNothing;
00496     MainMenu.NextMenu[2]=&StartProfileMenu;
00497 
00498     MainMenu.NextMenu[4]=&SetProgrammedMenu;
00499     MainMenu.NextMenu[5]=&SetProgrammedMenu;
00500     MainMenu.CurrentType=Index;
00501     MainMenu.Switch=true;
00502 
00503 }
00504 void StartProfileMenu()
00505 {
00506     LoadProfile(MyControls.ProfileNumber,0); //load the first element of the selected profile
00507     ProfileMenu();
00508 }
00509 void ProfileMenu()
00510 {
00511     MainMenu.IndexVal=0;
00512     MainMenu.MaxIndex=3;
00513     MainMenu.Offset=0;                       //
00514     MainMenu.MenuText[0]=MyControls.Message;
00515     MainMenu.MenuText[1]="Temperature";
00516     MainMenu.MenuText[2]="Power Level";
00517     MainMenu.MenuText[3]="Time       ";
00518     MainMenu.NextMenu[0]=&ShowProgramMenu;
00519     MainMenu.NextMenu[1]=&AlterDataMenu;
00520     MainMenu.NextMenu[2]=&AlterDataMenu;
00521     MainMenu.NextMenu[3]=&AlterDataMenu;
00522     MainMenu.NextMenu[4]=&DoNothing;
00523     MainMenu.Switch=false;
00524 }
00525 void AlterDataMenu()
00526 {
00527     if (MainMenu.CurrentType==Index) switch (MainMenu.IndexVal) {
00528             case 1:
00529                 MainMenu.CurrentType=TempC;
00530                 break;
00531             case 2:
00532                 MainMenu.CurrentType=PwrW;
00533                 break;
00534             case 3:
00535                 MainMenu.CurrentType=TimeS;
00536                 break;
00537             default:
00538                 MainMenu.CurrentType=Index;
00539         } else MainMenu.CurrentType=Index;
00540 }
00541 void DoNothing() //The purpose of this function is assign a function pointer to it rather than something wrong or a crash
00542 {
00543 }
00544 void SetProfile()
00545 {
00546     int i=MainMenu.Number-1;
00547     if (i<0)i=0;
00548     if (i>4)i=4;//sanitize
00549     MyControls.ProfileNumber=i;
00550     strncpy(MyControls.Message,ProfileArray[i]->Name,20);
00551     MyControls.Goal=Null;
00552     ShowProgramMenu();
00553 
00554 
00555 }
00556 void LoadProfile(int Prof,int Ele)
00557 {
00558     MyControls.Temperature= ProfileArray[Prof]->Element[Ele].Temp;
00559     MyControls.PercentPower=ProfileArray[Prof]->Element[Ele].Power;
00560     MyControls.TimeSeconds=ProfileArray[Prof]->Element[Ele].Seconds;
00561     MyControls.Goal=ProfileArray[Prof]->Element[Ele].GoalType;
00562     MyControls.ProfileNumber=Prof;
00563     MyControls.ElementNumber=Ele;
00564     MyControls.MessageNo=ProfileArray[Prof]->Element[Ele].MessageNo;
00565     StartTime=ElapsedTime.read();
00566     /*Mypc.printf("T %.1f P %d Sec %d Prof %d Ele %d\n",
00567                MyControls.Temperature,
00568                 MyControls.PercentPower,
00569                 MyControls.TimeSeconds,
00570                 MyControls.ProfileNumber,
00571                 MyControls.ElementNumber);
00572     */
00573 }
00574 
00575 void DisplayMenu()
00576 {
00577     DisplayFlag=true; //This flag is used to stop generating new interupts
00578     wait_us(SyncToZeroCrossingDelayRise+TriacPulseLength+25); //wait for interupts to finish
00579     MyOled.BusEnable(true);//Chip select
00580     bool Highlight=false;
00581     //We have a 32 pixecl height and 9 pixcel font.  3 lines 10 spacing
00582     if (MainMenu.Switch) {
00583         for (int i=0; i<3 ; i++) {
00584             MyOled.locate(6,(i*10)+1);
00585             if (MainMenu.IndexVal==(i+MainMenu.Offset)) {
00586                 MyOled.background(White);
00587                 MyOled.foreground(Black);
00588             } else {
00589                 MyOled.background(Black);
00590                 MyOled.foreground(White);
00591             }
00592             MyOled.printf(MainMenu.MenuText[i+MainMenu.Offset]);
00593             MyOled.background(Black);
00594             MyOled.foreground(White);
00595         }
00596     } else  {
00597         time_t t;
00598         if (MainMenu.CurrentType==TempC)sprintf(Buff[0],"  %6.1f ",MyControls.Temperature);
00599         else sprintf(Buff[0],"  %6.1f ",valT);
00600         if (MainMenu.CurrentType==PwrW)sprintf(Buff[1],"  %5d%c ",MyControls.PercentPower,38);
00601         else sprintf(Buff[1],"  %5d%c ",Demand,38);
00602         if (MainMenu.CurrentType==TimeS) {
00603             t=MyControls.TimeSeconds;
00604             strftime(Buff[2],10,"%T ",localtime(&t));
00605         } else {
00606             t=(int)ElapsedTime.read()-StartTime;
00607             strftime(Buff[2],10,"%T ",localtime(&t));
00608         }
00609         for (int i=0; i<3 ; i++) {
00610             MyOled.locate(6,(i*10)+1);
00611             if (MainMenu.IndexVal==(i+MainMenu.Offset)) {
00612                 Highlight=true;
00613             } else {
00614                 Highlight=false;
00615             }
00616             if (Highlight&&MainMenu.CurrentType==Index) {
00617                 MyOled.background(White);
00618                 MyOled.foreground(Black);
00619                 MyOled.printf(MainMenu.MenuText[i+MainMenu.Offset]);
00620             } else {
00621                 MyOled.background(Black);
00622                 MyOled.foreground(White);
00623                 MyOled.printf(MainMenu.MenuText[i+MainMenu.Offset]);
00624             }
00625             if (Highlight&&MainMenu.CurrentType!=Index) {
00626                 MyOled.background(White);
00627                 MyOled.foreground(Black);
00628                 if ((i+MainMenu.Offset>=1)&&(i+MainMenu.Offset<=3))MyOled.printf(Buff[i+MainMenu.Offset-1]);
00629             } else {
00630                 MyOled.background(Black);
00631                 MyOled.foreground(White);
00632                 if ((i+MainMenu.Offset>=1)&&(i+MainMenu.Offset<=3))MyOled.printf(Buff[i+MainMenu.Offset-1]);
00633             }
00634         }
00635     }
00636     MyOled.BusEnable(false);//Chip select
00637     DisplayFlag=false;
00638 }
00639 int CalcDemand() //return percent power
00640 {
00641     float ErrorSignal, Differential;
00642     int power;
00643     ErrorSignal=MyControls.Temperature-valT;
00644     if (abs(ErrorSignal)<1)Integrated+=ErrorSignal;
00645     else Integrated=0;
00646     if (Integrated>20)Integrated=(float)20;
00647     if (Integrated<-1)Integrated=(float)-1;
00648     Differential=ErrorSignal-LastError;
00649     LastError=ErrorSignal;
00650     power=(int)(((float)70*ErrorSignal)+((float)0.6*Integrated)+(45*Differential));
00651     // Mypc.printf("Error %.2f Int %.2f Dif %.2f\n\r", ErrorSignal,Integrated,Differential);
00652     MainsOn ? Mypc.printf("Mains On\n\r") : Mypc.printf("Mains Off\n\r");
00653     Mypc.printf("DebugValue %d\n\r",DebugValue);
00654     Mypc.printf("Elapsed time %d\n\r",ElapsedTime.read_us());
00655 
00656     if (power<0)power=0;
00657     if (power>MyControls.PercentPower)power=MyControls.PercentPower;
00658     return power;
00659 }
00660 void DisplayMessage()
00661 {
00662     int x,y,z;
00663     z=MyControls.MessageNo; //convert to int before compare
00664     if (z>7)MyControls.MessageNo=0;
00665     if (z==0) {
00666         strncpy(MyControls.Message,ProfileArray[MyControls.ProfileNumber]->Name,20);
00667     } else {
00668         y=string_length(Message[MyControls.MessageNo]) ;
00669         for (x=0; x<18; x++) {
00670             if (x<y)MainMenu.MenuText[0][x]=Message[MyControls.MessageNo][x];
00671             else MainMenu.MenuText[0][x]=' ';
00672         }
00673         MainMenu.MenuText[0][19]='\0';
00674     }
00675 }
00676 int main()
00677 {
00678     Mypc.printf("Starting Main\n\r");
00679     setup();
00680     int Position=wheel.getPulses();
00681     int UpdateTime=750;
00682     uint64_t TempTime=0;
00683     int64_t t;// time is uint64_t but int64_t used for comparison in if statement
00684     while (true) { //main loop
00685         if (Position!=wheel.getPulses()) {
00686             MainMenu.EncoderHandler( Position<wheel.getPulses(), MainMenu.CurrentType);
00687             Position=wheel.getPulses();
00688             UpdateTime=250; //Quick display refresh when knob moved
00689             //Mypc.printf("Pos %d \n",Position);
00690             //DisplayMenu();
00691         }
00692         /*Had a problem where mains detection was dropping out here.
00693         The sum ElapsedTime.read_ms()-CycleTime was sometimes =0.
00694         But 0>75 is true. eg if ((ElapsedTime.read_ms()-CycleTime)>75) would be true
00695         if the sum result was 0. Doing the sum first was ok.  Had the same problem with Debounce.
00696         */
00697         t=ElapsedTime.read_us()-CycleTime;
00698         if (t>75000) { //mains not proven. Read temp.  When mains is on this is done synced to mains
00699             CycleCount=0;
00700             MainsOn=false;
00701             GetTemperature();
00702         }
00703         t=ElapsedTime.read_ms()-LastTime;
00704         if (t>UpdateTime) {
00705             LastTime=ElapsedTime.read_ms();
00706             UpdateTime=750;
00707             DisplayMenu();
00708         }
00709         t=ElapsedTime.read_ms()-TempTime;
00710         if (t>15000) { //upped to 15s which will give a smaller temp rise by averaging over a longer period
00711             if (valT>99.0f) {
00712                 if (valT-LastTemp<0.15f)Boiling=true;
00713                 else    Boiling=false; // still rising
00714             } else  Boiling=false;  //too cold
00715             LastTemp=valT;
00716             TempTime=ElapsedTime.read_ms();
00717         }
00718         t=ElapsedTime.read_ms()-DemandTime;
00719         if (t>3000) {
00720             Demand=CalcDemand();
00721             DemandTime=ElapsedTime.read_ms();
00722         }
00723         t=ElapsedTime.read_ms()-CheckTime;
00724         if (t>3000) {
00725             CheckTime=ElapsedTime.read_ms();
00726             switch (MyControls.Goal) {
00727                 //enum goal { Null,Boil, Temp, Time};
00728                 case Boil:
00729                     if (Boiling ) {
00730                         DisplayMessage();
00731                         if (MyControls.ElementNumber<5) {
00732                             MyControls.ElementNumber++;
00733                             LoadProfile(MyControls.ProfileNumber,MyControls.ElementNumber);
00734                         } else {
00735                             MyControls.Temperature=25.0;
00736                             MyControls.PercentPower=0;
00737                             MyControls.TimeSeconds=0;
00738                         }
00739                     }
00740                     break;
00741                 case Temp:
00742                     if (abs(valT-MyControls.Temperature)<(float)0.25) {
00743                         DisplayMessage();
00744                         if  (MyControls.ElementNumber<5) {
00745                             MyControls.ElementNumber++;
00746                             LoadProfile(MyControls.ProfileNumber,MyControls.ElementNumber);
00747                         } else {
00748                             MyControls.Temperature=25.0;
00749                             MyControls.PercentPower=0;
00750                             MyControls.TimeSeconds=0;
00751                         }
00752                     }
00753                     break;
00754                 case Time:
00755                     t=ElapsedTime.read()-StartTime;
00756                     if (t>MyControls.TimeSeconds) {
00757                         DisplayMessage();
00758                         if(MyControls.ElementNumber<5) {
00759                             MyControls.ElementNumber++;
00760                             LoadProfile(MyControls.ProfileNumber,MyControls.ElementNumber);
00761                         } else {
00762                             MyControls.Temperature=25.0;
00763                             MyControls.PercentPower=0;
00764                             MyControls.TimeSeconds=0;
00765                         }
00766                     }
00767                     break;
00768             }
00769         }//goal test
00770     }
00771 }