1 | #include <SPI.h>
|
2 | #include <TFT_ILI9163C.h>
|
3 | #include <PID_v1.h>
|
4 | #include <EEPROM.h>
|
5 | #include "TimerOne.h"
|
6 | #include "definitions.h"
|
7 |
|
8 |
|
9 | /*
|
10 | * If your display stays white, uncomment this.
|
11 | * Cut reset trace (on THT on upper layer/0R), connect STBY_NO (A1) with reset of TFT (at 4050).
|
12 | * See also readme in mechanical folder for reference.
|
13 | */
|
14 | //#define USE_TFT_RESET
|
15 |
|
16 | /*
|
17 | * If red is blue and blue is red change this
|
18 | * If not sure, leave commented, you will be shown a setup screen
|
19 | */
|
20 | //#define HARDWARE_DEFINED_TFT 2
|
21 | /*
|
22 | * Based on your Hardware-Revision there may be modifications to the PCB.
|
23 | * In V3 and up is a second voltage measurement circuit.
|
24 | * HW REVS:
|
25 | * 1.5 - 2.8:
|
26 | * For THT this should be set to anything < 3
|
27 | * Normally leave this commented as it is stored in EEPROM
|
28 | */
|
29 |
|
30 | // V 1.5 - 2.11, Maiskolben THT
|
31 | //#define HARDWARE_REVISION 2
|
32 | // V 3.0 and 3.1
|
33 | //#define HARDWARE_REVISION 3
|
34 |
|
35 | /*
|
36 | * Only used for testing, do not use.
|
37 | */
|
38 | //#define INSTALL
|
39 | //#define TEST_ADC
|
40 |
|
41 | 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;
|
42 | volatile uint8_t pwm, threshold_counter;
|
43 | volatile int16_t cur_t, last_measured;
|
44 |
|
45 |
|
46 | int16_t stored[3] = {300, 350, 450}, set_t = TEMP_MIN, set_t_old, cur_t_old, target_t;
|
47 | double pid_val, cur_td, set_td;
|
48 | uint8_t store_to = 255;
|
49 | p_source power_source, power_source_old = NO_INIT;
|
50 | boolean blink;
|
51 | uint16_t cnt_measure_voltage, cnt_compute, cnt_sw_poll, cnt_but_press, cnt_off_press, cnt_but_store;
|
52 | float v_c1, v_c2, v_c3, v_in, v;
|
53 | uint8_t array_index, array_count;
|
54 | uint32_t sendNext;
|
55 | uint32_t last_temperature_drop;
|
56 | uint32_t last_on_state;
|
57 | boolean wasOff = true, old_stby = false;
|
58 | boolean autopower = true, bootheat = false, fahrenheit = false;
|
59 | uint8_t revision = 1;
|
60 | boolean menu_dismissed = false;
|
61 | boolean autopower_repeat_under = false;
|
62 | boolean force_redraw = false;
|
63 | boolean power_down = false;
|
64 | uint16_t charge = 0;
|
65 | float adc_offset = ADC_TO_TEMP_OFFSET;
|
66 | float adc_gain = ADC_TO_TEMP_GAIN;
|
67 |
|
68 | #define RGB_DISP 0x0
|
69 | #define BGR_DISP 0x2
|
70 |
|
71 | #ifdef USE_TFT_RESET
|
72 | TFT_ILI9163C tft = TFT_ILI9163C(TFT_CS, TFT_DC, STBY_NO);
|
73 | #else
|
74 | TFT_ILI9163C tft = TFT_ILI9163C(TFT_CS, TFT_DC);
|
75 | #endif
|
76 | #define BLACK 0x0000
|
77 | #define BLUE 0x001F
|
78 | #define RED 0xF800
|
79 | #define GREEN 0x07E0
|
80 | #define CYAN 0x07FF
|
81 | #define MAGENTA 0xF81F
|
82 | #define YELLOW 0xFFE0
|
83 | #define WHITE 0xFFFF
|
84 | #define GRAY 0x94B2
|
85 |
|
86 | PID heaterPID(&cur_td, &pid_val, &set_td, kp, ki, kd, DIRECT);
|
87 |
|
88 | void setup(void) {
|
89 | digitalWrite(HEATER_PWM, LOW);
|
90 | pinMode(HEATER_PWM, OUTPUT);
|
91 | pinMode(POWER, INPUT_PULLUP);
|
92 | pinMode(HEAT_LED, OUTPUT);
|
93 | digitalWrite(HEAT_LED, HIGH);
|
94 | pinMode(TEMP_SENSE, INPUT);
|
95 | pinMode(SW_T1, INPUT_PULLUP);
|
96 | pinMode(SW_T2, INPUT_PULLUP);
|
97 | pinMode(SW_T3, INPUT_PULLUP);
|
98 | pinMode(SW_UP, INPUT_PULLUP);
|
99 | pinMode(SW_DOWN, INPUT_PULLUP);
|
100 | pinMode(STBY_NO, INPUT_PULLUP);
|
101 | pinMode(SW_STBY, INPUT_PULLUP);
|
102 | pinMode(TFT_CS, OUTPUT);
|
103 | digitalWrite(TFT_CS, HIGH);
|
104 | Serial.begin(115200);
|
105 |
|
106 | boolean force_menu = false;
|
107 | if (EEPROM.read(0) != EEPROM_CHECK) {
|
108 | EEPROM.update(0, EEPROM_CHECK);
|
109 | updateEEPROM();
|
110 | force_menu = true;
|
111 | }
|
112 | tft.begin();
|
113 | #ifdef HARDWARE_DEFINED_TFT
|
114 | #if HARDWARE_DEFINED_TFT == 1
|
115 | EEPROM.update(EEPROM_DISPLAY, RGB_DISP);
|
116 | setDisplayMode(0);
|
117 | #else
|
118 | EEPROM.update(EEPROM_DISPLAY, BGR_DISP);
|
119 | setDisplayMode(1);
|
120 | #endif
|
121 | #else
|
122 | 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)) {
|
123 | tft.fillScreen(BLACK);
|
124 | setDisplayMode(1);
|
125 | tft.setTextSize(2);
|
126 | tft.setCursor(0,0);
|
127 | tft.setTextColor(WHITE);
|
128 | tft.print(F("What color is displayed?"));
|
129 | tft.setCursor(10,112);
|
130 | tft.setTextColor(RED);
|
131 | tft.print("RED BLUE");
|
132 | while (true) {
|
133 | if (!digitalRead(SW_T1)) {
|
134 | EEPROM.update(EEPROM_DISPLAY, BGR_DISP);
|
135 | setDisplayMode(1);
|
136 | break;
|
137 | }
|
138 | if (!digitalRead(SW_T3)) {
|
139 | EEPROM.update(EEPROM_DISPLAY, RGB_DISP);
|
140 | setDisplayMode(0);
|
141 | break;
|
142 | }
|
143 | }
|
144 | tft.fillScreen(BLACK);
|
145 | tft.setTextColor(YELLOW);
|
146 | tft.drawBitmap(0, 20, maiskolben, 160, 64, YELLOW);
|
147 | tft.setCursor(20,86);
|
148 | tft.setTextColor(YELLOW);
|
149 | tft.setTextSize(2);
|
150 | tft.print("Maiskolben");
|
151 | tft.setCursor(35,104);
|
152 | tft.print("Welcome!");
|
153 | delay(4000);
|
154 | while (!digitalRead(SW_T3) || !digitalRead(SW_T1)) delay(100);
|
155 | } else {
|
156 | setDisplayMode(EEPROM.read(EEPROM_DISPLAY) == BGR_DISP);
|
157 | }
|
158 | #endif
|
159 | #ifdef INSTALL
|
160 | if (EEPROM.read(EEPROM_INSTALL) != EEPROM_CHECK) {
|
161 | tft.fillScreen(BLACK);
|
162 | tft.setTextColor(RED, BLACK);
|
163 | tft.setCursor(0,0);
|
164 | tft.setTextSize(2);
|
165 | tft.println("Installation");
|
166 | for (int16_t i = -255; i < 256; i++) {
|
167 | analogWrite(HEAT_LED, 255-abs(i));
|
168 | delay(1);
|
169 | }
|
170 | uint16_t adc1 = 0, adc2 = 0;
|
171 | while (digitalRead(SW_STBY)) {
|
172 | int t = getTemperature();
|
173 | uint16_t adc = analogRead(TEMP_SENSE);
|
174 | Serial.println(t);
|
175 | digitalWrite(HEATER_PWM, !digitalRead(SW_T1) | !digitalRead(SW_T2) | !digitalRead(SW_T3)/* | !digitalRead(SW_UP) | !digitalRead(SW_DOWN)*/);
|
176 | if (!digitalRead(SW_DOWN)) {
|
177 | if (!adc) {
|
178 | digitalWrite(HEATER_PWM, HIGH);
|
179 | } else {
|
180 | adc1 = adc;
|
181 | }
|
182 | }
|
183 | if (!digitalRead(SW_UP)) {
|
184 | if (!adc) {
|
185 | digitalWrite(HEATER_PWM, HIGH);
|
186 | } else {
|
187 | adc2 = adc;
|
188 | }
|
189 | }
|
190 | tft.setCursor(0,18);
|
191 | tft.print(t);
|
192 | tft.println(" ");
|
193 | tft.print(adc);
|
194 | tft.println(" ");
|
195 | tft.println(adc * adc_gain + adc_offset);
|
196 | if (adc1 != 0 && adc2 != 0) {
|
197 | adc_gain = DELTA_REF_T / (float)(adc2 - adc1);
|
198 | adc_offset = REF_T1 - adc_gain * adc1;
|
199 | tft.println(adc_gain);
|
200 | tft.println(adc_offset);
|
201 | }
|
202 | delay(50);
|
203 | }
|
204 | EEPROM.update(EEPROM_OPTIONS, (fahrenheit << 2) | (bootheat << 1) | autopower);
|
205 | EEPROM.update(EEPROM_VERSION, EE_VERSION);
|
206 | EEPROM.update(EEPROM_INSTALL, EEPROM_CHECK);
|
207 | EEPROM.put(EEPROM_ADCTTG, adc_gain);
|
208 | EEPROM.put(EEPROM_ADCOFF, adc_offset);
|
209 |
|
210 | tft.println("done.");
|
211 | delay(1000);
|
212 | asm volatile("jmp 0");
|
213 | }
|
214 | #endif
|
215 | if (EEPROM.read(EEPROM_VERSION) != EE_VERSION) {
|
216 | force_menu = true;
|
217 | }
|
218 | tft.fillScreen(BLACK);
|
219 | uint8_t options = EEPROM.read(EEPROM_OPTIONS);
|
220 | autopower = options & 1;
|
221 | bootheat = options & 2;
|
222 | fahrenheit = options & 4;
|
223 | if (force_menu) optionMenu();
|
224 | else {
|
225 | updateRevision();
|
226 | tft.drawBitmap(0, 20, maiskolben, 160, 64, YELLOW);
|
227 | tft.setCursor(20,86);
|
228 | tft.setTextColor(YELLOW);
|
229 | tft.setTextSize(2);
|
230 | tft.print("Maiskolben");
|
231 | tft.setCursor(50,110);
|
232 | tft.setTextSize(1);
|
233 | tft.print("Version ");
|
234 | tft.print(VERSION);
|
235 | tft.setCursor(46,120);
|
236 | tft.print("HW Revision ");
|
237 | tft.print(revision);
|
238 | //Allow Options to be set at startup
|
239 | delay(100);
|
240 | attachInterrupt(digitalPinToInterrupt(SW_STBY), optionMenu, LOW);
|
241 | for (int i = 0; i < 10 && !menu_dismissed; i++) {
|
242 | digitalWrite(HEAT_LED, i % 2);
|
243 | delay(250);
|
244 | }
|
245 | detachInterrupt(digitalPinToInterrupt(SW_STBY));
|
246 | }
|
247 | /*
|
248 | * lower frequency = noisier tip
|
249 | * higher frequency = needs higher pwm
|
250 | */
|
251 | //PWM Prescaler = 1024 31Hz
|
252 | //TCCR2B = (TCCR2B & 0b11111000) | 7;
|
253 | //PWM Prescaler = 256 122Hz
|
254 | //TCCR2B = (TCCR2B & 0b11111000) | 6;
|
255 | //PWM Prescaler = 128 245Hz
|
256 | TCCR2B = (TCCR2B & 0b11111000) | 5;
|
257 | //PWM Prescaler = 64 490Hz
|
258 | //TCCR2B = (TCCR2B & 0b11111000) | 4
|
259 | //PWM Prescaler = 32 980Hz
|
260 | //TCCR2B = (TCCR2B & 0b11111000) | 3;
|
261 | //PWM Prescaler = 8 3.9kHz
|
262 | //TCCR2B = (TCCR2B & 0b11111000) | 2
|
263 | //PWM Prescaler = 1 31kHz - no Noise
|
264 | //TCCR2B = (TCCR2B & 0b11111000) | 1;
|
265 | stby = EEPROM.read(1);
|
266 | for (uint8_t i = 0; i < 3; i++) {
|
267 | stored[i] = EEPROM.read(2+i*2) << 8;
|
268 | stored[i] |= EEPROM.read(3+i*2);
|
269 | }
|
270 | set_t = EEPROM.read(EEPROM_SET_T) << 8;
|
271 | set_t |= EEPROM.read(EEPROM_SET_T+1);
|
272 |
|
273 | for (uint8_t i = 0; i < 50; i++)
|
274 | measureVoltage(); //measure average 50 times to get realistic results
|
275 |
|
276 | tft.fillScreen(BLACK);
|
277 | last_measured = getTemperature();
|
278 | Timer1.initialize(1000);
|
279 | Timer1.attachInterrupt(timer_isr);
|
280 | heaterPID.SetMode(AUTOMATIC);
|
281 | sendNext = millis();
|
282 | if (bootheat) {
|
283 | threshold_counter = TEMP_UNDER_THRESHOLD;
|
284 | setOff(false);
|
285 | }
|
286 | if (EEPROM.read(EEPROM_ADCTTG) == 255) { //Override unset values from older versions
|
287 | EEPROM.put(EEPROM_ADCTTG, adc_gain);
|
288 | EEPROM.put(EEPROM_ADCOFF, adc_offset);
|
289 | }
|
290 | EEPROM.get(EEPROM_ADCTTG, adc_gain);
|
291 | EEPROM.get(EEPROM_ADCOFF, adc_offset);
|
292 | }
|
293 |
|
294 | void updateRevision(void) {
|
295 | #if (HARDWARE_REVISION > 2)
|
296 | EEPROM.update(EEPROM_REVISION, HARDWARE_REVISION);
|
297 | revision = 3;
|
298 | #else
|
299 | if (EEPROM.read(EEPROM_VERSION) < 26 || EEPROM.read(EEPROM_REVISION) > 100) {
|
300 | EEPROM.update(EEPROM_REVISION, 2);
|
301 | revision = 2;
|
302 | } else {
|
303 | revision = EEPROM.read(EEPROM_REVISION);
|
304 | }
|
305 | #endif
|
306 | }
|
307 |
|
308 | void setDisplayMode(boolean bgr) {
|
309 | tft.colorSpace(bgr);
|
310 | tft.setRotation(3);
|
311 | }
|
312 |
|
313 | void optionMenu(void) {
|
314 | tft.fillScreen(BLACK);
|
315 | digitalWrite(HEAT_LED, LOW);
|
316 | tft.setTextSize(2);
|
317 | tft.setCursor(0,0);
|
318 | tft.setTextColor(WHITE);
|
319 | tft.println("Options\n");
|
320 | tft.setTextColor(WHITE);
|
321 | tft.setCursor(10,112);
|
322 | tft.print("ON OFF EXIT");
|
323 | uint8_t options = 3;
|
324 | uint8_t opt = 0;
|
325 | boolean redraw = true;
|
326 | while (true) {
|
327 | if (redraw) {
|
328 | tft.setCursor(0,36);
|
329 | #ifdef SHUTOFF_ACTIVE
|
330 | tft.setTextColor(autopower?GREEN:RED);
|
331 | #else
|
332 | tft.setTextColor(GRAY);
|
333 | #endif
|
334 | tft.println(" Autoshutdown");
|
335 | #ifdef BOOTHEAT_ACTIVE
|
336 | tft.setTextColor(bootheat?GREEN:RED);
|
337 | #else
|
338 | tft.setTextColor(GRAY);
|
339 | #endif
|
340 | tft.println(" Heat on boot");
|
341 | tft.setTextColor(fahrenheit?GREEN:RED);
|
342 | tft.println(" Fahrenheit");
|
343 |
|
344 | tft.setCursor(0, (opt+2)*18);
|
345 | tft.setTextColor(WHITE);
|
346 | tft.print(">");
|
347 | redraw = false;
|
348 | }
|
349 | if (!digitalRead(SW_UP)) {
|
350 | tft.setCursor(0, (opt+2)*18);
|
351 | tft.setTextColor(BLACK);
|
352 | tft.print(">");
|
353 | opt = (opt+options-1)%options;
|
354 | while (!digitalRead(SW_UP)) delay(100);
|
355 | redraw = true;
|
356 | }
|
357 | if (!digitalRead(SW_DOWN)) {
|
358 | tft.setCursor(0, (opt+2)*18);
|
359 | tft.setTextColor(BLACK);
|
360 | tft.print(">");
|
361 | opt = (opt+1)%options;
|
362 | while (!digitalRead(SW_DOWN)) delay(100);
|
363 | redraw = true;
|
364 | }
|
365 | if (!digitalRead(SW_T1)) {
|
366 | switch (opt) {
|
367 | case 0: autopower = 1; break;
|
368 | case 1: bootheat = 1; break;
|
369 | case 2: fahrenheit = 1; break;
|
370 | }
|
371 | redraw = true;
|
372 | }
|
373 | if (!digitalRead(SW_T2)) {
|
374 | switch (opt) {
|
375 | case 0: autopower = 0; break;
|
376 | case 1: bootheat = 0; break;
|
377 | case 2: fahrenheit = 0; break;
|
378 | }
|
379 | redraw = true;
|
380 | }
|
381 | if (!digitalRead(SW_T3)) break;
|
382 | }
|
383 | EEPROM.update(EEPROM_OPTIONS, (fahrenheit << 2) | (bootheat << 1) | autopower);
|
384 | updateRevision();
|
385 | EEPROM.update(EEPROM_VERSION, EE_VERSION);
|
386 | if (EEPROM.read(EEPROM_VERSION) < 30) {
|
387 | EEPROM.put(EEPROM_ADCTTG, ADC_TO_TEMP_GAIN);
|
388 | EEPROM.put(EEPROM_ADCOFF, ADC_TO_TEMP_OFFSET);
|
389 | }
|
390 | menu_dismissed = true;
|
391 | }
|
392 |
|
393 | void updateEEPROM(void) {
|
394 | EEPROM.update(1, stby);
|
395 | for (uint8_t i = 0; i < 3; i++) {
|
396 | EEPROM.update(2+i*2, stored[i] >> 8);
|
397 | EEPROM.update(3+i*2, stored[i] & 0xFF);
|
398 | }
|
399 | EEPROM.update(8, set_t >> 8);
|
400 | EEPROM.update(9, set_t & 0xFF);
|
401 | EEPROM.update(EEPROM_OPTIONS, (fahrenheit << 2) | (bootheat << 1) | autopower);
|
402 | }
|
403 |
|
404 | void powerDown(void) {
|
405 | if (power_source != POWER_LIPO) {
|
406 | power_down = false;
|
407 | return;
|
408 | }
|
409 | //Timer1.stop();
|
410 | setOff(true);
|
411 | delay(10);
|
412 | tft.fillScreen(BLACK);
|
413 | tft.setTextSize(4);
|
414 | tft.setTextColor(RED);
|
415 | tft.setCursor(50,40);
|
416 | tft.print("OFF");
|
417 | delay(3000);
|
418 | SPI.end();
|
419 | digitalWrite(POWER, LOW);
|
420 | pinMode(POWER, OUTPUT);
|
421 | delay(100);
|
422 | force_redraw = true;
|
423 | power_down = false;
|
424 | Timer1.start(); //unsuccessful
|
425 | }
|
426 |
|
427 | float toFahrenheit(float t) {
|
428 | return t * 1.8 + 32;
|
429 | }
|
430 |
|
431 | int getTemperature(void) {
|
432 | analogRead(TEMP_SENSE);//Switch ADC MUX
|
433 | uint16_t adc = median(TEMP_SENSE);
|
434 | #ifdef TEST_ADC
|
435 | Serial.println(adc);
|
436 | #endif
|
437 | if (adc >= 900) { //Illegal value, tip not plugged in - would be around 560deg
|
438 | analogWrite(HEATER_PWM, 0);
|
439 | if (!off)
|
440 | setError(NO_TIP);
|
441 | return 999;
|
442 | } else {
|
443 | analogWrite(HEATER_PWM, pwm); //switch heater back to last value
|
444 | }
|
445 | //return round(adc < 210 ? (((float)adc) * 0.530805 + 38.9298) : (((float)adc) * 0.415375 + 64.6123)); //old conversion
|
446 | return round(((float) adc) * adc_gain + adc_offset);
|
447 | }
|
448 |
|
449 | void measureVoltage(void) {
|
450 | analogRead(BAT_C1); //Switch analog MUX before measuring
|
451 | v_c1 = v_c1*.9+(analogRead(BAT_C1)*5/1024.0)*.1; //no divisor
|
452 | analogRead(BAT_C2);
|
453 | v_c2 = v_c2*.9+(analogRead(BAT_C2)*5/512.0)*.1; //divisor 1:1 -> /2
|
454 | analogRead(BAT_C3);
|
455 | v_c3 = v_c3*.9+(analogRead(BAT_C3)*(5.0*3.0)/1024.0)*.1; //maximum measurable is ~15V
|
456 | v = v_c3;
|
457 | if (revision < 3) return;
|
458 | #ifdef VIN
|
459 | analogRead(VIN);
|
460 | v_in = v_in*.9+(analogRead(VIN)*25/1024.0)*.1; //maximum measurable is ~24.5V
|
461 | v = v_in; //backwards compatibility
|
462 | #endif
|
463 | }
|
464 |
|
465 | uint16_t median(uint8_t analogIn) {
|
466 | uint16_t adcValue[3];
|
467 | for (uint8_t i = 0; i < 3; i++) {
|
468 | adcValue[i] = analogRead(analogIn); // read the input 3 times
|
469 | }
|
470 | uint16_t tmp;
|
471 | if (adcValue[0] > adcValue[1]) {
|
472 | tmp = adcValue[0];
|
473 | adcValue[0] = adcValue[1];
|
474 | adcValue[1] = tmp;
|
475 | }
|
476 | if (adcValue[1] > adcValue[2]) {
|
477 | tmp = adcValue[1];
|
478 | adcValue[1] = adcValue[2];
|
479 | adcValue[2] = tmp;
|
480 | }
|
481 | if (adcValue[0] > adcValue[1]) {
|
482 | tmp = adcValue[0];
|
483 | adcValue[0] = adcValue[1];
|
484 | adcValue[1] = tmp;
|
485 | }
|
486 | return adcValue[1];
|
487 | }
|
488 |
|
489 | void timer_sw_poll(void) {
|
490 | if (power_down) return;
|
491 | if (!digitalRead(SW_STBY)) {
|
492 | if (cnt_off_press == 100) {
|
493 | setOff(!off);
|
494 | }
|
495 | if (cnt_off_press == 200 && power_source == POWER_LIPO) {
|
496 | setOff(true);
|
497 | power_down = true;
|
498 | return;
|
499 | }
|
500 | cnt_off_press = min(201, cnt_off_press+1);
|
501 | } else {
|
502 | if (cnt_off_press > 0 && cnt_off_press <= 100) {
|
503 | setStandby(!stby);
|
504 | }
|
505 | cnt_off_press = 0;
|
506 | }
|
507 | boolean t1 = !digitalRead(SW_T1);
|
508 | boolean t2 = !digitalRead(SW_T2);
|
509 | boolean t3 = !digitalRead(SW_T3);
|
510 |
|
511 | //simultanious push of multiple buttons
|
512 | if (t1 + t2 + t3 > 1) {
|
513 | store_to = 255;
|
514 | store_invalid = true;
|
515 | } else if (error != NO_ERROR) {
|
516 | if (!(t1 | t2 | t3)) {
|
517 | store_invalid = false;
|
518 | } else if (!store_invalid && t3) {
|
519 | error = NO_ERROR; //dismiss
|
520 | set_t_old = 0; //refresh set_t display
|
521 | store_invalid = true; //wait for release
|
522 | }
|
523 | } else {
|
524 | //all buttons released
|
525 | if (!(t1 | t2 | t3)) {
|
526 | if (store_to != 255) {
|
527 | if (cnt_but_store <= 100) {
|
528 | set_t = stored[store_to];
|
529 | setStandby(false);
|
530 | updateEEPROM();
|
531 | }
|
532 | }
|
533 | store_to = 255;
|
534 | store_invalid = false;
|
535 | cnt_but_store = 0;
|
536 | } else
|
537 | //one button pressed
|
538 | if (!store_invalid) {
|
539 | store_to = t2 + 2*t3;
|
540 | if (cnt_but_store > 100) {
|
541 | if (set_t != stored[store_to] && !stby) {
|
542 | stored[store_to] = set_t;
|
543 | cnt_but_store = 100;
|
544 | updateEEPROM();
|
545 | }
|
546 | }
|
547 | cnt_but_store++;
|
548 | }
|
549 | }
|
550 | boolean sw_up = !digitalRead(SW_UP);
|
551 | boolean sw_down = !digitalRead(SW_DOWN);
|
552 | boolean sw_changed = (sw_up != sw_up_old) || (sw_down !=sw_down_old);
|
553 | sw_up_old = sw_up;
|
554 | sw_down_old = sw_down;
|
555 | if((sw_up && sw_down) || !(sw_up || sw_down)) {
|
556 | cnt_but_press = 0;
|
557 | return;
|
558 | }
|
559 | if(sw_up || sw_down) {
|
560 | cnt_but_press++;
|
561 | if((cnt_but_press >= 100) || sw_changed) {
|
562 | setStandby(false);
|
563 | if(sw_up && set_t < TEMP_MAX) set_t++;
|
564 | else if (sw_down && set_t > TEMP_MIN) set_t--;
|
565 | if(!sw_changed) cnt_but_press = 97;
|
566 | updateEEPROM();
|
567 | }
|
568 | }
|
569 | }
|
570 |
|
571 | void setStandby(boolean state) {
|
572 | if (stby_layoff) return;
|
573 | if (state == stby) return;
|
574 | stby = state;
|
575 | last_measured = cur_t;
|
576 | last_temperature_drop = millis();
|
577 | last_on_state = millis()/1000;
|
578 | EEPROM.update(1, stby);
|
579 | }
|
580 | void setStandbyLayoff(boolean state) {
|
581 | if (state == stby_layoff) return;
|
582 | stby_layoff = state;
|
583 | stby = false;
|
584 | last_measured = cur_t;
|
585 | last_on_state = millis()/1000;
|
586 | }
|
587 |
|
588 | void setOff(boolean state) {
|
589 | if (state == off) return;
|
590 | if (!state)
|
591 | analogWrite(HEATER_PWM, 0);
|
592 | else
|
593 | setStandby(false);
|
594 | if (power_source == POWER_USB && !state) {
|
595 | state = true; //don't switch on, if powered via USB
|
596 | setError(USB_ONLY);
|
597 | }
|
598 | last_on_state = millis()/1000;
|
599 | off = state;
|
600 | wasOff = true;
|
601 | last_measured = cur_t;
|
602 | }
|
603 |
|
604 | void printTemp(float t) {
|
605 | if (fahrenheit) {
|
606 | t = toFahrenheit(t);
|
607 | }
|
608 | if (t < 100) tft.write(' ');
|
609 | tft.print((int)t);
|
610 | }
|
611 |
|
612 | void display(void) {
|
613 | if (force_redraw) tft.fillScreen(BLACK);
|
614 | int16_t temperature = cur_t; //buffer volatile value
|
615 | boolean yell = stby || (stby_layoff && blink);
|
616 | tft.drawCircle(20,63,8, off?RED:yell?YELLOW:GREEN);
|
617 | tft.drawCircle(20,63,7,off?RED:yell?YELLOW:GREEN);
|
618 | tft.fillRect(19,55,3,3,BLACK);
|
619 | tft.drawFastVLine(20,53,10, off?RED:yell?YELLOW:GREEN);
|
620 | if (error != NO_ERROR) {
|
621 | if (error != error_old || force_redraw) {
|
622 | error_old = error;
|
623 | tft.setTextSize(1);
|
624 | tft.setTextColor(RED, BLACK);
|
625 | tft.setCursor(0,96);
|
626 | switch (error) {
|
627 | case EXCESSIVE_FALL:
|
628 | tft.print(F("Error: Temperature dropped\nTip slipped out?"));
|
629 | break;
|
630 | case NOT_HEATING:
|
631 | tft.print(F("Error: Not heating\nWeak power source or short"));
|
632 | break;
|
633 | case BATTERY_LOW:
|
634 | tft.print(F("Error: Battery low\nReplace or charge"));
|
635 | break;
|
636 | case USB_ONLY:
|
637 | tft.print(F("Error: Power too low\nConnect power >5V"));
|
638 | break;
|
639 | case NO_TIP:
|
640 | tft.print(F("Error: No tip connected\nTip slipped out?"));
|
641 | break;
|
642 | }
|
643 | tft.setTextSize(2);
|
644 | tft.setTextColor(YELLOW, BLACK);
|
645 | tft.setCursor(10,112);
|
646 | tft.print(F(" OK "));
|
647 |
|
648 | tft.setTextColor(RED, BLACK);
|
649 | tft.setCursor(36,26);
|
650 | tft.setTextSize(3);
|
651 | tft.print(F(" ERR "));
|
652 | }
|
653 | } else {
|
654 | if (error != error_old || force_redraw) {
|
655 | tft.fillRect(0, 96, 160, 16, BLACK);
|
656 | error_old = NO_ERROR;
|
657 | }
|
658 | tft.setTextSize(2);
|
659 | tft.setCursor(15,112);
|
660 | tft.setTextColor(WHITE, BLACK);
|
661 | printTemp(stored[0]);
|
662 | tft.write(' ');
|
663 | printTemp(stored[1]);
|
664 | tft.write(' ');
|
665 | printTemp(stored[2]);
|
666 |
|
667 | if (set_t_old != set_t || old_stby != (stby || stby_layoff) || force_redraw) {
|
668 | tft.setCursor(36,26);
|
669 | tft.setTextSize(3);
|
670 | if (stby || stby_layoff) {
|
671 | old_stby = true;
|
672 | tft.setTextColor(YELLOW, BLACK);
|
673 | tft.print(F("STBY "));
|
674 | } else {
|
675 | old_stby = false;
|
676 | set_t_old = set_t;
|
677 | tft.setTextColor(WHITE, BLACK);
|
678 | tft.write(' ');
|
679 | printTemp(set_t);
|
680 | tft.write(247);
|
681 | tft.write(fahrenheit?'F':'C');
|
682 | tft.fillTriangle(149, 50, 159, 50, 154, 38, (set_t < TEMP_MAX) ? WHITE : GRAY);
|
683 | tft.fillTriangle(149, 77, 159, 77, 154, 90, (set_t > TEMP_MIN) ? WHITE : GRAY);
|
684 | }
|
685 | }
|
686 | if (!off) {
|
687 | #ifdef SHUTOFF_ACTIVE
|
688 | if (autopower) {
|
689 | int16_t tout;
|
690 | if (stby || stby_layoff) {
|
691 | tout = min(max(0,(last_on_state + OFF_TIMEOUT - (millis())/1000)), OFF_TIMEOUT);
|
692 | } else {
|
693 | tout = min(max(0,(last_temperature_drop + STANDBY_TIMEOUT - (millis())/1000)), STANDBY_TIMEOUT);
|
694 | }
|
695 | tft.setTextColor(stby?RED:YELLOW, BLACK);
|
696 | tft.setTextSize(2);
|
697 | tft.setCursor(46,78);
|
698 | if (tout < 600) tft.write('0');
|
699 | tft.print(tout/60);
|
700 | tft.write(':');
|
701 | if (tout%60 < 10) tft.write('0');
|
702 | tft.print(tout%60);
|
703 | }
|
704 | #endif
|
705 | } else if (temperature != 999) {
|
706 | tft.fillRect(46, 78, 60, 20, BLACK);
|
707 | }
|
708 | }
|
709 | if (cur_t_old != temperature || force_redraw) {
|
710 | tft.setCursor(36,52);
|
711 | tft.setTextSize(3);
|
712 | if (temperature == 999) {
|
713 | tft.setTextColor(RED, BLACK);
|
714 | tft.print(F(" ERR "));
|
715 | tft.setCursor(44,76);
|
716 | tft.setTextSize(2);
|
717 | tft.print(F("NO TIP"));
|
718 | } else {
|
719 | if (cur_t_old == 999) {
|
720 | tft.fillRect(44,76,72,16,BLACK);
|
721 | }
|
722 | 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);
|
723 | if (temperature < TEMP_COLD) {
|
724 | tft.print(F("COLD "));
|
725 | } else {
|
726 | tft.write(' ');
|
727 | printTemp(temperature);
|
728 | tft.write(247);
|
729 | tft.write(fahrenheit?'F':'C');
|
730 | }
|
731 | }
|
732 | if (temperature < cur_t_old)
|
733 | tft.fillRect(max(0, (temperature - TEMP_COLD)/2.4), 0, 160-max(0, (temperature - TEMP_COLD)/2.4), BAR_HEIGHT, BLACK);
|
734 | else if (cur_t != 999) {
|
735 | for (int16_t i = max(0, (cur_t_old - TEMP_COLD)/2.4); i < max(0, (temperature - TEMP_COLD)/2.4); i++) {
|
736 | tft.drawFastVLine(i, 0, BAR_HEIGHT, tft.Color565(min(255, max(0, i*5)), min(255, max(0, 450-i*2.5)), 0));
|
737 | }
|
738 | }
|
739 | cur_t_old = temperature;
|
740 | }
|
741 | if (v_c3 > 1.0) {
|
742 | tft.setTextColor(YELLOW, BLACK);
|
743 | tft.setCursor(122,5);
|
744 | tft.setTextSize(2);
|
745 | int power = min(15,v)*min(15,v)/4.8*pwm/255;
|
746 | if (power < 10) tft.write(' ');
|
747 | tft.print(power);
|
748 | tft.write('W');
|
749 |
|
750 | if (v < 5.0) {
|
751 | power_source = POWER_USB;
|
752 | } else if (v_c2 < 1.0) {
|
753 | power_source = POWER_CORD;
|
754 | } else {
|
755 | power_source = POWER_LIPO; //Set charging later to not redraw if charging mode toggles
|
756 | }
|
757 | if (power_source != power_source_old || force_redraw) {
|
758 | tft.fillRect(0, 5, 128, 20, BLACK);
|
759 | tft.fillRect(11, 25, 21, 20, BLACK);
|
760 | switch (power_source) {
|
761 | case POWER_CHARGING:
|
762 | case POWER_LIPO:
|
763 | for (uint8_t i = 0; i < 3; i++) {
|
764 | tft.drawRect(11, 5+i*14, 20, 12, WHITE);
|
765 | //tft.fillRect(12, 6+i*14, 18, 10, BLACK);
|
766 | tft.drawFastVLine(31,8+i*14,6,WHITE);
|
767 | }
|
768 | break;
|
769 | case POWER_USB:
|
770 | tft.setTextSize(1);
|
771 | tft.setTextColor(RED, BLACK);
|
772 | tft.setCursor(0,5);
|
773 | tft.print("USB power only\nConnect power supply.");
|
774 | if (!off) setError(USB_ONLY);
|
775 | break;
|
776 | }
|
777 | power_source_old = power_source;
|
778 | }
|
779 | if (power_source == POWER_CORD) {
|
780 | /*if (v > v_c3) {
|
781 | tft.setTextSize(2);
|
782 | tft.setTextColor(GREEN, BLACK);
|
783 | tft.setCursor(0,5);
|
784 | tft.print(v);
|
785 | tft.print("V ");
|
786 | } else {*/
|
787 | 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));
|
788 | //}
|
789 | } else if (power_source == POWER_LIPO || power_source == POWER_CHARGING) {
|
790 | float volt[] = {v_c1, v_c2-v_c1, v_c3-v_c2};
|
791 | 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))};
|
792 | if (power_source == POWER_CHARGING) {
|
793 | uint8_t p = min(16, (millis() / 100) % 20);
|
794 | for (uint8_t i = 0; i < 3; i++) {
|
795 | volt_disp[i] = max(0, min(volt_disp[i], p));
|
796 | }
|
797 | }
|
798 | for (uint8_t i = 0; i < 3; i++) {
|
799 | if (volt[i] < 3.20) {
|
800 | setError(BATTERY_LOW);
|
801 | tft.fillRect(13, 7+14*i, volt_disp[i], 8, blink?RED:BLACK);
|
802 | } else {
|
803 | 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));
|
804 | }
|
805 | tft.fillRect(13+volt_disp[i], 7+14*i, 17-volt_disp[i], 8, BLACK);
|
806 | }
|
807 | }
|
808 | }
|
809 | #ifdef SHUTOFF_ACTIVE
|
810 | if (autopower) {
|
811 | if (!stby_layoff) {
|
812 | if (pwm > max(20, (cur_t-150)/50*round(25-min(15,v)))+5) {
|
813 | //if (target_t-cur_t > 0.715*exp(0.0077*target_t)) {
|
814 | //if (cur_t / (double)target_t < STANDBY_TEMPERATURE_DROP) {
|
815 | if (autopower_repeat_under || stby) {
|
816 | if (stby && !wasOff) {
|
817 | setStandby(false);
|
818 | } else {
|
819 | last_temperature_drop = millis()/1000;
|
820 | }
|
821 | }
|
822 | autopower_repeat_under = true;
|
823 | } else if (wasOff) {
|
824 | wasOff = false;
|
825 | } else {
|
826 | autopower_repeat_under = false; //over the max pwm for at least two times
|
827 | }
|
828 | }
|
829 | if (!off && !stby && millis()/1000 > (last_temperature_drop + STANDBY_TIMEOUT)) {
|
830 | setStandby(true);
|
831 | }
|
832 | if (!off && (stby || stby_layoff) && millis()/1000 > (last_on_state + OFF_TIMEOUT)) {
|
833 | setOff(true);
|
834 | }
|
835 | }
|
836 | #endif
|
837 | blink = !blink;
|
838 | force_redraw = false;
|
839 | }
|
840 |
|
841 | void compute(void) {
|
842 | #ifndef USE_TFT_RESET
|
843 | setStandbyLayoff(!digitalRead(STBY_NO)); //do not measure while heater is active, potential is not neccessary == GND
|
844 | #endif
|
845 | cur_t = getTemperature();
|
846 | if (off) {
|
847 | target_t = 0;
|
848 | if (cur_t < adc_offset + TEMP_RISE) {
|
849 | threshold_counter = TEMP_UNDER_THRESHOLD; //reset counter
|
850 | }
|
851 | } else {
|
852 | if (stby_layoff || stby) {
|
853 | target_t = TEMP_STBY;
|
854 | } else {
|
855 | target_t = set_t;
|
856 | }
|
857 | if (cur_t-last_measured <= -30 && last_measured != 999) {
|
858 | setError(EXCESSIVE_FALL); //decrease of more than 30 degree is uncommon, short of ring and gnd is possible.
|
859 | }
|
860 | if (cur_t < adc_offset + TEMP_RISE) {
|
861 | if (threshold_counter == 0) {
|
862 | setError(NOT_HEATING); //temperature is not reached in desired time, short of sensor and gnd too?
|
863 | } else {
|
864 | threshold_counter--;
|
865 | }
|
866 | } else {
|
867 | threshold_counter = THRES_MAX_DECEED; //reset counter to a smaller value to allow small oscillation of temperature
|
868 | }
|
869 | }
|
870 |
|
871 | set_td = target_t;
|
872 | cur_td = cur_t;
|
873 | last_measured = cur_t;
|
874 |
|
875 | heaterPID.Compute();
|
876 | if (error != NO_ERROR || off)
|
877 | pwm = 0;
|
878 | else
|
879 | pwm = min(255,pid_val*255);
|
880 | analogWrite(HEATER_PWM, pwm);
|
881 | }
|
882 |
|
883 | void timer_isr(void) {
|
884 | if (cnt_compute >= TIME_COMPUTE_IN_MS) {
|
885 | analogWrite(HEATER_PWM, 0); //switch off heater to let the low pass settle
|
886 |
|
887 | if (cnt_compute >= TIME_COMPUTE_IN_MS+DELAY_BEFORE_MEASURE) {
|
888 | compute();
|
889 | cnt_compute=0;
|
890 | }
|
891 | }
|
892 | cnt_compute++;
|
893 |
|
894 | if(cnt_sw_poll >= TIME_SW_POLL_IN_MS){
|
895 | timer_sw_poll();
|
896 | cnt_sw_poll=0;
|
897 | }
|
898 | cnt_sw_poll++;
|
899 |
|
900 | if(cnt_measure_voltage >= TIME_MEASURE_VOLTAGE_IN_MS) {
|
901 | measureVoltage();
|
902 | cnt_measure_voltage=0;
|
903 | }
|
904 | cnt_measure_voltage++;
|
905 | }
|
906 |
|
907 | void setError(error_type e) {
|
908 | error = e;
|
909 | setOff(true);
|
910 | }
|
911 |
|
912 | void loop(void) {
|
913 | analogWrite(HEAT_LED, pwm);
|
914 | //Switch to following if the oscillation of the led bothers you
|
915 | //digitalWrite(HEAT_LED, cur_t+5 < target || (abs((int16_t)cur_t-(int16_t)target) <= 5 && (millis()/(stby?1000:500))%2));
|
916 |
|
917 | if (sendNext <= millis()) {
|
918 | sendNext += 100;
|
919 | #ifndef TEST_ADC
|
920 | Serial.print(stored[0]);
|
921 | Serial.print(";");
|
922 | Serial.print(stored[1]);
|
923 | Serial.print(";");
|
924 | Serial.print(stored[2]);
|
925 | Serial.print(";");
|
926 | Serial.print(off?0:1);
|
927 | Serial.print(";");
|
928 | Serial.print(error);
|
929 | Serial.print(";");
|
930 | Serial.print(stby?1:0);
|
931 | Serial.print(";");
|
932 | Serial.print(stby_layoff?1:0);
|
933 | Serial.print(";");
|
934 | Serial.print(set_t);
|
935 | Serial.print(";");
|
936 | Serial.print(cur_t);
|
937 | Serial.print(";");
|
938 | Serial.print(pid_val);
|
939 | Serial.print(";");
|
940 | Serial.print(v_c2>1.0?v_c1:0.0);
|
941 | Serial.print(";");
|
942 | Serial.print(v_c2);
|
943 | Serial.print(";");
|
944 | Serial.println(v);
|
945 | #endif
|
946 | Serial.flush();
|
947 | display();
|
948 | }
|
949 | if (Serial.available()) {
|
950 | uint16_t t = 0;
|
951 | switch (Serial.read()) {
|
952 | //Set new Temperature (eg. S350 to set to 350C)
|
953 | case 'T':
|
954 | if (Serial.available() >= 3) {
|
955 | t = serialReadTemp();
|
956 | //Serial.println(t);
|
957 | if (t <= TEMP_MAX && t >= TEMP_MIN) {
|
958 | set_t = t;
|
959 | updateEEPROM();
|
960 | }
|
961 | }
|
962 | break;
|
963 | //Store new Preset (eg. P1200 to store 200C to Preset 1, NOT 0 indexed)
|
964 | case 'P':
|
965 | if (Serial.available() >= 4) {
|
966 | uint8_t slot = Serial.read()-'1';
|
967 | if (slot < 3) {
|
968 | t = serialReadTemp();
|
969 | if (t <= TEMP_MAX && t >= TEMP_MIN) {
|
970 | stored[slot] = t;
|
971 | updateEEPROM();
|
972 | }
|
973 | }
|
974 | }
|
975 | break;
|
976 | //Clear errors
|
977 | case 'C':
|
978 | error = NO_ERROR;
|
979 | break;
|
980 | //Set standby
|
981 | case 'S':
|
982 | setStandby(Serial.read() == '1');
|
983 | break;
|
984 | //Set on/off
|
985 | case 'O':
|
986 | setOff(Serial.read() == '0');
|
987 | break;
|
988 | }
|
989 | }
|
990 | delay(DELAY_MAIN_LOOP);
|
991 | if (power_down) {
|
992 | powerDown();
|
993 | }
|
994 | }
|
995 |
|
996 | uint16_t serialReadTemp() {
|
997 | uint16_t t;
|
998 | uint8_t n;
|
999 | n = Serial.read()-'0';
|
1000 | t = min(9, max(0, n))*100;
|
1001 | n = Serial.read()-'0';
|
1002 | t += min(9, max(0, n))*10;
|
1003 | n = Serial.read()-'0';
|
1004 | t += min(9, max(0, n))*1;
|
1005 | return t;
|
1006 | }
|