diff --git a/README.md b/README.md deleted file mode 100644 index 2d21b1f..0000000 --- a/README.md +++ /dev/null @@ -1,6 +0,0 @@ -Raspberry Pi controlled Kiln -============================ - -This project implements a wifi-accessible temperature controller for a ceramic firing kiln. - -Please see the BOM for all the parts we purchased for this project. Additional circuit diagrams and 3D models will be forthcoming. diff --git a/firmware/controller/controller.ino b/firmware/controller/controller.ino index b858607..46cca62 100644 --- a/firmware/controller/controller.ino +++ b/firmware/controller/controller.ino @@ -3,7 +3,7 @@ #define PIN_STEP2 8 #define PIN_STEP3 7 #define PIN_STEP4 6 -#define PIN_AUXTEMP A1 +#define PIN_AUXTEMP 2 #define PIN_TEMP_CS 4 #define PIN_LOADCELL A3 #define PIN_FLAME_A A2 @@ -12,8 +12,17 @@ #define STEP_SPEED 275//in steps per second #define TEMP_UPDATE 250 //milliseconds -#define MOTOR_TIMEOUT 5000 //milliseconds +#define AUX_UPDATE 1000 //milliseconds +#define MOTOR_TIMEOUT 60000 //milliseconds +#define NUM_AUXTEMP 2 + +#define NO_PORTB_PINCHANGES +#define NO_PORTC_PINCHANGES +#define DISABLE_PCINT_MULTI_SERVICE + +#include +#include #include #include #include @@ -28,7 +37,7 @@ struct Status { float main_temp; float ambient; float weight; - float aux_temp[2]; + float aux_temp[NUM_AUXTEMP]; } status; uint8_t* status_data = (uint8_t*) &status; @@ -36,22 +45,34 @@ const float step_interval = 1. / STEP_SPEED * 1000.; //milliseconds //intermediate variables Adafruit_MAX31855 thermo(PIN_TEMP_CS); -Stepper stepper(2048, PIN_STEP1, PIN_STEP3, PIN_STEP2, PIN_STEP4); +Stepper stepper(2048, PIN_STEP4, PIN_STEP2, PIN_STEP3, PIN_STEP1); +pushbutton reglimit = pushbutton(PIN_REGLIMIT, 5); +OneWire oneWire(PIN_AUXTEMP); +DallasTemperature sensors(&oneWire); +DeviceAddress aux_addr[NUM_AUXTEMP]; char i2c_command; float next_step; unsigned long next_temp; +unsigned long next_aux = 0; unsigned char motor_active = false; unsigned long stepper_target = 0; -unsigned int n_clicks = 0; //Number of full rotations +unsigned long num_aux = 0; +int n_clicks = 0; //Number of full rotations boolean limit_state = false; unsigned long limit_last; void setup() { + //setup ignition mosfet + pinMode(PIN_IGNITE, OUTPUT); + digitalWrite(PIN_IGNITE, LOW); + status.ignite = 0.; + status.flame = false; status.weight = 0.; - status.aux_temp[0] = 0.; - status.aux_temp[1] = 0.; + status.aux_temp[0] = -1.; + status.aux_temp[1] = -1.; + sensors.begin(); //Setup I2C Wire.begin(0x08); @@ -61,33 +82,32 @@ void setup() { //Set up regulator stepper status.motor = 0; - //setup regulator limit switch - pinMode(PIN_REGLIMIT, INPUT); - digitalWrite(PIN_REGLIMIT, HIGH); - - //setup ignition mosfet - pinMode(PIN_IGNITE, OUTPUT); - digitalWrite(PIN_IGNITE, LOW); - status.ignite = false; - - //set initial temperature + //set initial thermocouple temperature delay(500); update_temp(); next_temp = millis() + TEMP_UPDATE; + + //Setup auxtemp ds18b20 sensors + num_aux = sensors.getDeviceCount(); + num_aux = NUM_AUXTEMP < num_aux ? NUM_AUXTEMP : num_aux; + for (int i = 0; i < num_aux; i++) { + sensors.getAddress(aux_addr[i], i); + sensors.setResolution(aux_addr[i], 12); + status.aux_temp[i] = 0.; + } + if (num_aux > 0) { + sensors.setWaitForConversion(false); + sensors.requestTemperatures(); + next_aux = millis() + AUX_UPDATE; + } } int dir; unsigned long now; void loop() { now = millis(); - if (digitalRead(PIN_REGLIMIT) == LOW) { - if (limit_last == 0) { - limit_last = millis(); - } else if ((millis() - limit_last) > 5) { - n_clicks += dir; - limit_last = 0; - } - } + reglimit.update(); + status.aux_temp[1] = reglimit.n_clicks; if (stepper_target != status.motor && now > next_step) { dir = status.motor < stepper_target ? 1 : -1; @@ -95,7 +115,7 @@ void loop() { //Limit switch tripped if (stepper_target == 0) { - if (n_clicks == 0) + if (reglimit.n_clicks == 0) status.motor = 0; } else { status.motor += dir; @@ -119,6 +139,12 @@ void loop() { next_temp += TEMP_UPDATE; } + //update auxtemp + if (num_aux > 0 && now > next_aux) { + update_aux(); + next_aux += AUX_UPDATE; + } + //check flame status } @@ -134,9 +160,14 @@ void update_temp() { thermo.readAll(status.main_temp, status.ambient); } +void update_aux() { + for (int i = 0; i < num_aux; i++) { + status.aux_temp[i] = sensors.getTempC(aux_addr[i]); + } + sensors.requestTemperatures(); +} + void i2c_update() { - //update temperatures - if (i2c_command == 'M') { Wire.write((byte*) &(status.motor), 4); } else if (i2c_command == 'I') { @@ -170,7 +201,8 @@ void i2c_action(int nbytes) { set_regulator(*((unsigned int*) buffer)); break; case 'I': - digitalWrite(PIN_IGNITE, buffer[0]); + analogWrite(PIN_IGNITE, buffer[0]); + status.ignite = buffer[0]; break; } diff --git a/firmware/controller/pushbutton.h b/firmware/controller/pushbutton.h index e21aee5..2eb0212 100644 --- a/firmware/controller/pushbutton.h +++ b/firmware/controller/pushbutton.h @@ -1,7 +1,7 @@ class pushbutton : public CallBackInterface { public: - uint8_t n_clicks; + int n_clicks; uint8_t pin; unsigned int interval; unsigned long last; @@ -11,8 +11,6 @@ class pushbutton : public CallBackInterface n_clicks = 0; last = 0; init(); - state = digitalRead(pin); - }; void cbmethod() { last = millis(); @@ -31,12 +29,11 @@ class pushbutton : public CallBackInterface private: int dir; - boolean state; void init () { pinMode(pin, INPUT); digitalWrite(pin, HIGH); - PCintPort::attachInterrupt(pin, this, FALLING); + PCintPort::attachInterrupt(pin, this, CHANGE); }; }; diff --git a/firmware/controller_soft/controller_soft.ino b/firmware/controller_soft/controller_soft.ino deleted file mode 100644 index 94d7e85..0000000 --- a/firmware/controller_soft/controller_soft.ino +++ /dev/null @@ -1,174 +0,0 @@ -#define PIN_IGNITE 10 -#define PIN_STEP1 9 -#define PIN_STEP2 8 -#define PIN_STEP3 7 -#define PIN_STEP4 6 -#define PIN_AUXTEMP A1 -#define PIN_TEMP_CS 4 -#define PIN_LOADCELL A3 -#define PIN_FLAME_A A2 -#define PIN_FLAME_D 1 -#define PIN_REGLIMIT 5 - -#define STEP_SPEED 275//in steps per second -#define TEMP_UPDATE 250 //milliseconds -#define MOTOR_TIMEOUT 5000 //milliseconds - -#define NO_PORTB_PINCHANGES -#define NO_PORTC_PINCHANGES -#define DISABLE_PCINT_MULTI_SERVICE - -#include -#include -#include -#include -#include -#include "pushbutton.h" - -struct Status { - unsigned char ignite; - unsigned char flame; - unsigned int motor; - float main_temp; - float ambient; - float weight; - float aux_temp[2]; -} status; -uint8_t* status_data = (uint8_t*) &status; - -const float step_interval = 1. / STEP_SPEED * 1000.; //milliseconds - -//intermediate variables -Adafruit_MAX31855 thermo(PIN_TEMP_CS); -Stepper stepper(2048, PIN_STEP4, PIN_STEP2, PIN_STEP3, PIN_STEP1); -pushbutton reglimit = pushbutton(PIN_REGLIMIT, 5); - -char i2c_command; -float next_step; -unsigned long next_temp; -unsigned char motor_active = false; -unsigned long stepper_target = 0; -int n_clicks = 0; //Number of full rotations -boolean limit_state = false; -unsigned long limit_last; - -void setup() { - status.flame = false; - status.weight = 0.; - status.aux_temp[0] = 0.; - status.aux_temp[1] = 0.; - - //Setup I2C - Wire.begin(0x08); - Wire.onRequest(i2c_update); - Wire.onReceive(i2c_action); - - //Set up regulator stepper - status.motor = 0; - - //setup ignition mosfet - pinMode(PIN_IGNITE, OUTPUT); - digitalWrite(PIN_IGNITE, LOW); - status.ignite = false; - - //set initial temperature - delay(500); - update_temp(); - next_temp = millis() + TEMP_UPDATE; -} - -int dir; -unsigned long now; -void loop() { - now = millis(); - reglimit.update(); - status.aux_temp[0] = reglimit.n_clicks; - - if (stepper_target != status.motor && now > next_step) { - dir = status.motor < stepper_target ? 1 : -1; - stepper.step(dir); - - //Limit switch tripped - if (stepper_target == 0) { - if (reglimit.n_clicks == 0) - status.motor = 0; - } else { - status.motor += dir; - } - - next_step += step_interval; - } - - //put motor to sleep after timeout - if (motor_active && (now - next_step) > MOTOR_TIMEOUT) { - digitalWrite(PIN_STEP1, LOW); - digitalWrite(PIN_STEP2, LOW); - digitalWrite(PIN_STEP3, LOW); - digitalWrite(PIN_STEP4, LOW); - motor_active = false; - } - - //update temperature - if (now > next_temp) { - update_temp(); - next_temp += TEMP_UPDATE; - } - - //check flame status -} - -void set_regulator(unsigned long pos) { - motor_active = true; - reglimit.setDir(status.motor < pos ? 1 : -1); - if (stepper_target == status.motor) - next_step = millis(); //Start stepping immediately - stepper_target = pos; -} - -void update_temp() { - thermo.readAll(status.main_temp, status.ambient); -} - -void i2c_update() { - //update temperatures - - if (i2c_command == 'M') { - Wire.write((byte*) &(status.motor), 4); - } else if (i2c_command == 'I') { - Wire.write((byte*) &(status.ignite), 1); - } else if (i2c_command == 'T') { - Wire.write((byte*) &(status.main_temp), 4); - } else if (i2c_command == 'F') { - Wire.write((byte*) &(status.flame), 1); - } else { - Wire.write(status_data, sizeof(struct Status)); - } - - i2c_command = 0; -} - -byte buffer[32]; -void i2c_action(int nbytes) { - i2c_command = Wire.read(); - - int i = 0; - while (Wire.available()) { - buffer[i++] = Wire.read(); - } - - if (nbytes == 1) { - return; //Command already stored, no arguments - } - - switch (i2c_command) { - case 'M': - set_regulator(*((unsigned int*) buffer)); - break; - case 'I': - analogWrite(PIN_IGNITE, buffer[0]); - status.ignite = buffer[0]; - break; - } - - i2c_command = 0; -} diff --git a/firmware/controller_soft/protocol.cpp b/firmware/controller_soft/protocol.cpp deleted file mode 100644 index e31f2a2..0000000 --- a/firmware/controller_soft/protocol.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "protocol.h" - -char Comm::buffer[BUFFER_LENGTH+1]; -int Comm::_nacts; -char Comm::_commands[MAX_ACTIONS]; -char* (*Comm::_actions[MAX_ACTIONS])(int, char*); -char Comm::_current_cmd; -int Comm::_current_len; - -Comm::Comm(int addr) { - Wire.begin(addr); - Wire.onReceive(_handle_request); - Wire.onRequest(_handle_response); -} - -int Comm::action(char cmd, char* (*func)(int, char*)) { - if (_nacts >= MAX_ACTIONS) - return 1; - - _actions[_nacts] = func; - return 0; -} - -void Comm::_handle_request(int nbytes) { - _current_cmd = Wire.read(); - _current_len = nbytes-1; - for (int i = 0; i < nbytes-1; i++) { - buffer[i] = Wire.read(); - } -} - -void Comm::_handle_response() { - for (int i = 0; i < MAX_ACTIONS; i++) { - if (_commands[i] == _current_cmd) { - _actions[i](_current_len, buffer); - } - } - Wire.write(buffer); -} diff --git a/firmware/controller_soft/protocol.h b/firmware/controller_soft/protocol.h deleted file mode 100644 index 15ba41e..0000000 --- a/firmware/controller_soft/protocol.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef PROTOCOL_H -#define PROTOCOL_H - -#include "Wire.h" - -#define MAX_ACTIONS 16 - -class Comm { - private: - static char buffer[BUFFER_LENGTH+1]; - static int _nacts; - static char _commands[MAX_ACTIONS]; - static char* (*_actions[MAX_ACTIONS])(int, char*); - static char _current_cmd; - static int _current_len; - - static void _handle_request(int); - static void _handle_response(void); - - public: - Comm(int addr); - int action(char, char* (*)(int, char*)); -}; - -#endif //PROTOCOL_H diff --git a/firmware/controller_soft/pushbutton.h b/firmware/controller_soft/pushbutton.h deleted file mode 100644 index 2eb0212..0000000 --- a/firmware/controller_soft/pushbutton.h +++ /dev/null @@ -1,39 +0,0 @@ -class pushbutton : public CallBackInterface -{ - public: - int n_clicks; - uint8_t pin; - unsigned int interval; - unsigned long last; - - pushbutton (uint8_t _pin, unsigned int _interval): pin(_pin) , interval(_interval) { - dir = 0; - n_clicks = 0; - last = 0; - init(); - }; - void cbmethod() { - last = millis(); - }; - - void update() { - if (last != 0 && (millis() - last) > interval) { - n_clicks += dir; - last = 0; - } - } - - void setDir(int d) { - dir = d; - } - - private: - int dir; - - void init () { - pinMode(pin, INPUT); - digitalWrite(pin, HIGH); - PCintPort::attachInterrupt(pin, this, CHANGE); - }; -}; - diff --git a/kiln/manager.py b/kiln/manager.py index ef27e76..6063c06 100644 --- a/kiln/manager.py +++ b/kiln/manager.py @@ -127,6 +127,7 @@ class Profile(threading.Thread): self.pid = PID.PID(Kp, Ki, Kd) self.callback = callback self.running = True + self.duty_cycle = False self.start() @property @@ -142,8 +143,8 @@ class Profile(threading.Thread): self.running = False def run(self): + _next = time.time()+self.interval while not self.completed and self.running: - now = time.time() ts = self.elapsed #find epoch for i in range(len(self.schedule)-1): @@ -157,13 +158,25 @@ class Profile(threading.Thread): temp = self.therm.temperature.temp if temp == -1: continue #skip invalid temperature readings - - pid_out = self.pid.update(temp) - if pid_out < 0: pid_out = 0 - if pid_out > 1: pid_out = 1 - self.regulator.set(pid_out) + elif temp - setpoint > 10: + self.regulator.off() + self.duty_cycle = True + pid_out = -1 + elif self.duty_cycle: + if temp - setpoint < -5: + self.regulator.ignite() + self.duty_cycle = False + pid_out = -1 + else: + pid_out = self.pid.update(temp) + if pid_out < 0: pid_out = 0 + if pid_out > 1: pid_out = 1 + self.regulator.set(pid_out) if self.callback is not None: self.callback(temp, setpoint, pid_out) - time.sleep(self.interval - (time.time()-now)) + sleep = _next - time.time() + if sleep > 0: + time.sleep(sleep) + _next += self.interval diff --git a/kiln/stepper.py b/kiln/stepper.py index c22aae9..e603d4d 100644 --- a/kiln/stepper.py +++ b/kiln/stepper.py @@ -244,10 +244,10 @@ class Regulator(threading.Thread): pass class Breakout(object): - def __init__(self, addr, maxsteps=6500, minsteps=2300): + def __init__(self, addr, maxsteps=6500, minsteps=((2600, 0), (2300, 15)) ): import breakout self.device = breakout.Breakout(addr) - self.min = minsteps + self.min_interp = minsteps self.max = maxsteps def exit(): @@ -255,31 +255,41 @@ class Breakout(object): self.off() atexit.register(exit) - def ignite(self, start=2500, delay=5): + @property + def min(self): + temp = self.device.status.aux_temp0 + if temp > self.min_interp[1][1]: + return self.min_interp[1][0] + elif temp <= self.min_interp[0][1]: + return self.min_interp[0][0] + else: + mrange = self.min_interp[0][0] - self.min_interp[1][0] + trange = self.min_interp[1][1] - self.min_interp[0][1] + mix = (temp - self.min_interp[0][1]) / float(trange) + return mrange * mix + self.min_interp[1][0] + + def ignite(self, start=2400): logger.info("Igniting system") - self.device.ignite = 127 - time.sleep(2) - self.device.ignite = 255 - time.sleep(delay) self.device.motor = start while self.device.motor != start: time.sleep(.1) self.device.motor = self.min - #self.device.ignite = 127 @property def output(self): - out = (self.device.motor - self.min) / float(self.max - self.min) + m = self.min + out = (self.device.motor - m) / float(self.max - m) if out < 0: return -1 return out def set(self, value): + m = self.min if self.device.motor == 0: raise ValueError('Must ignite first') if not 0 <= value <= 1: raise ValueError('Must give value between 0 and 1') - self.device.motor = int((self.max - self.min)*value + self.min) + self.device.motor = int((self.max - m)*value + m) def off(self): self.device.motor = 0