comparison tempctrl.c @ 31:03592ca4d37e

Port tempctrl.c from AVR. I removed the beep code as I don't have a beeper on the STM32 board. Reworked the heat/cool stuff so it can use separate ports.
author Daniel O'Connor <darius@dons.net.au>
date Tue, 27 Nov 2012 13:20:52 +1030
parents
children d3b7d4964807
comparison
equal deleted inserted replaced
30:435c6330896c 31:03592ca4d37e
1 /*
2 * Temperature control logic, copied from AVR version
3 *
4 * Copyright (c) 2012
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 <assert.h>
30 #include <stdio.h>
31 #include <stdint.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/time.h>
35 #include <time.h>
36 #include "stm32f10x.h"
37
38 #include "1wire.h"
39 #include "flash.h"
40 #include "tempctrl.h"
41
42 /* Holds all the settings needed */
43 typedef struct {
44 uint8_t fermenter_ROM[8];
45 uint8_t fridge_ROM[8];
46 uint8_t ambient_ROM[8];
47 int16_t target_temp;
48 uint16_t hysteresis;
49 /* How much to under/overshoot on heating/cooling */
50 int16_t minheatovershoot;
51 int16_t mincoolovershoot;
52
53 /* Minimum time the cooler can be on/off */
54 int16_t mincoolontime;
55 int16_t mincoolofftime;
56
57 /* Minimum time the heater can be on/off */
58 int16_t minheatontime;
59 int16_t minheatofftime;
60
61 #define TC_MODE_AUTO 'a' /* Automatic control */
62 #define TC_MODE_HEAT 'h' /* Force heating */
63 #define TC_MODE_COOL 'c' /* Force cooling */
64 #define TC_MODE_IDLE 'i' /* Force idle */
65 #define TC_MODE_NOTHING 'n' /* Do nothing (like idle but log nothing) */
66 char mode;
67
68 /* GPIO port the heater & cooler are on */
69 GPIO_TypeDef *coolport;
70 uint16_t coolpin;
71
72 GPIO_TypeDef *heatport;
73 uint16_t heatpin;
74
75 /* Check/stale times */
76 int16_t check_interval;
77 int16_t stale_factor;
78
79 uint8_t pad[3]; /* Pad to multiple of 4 bytes */
80 } settings_t;
81
82 /* Current settings in RAM */
83 static settings_t settings;
84
85 /* Defaults that are shoved into SPI flash if it isn't inited */
86 const settings_t default_settings = {
87 .fermenter_ROM = { 0x10, 0x4c, 0x7d, 0x53, 0x01, 0x08, 0x00, 0xff },
88 .fridge_ROM = { 0x10, 0x6d, 0x40, 0x53, 0x01, 0x08, 0x00, 0x16 },
89 .ambient_ROM = { 0x10, 0x76, 0x05, 0x53, 0x01, 0x08, 0x00, 0x8e },
90 .target_temp = 1000,
91 .hysteresis = 100,
92 .minheatovershoot = 50,
93 .mincoolovershoot = -50,
94 .mincoolontime = 300,
95 .mincoolofftime = 600,
96 .minheatontime = 60,
97 .minheatofftime = 60,
98 .mode = TC_MODE_AUTO,
99 .coolport = GPIOE,
100 .coolpin = GPIO_Pin_4,
101 .heatport = GPIOE,
102 .heatpin = GPIO_Pin_5,
103 .check_interval = 10,
104 .stale_factor = 3,
105 };
106
107 /* Local variable declarations */
108
109 /* Local function prototypes */
110 static void tempctrl_load_or_init_settings(void);
111 static void tempctrl_default_settings(void);
112 static void tempctrl_write_settings(void);
113 static void setstate(char state);
114 static const char * state2long(char s);
115 static void printtemp(const char *name, int tmp, const char *trailer);
116
117 /*
118 * tempctrl_init
119 *
120 */
121 void
122 tempctrl_init(void) {
123 tempctrl_load_or_init_settings();
124 }
125
126 /*
127 * tempctrl_update
128 *
129 * Should be called in a normal context, could run things that take a long time.
130 * (ie 1wire bus stuff)
131 *
132 */
133 void
134 tempctrl_update(void) {
135 /* State variables */
136 static int32_t checktime = 0; // Time of next check
137 static int32_t lastdata = INT32_MIN; // Last time we got data
138
139 static int16_t fermenter_temp = 0; // Fermenter temperature
140 static int16_t fridge_temp = 0; // Fridge temperature
141 static int16_t ambient_temp = 0; // Ambient temperature
142 static int32_t lastheaton = INT32_MIN; // Last time the heater was on
143 static int32_t lastheatoff = INT32_MIN;// Last time the heater was off
144 static int32_t lastcoolon = INT32_MIN; // Last time the cooler was on
145 static int32_t lastcooloff = INT32_MIN;// Last time the cooler was off
146 static char currstate = 'i'; // Current state
147 /* We init to times to INT32_MIN so that things function properly when
148 * now < settings.minheat/cool/on/offtime */
149
150 /* Temporary variables */
151 int32_t tempt;
152 int16_t diff;
153 char nextstate;
154 int forced;
155 int stale;
156 time_t t;
157 struct tm tm;
158 char buf[23];
159
160 t = time(NULL);
161
162 /* Time to check temperatures? */
163 if (t < checktime)
164 return;
165
166 checktime = t + settings.check_interval;
167
168 /* Don't do any logging, just force idle and leave */
169 if (settings.mode == TC_MODE_NOTHING) {
170 nextstate = 'i';
171 goto setstate;
172 }
173
174 /* Update our temperatures
175 * Can take a while (800ms each!)
176 */
177 tempt = OWGetTemp(settings.fermenter_ROM);
178 fridge_temp = OWGetTemp(settings.fridge_ROM);
179 ambient_temp = OWGetTemp(settings.ambient_ROM);
180
181 /* We only care about this one, only update the value we decide on
182 * only if it is valid
183 */
184 if (tempt > OW_TEMP_BADVAL) {
185 fermenter_temp = tempt;
186 lastdata = t;
187 }
188
189 /* Check for stale data */
190 if (lastdata + (settings.check_interval * settings.stale_factor) < t)
191 stale = 1;
192 else
193 stale = 0;
194
195 /* Default to remaining as we are */
196 nextstate = '-';
197
198 /* Temperature diff, -ve => too cold, +ve => too warm */
199 diff = fermenter_temp - settings.target_temp;
200
201 switch (currstate) {
202 case 'i':
203 /* If we're idle then only heat or cool if the temperate difference is out of the
204 * hysteresis band
205 */
206 if (abs(diff) > settings.hysteresis) {
207 if (diff < 0 && settings.minheatofftime + lastheatoff < t)
208 nextstate = 'h';
209 else if (diff > 0 && settings.mincoolofftime + lastcooloff < t)
210 nextstate = 'c';
211 }
212 break;
213
214 case 'c':
215 /* Work out if we should go idle (based on min on time & overshoot) */
216 if (diff + settings.mincoolovershoot < 0 &&
217 settings.mincoolontime + lastcoolon < t)
218 nextstate = 'i';
219 break;
220
221 case 'h':
222 if (diff - settings.minheatovershoot > 0 &&
223 settings.minheatontime + lastheaton < t)
224 nextstate = 'i';
225 break;
226
227 default:
228 printf("\r\nUnknown state %c, going to idle\n", currstate);
229 nextstate = 'i';
230 break;
231 }
232
233 /* Override if we have stale data */
234 if (stale)
235 nextstate = 'i';
236
237 /* Handle state forcing */
238 if (settings.mode != TC_MODE_AUTO)
239 forced = 1;
240 else
241 forced = 0;
242
243 if (settings.mode == TC_MODE_IDLE)
244 nextstate = 'i';
245 else if (settings.mode == TC_MODE_HEAT)
246 nextstate = 'h';
247 else if (settings.mode == TC_MODE_COOL)
248 nextstate = 'c';
249
250 // Keep track of when we last turned things on or off
251 switch (nextstate) {
252 case 'c':
253 if (currstate == 'h')
254 lastheatoff = t;
255 lastcoolon = t;
256 break;
257
258 case 'h':
259 if (currstate == 'c')
260 lastcooloff = t;
261 lastheaton = t;
262 break;
263
264 default:
265 if (currstate == 'c')
266 lastcooloff = t;
267 if (currstate == 'h')
268 lastheatoff = t;
269 }
270
271 if (nextstate != '-')
272 currstate = nextstate;
273
274
275 gmtime_r(&t, &tm);
276 assert(strftime(buf, sizeof(buf) - 1, "%Y/%m/%d %H:%M:%S: ", &tm) != 0);
277 fputs(buf, stdout);
278 printtemp("Tr", settings.target_temp, ", ");
279 printtemp("Fm", tempt, ", "); // Use actual value from sensor
280 printtemp("Fr", fridge_temp, ", ");
281 printtemp("Am", ambient_temp, ", ");
282 printf("St: %s, Fl: %s%s\r\n", state2long(currstate),
283 forced ? "F" : "",
284 stale ? "S" : "");
285
286 setstate:
287 setstate(currstate);
288 }
289
290 /*
291 * Print out temperature (or short error code) with specified trailer
292 */
293 static void
294 printtemp(const char *name, int tmp, const char *trailer) {
295 if (tmp > OW_TEMP_BADVAL)
296 printf("%s: %d.%02d%s", name, GETWHOLE(tmp), GETFRAC(tmp), trailer);
297 else
298 printf("%s: %s%s", name, OWTempStatusStr(tmp, 1), trailer);
299 }
300
301 /* Read the settings from SPI flash
302 * If the CRC fails then reload from onboard flash
303 */
304 static void
305 tempctrl_load_or_init_settings(void) {
306 /* XXX: todo */
307 if (!flashreadblock(0, sizeof(settings), &settings)) {
308 fputs("CRC fails, loading defaults\r\n", stdout);
309 tempctrl_default_settings();
310 tempctrl_write_settings();
311 }
312 }
313
314 /* Load in the defaults from flash */
315 static void
316 tempctrl_default_settings(void) {
317 memcpy(&settings, &default_settings, sizeof(settings_t));
318 }
319
320 /* Write the current settings out to SPI flash */
321 static void
322 tempctrl_write_settings(void) {
323 flashwriteblock(0, sizeof(settings), &settings);
324 }
325
326 /* Set the relays to match the desired state */
327 static void
328 setstate(char state) {
329 switch (state) {
330 case 'c':
331 GPIO_ResetBits(settings.heatport, settings.heatpin);
332 GPIO_SetBits(settings.coolport, settings.coolpin);
333 break;
334
335 case 'h':
336 GPIO_ResetBits(settings.coolport, settings.coolpin);
337 GPIO_SetBits(settings.heatport, settings.heatpin);
338 break;
339
340 default:
341 printf("Unknown state %c, setting idle\r\n", state);
342 /* fallthrough */
343
344 case 'i':
345 GPIO_ResetBits(settings.coolport, settings.coolpin);
346 GPIO_ResetBits(settings.heatport, settings.heatpin);
347 break;
348 }
349 }
350
351 /* Handle user command
352 *
353 */
354 void
355 tempctrl_cmd(int argc, char **argv) {
356 int16_t data;
357 uint8_t ROM[8];
358
359 if (argc < 1) {
360 printf("Unable to parse tc subcommand\r\n");
361 return;
362 }
363
364 if (!strcasecmp(argv[0], "help")) {
365 printf("tc help This help\r\n"
366 "tc save Save settings to EEPROM\r\n"
367 "tc load Load or default settings from EEPROM\r\n"
368 "tc dflt Load defaults from flash\r\n"
369 "tc list List current settings\r\n"
370 "tc mode [achin] Change control mode, must be one of\r\n"
371 " a Auto\r\n"
372 " c Always cool\r\n"
373 " h Always heat\r\n"
374 " i Always idle\r\n"
375 " n Like idle but don't log anything\r\n"
376 "tc X Y Set X to Y where X is one of\r\n"
377 " targ Target temperature\r\n"
378 " hys Hysteresis range\r\n"
379 " mhov Minimum heat overshoot\r\n"
380 " mcov Minimum cool overshoot\r\n"
381 " mcon Minimum cool on time\r\n"
382 " mcoff Minimum cool off time\r\n"
383 " mhin Minimum heat on time\r\n"
384 " mhoff Minimum heat off time\r\n"
385 "tc A B Set temperature sensor ID\r\n"
386 " Where A is ferm, frg or amb\r\n"
387 " and B is of the form xx:xx:xx:xx:xx:xx:xx:xx\r\n"
388 "\r\n"
389 " Times are in seconds\r\n"
390 " Temperatures are in hundredths of degrees Celcius\r\n"
391 );
392 return;
393 }
394
395 if (!strcasecmp(argv[0], "save")) {
396 tempctrl_write_settings();
397 return;
398 }
399 if (!strcasecmp(argv[0], "load")) {
400 tempctrl_load_or_init_settings();
401 return;
402 }
403 if (!strcasecmp(argv[0], "dflt")) {
404 tempctrl_default_settings();
405 return;
406 }
407 if (!strcasecmp(argv[0], "list")) {
408 printf("Fermenter ROM ID %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\r\n"
409 "Fridge ROM ID %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\r\n"
410 "Ambient ROM ID %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\r\n"
411 "Mode - %c, Target - %d, Hystersis - %d\r\n"
412 "Min heat overshoot - %d, Min cool overshoot - %d\r\n"
413 "Min cool on time - %d, Min cool off time - %d\r\n"
414 "Min heat on time - %d, Min heat off time - %d\r\n",
415 settings.fermenter_ROM[0], settings.fermenter_ROM[1], settings.fermenter_ROM[2], settings.fermenter_ROM[3],
416 settings.fermenter_ROM[4], settings.fermenter_ROM[5], settings.fermenter_ROM[6], settings.fermenter_ROM[7],
417 settings.fridge_ROM[0], settings.fridge_ROM[1], settings.fridge_ROM[2], settings.fridge_ROM[3],
418 settings.fridge_ROM[4], settings.fridge_ROM[5], settings.fridge_ROM[6], settings.fridge_ROM[7],
419 settings.ambient_ROM[0], settings.ambient_ROM[1], settings.ambient_ROM[2], settings.ambient_ROM[3],
420 settings.ambient_ROM[4], settings.ambient_ROM[5], settings.ambient_ROM[6], settings.ambient_ROM[7],
421 settings.mode, settings.target_temp, settings.hysteresis,
422 settings.minheatovershoot, settings.mincoolovershoot,
423 settings.mincoolontime, settings.minheatontime,
424 settings.minheatontime, settings.minheatofftime
425 );
426 return;
427 }
428 if (!strcasecmp(argv[0], "mode")) {
429 if (argc < 2) {
430 fputs("Incorrect number of arguments\r\n", stdout);
431 return;
432 }
433
434 switch (argv[1][0]) {
435 case TC_MODE_AUTO:
436 case TC_MODE_HEAT:
437 case TC_MODE_COOL:
438 case TC_MODE_IDLE:
439 case TC_MODE_NOTHING:
440 settings.mode = argv[1][0];
441 break;
442
443 default:
444 printf("Unknown mode character '%c'\r\n", argv[1][0]);
445 break;
446 }
447 return;
448 }
449 if (!strcasecmp(argv[0], "ferm") ||
450 !strcasecmp(argv[0], "frg") ||
451 !strcasecmp(argv[0], "amb")) {
452 if (argc < 2) {
453 fputs("Incorrect number of arguments\r\n", stdout);
454 return;
455 }
456
457 if (sscanf(argv[1], "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
458 &ROM[0], &ROM[1], &ROM[2], &ROM[3],
459 &ROM[4], &ROM[5], &ROM[6], &ROM[7]) != 8) {
460 printf("Unable to parse ROM ID\r\n");
461 } else {
462 if (!strcasecmp(argv[0], "ferm"))
463 memcpy(&settings.fermenter_ROM, ROM, sizeof(ROM));
464 if (!strcasecmp(argv[0], "frg"))
465 memcpy(&settings.fridge_ROM, ROM, sizeof(ROM));
466 if (!strcasecmp(argv[0], "amb"))
467 memcpy(&settings.ambient_ROM, ROM, sizeof(ROM));
468 }
469 return;
470 }
471
472 /* Handle setting the multitude of variables
473 * It's last to simplify things */
474 if (argc < 3) {
475 fputs("Incorrect number of arguments for variable/value\r\n", stdout);
476 return;
477 }
478
479 if (sscanf(argv[2], "%hd", &data) != 1) {
480 printf("Unable to parse value for variable\r\n");
481 return;
482 }
483
484 if (!strcasecmp(argv[1], "targ")) {
485 settings.target_temp = data;
486 } else if (!strcasecmp(argv[1], "hys")) {
487 settings.hysteresis = data;
488 } else if (!strcasecmp(argv[1], "mhov")) {
489 settings.minheatovershoot = data;
490 } else if (!strcasecmp(argv[1], "mcov")) {
491 settings.mincoolovershoot = data;
492 } else if (!strcasecmp(argv[1], "mcon")) {
493 settings.mincoolontime = data;
494 } else if (!strcasecmp(argv[1], "mcoff")) {
495 settings.mincoolofftime = data;
496 } else if (!strcasecmp(argv[1], "mhon")) {
497 settings.minheatontime = data;
498 } else if (!strcasecmp(argv[1], "mhoff")) {
499 settings.minheatofftime = data;
500 } else {
501 printf("Unknown setting\r\n");
502 }
503 }
504
505 static const char*
506 state2long(char s) {
507 switch (s) {
508 case 'i':
509 return "idle";
510 break;
511
512 case 'c':
513 return "cool";
514 break;
515
516 case 'h':
517 return "heat";
518 break;
519
520 case '-':
521 return "-";
522 break;
523
524 default:
525 return "unknown";
526 break;
527 }
528 }