comparison tempctrl.c @ 41:5898fba6593c

Add temperature control. - Split out console stuff to cons.[ch]. Set up stdio so we can use printf etc. - Use \r\n as the line terminator consistently. - Add OWGetTemp to get temperatures from a device. - Load/save settings in EEPROM, defaults loaded from flash. Nearly feature complete except you can't edit ROM IDs without a programming tool :) (To be fixed) Needs more testing.
author darius@inchoate.localdomain
date Sun, 06 Jul 2008 22:19:53 +0930
parents
children 97ae82023d5b
comparison
equal deleted inserted replaced
40:1061fdbdc44f 41:5898fba6593c
1 /*
2 * Temperature control logic
3 *
4 * Copyright (c) 2008
5 * Daniel O'Connor <darius@dons.net.au>. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include <stdio.h>
30 #include <stdint.h>
31 #include <stdlib.h>
32 #include <avr/interrupt.h>
33 #include <avr/pgmspace.h>
34 #include <avr/eeprom.h>
35 #include <util/crc16.h>
36
37 #include "cons.h"
38 #include "1wire.h"
39 #include "tempctrl.h"
40
41 /* Helpers for our number system */
42 #define GETWHOLE(x) ((x) / 100)
43 #define GETFRAC(x) ((x) - (GETWHOLE(x) * 100))
44
45 typedef struct {
46 int32_t sec;
47 int32_t usec;
48 } time_t;
49
50 /* Holds all the settings needed */
51 typedef struct {
52 uint8_t fermenter_ROM[8];
53 uint8_t fridge_ROM[8];
54 uint8_t ambient_ROM[8];
55 int16_t target_temp;
56 uint16_t hysteresis;
57
58 /* How much to under/overshoot on heating/cooling */
59 int16_t minheatovershoot;
60 int16_t mincoolovershoot;
61
62 /* Minimum time the cooler can be on/off */
63 int16_t mincoolontime;
64 int16_t mincoolofftime;
65
66 /* Minimum time the heater can be on/off */
67 int16_t minheatontime;
68 int16_t minheatofftime;
69
70 #define TC_MODE_AUTO 'a' /* Automatic control */
71 #define TC_MODE_HEAT 'h' /* Force heating */
72 #define TC_MODE_COOL 'c' /* Force cooling */
73 #define TC_MODE_IDLE 'i' /* Force idle */
74 #define TC_MODE_NOTHING 'n' /* Do nothing (like idle but log nothing) */
75 char mode;
76
77 /* Bit patterns for various modes */
78 uint8_t coolbits;
79 uint8_t heatbits;
80 uint8_t idlebits;
81
82 /* Check/stale times */
83 int16_t check_interval;
84 int16_t stale_factor;
85 } __attribute__((packed)) settings_t;
86
87 /* Current settings in RAM */
88 static settings_t settings;
89
90 /* Our map of EEPROM */
91 struct {
92 settings_t settings;
93 uint16_t crc;
94 } ee_area __attribute__((section(".eeprom")));
95
96 /* Defaults that are shoved into EEPROM if it isn't inited */
97 const PROGMEM settings_t default_settings = {
98 .fermenter_ROM = { 0x10, 0xeb, 0x48, 0x21, 0x01, 0x08, 0x00, 0xdf },
99 .fridge_ROM = { 0x10, 0xa6, 0x2a, 0xc4, 0x00, 0x08, 0x00, 0x11 },
100 .ambient_ROM = { 0x10, 0x97, 0x1b, 0xfe, 0x00, 0x08, 0x00, 0xd1 },
101 .target_temp = 1400,
102 .hysteresis = 100,
103 .minheatovershoot = 50,
104 .mincoolovershoot = -50,
105 .mincoolontime = 300,
106 .mincoolofftime = 600,
107 .minheatontime = 60,
108 .minheatofftime = 60,
109 .mode = TC_MODE_AUTO,
110 .coolbits = _BV(7),
111 .heatbits = _BV(6),
112 .idlebits = 0x00,
113 .check_interval = 10,
114 .stale_factor = 3
115 };
116
117 /* Local variable declarations */
118 volatile static time_t now;
119
120 /* Local function prototypes */
121 static int gettemp(const PROGMEM char *name, uint8_t *ROM, int16_t *temp, uint8_t last);
122 static void tempctrl_load_or_init_settings(void);
123 static void tempctrl_default_settings(void);
124 static void tempctrl_write_settings(void);
125 static void setstate(char state);
126 static const PROGMEM char*state2long(char s);
127
128 /*
129 * tempctrl_init
130 *
131 * Setup timer, should be called with interrupts disabled.
132 *
133 */
134 void
135 tempctrl_init(void) {
136 /* Setup timer */
137 /* 16Mhz / 1024 = 15625 Hz / 125 = 125 Hz = IRQ every 8 ms */
138
139 /* CTC mode, no output on pin, Divide clock by 1024 */
140 TCCR0 = _BV(WGM01)| _BV(CS02) | _BV(CS00);
141
142 /* Compare with ... */
143 OCR0 = 125;
144
145 /* Enable interrupt for match on A */
146 TIMSK = _BV(OCIE0);
147
148 now.sec = 0;
149 now.usec = 0;
150
151 tempctrl_load_or_init_settings();
152 }
153
154 /*
155 * Timer 0 Compare IRQ
156 *
157 * Update time counter
158 */
159
160 ISR(TIMER0_COMP_vect) {
161 now.usec += 8000; /* 1000000 * 1 / F_CPU / 1024 / 125 */
162 while (now.usec > 1000000) {
163 now.usec -= 1000000;
164 now.sec++;
165 }
166 }
167
168 /*
169 * tempctrl_update
170 *
171 * Should be called in a normal context, could run things that take a long time.
172 * (ie 1wire bus stuff)
173 *
174 */
175 void
176 tempctrl_update(void) {
177 /* State variables */
178 static int32_t checktime = 0; // Time of next check
179 static int32_t lastdata = 0; // Last time we got data
180
181 static int16_t fermenter_temp = 0; // Fermenter temperature
182 static int16_t fridge_temp = 0; // Fridge temperature
183 static int16_t ambient_temp = 0; // Ambient temperature
184 // These are inited like this so we will still heat/cool when
185 // now < settings.minheatofftime
186 static int32_t lastheaton = -100000; // Last time the heater was on
187 static int32_t lastheatoff = -100000; // Last time the heater was off
188 static int32_t lastcoolon = -100000; // Last time the cooler was on
189 static int32_t lastcooloff = -100000; // Last time the cooler was off
190 static char currstate = 'i'; // Current state
191
192 /* Temporary variables */
193 int32_t t;
194 int16_t diff;
195 char nextstate;
196 int forced;
197 int stale;
198
199 t = gettod();
200 /* Time to check temperatures? */
201 if (t < checktime)
202 return;
203
204 checktime = t + settings.check_interval;
205
206 /* Don't do any logging, just force idle and leave */
207 if (settings.mode == TC_MODE_NOTHING) {
208 nextstate = 'i';
209 goto setstate;
210 }
211
212 /* Update our temperatures */
213 printf_P(PSTR("Time: %ld, Target: %d.%02d, "), now.sec, GETWHOLE(settings.target_temp),
214 GETFRAC(settings.target_temp));
215
216 if (gettemp(PSTR("Fermenter"), settings.fermenter_ROM, &fermenter_temp, 0))
217 lastdata = t;
218
219 /* Check for stale data */
220 if (lastdata + (settings.check_interval * settings.stale_factor) < t)
221 stale = 1;
222 else
223 stale = 0;
224
225 gettemp(PSTR("Fridge"), settings.fridge_ROM, &fridge_temp, 0);
226 gettemp(PSTR("Ambient"), settings.ambient_ROM, &ambient_temp, 1);
227
228 /* Default to remaining as we are */
229 nextstate = '-';
230
231 /* Temperature diff, -ve => too cold, +ve => too warm */
232 diff = fermenter_temp - settings.target_temp;
233
234 switch (currstate) {
235 case 'i':
236 /* If we're idle then only heat or cool if the temperate difference is out of the
237 * hysteresis band
238 */
239 if (abs(diff) > settings.hysteresis) {
240 if (diff < 0 && settings.minheatofftime + lastheatoff < t)
241 nextstate = 'h';
242 else if (diff > 0 && settings.mincoolofftime + lastcooloff < t)
243 nextstate = 'c';
244 }
245 break;
246
247 case 'c':
248 /* Work out if we should go idle (based on min on time & overshoot) */
249 if (diff + settings.mincoolovershoot < 0 &&
250 settings.mincoolontime + lastcoolon < t)
251 nextstate = 'i';
252 break;
253
254 case 'h':
255 if (diff - settings.minheatovershoot > 0 &&
256 settings.minheatontime + lastheaton < t)
257 nextstate = 'i';
258 break;
259
260 default:
261 printf_P(PSTR("\r\nUnknown state %c, going to idle\n"), currstate);
262 nextstate = 'i';
263 break;
264 }
265
266 /* Override if we have stale data */
267 if (stale)
268 nextstate = 'i';
269
270 /* Handle state forcing */
271 if (settings.mode != TC_MODE_AUTO)
272 forced = 1;
273 else
274 forced = 0;
275
276 if (settings.mode == TC_MODE_IDLE)
277 nextstate = 'i';
278 else if (settings.mode == TC_MODE_HEAT)
279 nextstate = 'h';
280 else if (settings.mode == TC_MODE_COOL)
281 nextstate = 'c';
282
283 if (nextstate != '-')
284 currstate = nextstate;
285
286 printf_P(PSTR(", State: %S, Flags: %S%S\r\n"), state2long(currstate),
287 forced ? PSTR("F") : PSTR(""),
288 stale ? PSTR("S") : PSTR(""));
289
290 setstate:
291 setstate(currstate);
292 }
293
294 /*
295 * Log a temperature & store it if valid
296 *
297 * Returns 1 if it was valid, 0 otherwise
298 */
299 static int
300 gettemp(const PROGMEM char *name, uint8_t *ROM, int16_t *temp, uint8_t last) {
301 int16_t tmp;
302
303 tmp = OWGetTemp(ROM);
304 printf_P(PSTR("%S: "), name);
305 if (tmp > OW_TEMP_BADVAL) {
306 printf_P(PSTR("%d.%02d%S"), GETWHOLE(tmp), GETFRAC(tmp), last ? PSTR("") : PSTR(", "));
307 *temp = tmp;
308 return(1);
309 } else {
310 printf_P(PSTR("NA (%d)%S"), tmp, last ? PSTR("") : PSTR(", "));
311 return(0);
312 }
313 }
314
315 /* Return 'time of day' (really uptime) */
316 int32_t
317 gettod(void) {
318 int32_t t;
319
320 cli();
321 t = now.sec;
322 sei();
323
324 return(t);
325 }
326
327 /* Read the settings from EEPROM
328 * If the CRC fails then reload from flash
329 */
330 static void
331 tempctrl_load_or_init_settings(void) {
332 uint8_t *dptr;
333 uint16_t crc, strcrc;
334 int i;
335
336 crc = 0;
337 eeprom_busy_wait();
338 eeprom_read_block(&settings, &ee_area.settings, sizeof(settings_t));
339 strcrc = eeprom_read_word(&ee_area.crc);
340
341 dptr = (uint8_t *)&settings;
342
343 for (i = 0; i < sizeof(settings_t); i++)
344 crc = _crc16_update(crc, dptr[i]);
345
346 /* All OK? */
347 if (crc == strcrc)
348 return;
349
350 printf_P(PSTR("CRC mismatch got 0x%04x vs 0x%04x, setting defaults\r\n"), crc, strcrc);
351 tempctrl_default_settings();
352 tempctrl_write_settings();
353 }
354
355 /* Load in the defaults from flash */
356 static void
357 tempctrl_default_settings(void) {
358 memcpy_P(&settings, &default_settings, sizeof(settings_t));
359 }
360
361 /* Write the current settings out to EEPROM */
362 static void
363 tempctrl_write_settings(void) {
364 uint16_t crc;
365 uint8_t *dptr;
366 int i;
367
368 eeprom_busy_wait();
369 eeprom_write_block(&settings, &ee_area.settings, sizeof(settings_t));
370
371 dptr = (uint8_t *)&settings;
372 crc = 0;
373 for (i = 0; i < sizeof(settings_t); i++)
374 crc = _crc16_update(crc, dptr[i]);
375
376 eeprom_write_word(&ee_area.crc, crc);
377 }
378
379 /* Set the relays to match the desired state */
380 static void
381 setstate(char state) {
382 switch (state) {
383 case 'c':
384 PORTC = settings.coolbits;
385 break;
386
387 case 'h':
388 PORTC = settings.heatbits;
389 break;
390
391 default:
392 printf_P(PSTR("Unknown state %c, setting idle\r\n"), state);
393 /* fallthrough */
394
395 case 'i':
396 PORTC = settings.idlebits;
397 break;
398 }
399 }
400
401 /* Handle user command
402 *
403 */
404 void
405 tempctrl_cmd(char *buf) {
406 char cmd[6];
407 int16_t data;
408 int i;
409
410 i = sscanf_P(buf, PSTR("tc %5s %d"), cmd, &data);
411
412 if (i == 1) {
413 if (!strcasecmp_P(cmd, PSTR("help"))) {
414 printf_P(PSTR(
415 "tc help This help\r\n"
416 "tc save Save settings to EEPROM\r\n"
417 "tc load Load or default settings from EEPROM\r\n"
418 "tc dflt Load defaults from flash\r\n"
419 "tc list List current settings\r\n"
420 "tc mode [achin] Change control mode, must be one of\r\n"
421 " a Auto\r\n"
422 " c Always cool\r\n"
423 " h Always heat\r\n"
424 " i Always idle\r\n"
425 " n Like idle but don't log anything\r\n"
426 "\r\n"
427 "tc X Y Set X to Y where X is one of\r\n"
428 " targ Target temperature\r\n"
429 " hys Hysteresis range\r\n"
430 " mhov Minimum heat overshoot\r\n"
431 " mcov Minimum cool overshoot\r\n"
432 " mcon Minimum cool on time\r\n"
433 " mcoff Minimum cool off time\r\n"
434 " mhin Minimum heat on time\r\n"
435 " mhoff Minimum heat off time\r\n"
436 " Times are in seconds\r\n"
437 " Temperatures are in hundredths of degrees Celcius\r\n"
438 ));
439 return;
440 }
441
442 if (!strcasecmp_P(cmd, PSTR("save"))) {
443 tempctrl_write_settings();
444 return;
445 }
446 if (!strcasecmp_P(cmd, PSTR("load"))) {
447 tempctrl_load_or_init_settings();
448 return;
449 }
450 if (!strcasecmp_P(cmd, PSTR("dflt"))) {
451 tempctrl_default_settings();
452 return;
453 }
454 if (!strcasecmp_P(cmd, PSTR("list"))) {
455 printf_P(PSTR("Fermenter ROM ID %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\r\n"
456 "Fridge ROM ID %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\r\n"
457 "Ambient ROM ID %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\r\n"
458 "Target - %d, Hystersis - %d\r\n"
459 "Min heat overshoot - %d, Min cool overshoot - %d\r\n"
460 "Min cool on time - %d, Min cool off time - %d\r\n"
461 "Min heat on time - %d, Min heat off time -%d\r\n"),
462 settings.fermenter_ROM[0], settings.fermenter_ROM[1], settings.fermenter_ROM[2], settings.fermenter_ROM[3],
463 settings.fermenter_ROM[4], settings.fermenter_ROM[5], settings.fermenter_ROM[6], settings.fermenter_ROM[7],
464 settings.fridge_ROM[0], settings.fridge_ROM[1], settings.fridge_ROM[2], settings.fridge_ROM[3],
465 settings.fridge_ROM[4], settings.fridge_ROM[5], settings.fridge_ROM[6], settings.fridge_ROM[7],
466 settings.ambient_ROM[0], settings.ambient_ROM[1], settings.ambient_ROM[2], settings.ambient_ROM[3],
467 settings.ambient_ROM[4], settings.ambient_ROM[5], settings.ambient_ROM[6], settings.ambient_ROM[7],
468 settings.target_temp, settings.hysteresis,
469 settings.minheatovershoot, settings.mincoolovershoot,
470 settings.mincoolontime, settings.minheatontime,
471 settings.minheatontime, settings.minheatofftime);
472 return;
473 }
474 if (!strcasecmp_P(cmd, PSTR("mode"))) {
475 switch (buf[8]) {
476 case TC_MODE_AUTO:
477 case TC_MODE_HEAT:
478 case TC_MODE_COOL:
479 case TC_MODE_IDLE:
480 case TC_MODE_NOTHING:
481 settings.mode = buf[8];
482 break;
483
484 default:
485 printf_P(PSTR("Unknown mode character '%c'\r\n"), buf[8]);
486 break;
487 }
488 return;
489 }
490
491 }
492
493 if (i != 2) {
494 printf_P(PSTR("Unable to parse command\r\n"));
495 return;
496 }
497
498 if (!strcasecmp_P(cmd, PSTR("targ"))) {
499 settings.target_temp = data;
500 } else if (!strcasecmp_P(cmd, PSTR("hys"))) {
501 settings.hysteresis = data;
502 } else if (!strcasecmp_P(cmd, PSTR("mhov"))) {
503 settings.minheatovershoot = data;
504 } else if (!strcasecmp_P(cmd, PSTR("mcov"))) {
505 settings.mincoolovershoot = data;
506 } else if (!strcasecmp_P(cmd, PSTR("mcon"))) {
507 settings.mincoolontime = data;
508 } else if (!strcasecmp_P(cmd, PSTR("mcoff"))) {
509 settings.mincoolofftime = data;
510 } else if (!strcasecmp_P(cmd, PSTR("mhon"))) {
511 settings.minheatontime = data;
512 } else if (!strcasecmp_P(cmd, PSTR("mhoff"))) {
513 settings.minheatofftime = data;
514 } else {
515 printf_P(PSTR("Unknown setting\r\n"));
516 return;
517 }
518 }
519
520 static const PROGMEM char*
521 state2long(char s) {
522 switch (s) {
523 case 'i':
524 return PSTR("idle");
525 break;
526
527 case 'c':
528 return PSTR("cool");
529 break;
530
531 case 'h':
532 return PSTR("heat");
533 break;
534
535 case '-':
536 return PSTR("-");
537 break;
538
539 default:
540 return PSTR("unknown");
541 break;
542 }
543 }
544