diff --git a/avrTinyThreads.h b/avrTinyThreads.h new file mode 100644 index 0000000..b9c4be2 --- /dev/null +++ b/avrTinyThreads.h @@ -0,0 +1,62 @@ +/*------------------------------------------------------------------*/ +/* Projet: AVR tiny Threads */ +/* File : avrTinyThreads.h */ +/* Author: user@W500 greatly inpired by Adam Dunkels 08/28/20 */ +/*------------------------------------------------------------------*/ +#ifndef __avrTinyThreads_h__ +#define __avrTinyThreads_h__ +typedef struct AT { + unsigned short c; +} S_at ; +#define AT_ST_WAIT 0 +#define AT_ST_QUIT 1 +#define AT_ST_STOP 2 +#define AT_SET(AT) (AT)->c = 0 ; +#define AT_BODY(params) char params // BODY +#define AT_START(AT) { \ + char AT_YIELD_FLAG = 1; \ + switch((AT)->c) { case 0: // START +#define AT_STOP(AT) } ; \ + AT_YIELD_FLAG = 0; \ + AT_SET(AT); \ + return AT_ST_STOP; } // STOP +#define AT_QUIT(AT) \ + do { \ + AT_SET(AT); \ + return AT_ST_QUIT; \ + } while(0) // QUIT +#define AT_RESET(AT) \ + do { \ + AT_SET(AT); \ + return AT_ST_WAIT; \ + } while(0) // RESET + +#define AT_WAIT_UNTIL(AT, exp) \ + do { \ + (AT)->c = __LINE__; \ + case __LINE__: \ + if(!(exp)) { \ + return AT_ST_WAIT; \ + } \ + } while(0) // UNTIL +#define AT_WAIT_WHILE(AT, expr) \ + AT_WAIT_UNTIL((AT), !(expr)) // WHILE +#define AT_RUN(fct) \ + ((fct) < AT_ST_QUIT) // RUN +#define AT_WAIT_THREAD(AT, thread) \ + AT_WAIT_WHILE((AT), AT_RUN(thread)) // WAIT_THREAD + +// checked for n calls in a thread : seems OK +#define AT_DELAY(AT, ms) \ + do { \ + static unsigned long _AT_ttl ; \ + _AT_ttl = millis() + (unsigned int)(ms); \ + AT_WAIT_UNTIL(AT, (millis() > _AT_ttl )); \ + } while(0) // DELAY +// AT_WAIT_UNTIL(sat, millis() - started > cycleTime ); + #define AT_SYN(AT, origin, ms) \ + do { \ \ + AT_WAIT_UNTIL(AT, (millis() - (origin) > (ms) )); \ + } while(0) // DELAY + +#endif // __avrTinyThreads_h__ diff --git a/firedrake.ino b/firedrake.ino new file mode 100644 index 0000000..d967252 --- /dev/null +++ b/firedrake.ino @@ -0,0 +1,205 @@ +/*------------------------------------------------------------------*/ +/* Projet: Firedrake 2020-09-03 */ +/* File : firedrake.c */ +/* Author: user@W500 */ +/*------------------------------------------------------------------*/ +// MODIFIED 10/28/21 user@khp: 2 pump & independant eyes +// Computed température in therm100K.h is 4° too hight + +#include "avrTinyThreads.h" // pseudo threads for avr +//#define TEST // if uncommented then test +#define IHM // uncommented = log on serial monitor +#ifdef TEST +#define MAXHEAT 7 // WIP TEST in seconds => ~ 4 cycles ??? +#define MAXTEMP 80.0 // WIP TEST in degree +#else +#define MAXHEAT 240 // seconds => 4mn => 120 cycles of 2s +#define MAXTEMP 150.0 // degree +#endif +#define MIMTEMP -270.0 // thermistor not present or broken +#define R_PUMP 13 // pump (BC337) yellow led, must move to pin 8 +#define HEATER 12 // heater relay, blue test led +#define R_EYE 11 // ~ Eye red led +#define L_EYE 10 // ~ Eye red led +#define GAUGE 9 // ~ Gauge green led +#define KNOCK 8 // button (must move to another pin) +#include "serialTrace.h" // infos / trace to serial monitor + +#include "therm100K.h" // Function for ntc thermistor 100K + +// threads; Knock Eye Heat Pump Gauge Alert Vigil +static S_at atK, atE, atH, atP, atG, atA, atV ; +uint16_t th=2000, tp=3000, toff=2000 ; +// heatTime=0, eyes open, stay, close, wait knock +uint32_t started=0, cycleTime = ( 255 * 6 ) + 3000 + ( 255 * 12 ) + toff; +bool unSafe = false, tooHot =false, failedTherm=false, notEmpty=true, cycle=false; + + +void initIO() { + pinMode(KNOCK, INPUT_PULLUP); + pinMode(R_EYE, OUTPUT); pinMode(L_EYE, OUTPUT); + analogWrite(R_EYE, 0); analogWrite(L_EYE, 0); // Eyes unlighted + pinMode(HEATER, OUTPUT); pinMode(R_PUMP, OUTPUT); + digitalWrite(HEATER, LOW); digitalWrite(R_PUMP, LOW) ; + pinMode(GAUGE, OUTPUT); analogWrite(GAUGE, 254); // gauge at max + Th_Setup() ; // thermistor I/O setup +} +void setup() { + ttySetIHM(9600); ttyPutMem(); // IHM & show free mem + initIO(); // Setup: set LEDs & button + AT_SET(&atK); AT_SET(&atE); AT_SET(&atH); AT_SET(&atP); + AT_SET(&atA); AT_SET(&atG); AT_SET(&atV); + ttyPutUin("CycleTime = ", cycleTime); + int raw = Th_ReadRaw(); + ttyPutInt("Raw temperature = ",raw); + float r_therm = Th_Compute_Therm_R(raw, TH_SERIE_R, PullUp); + ttyPutFloat("Thermistor bridge Ohm = ", r_therm); + float deg = Th_Compute_Simple_SH(r_therm); // uses Steinhart-Hart formula + ttyPutFloat("Th_Compute_Simple_SH = ", deg); + deg = Th_Get_Degre() ; + ttyPutFloat("Th_Get_Degre = ",deg); +} +/*------------------------------------------------------------------*/ +void ledPWM(uint8_t led, uint8_t ratio) { + int val=constrain(ratio,0,254); // limit to values [0..254] + analogWrite(led, val); +} +bool getButton() { // haste hack to deflect bounces + int nb=0; + for (int i=0;i<10;i++) { + delay(10); // TODO ? : move in a thread ? + nb+= digitalRead(KNOCK); // Pullup pin: HIGH <=> button off + } // & LOW <=> button on + return (nb<5) ; +} +/*------------------------------------------------------------------*/ +// Thread to brighten or darken eyes gradatory +// Use PWM from start to aim lasting a period, inc is >0 for up & <0 for down +static AT_BODY(atEye(S_at *sat, int start, int aim, int inc, int period)) +{ + static int i=0 ; + AT_START(sat); + ledPWM(R_EYE, start) ; ledPWM(L_EYE, start) ; + do { + AT_DELAY(sat, period); + i=i+inc; + ledPWM(R_EYE, i) ; ledPWM(L_EYE, i) ; + } while (i != aim ); + AT_STOP(sat); +} +/*------------------------------------------------------------------*/ +// Thread checking knocks +static AT_BODY(atKnock(S_at *sat)) +{ + AT_START(sat); + while (1) { + AT_WAIT_UNTIL(sat, getButton() ); + cycle=true; + started=millis(); + AT_WAIT_THREAD(sat, atEye(&atE,0,254,1,6)); // Opening + AT_DELAY(sat, tp); + AT_WAIT_THREAD(sat, atEye(&atE,254,0,-1,12)); // Closing + cycle=false; + AT_WAIT_UNTIL(sat, millis() - started > cycleTime ); // end time + } + AT_STOP(sat); +} +/*------------------------------------------------------------------*/ +uint16_t addElapsed(uint16_t period) { + static uint32_t heatTime; + heatTime += period; + return (uint16_t) ( heatTime / 1000 ) ; +} +// Thread actuating heating +static AT_BODY(atHeater(S_at *sat, uint16_t offset, uint16_t period )) +{ + static uint32_t former = 0; int burnt = 0; + AT_START(sat); + while( notEmpty && cycle ) { // cycle started if not empty + AT_DELAY(sat, period); // offset time + ttyPutStr("heat on"); + former = millis(); + digitalWrite(HEATER, HIGH); + AT_WAIT_UNTIL(sat, (millis() - former > period ) || unSafe ); // heating time + digitalWrite(HEATER, LOW); + burnt = addElapsed(period) ; ttyPutUin("Burning time: ", burnt); + notEmpty = (burnt <= MAXHEAT); // check if enough for next cycle + AT_WAIT_UNTIL(sat, millis() - started > cycleTime ); // end cycle time + } + AT_STOP(sat); +} +// Thread actuating pumping +static AT_BODY(atPump(S_at *sat, uint16_t offset, uint16_t period )) +{ + AT_START(sat); + while( notEmpty && cycle ) { + AT_DELAY(sat, offset); //ttyPutStr("pump on"); + digitalWrite(R_PUMP, HIGH); + AT_DELAY(sat, period); + digitalWrite(R_PUMP, LOW); // ttyPutStr("pump off"); + AT_WAIT_UNTIL(sat, (millis() - started > cycleTime) || unSafe ); // end time + } + AT_STOP(sat); +} +/*------------------------------------------------------------------*/ +// Thread to flash green led when action needed +static AT_BODY(atAlert(S_at *sat, int blink_temp, int blink_fail, int blink_empty)) +{ + AT_START(sat); + while ( ! notEmpty || unSafe ) { + if ( tooHot ) + AT_DELAY(sat, blink_temp); + else if ( failedTherm ) + AT_DELAY(sat, blink_fail); + else + AT_DELAY(sat, blink_empty); + digitalWrite(GAUGE, !digitalRead(GAUGE)); + } + AT_STOP(sat); +} +// Thread liquid monitoring +static AT_BODY(atGauge(S_at *sat)) +{ + static uint16_t elapsed = 0, ratio = 0; + AT_START(sat); + while ( notEmpty && cycle ) { + ratio = map(addElapsed(0), 0, MAXHEAT, 254, 0); + ledPWM(GAUGE, ratio); + AT_WAIT_UNTIL(sat, millis() - started > cycleTime ); // end time + } + AT_STOP(sat); +} +// Thread temperature security +static AT_BODY(atVigil( S_at *sat, int watch )) +{ + static float deg=0; static int ioMonitor = 0; + AT_START(sat); + deg = Th_Get_Degre(); // do the Steinhart-Hart formula for each call + while (deg < MAXTEMP && deg > MIMTEMP) { + AT_DELAY(sat, watch); + ioMonitor++; + if (ioMonitor > 40) { // wait 40 * watch ms to print Temperature + deg = Th_Get_Degre() ; + ttyPutFloat("Temperature = ",deg); + ioMonitor = 0; + // delay(1000); //TEST + } + } + ttyPutFloat("RISKY Temperature = ",deg); + // Prevent heating as temperature is out of safe range, & set alert + unSafe = true ; // impeach heater & pump + if (deg < MIMTEMP ) failedTherm = true ; // for gauge blinking + if (deg > MAXTEMP ) tooHot = true ; // for gauge blinking fast + + AT_STOP(sat); +} +/*------------------------------------------------------------------*/ +void loop() { + atVigil(&atV,200); // heat watch of 200ms + atKnock(&atK); // check for knock + atHeater(&atH,1500,th); // offset & duration for heater + atPump(&atP,1500,tp); // offset & duration for pump + atAlert(&atA,50,200,400); // set alerts to Gauge Led + atGauge(&atG); // monitor heat time +} +/*------------------------------------------------------------------*/ diff --git a/serialTrace.h b/serialTrace.h new file mode 100644 index 0000000..95892ce --- /dev/null +++ b/serialTrace.h @@ -0,0 +1,103 @@ +/*------------------------------------------------------------------*/ +/* Projet: Dev Tools */ +/* File : serialTrace.h */ +/* Author: user@W500 08/28/20 */ +/*------------------------------------------------------------------*/ +// RCS CI/CO : Cx v v, terminate log : CcCc, view log : Cx v l +// TODO: all this should be in a class or a singleton class +// TODO: strings should be in Flash 32kb code +// #include + +#ifndef __SERIALTRACE_H__ +#define __SERIALTRACE_H__ +extern char *__brkval; +unsigned long freeMemory() { + char top; + return &top - (__brkval ? __brkval : __malloc_heap_start); +} + +void retard(unsigned long ms, int mini) { +#ifndef TEST + delay(ms); +#else + delay(mini); +#endif +} +// functions used for I/O using serial monitor +#ifndef IHM // if IHM undefined then just empty functions. +// So users don't need to change code, just define/undefine IHM. +// A bit of a waste, but main purpose is testing. +bool ttyAskN ( String prompt ) {} +bool ttyAskY ( String prompt ) {} +char ttyGetChar ( String prompt ) {} +int ttyGetInt ( String prompt ) {} +void ttyPutInt ( String prompt, int num ) {} +void ttyPutMem ( ) {} +void ttyPutStr ( String txt ) {} +void ttyPutUin ( String prompt, unsigned int num ) {} +void ttyPutFloat ( String prompt, float num ) {} +void ttySetIHM ( long baud ) {} +void ttyWipe ( short lines ) {} + +#else +void ttySetIHM(long baud) { + Serial.begin(baud); // WIP check if baud in correct values ? + for (int i=0;i<10;i++) Serial.print("\n\n\n") ; +} +void ttyPutStr(String txt) { Serial.println(txt); +} +void ttyPutMem() { + char buf[32]; + unsigned long octets = freeMemory(); + int k = octets / 1024 ; int o = octets % 1024; + sprintf(buf, "SRAM: %dKo%d free",k,o); + Serial.println (buf) ; +} +void ttyPutInt(String prompt, int num ) { + char buf[64]; + sprintf(buf, "%s %d", prompt.c_str(), num ) ; + Serial.println (buf) ; +} +void ttyPutUin(String prompt, unsigned int num ) { + char buf[64]; + sprintf(buf, "%s %d", prompt.c_str(), num ) ; + Serial.println(buf) ; +} +int ttyGetInt(String prompt) { + Serial.println(prompt); + while (Serial.available()==0) {} //Wait for user input + String val=Serial.readString(); + return val.toInt(); +} +void ttyPutFloat ( String prompt, float num ) { + Serial.print(prompt); + Serial.println (num) ; + } +void ttyPutFloatRound ( String prompt, float num ) { + char buf[64]; + sprintf(buf, "%s %d", prompt.c_str(), int(num) ) ; + Serial.println (buf) ; + } +char ttyGetChar(String prompt) { + Serial.println(prompt); + while (Serial.available()==0) {} //Wait for user input + return Serial.read(); +} +bool ttyAskY(String prompt){ + byte R = 223 & (byte)ttyGetChar(prompt); + if ('Y' == R ) return true; + else return false ; +} +bool ttyAskN(String prompt){ + byte R = 223 & (byte)ttyGetChar(prompt); + if ('N' == R ) return true; + return false ; +} +void ttyWipe (short lines) { +//const short ESC=27 ; KO in Arduino serial monitor, works with others tty +// Serial.write(ESC); Serial.print("[2J"); // cls +// Serial.write(ESC); Serial.print("[H"); // cursor to home + while (lines--) Serial.print("\n") ; // somehow overkill ! +} +#endif // IHM +#endif // __SERIALTRACE_H__ diff --git a/therm100K.h b/therm100K.h new file mode 100644 index 0000000..67dcfa7 --- /dev/null +++ b/therm100K.h @@ -0,0 +1,138 @@ +/*----------------------------------------------------------------------------*/ +/* Projet: Firedrake */ +/* File : therm100K.h */ +/* Author: user@W500 09/20/20 */ +/*----------------------------------------------------------------------------*/ +/* + BRIDGE PULL UP MOUNTING (Pulldown is opposite) + + TH_VCC Analog In: TH_PIN TH_GND + | | | + x----/\/\/\/\/\/\/\/\/\/\/\/\/\-----x----/\/\/\/\/\/\/\/\/\/\/\/\/\--------x + Serie Resistor: TH_SERIE_R Thermistor: TH_R0, TH_T0, TH_BETA + */ +/*----------------------------------------------------------------------------*/ +// Tested on Uno board. +// Using direct computing of simplyfied Seinhart-Hart takes 330µs. +// Using 30 values look up table is 3 time less time, 100µs & error less than 1°. +// For the time being we do the math for each call, choice should be fixed +// at setup and Th_Get_Degre() should take it in account. +// TODO: all this should be in a class in a library + +#ifndef __THERM100K_H__ +#define __THERM100K_H__ + +#define TH_VCC 2 // digital output for bridge supply +#define TH_GND 6 // digital output for bridge ground +#define TH_PIN 5 // adc pin to bridge middle pont +#define TH_ADCSTEPS 1023 // analog max value 2^(adc digits)-1 + +// WIP: we use standard values for this kind of thermistor, +// but this yield a température 4° too high +// #define TH_R0 119.4 // nominal resistance in KOhm at T0°C => 4° too much at 18° +#define TH_R0 103.9 // nominal resistance in KOhm at T0°C => -.2°/+.6° at 18,5° +#define TH_T0 27.2 // temp at nominal resistance (mostly 25°C) +#define TH_BETA 3.58 // thermistor beta coeff [3-4] Kelvin +#define TH_SERIE_R 4.7 // Serie resistor +#define TH_MOUNT_R PullUp // Serie mounting in "PullUp" or "PullDown" +#define TH_KILO 1000.0 + +enum T_SeriePull { PullUp, PullDown }; // discriminate serie resistor mounting +float Th_Compute_Therm_R(int raw, float rSerie, T_SeriePull rPull); +float Th_Compute_Simple_SH(float r_therm); + +#define LUT_NB 30 // Look up table count, 30 values takes 10ms to build +struct { + int adc; float deg; +} Th_Lut[LUT_NB]; // Look up table +void Th_PrLookUp() { +#ifdef IHM + for (int i=0; i < LUT_NB; i++) { Serial.print("LUT adc "); Serial.print(Th_Lut[i].adc); + Serial.print(" => "); Serial.print(Th_Lut[i].deg); Serial.println("°C");} +#endif +} +/* This takes 9880 µseconds for 30 values on an UNO board. */ +void Th_MkLookUp(){ // builds a look up table adc <=> deg + float r_therm = 0; int adcVal = 1, astep = floor(float(TH_ADCSTEPS) /(LUT_NB-1)); + for (int i=0; i < LUT_NB; i++) { + Th_Lut[i].adc = adcVal; + r_therm = Th_Compute_Therm_R(adcVal, TH_SERIE_R, TH_MOUNT_R); + Th_Lut[i].deg = Th_Compute_Simple_SH(r_therm) ; + adcVal += astep ; + } +} +float Th_LookUpFind(int raw){ + float result=0, t ; int last = LUT_NB -1, idx_out=-1; // index for out array + if (raw <= Th_Lut[0].adc) idx_out = 0; + else if (raw >= Th_Lut[last].adc) idx_out = last; + + if (idx_out >= 0) { + result = float(Th_Lut[idx_out].deg) ; // out of bounds: first or last +#ifdef IHM + Serial.print("LUT: out of bounds at = "); Serial.println(idx_out); +#endif + } + else { + int pos = 1; // [0] allready tested + while(raw > Th_Lut[pos].adc) pos++; // looking for upper bound + if (raw == Th_Lut[pos].adc) result = float(Th_Lut[pos].deg); // exact match + else { // interpolate between pos-1 & pos + float part = 0; + part = float(raw - Th_Lut[pos-1].adc); + part /= float(Th_Lut[pos].adc - Th_Lut[pos-1].adc); + result = Th_Lut[pos-1].deg + part * (Th_Lut[pos].deg - Th_Lut[pos-1].deg); + } + } + return result; +} +void Th_Setup() { + pinMode(TH_VCC, OUTPUT); + pinMode(TH_GND, OUTPUT); + digitalWrite(TH_VCC, LOW); + digitalWrite(TH_GND, LOW); + analogReference(DEFAULT); +} +int Th_ReadRaw() { + int raw = 0; + digitalWrite(TH_VCC, HIGH); + delay(500); // WIP: give some time to reach 5V ? + raw = analogRead(TH_PIN); + digitalWrite(TH_VCC, LOW); + return raw; +} +/* This takes 80 µseconds on an UNO board. */ +float Th_Compute_Therm_R(int raw, float rSerie, T_SeriePull rPull) { + float val = 0 ; + switch (rPull) { + case(PullUp): val = rSerie * TH_KILO / ((float(TH_ADCSTEPS) / raw) - 1); + break; + case(PullDown): val = rSerie * TH_KILO * ((float(TH_ADCSTEPS) / raw) - 1); + break; +#ifdef IHM + default: Serial.println(); Serial.println( "BUG in Th_Compute_Therm_R" ); + Serial.println(); +#endif + } + return val; +} +/* This is computed using the simplified Steinhart-Hart formula + and takes 250 to 270 µseconds on an UNO board. + If more speed is needed, you may load a look up table and + interpolate the ADC reading. +*/ +float Th_Compute_Simple_SH(float r_therm) { + float s_h = 0; + s_h = r_therm / (TH_R0 * TH_KILO); // R/R0 + s_h = log(s_h); // ln(R/R0 ) + s_h /= TH_BETA * 1000.0; // 1/B * ln(R/R0 ) + s_h += 1.0 / (25.0 + 273.15 ); // + 1/T0 in Kelvin + s_h = 1.0 / s_h ; // invert + s_h = s_h - 273.15; // in Celsius + return s_h; +} +float Th_Get_Degre () { + int raw = Th_ReadRaw(); + float r_therm = Th_Compute_Therm_R(raw, TH_SERIE_R, TH_MOUNT_R); + return Th_Compute_Simple_SH(r_therm); +} +#endif // __THERM100K_H__