summaryrefslogtreecommitdiff
path: root/pcr-controller/pcr-controller.c
diff options
context:
space:
mode:
Diffstat (limited to 'pcr-controller/pcr-controller.c')
-rw-r--r--pcr-controller/pcr-controller.c385
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();
+ }
+}