diff options
Diffstat (limited to 'pcr-controller/pcr-controller.c')
-rw-r--r-- | pcr-controller/pcr-controller.c | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/pcr-controller/pcr-controller.c b/pcr-controller/pcr-controller.c new file mode 100644 index 0000000..d3b0ea6 --- /dev/null +++ b/pcr-controller/pcr-controller.c @@ -0,0 +1,385 @@ +/* + * r3PCR Teensy Controller Code + * + * + * Copyright (C) 2013-2014 Bernhard Tittelbach <xro@realraum.at> +* uses avr-utils, anyio & co by Christian Pointner <equinox@spreadspace.org> + * + * This file is part of spreadspace avr utils. + * + * spreadspace avr utils is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * spreadspace avr utils is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with spreadspace avr utils. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include <avr/io.h> +#include <avr/wdt.h> +#include <avr/interrupt.h> +#include <avr/power.h> +#include <stdio.h> + +#include "util.h" +#include "led.h" +#include "anyio.h" + +#include "onewire.h" +#include "ds1820.h" + +#include "pwm.h" +#include "pid_control.h" +#include "temp_curve.h" +#include "cmd_queue.h" + +#define PIN_HIGH(PORT, PIN) PORT |= (1 << PIN) +#define PIN_LOW(PORT, PIN) PORT &= ~(1 << PIN) +#define PINMODE_OUTPUT PIN_HIGH //just use DDR instead of PORT +#define PINMODE_INPUT PIN_LOW //just use DDR instead of PORT + +#define OP_SETBIT |= +#define OP_CLEARBIT &= ~ +#define OP_CHECK & +#define PIN_SW(PORTDDRREG, PIN, OP) PORTDDRREG OP (1 << PIN) + +#define HIGHv OP_SETBIT +#define LOWv OP_CLEARBIT + +#define PELTIER_INA_PIN PINF7 +#define PELTIER_INA_PORT PORTF +#define PELTIER_INA_DDR DDRF + +#define PELTIER_INB_PIN PINB6 +#define PELTIER_INB_PORT PORTB +#define PELTIER_INB_DDR DDRB + +//OC1A +#define PELETIER_PWM_EN_PIN PINB5 +#define PELETIER_PWM_EN_PORT PORTB +#define PELETIER_PWM_EN_DDR DDRB + +//OC4D +#define TOPHEAT_PIN PIND7 +#define TOPHEAT_PORT PORTD +#define TOPHEAT_DDR DDRD + +#define PUMP_PIN PINC6 +#define PUMP_PORT PORTC +#define PUMP_DDR DDRC + +#define ONEWIRE_PIN PINC7 +#define ONEWIRE_PINBASE PINC + +uint8_t num_temp_sensors_ = 0; +int16_t raw_temp_ = 0; +uint8_t temp_is_fresh_ = 0; +uint8_t debug_ = 0; +uint8_t monitor_temp_ = 0; +uint8_t pump_autoon_ = 0; +// at f_system_clk = 10Hz, system_clk_ will not overrun for at least 13 years. PCR won't run that long +volatile uint32_t system_clk_ = 0; + +//with F_CPU = 16MHz and TIMER3 Prescaler set to /1024, TIMER3 increments with f = 16KHz. Thus if TIMER3 reaches 16, 1ms has passed. +#define T3_MS *16 +//set TICK_TIME to 1/10 of a second +#define SYSCLKTICK_DURATION_IN_MS 100 +#define TICK_TIME (SYSCLKTICK_DURATION_IN_MS T3_MS) + +ISR(TIMER3_COMPA_vect) +{ + //increment system_clk every TIME_TICK (aka 100ms) + system_clk_++; + //set up "clock" comparator for next tick + OCR3A = (OCR3A + TICK_TIME) & 0xFFFF; + if (debug_) + led_toggle(); +} + +void initSysClkTimer3(void) +{ + system_clk_ = 0; + // set counter to 0 + TCNT3 = 0x0000; + // no outputs + TCCR3A = 0; + // Prescaler for Timer3: F_CPU / 1024 -> counts with f= 16KHz ms + TCCR3B = _BV(CS32) | _BV(CS30); + // set up "clock" comparator for first tick + OCR3A = TICK_TIME & 0xFFFF; + // enable interrupt + TIMSK3 = _BV(OCIE3A); +} + +void queryAndSaveTemperature(uint8_t bit_resolution) +{ + static uint32_t conversion_start_time = 0; + uint8_t sensor_index = 0; + + if (num_temp_sensors_ == 0) + { + num_temp_sensors_ = ds1820_discover(); + } + + if (conversion_start_time == 0) + { + // -- trigger temperatur conversion in sensor + for (sensor_index=0; sensor_index < num_temp_sensors_; sensor_index++) + { + ds1820_set_resolution(sensor_index, bit_resolution); + ds1820_start_measuring(sensor_index); + } + conversion_start_time = system_clk_; + // -- end trigger + } else + if ( (system_clk_ - conversion_start_time)*SYSCLKTICK_DURATION_IN_MS > ds1820_get_conversion_time_ms(bit_resolution)) + { + // -- receive temperatur after conversion time has passed + for (sensor_index=0; sensor_index < num_temp_sensors_; sensor_index++) + { + raw_temp_ = ds1820_read_temperature(sensor_index); + if (raw_temp_ != DS1820_ERROR) + { + temp_is_fresh_ = 1; + break; //we need only one successfully read value + } + } + conversion_start_time = 0; + // -- end receive + } +} + +void printRawTemp(int16_t raw_temp) +{ + int16_t decimals = 100 * (raw_temp % 16) / 16; + if (decimals < 0) + decimals *= -1; + printf("%d.%02d", raw_temp / 16, decimals); +} + +void printStatus(void) +{ + if (num_temp_sensors_ == 0) + { + printf("{\"cmd\":\"s\",\"cmd_ok\":false,\"error\": \"No DS1820 sensors on 1wire bus, thus no temperature\"}\r\n"); + return; + } + if (raw_temp_ == DS1820_ERROR) + { + printf("{\"cmd\":\"s\",\"cmd_ok\":false,\"error\":\"talking to DS18b20, no valid temperature!\"}\r\n"); + } else { + printf("{\"cmd\":\"s\",\"t\":%lu, \"currtemp\":", system_clk_); + printRawTemp(raw_temp_); + printf(", \"targettemp\":"); + printRawTemp(pid_getTargetValue()); + printf(", \"curve\":%s", (tcurve_isSet()?"true":"false")); + printf(", \"curve_t_elapsed\":%u", tcurve_getTimeElapsed()); + printf(", \"cycles_left\":%u}\r\n", tcurve_getRepeatsLeft()); + } +} + +//~ void readIntoBuffer(char *buffer, uint8_t buflen) +//~ { + //~ while (anyio_bytes_received() == 0); + //~ int ReceivedByte=0; + //~ do { + //~ ReceivedByte = fgetc(stdin); + //~ if (ReceivedByte != EOF) + //~ { + //~ *buffer = (char) ReceivedByte; + //~ buffer++; + //~ buflen --; + //~ } + //~ } while (ReceivedByte != '\n' && ReceivedByte != '\r' && buflen > 1); + //~ *buffer = 0; +//~ } + +//~ int16_t readNumber(void) +//~ { + //~ char buffer[20]; + //~ readIntoBuffer(buffer, 20); + //~ return atoi(buffer); +//~ } + +void setPeltierCoolingDirectionPower(int16_t value) +{ + if (value > 255) + value = 255; + if (value < -255) + value = -255; + + if (value >= 0) + { + PIN_LOW(PELTIER_INA_PORT, PELTIER_INA_PIN); + PIN_HIGH(PELTIER_INB_PORT, PELTIER_INB_PIN); + pwm_b5_set((uint8_t) value); + } else { + PIN_HIGH(PELTIER_INA_PORT, PELTIER_INA_PIN); + PIN_LOW(PELTIER_INB_PORT, PELTIER_INB_PIN); + pwm_b5_set((uint8_t) (-1 * value)); + } + if (debug_) + printf("Peltier value: %d, INA: %d, INB: %d, OCR1AH: %d, OCR1AL: %d\r\n", value, (PELTIER_INA_PORT & _BV(PELTIER_INA_PIN)) > 0, (PELTIER_INB_PORT & _BV(PELTIER_INB_PIN)) > 0, OCR1AH, OCR1AL); +} + +void setTopHeaderValue(int16_t value) +{ + pwm_d7_set((uint8_t) (value & 0xFF)); +} + +void handle_cmd(uint8_t cmd) +{ + switch(cmd) { + case ' ': + case '\n': + case '\r': + return; + case 'R': + case 'r': reset2bootloader(); break; + case '?': debug_ = ~debug_; break; + case 'M': monitor_temp_ = 1; break; + case 'm': monitor_temp_ = 0; break; + case '=': pid_setTargetValue(raw_temp_); break; + case '#': pid_setTargetValue(PID_DISABLED); break; + case 't': + case 's': printStatus(); return; + case 'L': led_toggle(); break; + case 'l': cmdq_queueCmdWithNumArgs((void*) led_toggle, 0, cmd); return; + case 'p': + case 'i': + case 'd': + pid_printVars(); + return; + case 'T': cmdq_queueCmdWithNumArgs((void*) pid_setTargetValue, 1, cmd); return; + case 'P': cmdq_queueCmdWithNumArgs((void*) pid_setP, 1, cmd); return; + case 'I': cmdq_queueCmdWithNumArgs((void*) pid_setI, 1, cmd); return; + case 'D': cmdq_queueCmdWithNumArgs((void*) pid_setD, 1, cmd); return; + case 'A': + PIN_HIGH(PUMP_PORT, PUMP_PIN); + pump_autoon_ = 0; + break; + case 'a': + PIN_LOW(PUMP_PORT, PUMP_PIN); + pump_autoon_ = 0; + break; + case 'B': cmdq_queueCmdWithNumArgs((void*) setTopHeaderValue, 1, cmd); return; + case 'b': setTopHeaderValue(0); break; + //~ case 'B': PIN_HIGH(TOPHEAT_PORT,TOPHEAT_PIN); break; + //~ case 'b': PIN_LOW(TOPHEAT_PORT,TOPHEAT_PIN); break; + case '@': pump_autoon_ = 1; break; + case '.': tcurve_printCurve(cmd); return; + case '-': //reset temp curve + tcurve_reset(); + break; + case '+': //add temp curve entry + //~ tcurve_add(readNumber(), readNumber()); + cmdq_queueCmdWithNumArgs((void*) tcurve_add, 2, cmd); + return; + case '>': cmdq_queueCmdWithNumArgs((void*) tcurve_setRepeatStartPosToLatestEntry, 0, cmd); return; + case '<': cmdq_queueCmdWithNumArgs((void*) tcurve_setRepeatEndPosToLatestEntry, 0, cmd); return; + case 'Z': cmdq_queueCmdWithNumArgs((void*) tcurve_setRepeats, 1, cmd); return; + default: printf("{\"cmd\":\"%c\",\"cmd_ok\":false,\"error\":\"unknown cmd\"}\r\n",cmd); return; + } + printf("{\"cmd\":\"%c\",\"cmd_ok\":true}\r\n",cmd); +} + +int main(void) +{ + /* Disable watchdog if enabled by bootloader/fuses */ + MCUSR &= ~(1 << WDRF); + wdt_disable(); + + cpu_init(); + led_init(); + anyio_init(115200, 0); + sei(); + + led_off(); + owi_init(ONEWIRE_PIN, &ONEWIRE_PINBASE); + PINMODE_OUTPUT(PUMP_DDR, PUMP_PIN); + PIN_LOW(PUMP_PORT, PUMP_PIN); + PINMODE_OUTPUT(PELETIER_PWM_EN_DDR, PELETIER_PWM_EN_PIN); + PINMODE_OUTPUT(PELTIER_INB_DDR, PELTIER_INB_PIN); + PINMODE_OUTPUT(PELTIER_INA_DDR, PELTIER_INA_PIN); + PINMODE_OUTPUT(TOPHEAT_DDR, TOPHEAT_PIN); + PIN_LOW(TOPHEAT_PORT, TOPHEAT_PIN); + + pwm_init(); + pwm_b5_set(0); + + pid_loadFromEEPROM(); + + num_temp_sensors_ = ds1820_discover(); + + uint32_t last_time = 0; + uint32_t last_time2 = 0; + initSysClkTimer3(); //start system clock + + for(;;) + { + int16_t BytesReceived = anyio_bytes_received(); + while(BytesReceived > 0) + { + int ReceivedByte = fgetc(stdin); + if (ReceivedByte != EOF) + { + // Ask cmdq_addCharToArgumentBuffer if it wants the current char, otherwise let handle_cmd() have it + if (cmdq_addCharToArgumentBuffer(ReceivedByte)) + handle_cmd(ReceivedByte); + } + BytesReceived--; + } + + cmdq_doWork(); //may call queued functions + + queryAndSaveTemperature(11); //at 11bit resolution + + if (temp_is_fresh_) + { + temp_is_fresh_ = 0; //once used, temp is used up ;-> + + if (monitor_temp_) + printStatus(); + + if (tcurve_isSet()) + { + uint16_t time_elapsed = (uint16_t) (system_clk_ - last_time); + last_time = system_clk_; + //PID_DISABLED == TCURVE_ERROR so this works out fine and we disable heating and PID in this case + pid_setTargetValue(tcurve_getTempToSet(raw_temp_, time_elapsed)); + } + + // PID control + // make sure this is called at exact periodic intervals (i.e. make sure there are no large variable delays in for loop) + // e.g. enable periodic temp monitoring 'm' rather than querying temp at some intervall 's' + if (pid_isEnabled()) + { + if (pump_autoon_) + PIN_HIGH(PUMP_PORT, PUMP_PIN); + if (debug_) + printf("pid_calc.."); + while (system_clk_ - last_time2 < 5); //wait until at least 500ms have passed since last time. Should be enough time for everything else to finish. (after 13 years, code will hang here) + if (debug_) + printf(" clk: %lu, elaps: %lu\r\n",system_clk_ , system_clk_ - last_time2); + last_time2 = system_clk_; + temp_is_fresh_ = 0; + setPeltierCoolingDirectionPower(pid_calc(raw_temp_)); + } + else + { + setPeltierCoolingDirectionPower(0); + if (pump_autoon_) + PIN_LOW(PUMP_PORT, PUMP_PIN); + } + } + + anyio_task(); + } +} |