Maiskolben/Maiskolben_TFT/Maiskolben_TFT.ino
Luca 34bfe65323 Added option to set °C or °F; fixed EEPROM issue
In boot menu you can now choose to display either celsius or farenheit.
An issue was fixed which loaded illegal values from EEPROM for ADC
offset and gain.
2017-11-30 15:03:33 +01:00

1005 lines
26 KiB
C++

#include <SPI.h>
#include <TFT_ILI9163C.h>
#include <PID_v1.h>
#include <EEPROM.h>
#include "TimerOne.h"
#include "definitions.h"
/*
* If your display stays white, uncomment this.
* Cut reset trace (on THT on upper layer/0R), connect STBY_NO (A1) with reset of TFT (at 4050).
* See also readme in mechanical folder for reference.
*/
//#define USE_TFT_RESET
/*
* If red is blue and blue is red change this
* If not sure, leave commented, you will be shown a setup screen
*/
//#define HARDWARE_DEFINED_TFT 2
/*
* Based on your Hardware-Revision there may be modifications to the PCB.
* In V3 and up is a second voltage measurement circuit.
* HW REVS:
* 1.5 - 2.8:
* For THT this should be set to anything < 3
* Normally leave this commented as it is stored in EEPROM
*/
// V 1.5 - 2.11, Maiskolben THT
//#define HARDWARE_REVISION 2
// V 3.0 and 3.1
//#define HARDWARE_REVISION 3
/*
* Only used for testing, do not use.
*/
//#define INSTALL
//#define TEST_ADC
volatile boolean off = true, stby = true, stby_layoff = false, sw_stby_old = false, sw_up_old = false, sw_down_old = false, clear_display = true, store_invalid = true, menu = false;
volatile uint8_t pwm, threshold_counter;
volatile int16_t cur_t, last_measured;
volatile error_type error = NO_ERROR;
error_type error_old;
int16_t stored[3] = {300, 350, 450}, set_t = TEMP_MIN, set_t_old, cur_t_old, target_t;
double pid_val, cur_td, set_td;
uint8_t store_to = 255;
p_source power_source, power_source_old = NO_INIT;
boolean blink;
uint16_t cnt_measure_voltage, cnt_compute, cnt_sw_poll, cnt_but_press, cnt_off_press, cnt_but_store;
float v_c1, v_c2, v_c3, v_in, v;
uint8_t array_index, array_count;
uint32_t sendNext;
uint32_t last_temperature_drop;
uint32_t last_on_state;
boolean wasOff = true, old_stby = false;
boolean autopower = true, bootheat = false, fahrenheit = false;
uint8_t revision = 1;
boolean menu_dismissed = false;
boolean autopower_repeat_under = false;
boolean force_redraw = false;
boolean power_down = false;
uint16_t charge = 0;
float adc_offset = ADC_TO_TEMP_OFFSET;
float adc_gain = ADC_TO_TEMP_GAIN;
#define RGB_DISP 0x0
#define BGR_DISP 0x2
#ifdef USE_TFT_RESET
TFT_ILI9163C tft = TFT_ILI9163C(TFT_CS, TFT_DC, STBY_NO);
#else
TFT_ILI9163C tft = TFT_ILI9163C(TFT_CS, TFT_DC);
#endif
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
#define GRAY 0x94B2
PID heaterPID(&cur_td, &pid_val, &set_td, kp, ki, kd, DIRECT);
void setup(void) {
digitalWrite(HEATER_PWM, LOW);
pinMode(HEATER_PWM, OUTPUT);
pinMode(POWER, INPUT_PULLUP);
pinMode(HEAT_LED, OUTPUT);
digitalWrite(HEAT_LED, HIGH);
pinMode(TEMP_SENSE, INPUT);
pinMode(SW_T1, INPUT_PULLUP);
pinMode(SW_T2, INPUT_PULLUP);
pinMode(SW_T3, INPUT_PULLUP);
pinMode(SW_UP, INPUT_PULLUP);
pinMode(SW_DOWN, INPUT_PULLUP);
pinMode(STBY_NO, INPUT_PULLUP);
pinMode(SW_STBY, INPUT_PULLUP);
pinMode(TFT_CS, OUTPUT);
digitalWrite(TFT_CS, HIGH);
Serial.begin(115200);
boolean force_menu = false;
if (EEPROM.read(0) != EEPROM_CHECK) {
EEPROM.update(0, EEPROM_CHECK);
updateEEPROM();
force_menu = true;
}
tft.begin();
#ifdef HARDWARE_DEFINED_TFT
#if HARDWARE_DEFINED_TFT == 1
EEPROM.update(EEPROM_DISPLAY, RGB_DISP);
setDisplayMode(0);
#else
EEPROM.update(EEPROM_DISPLAY, BGR_DISP);
setDisplayMode(1);
#endif
#else
if (force_menu || EEPROM.read(EEPROM_VERSION) < 23 || EEPROM.read(EEPROM_VERSION) == 255 || (EEPROM.read(EEPROM_DISPLAY) != BGR_DISP && EEPROM.read(EEPROM_DISPLAY) != RGB_DISP)) {
tft.fillScreen(BLACK);
setDisplayMode(1);
tft.setTextSize(2);
tft.setCursor(0,0);
tft.setTextColor(WHITE);
tft.print(F("What color is displayed?"));
tft.setCursor(10,112);
tft.setTextColor(RED);
tft.print("RED BLUE");
while (true) {
if (!digitalRead(SW_T1)) {
EEPROM.update(EEPROM_DISPLAY, BGR_DISP);
setDisplayMode(1);
break;
}
if (!digitalRead(SW_T3)) {
EEPROM.update(EEPROM_DISPLAY, RGB_DISP);
setDisplayMode(0);
break;
}
}
tft.fillScreen(BLACK);
tft.setTextColor(YELLOW);
tft.drawBitmap(0, 20, maiskolben, 160, 64, YELLOW);
tft.setCursor(20,86);
tft.setTextColor(YELLOW);
tft.setTextSize(2);
tft.print("Maiskolben");
tft.setCursor(35,104);
tft.print("Welcome!");
delay(4000);
while (!digitalRead(SW_T3) || !digitalRead(SW_T1)) delay(100);
} else {
setDisplayMode(EEPROM.read(EEPROM_DISPLAY) == BGR_DISP);
}
#endif
#ifdef INSTALL
if (EEPROM.read(EEPROM_INSTALL) != EEPROM_CHECK) {
tft.fillScreen(BLACK);
tft.setTextColor(RED, BLACK);
tft.setCursor(0,0);
tft.setTextSize(2);
tft.println("Installation");
for (int16_t i = -255; i < 256; i++) {
analogWrite(HEAT_LED, 255-abs(i));
delay(1);
}
uint16_t adc1 = 0, adc2 = 0;
while (digitalRead(SW_STBY)) {
int t = getTemperature();
uint16_t adc = analogRead(TEMP_SENSE);
Serial.println(t);
digitalWrite(HEATER_PWM, !digitalRead(SW_T1) | !digitalRead(SW_T2) | !digitalRead(SW_T3)/* | !digitalRead(SW_UP) | !digitalRead(SW_DOWN)*/);
if (!digitalRead(SW_DOWN)) {
if (!adc) {
digitalWrite(HEATER_PWM, HIGH);
} else {
adc1 = adc;
}
}
if (!digitalRead(SW_UP)) {
if (!adc) {
digitalWrite(HEATER_PWM, HIGH);
} else {
adc2 = adc;
}
}
tft.setCursor(0,18);
tft.print(t);
tft.println(" ");
tft.print(adc);
tft.println(" ");
tft.println(adc * adc_gain + adc_offset);
if (adc1 != 0 && adc2 != 0) {
adc_gain = DELTA_REF_T / (float)(adc2 - adc1);
adc_offset = REF_T1 - adc_gain * adc1;
tft.println(adc_gain);
tft.println(adc_offset);
}
delay(50);
}
EEPROM.update(EEPROM_OPTIONS, (fahrenheit << 2) | (bootheat << 1) | autopower);
EEPROM.update(EEPROM_VERSION, EE_VERSION);
EEPROM.update(EEPROM_INSTALL, EEPROM_CHECK);
EEPROM.put(EEPROM_ADCTTG, adc_gain);
EEPROM.put(EEPROM_ADCOFF, adc_offset);
tft.println("done.");
delay(1000);
asm volatile("jmp 0");
}
#endif
if (EEPROM.read(EEPROM_VERSION) != EE_VERSION) {
force_menu = true;
}
tft.fillScreen(BLACK);
uint8_t options = EEPROM.read(EEPROM_OPTIONS);
autopower = options & 1;
bootheat = options & 2;
fahrenheit = options & 4;
if (force_menu) optionMenu();
else {
updateRevision();
tft.drawBitmap(0, 20, maiskolben, 160, 64, YELLOW);
tft.setCursor(20,86);
tft.setTextColor(YELLOW);
tft.setTextSize(2);
tft.print("Maiskolben");
tft.setCursor(50,110);
tft.setTextSize(1);
tft.print("Version ");
tft.print(VERSION);
tft.setCursor(46,120);
tft.print("HW Revision ");
tft.print(revision);
//Allow Options to be set at startup
delay(100);
attachInterrupt(digitalPinToInterrupt(SW_STBY), optionMenu, LOW);
for (int i = 0; i < 10 && !menu_dismissed; i++) {
digitalWrite(HEAT_LED, i % 2);
delay(250);
}
detachInterrupt(digitalPinToInterrupt(SW_STBY));
}
/*
* lower frequency = noisier tip
* higher frequency = needs higher pwm
*/
//PWM Prescaler = 1024 31Hz
//TCCR2B = (TCCR2B & 0b11111000) | 7;
//PWM Prescaler = 256 122Hz
//TCCR2B = (TCCR2B & 0b11111000) | 6;
//PWM Prescaler = 128 245Hz
TCCR2B = (TCCR2B & 0b11111000) | 5;
//PWM Prescaler = 64 490Hz
//TCCR2B = (TCCR2B & 0b11111000) | 4
//PWM Prescaler = 32 980Hz
//TCCR2B = (TCCR2B & 0b11111000) | 3;
//PWM Prescaler = 8 3.9kHz
//TCCR2B = (TCCR2B & 0b11111000) | 2
//PWM Prescaler = 1 31kHz - no Noise
//TCCR2B = (TCCR2B & 0b11111000) | 1;
stby = EEPROM.read(1);
for (uint8_t i = 0; i < 3; i++) {
stored[i] = EEPROM.read(2+i*2) << 8;
stored[i] |= EEPROM.read(3+i*2);
}
set_t = EEPROM.read(EEPROM_SET_T) << 8;
set_t |= EEPROM.read(EEPROM_SET_T+1);
for (uint8_t i = 0; i < 50; i++)
measureVoltage(); //measure average 50 times to get realistic results
tft.fillScreen(BLACK);
last_measured = getTemperature();
Timer1.initialize(1000);
Timer1.attachInterrupt(timer_isr);
heaterPID.SetMode(AUTOMATIC);
sendNext = millis();
if (bootheat) {
threshold_counter = TEMP_UNDER_THRESHOLD;
setOff(false);
}
if (EEPROM.read(EEPROM_ADCTTG) == 255) { //Override unset values from older versions
EEPROM.put(EEPROM_ADCTTG, adc_gain);
EEPROM.put(EEPROM_ADCOFF, adc_offset);
}
EEPROM.get(EEPROM_ADCTTG, adc_gain);
EEPROM.get(EEPROM_ADCOFF, adc_offset);
}
void updateRevision(void) {
#if (HARDWARE_REVISION > 2)
EEPROM.update(EEPROM_REVISION, HARDWARE_REVISION);
revision = 3;
#else
if (EEPROM.read(EEPROM_VERSION) < 26 || EEPROM.read(EEPROM_REVISION) > 100) {
EEPROM.update(EEPROM_REVISION, 2);
revision = 2;
} else {
revision = EEPROM.read(EEPROM_REVISION);
}
#endif
}
void setDisplayMode(boolean bgr) {
tft.colorSpace(bgr);
tft.setRotation(3);
}
void optionMenu(void) {
tft.fillScreen(BLACK);
digitalWrite(HEAT_LED, LOW);
tft.setTextSize(2);
tft.setCursor(0,0);
tft.setTextColor(WHITE);
tft.println("Options\n");
tft.setTextColor(WHITE);
tft.setCursor(10,112);
tft.print("ON OFF EXIT");
uint8_t options = 3;
uint8_t opt = 0;
boolean redraw = true;
while (true) {
if (redraw) {
tft.setCursor(0,36);
#ifdef SHUTOFF_ACTIVE
tft.setTextColor(autopower?GREEN:RED);
#else
tft.setTextColor(GRAY);
#endif
tft.println(" Autoshutdown");
#ifdef BOOTHEAT_ACTIVE
tft.setTextColor(bootheat?GREEN:RED);
#else
tft.setTextColor(GRAY);
#endif
tft.println(" Heat on boot");
tft.setTextColor(fahrenheit?GREEN:RED);
tft.println(" Fahrenheit");
tft.setCursor(0, (opt+2)*18);
tft.setTextColor(WHITE);
tft.print(">");
redraw = false;
}
if (!digitalRead(SW_UP)) {
tft.setCursor(0, (opt+2)*18);
tft.setTextColor(BLACK);
tft.print(">");
opt = (opt+options-1)%options;
while (!digitalRead(SW_UP)) delay(100);
redraw = true;
}
if (!digitalRead(SW_DOWN)) {
tft.setCursor(0, (opt+2)*18);
tft.setTextColor(BLACK);
tft.print(">");
opt = (opt+1)%options;
while (!digitalRead(SW_DOWN)) delay(100);
redraw = true;
}
if (!digitalRead(SW_T1)) {
switch (opt) {
case 0: autopower = 1; break;
case 1: bootheat = 1; break;
case 2: fahrenheit = 1; break;
}
redraw = true;
}
if (!digitalRead(SW_T2)) {
switch (opt) {
case 0: autopower = 0; break;
case 1: bootheat = 0; break;
case 2: fahrenheit = 0; break;
}
redraw = true;
}
if (!digitalRead(SW_T3)) break;
}
EEPROM.update(EEPROM_OPTIONS, (fahrenheit << 2) | (bootheat << 1) | autopower);
updateRevision();
EEPROM.update(EEPROM_VERSION, EE_VERSION);
if (EEPROM.read(EEPROM_VERSION) < 30) {
EEPROM.put(EEPROM_ADCTTG, ADC_TO_TEMP_GAIN);
EEPROM.put(EEPROM_ADCOFF, ADC_TO_TEMP_OFFSET);
}
menu_dismissed = true;
}
void updateEEPROM(void) {
EEPROM.update(1, stby);
for (uint8_t i = 0; i < 3; i++) {
EEPROM.update(2+i*2, stored[i] >> 8);
EEPROM.update(3+i*2, stored[i] & 0xFF);
}
EEPROM.update(8, set_t >> 8);
EEPROM.update(9, set_t & 0xFF);
EEPROM.update(EEPROM_OPTIONS, (fahrenheit << 2) | (bootheat << 1) | autopower);
}
void powerDown(void) {
if (power_source != POWER_LIPO) {
power_down = false;
return;
}
//Timer1.stop();
setOff(true);
delay(10);
tft.fillScreen(BLACK);
tft.setTextSize(4);
tft.setTextColor(RED);
tft.setCursor(50,40);
tft.print("OFF");
delay(3000);
SPI.end();
digitalWrite(POWER, LOW);
pinMode(POWER, OUTPUT);
delay(100);
force_redraw = true;
power_down = false;
Timer1.start(); //unsuccessful
}
float toFahrenheit(float t) {
return t * 1.8 + 32;
}
int getTemperature(void) {
analogRead(TEMP_SENSE);//Switch ADC MUX
uint16_t adc = median(TEMP_SENSE);
#ifdef TEST_ADC
Serial.println(adc);
#endif
if (adc >= 900) { //Illegal value, tip not plugged in - would be around 560deg
analogWrite(HEATER_PWM, 0);
if (!off)
setError(NO_TIP);
return 999;
} else {
analogWrite(HEATER_PWM, pwm); //switch heater back to last value
}
//return round(adc < 210 ? (((float)adc) * 0.530805 + 38.9298) : (((float)adc) * 0.415375 + 64.6123)); //old conversion
return round(((float) adc) * adc_gain + adc_offset);
}
void measureVoltage(void) {
analogRead(BAT_C1); //Switch analog MUX before measuring
v_c1 = v_c1*.9+(analogRead(BAT_C1)*5/1024.0)*.1; //no divisor
analogRead(BAT_C2);
v_c2 = v_c2*.9+(analogRead(BAT_C2)*5/512.0)*.1; //divisor 1:1 -> /2
analogRead(BAT_C3);
v_c3 = v_c3*.9+(analogRead(BAT_C3)*(5.0*3.0)/1024.0)*.1; //maximum measurable is ~15V
v = v_c3;
if (revision < 3) return;
#ifdef VIN
analogRead(VIN);
v_in = v_in*.9+(analogRead(VIN)*25/1024.0)*.1; //maximum measurable is ~24.5V
v = v_in; //backwards compatibility
#endif
}
uint16_t median(uint8_t analogIn) {
uint16_t adcValue[3];
for (uint8_t i = 0; i < 3; i++) {
adcValue[i] = analogRead(analogIn); // read the input 3 times
}
uint16_t tmp;
if (adcValue[0] > adcValue[1]) {
tmp = adcValue[0];
adcValue[0] = adcValue[1];
adcValue[1] = tmp;
}
if (adcValue[1] > adcValue[2]) {
tmp = adcValue[1];
adcValue[1] = adcValue[2];
adcValue[2] = tmp;
}
if (adcValue[0] > adcValue[1]) {
tmp = adcValue[0];
adcValue[0] = adcValue[1];
adcValue[1] = tmp;
}
return adcValue[1];
}
void timer_sw_poll(void) {
if (power_down) return;
if (!digitalRead(SW_STBY)) {
if (cnt_off_press == 100) {
setOff(!off);
}
if (cnt_off_press == 200 && power_source == POWER_LIPO) {
setOff(true);
power_down = true;
return;
}
cnt_off_press = min(201, cnt_off_press+1);
} else {
if (cnt_off_press > 0 && cnt_off_press <= 100) {
setStandby(!stby);
}
cnt_off_press = 0;
}
boolean t1 = !digitalRead(SW_T1);
boolean t2 = !digitalRead(SW_T2);
boolean t3 = !digitalRead(SW_T3);
//simultanious push of multiple buttons
if (t1 + t2 + t3 > 1) {
store_to = 255;
store_invalid = true;
} else if (error != NO_ERROR) {
if (!(t1 | t2 | t3)) {
store_invalid = false;
} else if (!store_invalid && t3) {
error = NO_ERROR; //dismiss
set_t_old = 0; //refresh set_t display
store_invalid = true; //wait for release
}
} else {
//all buttons released
if (!(t1 | t2 | t3)) {
if (store_to != 255) {
if (cnt_but_store <= 100) {
set_t = stored[store_to];
setStandby(false);
updateEEPROM();
}
}
store_to = 255;
store_invalid = false;
cnt_but_store = 0;
} else
//one button pressed
if (!store_invalid) {
store_to = t2 + 2*t3;
if (cnt_but_store > 100) {
if (set_t != stored[store_to] && !stby) {
stored[store_to] = set_t;
cnt_but_store = 100;
updateEEPROM();
}
}
cnt_but_store++;
}
}
boolean sw_up = !digitalRead(SW_UP);
boolean sw_down = !digitalRead(SW_DOWN);
boolean sw_changed = (sw_up != sw_up_old) || (sw_down !=sw_down_old);
sw_up_old = sw_up;
sw_down_old = sw_down;
if((sw_up && sw_down) || !(sw_up || sw_down)) {
cnt_but_press = 0;
return;
}
if(sw_up || sw_down) {
cnt_but_press++;
if((cnt_but_press >= 100) || sw_changed) {
setStandby(false);
if(sw_up && set_t < TEMP_MAX) set_t++;
else if (sw_down && set_t > TEMP_MIN) set_t--;
if(!sw_changed) cnt_but_press = 97;
updateEEPROM();
}
}
}
void setStandby(boolean state) {
if (stby_layoff) return;
if (state == stby) return;
stby = state;
last_measured = cur_t;
last_temperature_drop = millis();
last_on_state = millis()/1000;
EEPROM.update(1, stby);
}
void setStandbyLayoff(boolean state) {
if (state == stby_layoff) return;
stby_layoff = state;
stby = false;
last_measured = cur_t;
last_on_state = millis()/1000;
}
void setOff(boolean state) {
if (state == off) return;
if (!state)
analogWrite(HEATER_PWM, 0);
else
setStandby(false);
if (power_source == POWER_USB && !state) {
state = true; //don't switch on, if powered via USB
setError(USB_ONLY);
}
last_on_state = millis()/1000;
off = state;
wasOff = true;
last_measured = cur_t;
}
void printTemp(float t) {
if (fahrenheit) {
t = toFahrenheit(t);
}
if (t < 100) tft.write(' ');
tft.print((int)t);
}
void display(void) {
if (force_redraw) tft.fillScreen(BLACK);
int16_t temperature = cur_t; //buffer volatile value
boolean yell = stby || (stby_layoff && blink);
tft.drawCircle(20,63,8, off?RED:yell?YELLOW:GREEN);
tft.drawCircle(20,63,7,off?RED:yell?YELLOW:GREEN);
tft.fillRect(19,55,3,3,BLACK);
tft.drawFastVLine(20,53,10, off?RED:yell?YELLOW:GREEN);
if (error != NO_ERROR) {
if (error != error_old || force_redraw) {
error_old = error;
tft.setTextSize(1);
tft.setTextColor(RED, BLACK);
tft.setCursor(0,96);
switch (error) {
case EXCESSIVE_FALL:
tft.print(F("Error: Temperature dropped\nTip slipped out?"));
break;
case NOT_HEATING:
tft.print(F("Error: Not heating\nWeak power source or short"));
break;
case BATTERY_LOW:
tft.print(F("Error: Battery low\nReplace or charge"));
break;
case USB_ONLY:
tft.print(F("Error: Power too low\nConnect power >5V"));
break;
case NO_TIP:
tft.print(F("Error: No tip connected\nTip slipped out?"));
break;
}
tft.setTextSize(2);
tft.setTextColor(YELLOW, BLACK);
tft.setCursor(10,112);
tft.print(F(" OK "));
tft.setTextColor(RED, BLACK);
tft.setCursor(36,26);
tft.setTextSize(3);
tft.print(F(" ERR "));
}
} else {
if (error != error_old || force_redraw) {
tft.fillRect(0, 96, 160, 16, BLACK);
error_old = NO_ERROR;
}
tft.setTextSize(2);
tft.setCursor(15,112);
tft.setTextColor(WHITE, BLACK);
printTemp(stored[0]);
tft.write(' ');
printTemp(stored[1]);
tft.write(' ');
printTemp(stored[2]);
if (set_t_old != set_t || old_stby != (stby || stby_layoff) || force_redraw) {
tft.setCursor(36,26);
tft.setTextSize(3);
if (stby || stby_layoff) {
old_stby = true;
tft.setTextColor(YELLOW, BLACK);
tft.print(F("STBY "));
} else {
old_stby = false;
set_t_old = set_t;
tft.setTextColor(WHITE, BLACK);
tft.write(' ');
printTemp(set_t);
tft.write(247);
tft.write(fahrenheit?'F':'C');
tft.fillTriangle(149, 50, 159, 50, 154, 38, (set_t < TEMP_MAX) ? WHITE : GRAY);
tft.fillTriangle(149, 77, 159, 77, 154, 90, (set_t > TEMP_MIN) ? WHITE : GRAY);
}
}
if (!off) {
#ifdef SHUTOFF_ACTIVE
if (autopower) {
int16_t tout;
if (stby || stby_layoff) {
tout = min(max(0,(last_on_state + OFF_TIMEOUT - (millis())/1000)), OFF_TIMEOUT);
} else {
tout = min(max(0,(last_temperature_drop + STANDBY_TIMEOUT - (millis())/1000)), STANDBY_TIMEOUT);
}
tft.setTextColor(stby?RED:YELLOW, BLACK);
tft.setTextSize(2);
tft.setCursor(46,78);
if (tout < 600) tft.write('0');
tft.print(tout/60);
tft.write(':');
if (tout%60 < 10) tft.write('0');
tft.print(tout%60);
}
#endif
} else if (temperature != 999) {
tft.fillRect(46, 78, 60, 20, BLACK);
}
}
if (cur_t_old != temperature || force_redraw) {
tft.setCursor(36,52);
tft.setTextSize(3);
if (temperature == 999) {
tft.setTextColor(RED, BLACK);
tft.print(F(" ERR "));
tft.setCursor(44,76);
tft.setTextSize(2);
tft.print(F("NO TIP"));
} else {
if (cur_t_old == 999) {
tft.fillRect(44,76,72,16,BLACK);
}
tft.setTextColor(off ? temperature < TEMP_COLD ? CYAN : RED : tft.Color565(min(10,abs(temperature-target_t))*25, 250 - min(10,max(0,(abs(temperature-target_t)-10)))*25, 0), BLACK);
if (temperature < TEMP_COLD) {
tft.print(F("COLD "));
} else {
tft.write(' ');
printTemp(temperature);
tft.write(247);
tft.write(fahrenheit?'F':'C');
}
}
if (temperature < cur_t_old)
tft.fillRect(max(0, (temperature - TEMP_COLD)/2.4), 0, 160-max(0, (temperature - TEMP_COLD)/2.4), BAR_HEIGHT, BLACK);
else if (cur_t != 999) {
for (int16_t i = max(0, (cur_t_old - TEMP_COLD)/2.4); i < max(0, (temperature - TEMP_COLD)/2.4); i++) {
tft.drawFastVLine(i, 0, BAR_HEIGHT, tft.Color565(min(255, max(0, i*5)), min(255, max(0, 450-i*2.5)), 0));
}
}
cur_t_old = temperature;
}
if (v_c3 > 1.0) {
tft.setTextColor(YELLOW, BLACK);
tft.setCursor(122,5);
tft.setTextSize(2);
int power = min(15,v)*min(15,v)/4.8*pwm/255;
if (power < 10) tft.write(' ');
tft.print(power);
tft.write('W');
if (v < 5.0) {
power_source = POWER_USB;
} else if (v_c2 < 1.0) {
power_source = POWER_CORD;
} else {
power_source = POWER_LIPO; //Set charging later to not redraw if charging mode toggles
}
if (power_source != power_source_old || force_redraw) {
tft.fillRect(0, 5, 128, 20, BLACK);
tft.fillRect(11, 25, 21, 20, BLACK);
switch (power_source) {
case POWER_CHARGING:
case POWER_LIPO:
for (uint8_t i = 0; i < 3; i++) {
tft.drawRect(11, 5+i*14, 20, 12, WHITE);
//tft.fillRect(12, 6+i*14, 18, 10, BLACK);
tft.drawFastVLine(31,8+i*14,6,WHITE);
}
break;
case POWER_USB:
tft.setTextSize(1);
tft.setTextColor(RED, BLACK);
tft.setCursor(0,5);
tft.print("USB power only\nConnect power supply.");
if (!off) setError(USB_ONLY);
break;
}
power_source_old = power_source;
}
if (power_source == POWER_CORD) {
/*if (v > v_c3) {
tft.setTextSize(2);
tft.setTextColor(GREEN, BLACK);
tft.setCursor(0,5);
tft.print(v);
tft.print("V ");
} else {*/
tft.drawBitmap(0, 5, power_cord, 24, 9, tft.Color565(max(0, min(255, (14.5-v)*112)), max(0, min(255, (v-11)*112)), 0));
//}
} else if (power_source == POWER_LIPO || power_source == POWER_CHARGING) {
float volt[] = {v_c1, v_c2-v_c1, v_c3-v_c2};
uint8_t volt_disp[] = {max(1,min(16,(volt[0]-3.0)*14.2)), max(1,min(16,(volt[1]-3.0)*14.2)), max(1,min(16,(volt[2]-3.0)*14.2))};
if (power_source == POWER_CHARGING) {
uint8_t p = min(16, (millis() / 100) % 20);
for (uint8_t i = 0; i < 3; i++) {
volt_disp[i] = max(0, min(volt_disp[i], p));
}
}
for (uint8_t i = 0; i < 3; i++) {
if (volt[i] < 3.20) {
setError(BATTERY_LOW);
tft.fillRect(13, 7+14*i, volt_disp[i], 8, blink?RED:BLACK);
} else {
tft.fillRect(13, 7+14*i, volt_disp[i], 8, tft.Color565(250-min(250, max(0, (volt[i]-3.4)*1000.0)), max(0,min(250, (volt[i]-3.15)*1000.0)), 0));
}
tft.fillRect(13+volt_disp[i], 7+14*i, 17-volt_disp[i], 8, BLACK);
}
}
}
#ifdef SHUTOFF_ACTIVE
if (autopower) {
if (!stby_layoff) {
if (pwm > max(20, (cur_t-150)/50*round(25-min(15,v)))+5) {
//if (target_t-cur_t > 0.715*exp(0.0077*target_t)) {
//if (cur_t / (double)target_t < STANDBY_TEMPERATURE_DROP) {
if (autopower_repeat_under || stby) {
if (stby && !wasOff) {
setStandby(false);
} else {
last_temperature_drop = millis()/1000;
}
}
autopower_repeat_under = true;
} else if (wasOff) {
wasOff = false;
} else {
autopower_repeat_under = false; //over the max pwm for at least two times
}
}
if (!off && !stby && millis()/1000 > (last_temperature_drop + STANDBY_TIMEOUT)) {
setStandby(true);
}
if (!off && (stby || stby_layoff) && millis()/1000 > (last_on_state + OFF_TIMEOUT)) {
setOff(true);
}
}
#endif
blink = !blink;
force_redraw = false;
}
void compute(void) {
#ifndef USE_TFT_RESET
setStandbyLayoff(!digitalRead(STBY_NO)); //do not measure while heater is active, potential is not neccessary == GND
#endif
cur_t = getTemperature();
if (off) {
target_t = 0;
if (cur_t < adc_offset + TEMP_RISE) {
threshold_counter = TEMP_UNDER_THRESHOLD; //reset counter
}
} else {
if (stby_layoff || stby) {
target_t = TEMP_STBY;
} else {
target_t = set_t;
}
if (cur_t-last_measured <= -30 && last_measured != 999) {
setError(EXCESSIVE_FALL); //decrease of more than 30 degree is uncommon, short of ring and gnd is possible.
}
if (cur_t < adc_offset + TEMP_RISE) {
if (threshold_counter == 0) {
setError(NOT_HEATING); //temperature is not reached in desired time, short of sensor and gnd too?
} else {
threshold_counter--;
}
} else {
threshold_counter = THRES_MAX_DECEED; //reset counter to a smaller value to allow small oscillation of temperature
}
}
set_td = target_t;
cur_td = cur_t;
last_measured = cur_t;
heaterPID.Compute();
if (error != NO_ERROR || off)
pwm = 0;
else
pwm = min(255,pid_val*255);
analogWrite(HEATER_PWM, pwm);
}
void timer_isr(void) {
if (cnt_compute >= TIME_COMPUTE_IN_MS) {
analogWrite(HEATER_PWM, 0); //switch off heater to let the low pass settle
if (cnt_compute >= TIME_COMPUTE_IN_MS+DELAY_BEFORE_MEASURE) {
compute();
cnt_compute=0;
}
}
cnt_compute++;
if(cnt_sw_poll >= TIME_SW_POLL_IN_MS){
timer_sw_poll();
cnt_sw_poll=0;
}
cnt_sw_poll++;
if(cnt_measure_voltage >= TIME_MEASURE_VOLTAGE_IN_MS) {
measureVoltage();
cnt_measure_voltage=0;
}
cnt_measure_voltage++;
}
void setError(error_type e) {
error = e;
setOff(true);
}
void loop(void) {
analogWrite(HEAT_LED, pwm);
//Switch to following if the oscillation of the led bothers you
//digitalWrite(HEAT_LED, cur_t+5 < target || (abs((int16_t)cur_t-(int16_t)target) <= 5 && (millis()/(stby?1000:500))%2));
if (sendNext <= millis()) {
sendNext += 100;
#ifndef TEST_ADC
Serial.print(stored[0]);
Serial.print(";");
Serial.print(stored[1]);
Serial.print(";");
Serial.print(stored[2]);
Serial.print(";");
Serial.print(off?0:1);
Serial.print(";");
Serial.print(error);
Serial.print(";");
Serial.print(stby?1:0);
Serial.print(";");
Serial.print(stby_layoff?1:0);
Serial.print(";");
Serial.print(set_t);
Serial.print(";");
Serial.print(cur_t);
Serial.print(";");
Serial.print(pid_val);
Serial.print(";");
Serial.print(v_c2>1.0?v_c1:0.0);
Serial.print(";");
Serial.print(v_c2);
Serial.print(";");
Serial.println(v);
#endif
Serial.flush();
display();
}
if (Serial.available()) {
uint16_t t = 0;
switch (Serial.read()) {
//Set new Temperature (eg. S350 to set to 350C)
case 'T':
if (Serial.available() >= 3) {
t = serialReadTemp();
//Serial.println(t);
if (t <= TEMP_MAX && t >= TEMP_MIN) {
set_t = t;
updateEEPROM();
}
}
break;
//Store new Preset (eg. P1200 to store 200C to Preset 1, NOT 0 indexed)
case 'P':
if (Serial.available() >= 4) {
uint8_t slot = Serial.read()-'1';
if (slot < 3) {
t = serialReadTemp();
if (t <= TEMP_MAX && t >= TEMP_MIN) {
stored[slot] = t;
updateEEPROM();
}
}
}
break;
//Clear errors
case 'C':
error = NO_ERROR;
break;
//Set standby
case 'S':
setStandby(Serial.read() == '1');
break;
//Set on/off
case 'O':
setOff(Serial.read() == '0');
break;
}
}
delay(DELAY_MAIN_LOOP);
if (power_down) {
powerDown();
}
}
uint16_t serialReadTemp() {
uint16_t t;
uint8_t n;
n = Serial.read()-'0';
t = min(9, max(0, n))*100;
n = Serial.read()-'0';
t += min(9, max(0, n))*10;
n = Serial.read()-'0';
t += min(9, max(0, n))*1;
return t;
}