diff --git a/lambda-test/command-test.c b/lambda-test/command-test.c index c447ccc..74c01ca 100644 --- a/lambda-test/command-test.c +++ b/lambda-test/command-test.c @@ -21,7 +21,7 @@ static bool testIsSimulation(void) { setupPorts(); - setHeaterOn(false); + setHeaterState(heaterStateOff); assertFalse(isSimulation()); runCommand("se"); @@ -48,7 +48,7 @@ static bool testHeater(void) { setupPorts(); - setHeaterOn(false); + setHeaterState(heaterStateOff); assertFalse(isHeaterOn()); runCommand("he"); diff --git a/lambda-test/rules-test.c b/lambda-test/rules-test.c index 1ef15d3..b58a7aa 100644 --- a/lambda-test/rules-test.c +++ b/lambda-test/rules-test.c @@ -16,7 +16,7 @@ /* Module rules */ -extern uint16_t age; +extern uint8_t measCount; extern int8_t dir; extern uint8_t airgate; extern Rule rules[]; @@ -25,6 +25,9 @@ Measurement meas = {0, 0, 0, 0}; + // prevent rule warmStart from kicking in + setHeaterState(heaterStateOn); + resetRules(true); dir = burning_down; reason(meas); @@ -276,32 +279,33 @@ static bool testFireOut(void) { - resetRules(true); Measurement meas = {0, 0, 0, 0}; + + resetRules(true); dir = firing_up; meas.tempI = 50; - age = 0; + measCount = 10; reason(meas); assertFalse(rules[5].fired); meas.tempI = TEMP_FIRE_OUT; - age = 0; + measCount = 10; reason(meas); assertFalse(rules[5].fired); meas.tempI = TEMP_FIRE_OUT_RESET; - age = 0; + measCount = 10; reason(meas); assertFalse(rules[5].fired); meas.tempI = TEMP_FIRE_OUT - 1; - age = 0; + measCount = 10; reason(meas); assertTrue(rules[5].fired); meas.tempI = TEMP_FIRE_OUT_RESET; - age = 0; + measCount = 10; reason(meas); assertFalse(rules[5].fired); @@ -312,25 +316,22 @@ static bool testWarmStart(void) { + Measurement meas = {101, 0, 0, 0}; + resetRules(true); - Measurement meas = {0, 0, 0, 0}; - - age = 0; + setHeaterState(heaterStateOff); airgate = 50; - - setHeaterOn(false); reason(meas); assertTrue(50 == airgate); assertTrue(heaterStateOff == getHeaterState()); assertFalse(rules[6].fired); - age = 0; + resetRules(true); + setHeaterState(heaterStateOff); dir = firing_up; - - setHeaterOn(false); reason(meas); assertTrue(100 == airgate); - assertTrue(heaterStateUp == getHeaterState()); + assertTrue(heaterStateOn == getHeaterState()); assertTrue(rules[6].fired); cancelAlert(); @@ -344,17 +345,17 @@ resetTime(); Measurement meas = {0, 0, 0, 0}; - setHeaterOn(false); + setHeaterState(heaterStateOff); meas.current = milliAmpsReady; reason(meas); assertTrue(heaterStateOff == getHeaterState()); - setHeaterOn(true); + setHeaterState(heaterStateOn); meas.current = 5000; reason(meas); - assertTrue(heaterStateUp == getHeaterState()); + assertTrue(heaterStateOn == getHeaterState()); - setHeaterOn(true); + setHeaterState(heaterStateOn); meas.current = milliAmpsReady; reason(meas); assertTrue(heaterStateReady == getHeaterState()); @@ -371,7 +372,7 @@ Measurement meas = {0, 0, 0, 0}; dir = firing_up; - setHeaterOn(true); + setHeaterState(heaterStateOn); meas.current = 0; reason(meas); assertTrue(heaterStateFault == getHeaterState()); @@ -388,7 +389,7 @@ Measurement meas = {0, 0, 0, 0}; dir = firing_up; - setHeaterOn(true); + setHeaterState(heaterStateOn); meas.current = 8000; reason(meas); assertTrue(heaterStateFault == getHeaterState()); @@ -405,7 +406,7 @@ Measurement meas = {0, 0, 0, 0}; dir = firing_up; - setHeaterOn(true); + setHeaterState(heaterStateOn); // more than 5 mins addTime(301); @@ -425,7 +426,7 @@ resetTime(); Measurement meas = {0, 0, 0, 0}; - setHeaterOn(true); + setHeaterState(heaterStateOn); // equal or more than 30 mins below TEMP_FIRE_OUT addTime(1800); @@ -447,7 +448,7 @@ resetTime(); Measurement meas = {0, 0, 0, 0}; - setHeaterOn(true); + setHeaterState(heaterStateOn); // equal or more than 3 hours below TEMP_AIRGATE_0 addTime(10800UL); @@ -463,100 +464,6 @@ return true; } -static bool testReasonDirBurnUp(void) { - - resetRules(true); - Measurement meas = {0, 0, 2000, 0}; - - age = 0; - reason(meas); - assertTrue(dir == none); - - meas.tempI = 9; - meas.lambda = LAMBDA_MAX; - age = 180; - reason(meas); - assertTrue(dir == none); - - meas.tempI = TEMP_FIRE_OUT; - meas.lambda = LAMBDA_MAX; - age = 180; - reason(meas); - assertTrue(dir == firing_up); - - meas.tempI = TEMP_AIRGATE_25 - 1; - meas.lambda = LAMBDA_MAX; - age = 180; - reason(meas); - assertTrue(dir == firing_up); - - meas.tempI = TEMP_AIRGATE_25 - 1; - meas.lambda = LAMBDA_MAX; - age = 180; - reason(meas); - assertTrue(dir == none); - - meas.tempI = TEMP_AIRGATE_25; - meas.lambda = LAMBDA_BURNING - 1; - age = 180; - reason(meas); - assertTrue(dir == burning); - - meas.tempI = TEMP_AIRGATE_25 + 1; - meas.lambda = LAMBDA_BURNING; - age = 180; - reason(meas); - assertTrue(dir == burning); - - cancelAlert(); - - return true; -} - -static bool testReasonDirBurnDown(void) { - - resetRules(true); - Measurement meas = {999, 0, 1999, 0}; - - age = 0; - reason(meas); - assertTrue(dir == none); - - meas.tempI = 800; - meas.lambda = LAMBDA_BURNING - 1; - age = 180; - reason(meas); - assertTrue(dir == burning); - - meas.tempI = TEMP_AIRGATE_25; - meas.lambda = LAMBDA_MAX; - age = 180; - reason(meas); - assertTrue(dir == burning); - - meas.tempI = TEMP_AIRGATE_25 - 1; - meas.lambda = LAMBDA_BURNING - 1; - age = 180; - reason(meas); - assertTrue(dir == burning); - - meas.tempI = TEMP_AIRGATE_25 - 1; - meas.lambda = LAMBDA_MAX; - age = 180; - reason(meas); - assertTrue(dir == none); - - meas.tempI = TEMP_FIRE_OUT; - meas.lambda = LAMBDA_MAX; - age = 180; - reason(meas); - assertTrue(dir == burning_down); - - cancelAlert(); - - return true; -} - /* Test "class" */ static const char class[] PROGMEM = "rules"; @@ -574,26 +481,22 @@ static const char testHeaterFaultNoheat_P[] PROGMEM = "testHeaterFaultNoheat"; static const char testHeaterTimeout0_P[] PROGMEM = "testHeaterTimeout0"; static const char testHeaterTimeout1_P[] PROGMEM = "testHeaterTimeout1"; -static const char testReasonDirBurnUp_P[] PROGMEM = "testReasonDirBurnUp"; -static const char testReasonDirBurnDown_P[] PROGMEM = "testReasonDirBurnDown"; /* Tests */ static TestCase const tests[] = { - {class, testAirgate50_P, testAirgate50}, + {class, testAirgate50_P, testAirgate50}, {class, testAirgate25_P, testAirgate25}, {class, testAirgateClose_P, testAirgateClose}, {class, testTooRich_P, testTooRich}, {class, testTooLean_P, testTooLean}, - {class, testFireOut_P, testFireOut}, + {class, testFireOut_P, testFireOut}, {class, testWarmStart_P, testWarmStart}, {class, testHeaterReady_P, testHeaterReady}, {class, testHeaterFaultNoconn_P, testHeaterFaultNoconn}, {class, testHeaterFaultShort_P, testHeaterFaultShort}, {class, testHeaterFaultNoheat_P, testHeaterFaultNoheat}, {class, testHeaterTimeout0_P, testHeaterTimeout0}, - {class, testHeaterTimeout1_P, testHeaterTimeout1}, - {class, testReasonDirBurnUp_P, testReasonDirBurnUp}, - {class, testReasonDirBurnDown_P, testReasonDirBurnDown} + {class, testHeaterTimeout1_P, testHeaterTimeout1} }; TestClass rulesClass = {tests, sizeof(tests) / sizeof(tests[0])}; diff --git a/lambda-test/sensors-test.c b/lambda-test/sensors-test.c index f324958..40f6e98 100644 --- a/lambda-test/sensors-test.c +++ b/lambda-test/sensors-test.c @@ -186,11 +186,11 @@ } static bool testSetHeaterOn(void) { - setHeaterOn(true); + setHeaterState(heaterStateOn); assertTrue(isHeaterOn()); - assertTrue(heaterStateUp == getHeaterState()); + assertTrue(heaterStateOn == getHeaterState()); - setHeaterOn(false); + setHeaterState(heaterStateOff); assertFalse(isHeaterOn()); assertTrue(heaterStateOff == getHeaterState()); @@ -198,7 +198,7 @@ } static bool testSetHeaterState(void) { - setHeaterOn(false); + setHeaterState(heaterStateOff); assertFalse(isHeaterOn()); assertTrue(heaterStateOff == getHeaterState()); @@ -211,7 +211,7 @@ static bool testGetHeaterUptime(void) { addTime(10); - setHeaterOn(true); + setHeaterState(heaterStateOn); assertTrue(0 == getHeaterUptime()); addTime(10); diff --git a/lambda/Makefile b/lambda/Makefile index 4dd53cb..0630869 100644 --- a/lambda/Makefile +++ b/lambda/Makefile @@ -3,7 +3,7 @@ MCU = atmega328p # Currently supported are 1 MHz and 8 MHz -F_CPU = 1000000 +F_CPU = 8000000 # Also try BAUD = 19200 or 38400 if you're feeling lucky. BAUD = 9600 diff --git a/lambda/alert.c b/lambda/alert.c index 59371c1..bc83fc2 100644 --- a/lambda/alert.c +++ b/lambda/alert.c @@ -84,3 +84,7 @@ bool isAlertActive(void) { return alertActive; } + +bool isBeeping(void) { + return beepCount > 0; +} diff --git a/lambda/alert.h b/lambda/alert.h index 7111dea..cc91e73 100644 --- a/lambda/alert.h +++ b/lambda/alert.h @@ -49,4 +49,9 @@ */ bool isAlertActive(void); +/** + * Returns true if (an alert) is currently beeping, false otherwise. + */ +bool isBeeping(void); + #endif /* ALERT_H_ */ diff --git a/lambda/command.c b/lambda/command.c index 5392ec2..59e7b9e 100644 --- a/lambda/command.c +++ b/lambda/command.c @@ -43,7 +43,7 @@ resetTime(); resetDisplay(); resetRules(true); - setHeaterOn(true); + setHeaterState(heaterStateOn); beep(1, 1, 31); } else if (strcmp_P(fields[0], PSTR("sd")) == 0) { @@ -66,12 +66,12 @@ } else if (strcmp_P(fields[0], PSTR("he")) == 0) { // oxygen sensor heater enable - setHeaterOn(true); + setHeaterState(heaterStateOn); beep(1, 1, 31); } else if (strcmp_P(fields[0], PSTR("hd")) == 0) { // oxygen sensor heater disable - setHeaterOn(false); + setHeaterState(heaterStateOff); beep(1, 1, 31); } else if (strcmp_P(fields[0], PSTR("cm")) == 0) { diff --git a/lambda/display.c b/lambda/display.c index 20a2893..3f032c8 100644 --- a/lambda/display.c +++ b/lambda/display.c @@ -88,6 +88,7 @@ } void cycleDisplay(void) { + bool beeping = isBeeping(); if (isAlertActive()) { // button pressed during alert cancelAlert(); @@ -97,8 +98,10 @@ position = displayPosCurrent; } } + if (! beeping) { + beep(1, 1, 31); + } updatePending = true; - beep(1, 1, 31); } void updateMeas(Measurement const meas) { diff --git a/lambda/integers.c b/lambda/integers.c index af2d6cd..b5fb14b 100644 --- a/lambda/integers.c +++ b/lambda/integers.c @@ -22,4 +22,3 @@ ((num - ((den < 0) ? den + 1 : den - 1)) / den) : ((num + ((den < 0) ? den + 1 : den - 1)) / den); } - diff --git a/lambda/lambda.c b/lambda/lambda.c index 873537c..c5daf8d 100644 --- a/lambda/lambda.c +++ b/lambda/lambda.c @@ -57,7 +57,7 @@ alert_P(1, 1, 31, PSTR(MSG_WELCOME), PSTR(""), false); // spend some time on being polite while (getTime() < 3) {} - setHeaterOn(true); + setHeaterState(heaterStateOn); uint32_t time = 0; Measurement meas; diff --git a/lambda/rules.c b/lambda/rules.c index 7a81821..d6c8ce0 100644 --- a/lambda/rules.c +++ b/lambda/rules.c @@ -15,13 +15,27 @@ #include "usart.h" -uint8_t age = 0; FireDir dir = none; uint8_t airgate = 100; +uint8_t measCount = 10; -static Measurement rulesMeasMax = {0, 0, 2000, 0}; -static Measurement rulesMeasPrev = {0, 0, 2000, 0}; -static bool prevInit = false; +static int32_t tempIAvg = 0; +static int16_t tempIMax = 0; +static int16_t tempIOldQueue[QUEUE_SIZE]; + +/** + * Pushes the given new value in the given fixed-size queue of old temperature + * measurements and returns the oldest value being pushed off the queue. + */ +static int16_t push(int16_t queue[], const size_t size, const int16_t value) { + int16_t last = queue[size - 1]; + for (size_t i = size - 1; i > 0; i--) { + queue[i] = queue[i - 1]; + } + queue[0] = value; + + return last; +} /** * Reminds to set the air gate to 50% when the fire is still firing up @@ -32,6 +46,7 @@ if ((dir == firing_up || dir == burning) && meas.tempI >= TEMP_AIRGATE_50 && meas.lambda >= LAMBDA_TOO_LEAN && airgate != 50) { + airgate = 50; alert_P(BEEPS, LENGTH, TONE, PSTR(MSG_AIRGATE_50_0), PSTR(""), false); *fired = true; @@ -60,7 +75,7 @@ Measurement const meas) { if (dir == burning_down && meas.tempI < TEMP_AIRGATE_0 && meas.lambda >= LAMBDA_MAX && airgate > 0) { - setHeaterOn(false); + setHeaterState(heaterStateOff); airgate = 0; alert_P(BEEPS, LENGTH, TONE, PSTR(MSG_AIRGATE_CLOSE_0), PSTR(""), false); @@ -108,7 +123,7 @@ static void fireOut(bool* const fired, int8_t const dir, Measurement const meas) { if (! *fired && dir == firing_up && meas.tempI < TEMP_FIRE_OUT && - rulesMeasMax.tempI - meas.tempI > (TEMP_FIRE_OUT_RESET - TEMP_FIRE_OUT)) { + tempIMax - meas.tempI > (TEMP_FIRE_OUT_RESET - TEMP_FIRE_OUT)) { alert_P(BEEPS, LENGTH, TONE, PSTR(MSG_FIRE_OUT_0), PSTR(""), false); *fired = true; } @@ -120,19 +135,17 @@ /** * Resets rules and some state and switches on the heating if it seems that * wood was added or the oven was fired up without resetting. + * TODO make a complete reset including time? + * TODO come up with something better than using the state of the heater */ static void warmStart(bool* const fired, int8_t const dir, Measurement const meas) { - if ((dir == firing_up || dir == burning) && ! isHeaterOn() && - getHeaterState() != heaterStateFault) { - // it seems wood has been added or - probably more likely - oven - // was fired up without resetting. Should probably work that way - // anyway, making manual reset unnecessary? - // TODO make a complete reset including time? + if (meas.tempI > TEMP_FIRE_OUT && (dir == firing_up || dir == burning) && + ! isHeaterOn() && (getHeaterState() != heaterStateFault)) { resetRules(false); airgate = 100; - rulesMeasMax.tempI = meas.tempI; - setHeaterOn(true); + tempIMax = meas.tempI; + setHeaterState(heaterStateOn); *fired = true; } } @@ -164,7 +177,6 @@ if (meas.current > milliAmpsShort || meas.current < milliAmpsDisconn || (getHeaterUptime() >= 300 && getHeaterState() != heaterStateReady)) { // short circuit or disconnected or did not warm up within 5 minutes - setHeaterOn(false); setHeaterState(heaterStateFault); alert_P(BEEPS, LENGTH, TONE, PSTR(MSG_HEATER_FAULT_0), PSTR(MSG_HEATER_FAULT_1), true); @@ -183,12 +195,12 @@ uint32_t heaterUptime = getHeaterUptime(); if (heaterUptime >= 1800 && meas.tempI < TEMP_FIRE_OUT && meas.lambda >= LAMBDA_MAX) { - setHeaterOn(false); + setHeaterState(heaterStateOff); alert_P(BEEPS, LENGTH, TONE, PSTR(MSG_FIRE_OUT_0), PSTR(""), false); } if (heaterUptime >= 10800 && meas.tempI < TEMP_AIRGATE_0 && meas.lambda >= LAMBDA_MAX) { - setHeaterOn(false); + setHeaterState(heaterStateOff); if (airgate > 0) { alert_P(BEEPS, LENGTH, TONE, PSTR(MSG_AIRGATE_CLOSE_0), PSTR(""), false); @@ -235,51 +247,51 @@ } // rules applied to every 10th measurement - if (age % 10 == 0) { + if (measCount == 10) { + measCount = 0; + size_t rulesSize = sizeof(rules) / sizeof(rules[0]); for (size_t i = 0; i < rulesSize; i++) { rules[i].cond(&(rules[i].fired), dir, meas); } + + tempIAvg = meas.tempI + tempIAvg - ((tempIAvg - 4) >> 3); + int16_t tempICur = tempIAvg >> 3; + int16_t tempIOld = push(tempIOldQueue, QUEUE_SIZE, tempICur); + + // simply skip if old temperature was 0°C - in practice it never goes + // below 3°C anyway. + if (tempIOld > 0) { + // try to figure out if the fire is building up, burning or burning + // down by comparing the current temperature value with one that is + // 3 minutes old + dir = none; + if ((tempICur - tempIOld) >= TEMP_DELTA_UP && + tempICur < TEMP_MIN && meas.lambda >= LAMBDA_BURNING) { + dir = firing_up; + } + if (tempICur >= TEMP_MIN || meas.lambda < LAMBDA_BURNING) { + dir = burning; + } + if ((tempIOld - tempICur) >= TEMP_DELTA_DOWN && + tempICur < TEMP_MIN && meas.lambda >= LAMBDA_BURNING && + tempIMax >= TEMP_AIRGATE_50) { + dir = burning_down; + } + } } - age++; - - // init previous measurements with current measurements - if (! prevInit) { - rulesMeasPrev = meas; - prevInit = true; - } - - // try to figure out if the fire is building up, burning or burning down - // by comparing current measurements with ones that are 3 minutes old. - if (age >= AGE_MEAS_PREV) { - dir = none; - if ((meas.tempI - rulesMeasPrev.tempI) >= TEMP_DELTA_UP && - meas.tempI < TEMP_MIN && meas.lambda >= LAMBDA_BURNING) { - dir = firing_up; - } - if (meas.tempI >= TEMP_MIN || meas.lambda < LAMBDA_BURNING) { - dir = burning; - } - if ((rulesMeasPrev.tempI - meas.tempI) >= TEMP_DELTA_DOWN && - meas.tempI < TEMP_MIN && meas.lambda >= LAMBDA_BURNING && - rulesMeasMax.tempI >= TEMP_AIRGATE_50) { - dir = burning_down; - } - - rulesMeasPrev = meas; - age = 0; - } - - rulesMeasMax.tempI = MAX(rulesMeasMax.tempI, meas.tempI); + measCount++; + tempIMax = MAX(tempIMax, meas.tempI); } void resetRules(bool const state) { if (state) { - prevInit = false; - rulesMeasMax.tempI = 0; - - age = 0; + tempIMax = 0; + measCount = 10; + for (size_t i = 0; i < QUEUE_SIZE; i++) { + tempIOldQueue[i] = 0; + } dir = none; airgate = 100; } diff --git a/lambda/rules.h b/lambda/rules.h index 6285921..10f43e3 100644 --- a/lambda/rules.h +++ b/lambda/rules.h @@ -15,13 +15,11 @@ #define LENGTH 10 #define TONE 31 -/** Age of previous measurements to compare against */ -#define AGE_MEAS_PREV 180 /** Exhaust reaches at least this temperature when the fire is burning */ #define TEMP_MIN 700 -/** Min. increase in temperature during AGE_MEAS_PREV when firing up */ +/** Min. increase in temperature during AGE_TEMPI_OLD when firing up */ #define TEMP_DELTA_UP 10 -/** Min. decrease in temperature during AGE_MEAS_PREV when burning down */ +/** Min. decrease in temperature during AGE_TEMPI_OLD when burning down */ #define TEMP_DELTA_DOWN 1 /** Min. temperature at which to set the air gate to 50% when firing up */ #define TEMP_AIRGATE_50 500 @@ -48,6 +46,9 @@ */ #define LAMBDA_TOO_LEAN 1400 +/** Last 18 firebox temperature values updated every 10 seconds = 3 minutes */ +#define QUEUE_SIZE 18 + typedef enum { none = 0, firing_up = 1, diff --git a/lambda/sensors.c b/lambda/sensors.c index 61ae08a..183c2d3 100644 --- a/lambda/sensors.c +++ b/lambda/sensors.c @@ -192,23 +192,19 @@ } } -void setHeaterOn(bool const on) { - if (on) { - PORTB |= (1 << PB2); - heaterState = heaterStateUp; - heaterOnTime = getTime(); - alert_P(1, 1, 31, PSTR(MSG_HEATER_UP_0), PSTR(MSG_HEATER_UP_1), true); - } else { - PORTB &= ~(1 << PB2); - heaterState = heaterStateOff; - } -} - +// TODO merge with getHeaterState() bool isHeaterOn(void) { return bit_is_set(PORTB, PB2); } void setHeaterState(int8_t const state) { + if (state == heaterStateOn) { + PORTB |= (1 << PB2); + heaterOnTime = getTime(); + alert_P(1, 1, 31, PSTR(MSG_HEATER_UP_0), PSTR(MSG_HEATER_UP_1), true); + } else if (state == heaterStateOff || state == heaterStateFault) { + PORTB &= ~(1 << PB2); + } heaterState = state; } diff --git a/lambda/sensors.h b/lambda/sensors.h index 5019ecc..c93ce3f 100644 --- a/lambda/sensors.h +++ b/lambda/sensors.h @@ -30,7 +30,7 @@ typedef enum { heaterStateFault = -1, heaterStateOff = 0, - heaterStateUp = 1, + heaterStateOn = 1, heaterStateReady = 2 } HeaterState; @@ -116,11 +116,6 @@ char* toInfo(uint16_t lambda); /** - * Turns the heater of the oxygen sensor on or off. - */ -void setHeaterOn(bool on); - -/** * Returns true if the heater of the oxygen sensor is turned on, * false otherwise. */