Brew heater controller. Programmed to mash grain and to boil wort
Dependencies: mbed QEI UniGraphic
Revision 0:c673d397e9dc, committed 2022-01-07
- Comitter:
- dswood
- Date:
- Fri Jan 07 12:08:12 2022 +0000
- Commit message:
- First release. Works but is a little clunky.
Changed in this revision
diff -r 000000000000 -r c673d397e9dc Boil1.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Boil1.h Fri Jan 07 12:08:12 2022 +0000 @@ -0,0 +1,44 @@ +#ifndef PROFILES +#define PROFILES +#include "Profiles.h" +#endif //Profiles + + +struct Profile Boil1 {{ +/*Name*/ "Boil 3 Hop adding "},{{ +/*Profile.Element[0].GoalType*/ Boil,//Raise temp to Boil +/*Profile.Element[0].MessageNo*/ 0, +/*Profile.Element[0].Temp*/ 120.0, +/*Profile.Element[0].Power*/ 100, +/*Profile.Element[0].Seconds*/ 0},{ + +/*Profile.Element[1].GoalType*/ Time, +/*Profile.Element[1].MessageNo*/ 1, +/*Profile.Element[1].Temp*/ 120.0, +/*Profile.Element[1].Power*/ 35, +/*Profile.Element[1].Seconds*/ 300},{ + +/*Profile.Element[2].GoalType*/ Time, //55mins boil bittering hops +/*Profile.Element[2].MessageNo*/ 1, +/*Profile.Element[2].Temp*/ 120.0, +/*Profile.Element[2].Power*/ 35, +/*Profile.Element[2].Seconds*/ 3300},{ + +/*Profile.Element[3].GoalType*/ Time, // Aroma +bitter hops +/*Profile.Element[3].MessageNo*/ 2, +/*Profile.Element[3].Temp*/ 120.0, +/*Profile.Element[3].Power*/ 35, +/*Profile.Element[3].Seconds*/ 900},{ + +/*Profile.Element[4].GoalType*/ Time, //Aroma hops +/*Profile.Element[4].MessageNo*/ 1, +/*Profile.Element[4].Temp*/ 120.0, +/*Profile.Element[4].Power*/ 35, +/*Profile.Element[4].Seconds*/ 300},{ + +/*Profile.Element[5].GoalType*/ Time,//Aroma Hops +/*Profile.Element[5].MessageNo*/ 5, +/*Profile.Element[5].Temp*/ 120, +/*Profile.Element[5].Power*/ 35, +/*Profile.Element[5].Seconds*/ 600}} +}; \ No newline at end of file
diff -r 000000000000 -r c673d397e9dc Boil2.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Boil2.h Fri Jan 07 12:08:12 2022 +0000 @@ -0,0 +1,44 @@ +#ifndef PROFILES +#define PROFILES +#include "Profiles.h" +#endif //Profiles + + +struct Profile Boil2 {{ +/*Name*/ "Boil 3 Hop adding++"},{{ //long version +/*Profile.Element[0].GoalType*/ Boil,//Raise temp to Boil +/*Profile.Element[0].MessageNo*/ 0, +/*Profile.Element[0].Temp*/ 120.0, +/*Profile.Element[0].Power*/ 100, +/*Profile.Element[0].Seconds*/ 0},{ + +/*Profile.Element[1].GoalType*/ Time, +/*Profile.Element[1].MessageNo*/ 1, +/*Profile.Element[1].Temp*/ 120.0, +/*Profile.Element[1].Power*/ 35, +/*Profile.Element[1].Seconds*/ 300},{ + +/*Profile.Element[2].GoalType*/ Time, //90mins boil bittering hops +/*Profile.Element[2].MessageNo*/ 1, +/*Profile.Element[2].Temp*/ 120.0, +/*Profile.Element[2].Power*/ 35, +/*Profile.Element[2].Seconds*/ 5400},{ + +/*Profile.Element[3].GoalType*/ Time, // Aroma +bitter hops +/*Profile.Element[3].MessageNo*/ 2, +/*Profile.Element[3].Temp*/ 120.0, +/*Profile.Element[3].Power*/ 35, +/*Profile.Element[3].Seconds*/ 900},{ + +/*Profile.Element[4].GoalType*/ Time, //Add Flocculant +/*Profile.Element[4].MessageNo*/ 1, +/*Profile.Element[4].Temp*/ 120.0, +/*Profile.Element[4].Power*/ 35, +/*Profile.Element[4].Seconds*/ 60},{ + +/*Profile.Element[5].GoalType*/ Time,//Aroma Hops +/*Profile.Element[5].MessageNo*/ 5, +/*Profile.Element[5].Temp*/ 120, +/*Profile.Element[5].Power*/ 35, +/*Profile.Element[5].Seconds*/ 600}} +}; \ No newline at end of file
diff -r 000000000000 -r c673d397e9dc Boil3.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Boil3.h Fri Jan 07 12:08:12 2022 +0000 @@ -0,0 +1,44 @@ +#ifndef PROFILES +#define PROFILES +#include "Profiles.h" +#endif //Profiles + + +struct Profile Boil3 {{ +/*Name*/ "Boil 3 Hop quick "},{{ //long version +/*Profile.Element[0].GoalType*/ Boil,//Raise temp to Boil +/*Profile.Element[0].MessageNo*/ 0, +/*Profile.Element[0].Temp*/ 120.0, +/*Profile.Element[0].Power*/ 100, +/*Profile.Element[0].Seconds*/ 0},{ + +/*Profile.Element[1].GoalType*/ Time, +/*Profile.Element[1].MessageNo*/ 1, +/*Profile.Element[1].Temp*/ 120.0, +/*Profile.Element[1].Power*/ 35, +/*Profile.Element[1].Seconds*/ 150},{ + +/*Profile.Element[2].GoalType*/ Time, //30mins boil bittering hops +/*Profile.Element[2].MessageNo*/ 1, +/*Profile.Element[2].Temp*/ 120.0, +/*Profile.Element[2].Power*/ 35, +/*Profile.Element[2].Seconds*/ 1800},{ + +/*Profile.Element[3].GoalType*/ Time, // Aroma +bitter hops +/*Profile.Element[3].MessageNo*/ 2, +/*Profile.Element[3].Temp*/ 120.0, +/*Profile.Element[3].Power*/ 35, +/*Profile.Element[3].Seconds*/ 840},{ + +/*Profile.Element[4].GoalType*/ Time, //Aroma hops +/*Profile.Element[4].MessageNo*/ 1, +/*Profile.Element[4].Temp*/ 120.0, +/*Profile.Element[4].Power*/ 35, +/*Profile.Element[4].Seconds*/ 60},{ + +/*Profile.Element[5].GoalType*/ Time,//Aroma Hops +/*Profile.Element[5].MessageNo*/ 5, +/*Profile.Element[5].Temp*/ 120, +/*Profile.Element[5].Power*/ 35, +/*Profile.Element[5].Seconds*/ 600}} +}; \ No newline at end of file
diff -r 000000000000 -r c673d397e9dc MashProfile.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MashProfile.h Fri Jan 07 12:08:12 2022 +0000 @@ -0,0 +1,44 @@ +#ifndef PROFILES +#define PROFILES +#include "Profiles.h" +#endif //Profiles + + +struct Profile MashProfile {{ +/*Name*/ "Mash Profile "},{{ +/*Profile.Element[0].GoalType*/ Temp,//Raise temp to 50C +/*Profile.Element[0].MessageNo*/ 3, +/*Profile.Element[0].Temp*/ 50.0, +/*Profile.Element[0].Power*/ 100, +/*Profile.Element[0].Seconds*/ 0},{ + +/*Profile.Element[1].GoalType*/ Time, +/*Profile.Element[1].MessageNo*/ 6, +/*Profile.Element[1].Temp*/ 60.0, +/*Profile.Element[1].Power*/ 30, +/*Profile.Element[1].Seconds*/ 2200},{ + +/*Profile.Element[2].GoalType*/ Time, +/*Profile.Element[2].MessageNo*/ 6, +/*Profile.Element[2].Temp*/ 66.0, +/*Profile.Element[2].Power*/ 30, +/*Profile.Element[2].Seconds*/ 4600},{ + +/*Profile.Element[3].GoalType*/ Time, +/*Profile.Element[3].MessageNo*/ 4, +/*Profile.Element[3].Temp*/ 70.0, +/*Profile.Element[3].Power*/ 35, +/*Profile.Element[3].Seconds*/ 2200},{ + +/*Profile.Element[4].GoalType*/ Time, +/*Profile.Element[4].MessageNo*/ 5, +/*Profile.Element[4].Temp*/ 50.0, +/*Profile.Element[4].Power*/ 1, +/*Profile.Element[4].Seconds*/ 600},{ + +/*Profile.Element[5].GoalType*/ Temp, +/*Profile.Element[5].MessageNo*/ 0, +/*Profile.Element[5].Temp*/ 50.0, +/*Profile.Element[5].Power*/ 1, +/*Profile.Element[5].Seconds*/ 0} } +}; \ No newline at end of file
diff -r 000000000000 -r c673d397e9dc MashProfileFull.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MashProfileFull.h Fri Jan 07 12:08:12 2022 +0000 @@ -0,0 +1,44 @@ +#ifndef PROFILES +#define PROFILES +#include "Profiles.h" +#endif //Profiles + + +struct Profile MashProfileFull {{ +/*Name*/ "Mash Profile Full "},{{ +/*Profile.Element[0].GoalType*/ Temp, //Raise temp to 50C +/*Profile.Element[0].MessageNo*/ 3, +/*Profile.Element[0].Temp*/ 50.0, +/*Profile.Element[0].Power*/ 100, +/*Profile.Element[0].Seconds*/ 0},{ + +/*Profile.Element[1].GoalType*/ Time, //Hold temp at 50C for 20 mins +/*Profile.Element[1].MessageNo*/ 6, +/*Profile.Element[1].Temp*/ 50.0, +/*Profile.Element[1].Power*/ 30, +/*Profile.Element[1].Seconds*/ 1200},{ + +/*Profile.Element[2].GoalType*/ Time, //Beta amylase +/*Profile.Element[2].MessageNo*/ 6, +/*Profile.Element[2].Temp*/ 60.0, +/*Profile.Element[2].Power*/ 30, +/*Profile.Element[2].Seconds*/ 2200},{ + +/*Profile.Element[3].GoalType*/ Time, //both beta and alpha compramise +/*Profile.Element[3].MessageNo*/ 6, +/*Profile.Element[3].Temp*/ 66.0, +/*Profile.Element[3].Power*/ 30, //hest time is 100s per celsius +/*Profile.Element[3].Seconds*/ 3300},{ // 45 mins + 600s + +/*Profile.Element[4].GoalType*/ Time, //alpha amllase +/*Profile.Element[4].MessageNo*/ 4, +/*Profile.Element[4].Temp*/ 70.0, +/*Profile.Element[4].Power*/ 35, +/*Profile.Element[4].Seconds*/ 2100},{ + +/*Profile.Element[5].GoalType*/ Temp, +/*Profile.Element[5].MessageNo*/ 5, +/*Profile.Element[5].Temp*/ 50.0, +/*Profile.Element[5].Power*/ 1, +/*Profile.Element[5].Seconds*/ 0}} +}; \ No newline at end of file
diff -r 000000000000 -r c673d397e9dc Profiles.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Profiles.h Fri Jan 07 12:08:12 2022 +0000 @@ -0,0 +1,17 @@ + +enum goal { Null,Boil, Temp, Time}; +char *Message[7]={"","Add Hops","Add Flocculant","Add Grain","Sparge","Finished","Phase Done"}; + +struct ProfileElement { + goal GoalType; + uint8_t MessageNo; + float Temp; + int8_t Power; + int Seconds; +}; +struct Profile { + char Name[20]; + ProfileElement Element[6]; +}; +char *TopLevelMenu[3]={"Top Menu ","Manual ","Programmed "}; +// 12345678901234567890 \ No newline at end of file
diff -r 000000000000 -r c673d397e9dc QEI.lib --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/QEI.lib Fri Jan 07 12:08:12 2022 +0000 @@ -0,0 +1,1 @@ +https://os.mbed.com/users/aberk/code/QEI/#5c2ad81551aa
diff -r 000000000000 -r c673d397e9dc UniGraphic.lib --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UniGraphic.lib Fri Jan 07 12:08:12 2022 +0000 @@ -0,0 +1,1 @@ +https://os.mbed.com/users/dswood/code/UniGraphic/#67b3634507da
diff -r 000000000000 -r c673d397e9dc main.cpp --- /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 + } +}
diff -r 000000000000 -r c673d397e9dc mbed.bld --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mbed.bld Fri Jan 07 12:08:12 2022 +0000 @@ -0,0 +1,1 @@ +https://os.mbed.com/users/mbed_official/code/mbed/builds/65be27845400 \ No newline at end of file