├── README.txt └── osPID_Firmware ├── AnalogButton.cpp ├── AnalogButton_local.h ├── EEPROMAnything.h ├── MAX31855.cpp ├── MAX31855_local.h ├── PID_AutoTune_v0.cpp ├── PID_AutoTune_v0_local.h ├── PID_v1.cpp ├── PID_v1_local.h ├── io.h ├── max6675.cpp ├── max6675_local.h └── osPID_Firmware.ino /README.txt: -------------------------------------------------------------------------------- 1 | /******************************************************** 2 | * osPID Firmware, Version 1.7 3 | * by Brett Beauregard & Rocket Scream 4 | * License: GPLv3 & BSD License (For autotune) 5 | * 27 April 2016 6 | ********************************************************/ 7 | 8 | NOTE: THIS FIRMWARE IS CONFIGURED FOR DIGITAL OUTPUT CARD 9 | V1.5 & TEMPERATURE INPUT CARD V1.2. IF YOU ARE USING 10 | A DIFFERENT I/O CONFIGURATION BE SURE TO UN-COMMENT THE 11 | APPROPRIATE #DEFINE STATEMENTS IN IO.H. 12 | 13 | Updates for version 1.7 14 | -output is disabled if input is in error state for both thermistor and thermocouple 15 | 16 | Updates for version 1.6 17 | -added support for v1.5 of the Temperature Input card (MAX31855 Thermocouple chip) 18 | 19 | Updates for version 1.5 20 | -restructured code to allow for different IO cards 21 | -added reflow profile support 22 | -eliminated LCD flicker 23 | -error message when thermocouple is disconnected 24 | -extreme code size / RAM improvement (mainly menu and EEPRom) 25 | -consolodated the code into fewer files 26 | * osPID_Firmware.ino - Just about everything 27 | * io.h - IO card code. pre-compiler flags control which card code is used 28 | * EEPROMAnything.h - halley's amazing EEPROMWriteAnything code. 29 | * AnalogButton .cpp _local.h - ospid button-reading/debounce code 30 | * PID_AutoTune_v0 .cpp _local.h - local copy of the autotune library (to avoid 31 | conflicts with possibly pre-installed copies) 32 | * PID_v1 .ccp _local.h - local copy of the PID library 33 | * max6675 .cpp _local.h - local copy of the max6675 library, used by the input card. 34 | -------------------------------------------------------------------------------- /osPID_Firmware/AnalogButton.cpp: -------------------------------------------------------------------------------- 1 | #include "AnalogButton_local.h" 2 | //#include "WProgram.h" 3 | #include "Arduino.h" 4 | 5 | AnalogButton::AnalogButton(uint8_t analogPin, int buttonValueReturn, 6 | int buttonValueUp, int buttonValueDown, int buttonValueOk) 7 | { 8 | // Store analog pin used to multiplex push button 9 | buttonPin = analogPin; 10 | 11 | // Add upper bound of tolerance for variation againts resistor values, temperature 12 | // and other possible drift 13 | buttonValueThresholdReturn = TOLERANCE*buttonValueReturn; 14 | buttonValueThresholdUp = TOLERANCE*buttonValueUp; 15 | buttonValueThresholdDown = TOLERANCE*buttonValueDown; 16 | buttonValueThresholdOk = TOLERANCE*buttonValueOk; 17 | } 18 | 19 | button_t AnalogButton::read(void) 20 | { 21 | int buttonValue; 22 | 23 | buttonValue = analogRead(buttonPin); 24 | 25 | if (buttonValue >= BUTTON_NONE_THRESHOLD) return BUTTON_NONE; 26 | if (buttonValue <= buttonValueThresholdReturn) return BUTTON_RETURN; 27 | if (buttonValue <= buttonValueThresholdUp) return BUTTON_UP; 28 | if (buttonValue <= buttonValueThresholdDown) return BUTTON_DOWN; 29 | if (buttonValue <= buttonValueThresholdOk) return BUTTON_OK; 30 | 31 | return BUTTON_NONE; 32 | } 33 | 34 | button_t AnalogButton::get(void) 35 | { 36 | static button_t buttonMask; 37 | static buttonState_t buttonState; 38 | static unsigned long debounceTimer; 39 | button_t buttonValue; 40 | button_t buttonStatus; 41 | 42 | // Initialize button status 43 | buttonStatus = BUTTON_NONE; 44 | 45 | switch (buttonState) 46 | { 47 | case BUTTON_STATE_SCAN: 48 | // Retrieve current button value 49 | buttonValue = read(); 50 | // If button press is detected 51 | if (buttonValue != BUTTON_NONE) 52 | { 53 | // Store current button press value 54 | buttonMask = buttonValue; 55 | // Retrieve current time 56 | debounceTimer = millis(); 57 | debounceTimer += DEBOUNCE_PERIOD; 58 | // Proceed to button debounce state 59 | buttonState = BUTTON_STATE_DEBOUNCE; 60 | } 61 | break; 62 | 63 | case BUTTON_STATE_DEBOUNCE: 64 | if (read() == buttonMask) 65 | { 66 | // If debounce period is completed 67 | if (millis() >= debounceTimer) 68 | { 69 | buttonStatus = buttonMask; 70 | // Proceed to wait for the button to be released 71 | buttonState = BUTTON_STATE_RELEASE; 72 | } 73 | } 74 | break; 75 | 76 | case BUTTON_STATE_RELEASE: 77 | if (read() == BUTTON_NONE) 78 | { 79 | buttonMask = BUTTON_NONE; 80 | buttonState = BUTTON_STATE_SCAN; 81 | } 82 | break; 83 | } 84 | 85 | return (buttonStatus); 86 | } 87 | -------------------------------------------------------------------------------- /osPID_Firmware/AnalogButton_local.h: -------------------------------------------------------------------------------- 1 | #ifndef AnalogButton_h 2 | #define AnalogButton_h 3 | 4 | #include 5 | 6 | enum button_t 7 | { 8 | BUTTON_NONE, 9 | BUTTON_RETURN, 10 | BUTTON_UP, 11 | BUTTON_DOWN, 12 | BUTTON_OK 13 | }; 14 | 15 | enum buttonState_t 16 | { 17 | BUTTON_STATE_SCAN, 18 | BUTTON_STATE_DEBOUNCE, 19 | BUTTON_STATE_RELEASE 20 | }; 21 | 22 | #define BUTTON_NONE_THRESHOLD 1000 23 | #define TOLERANCE 1.1 24 | #define DEBOUNCE_PERIOD 100 25 | 26 | class AnalogButton 27 | { 28 | public: 29 | AnalogButton(uint8_t analogPin, int buttonValueReturn, 30 | int buttonValueUp, int buttonValueDown, 31 | int buttonValueOk); 32 | 33 | button_t get(void); 34 | 35 | private: 36 | button_t read(void); 37 | 38 | // Analog pin used as button multiplexer 39 | uint8_t buttonPin; 40 | // Upper boound ADC value for each button 41 | int buttonValueThresholdReturn; 42 | int buttonValueThresholdUp; 43 | int buttonValueThresholdDown; 44 | int buttonValueThresholdOk; 45 | }; 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /osPID_Firmware/EEPROMAnything.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include // for type definitions 3 | 4 | template int EEPROM_writeAnything(int ee, const T& value) 5 | { 6 | const byte* p = (const byte*)(const void*)&value; 7 | unsigned int i; 8 | for (i = 0; i < sizeof(value); i++) 9 | EEPROM.write(ee++, *p++); 10 | return i; 11 | } 12 | 13 | template int EEPROM_readAnything(int ee, T& value) 14 | { 15 | byte* p = (byte*)(void*)&value; 16 | unsigned int i; 17 | for (i = 0; i < sizeof(value); i++) 18 | *p++ = EEPROM.read(ee++); 19 | return i; 20 | } 21 | -------------------------------------------------------------------------------- /osPID_Firmware/MAX31855.cpp: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * MAX31855 Library 3 | * Version: 1.10 4 | * Date: 24-07-2012 5 | * Company: Rocket Scream Electronics 6 | * Website: www.rocketscream.com 7 | * 8 | * This is a MAX31855 library for Arduino. Please check our wiki 9 | * (www.rocketscream.com/wiki) for more information on using this piece of 10 | * library. 11 | * 12 | * This library is licensed under Creative Commons Attribution-ShareAlike 3.0 13 | * Unported License. 14 | * 15 | * Revision Description 16 | * ======== =========== 17 | * 1.10 Added negative temperature support for both junction & thermocouple. 18 | * 1.00 Initial public release. 19 | * 20 | *******************************************************************************/ 21 | #include "MAX31855_local.h" 22 | 23 | MAX31855::MAX31855(unsigned char SO, unsigned char CS, unsigned char SCK) 24 | { 25 | so = SO; 26 | cs = CS; 27 | sck = SCK; 28 | 29 | // MAX31855 data output pin 30 | pinMode(so, INPUT); 31 | // MAX31855 chip select input pin 32 | pinMode(cs, OUTPUT); 33 | // MAX31855 clock input pin 34 | pinMode(sck, OUTPUT); 35 | 36 | // Default output pins state 37 | digitalWrite(cs, HIGH); 38 | digitalWrite(sck, LOW); 39 | } 40 | 41 | /******************************************************************************* 42 | * Name: readThermocouple 43 | * Description: Read the thermocouple temperature either in Degree Celsius or 44 | * Fahrenheit. Internally, the conversion takes place in the 45 | * background within 100 ms. Values are updated only when the CS 46 | * line is high. 47 | * 48 | * Argument Description 49 | * ========= =========== 50 | * 1. unit Unit of temperature required: CELSIUS or FAHRENHEIT 51 | * 52 | * Return Description 53 | * ========= =========== 54 | * temperature Temperature of the thermocouple either in Degree Celsius or 55 | * Fahrenheit. If fault is detected, FAULT_OPEN, FAULT_SHORT_GND or 56 | * FAULT_SHORT_VCC will be returned. These fault values are outside 57 | * of the temperature range the MAX31855 is capable of. 58 | *******************************************************************************/ 59 | double MAX31855::readThermocouple(unit_t unit) 60 | { 61 | unsigned long data; 62 | double temperature; 63 | 64 | // Initialize temperature 65 | temperature = 0; 66 | 67 | // Shift in 32-bit of data from MAX31855 68 | data = readData(); 69 | 70 | // If fault is detected 71 | if (data & 0x00010000) 72 | { 73 | // Check for fault type (3 LSB) 74 | switch (data & 0x00000007) 75 | { 76 | // Open circuit 77 | case 0x01: 78 | temperature = FAULT_OPEN; 79 | break; 80 | 81 | // Thermocouple short to GND 82 | case 0x02: 83 | temperature = FAULT_SHORT_GND; 84 | break; 85 | 86 | // Thermocouple short to VCC 87 | case 0x04: 88 | temperature = FAULT_SHORT_VCC; 89 | break; 90 | } 91 | } 92 | // No fault detected 93 | else 94 | { 95 | // Retrieve thermocouple temperature data and strip redundant data 96 | data = data >> 18; 97 | // Bit-14 is the sign 98 | temperature = (data & 0x00001FFF); 99 | 100 | // Check for negative temperature 101 | if (data & 0x00002000) 102 | { 103 | // 2's complement operation 104 | // Invert 105 | data = ~data; 106 | // Ensure operation involves lower 13-bit only 107 | temperature = data & 0x00001FFF; 108 | // Add 1 to obtain the positive number 109 | temperature += 1; 110 | // Make temperature negative 111 | temperature *= -1; 112 | } 113 | 114 | // Convert to Degree Celsius 115 | temperature *= 0.25; 116 | 117 | // If temperature unit in Fahrenheit is desired 118 | if (unit == FAHRENHEIT) 119 | { 120 | // Convert Degree Celsius to Fahrenheit 121 | temperature = (temperature * 9.0/5.0)+ 32; 122 | } 123 | } 124 | return (temperature); 125 | } 126 | 127 | /******************************************************************************* 128 | * Name: readJunction 129 | * Description: Read the thermocouple temperature either in Degree Celsius or 130 | * Fahrenheit. Internally, the conversion takes place in the 131 | * background within 100 ms. Values are updated only when the CS 132 | * line is high. 133 | * 134 | * Argument Description 135 | * ========= =========== 136 | * 1. unit Unit of temperature required: CELSIUS or FAHRENHEIT 137 | * 138 | * Return Description 139 | * ========= =========== 140 | * temperature Temperature of the cold junction either in Degree Celsius or 141 | * Fahrenheit. 142 | * 143 | *******************************************************************************/ 144 | double MAX31855::readJunction(unit_t unit) 145 | { 146 | double temperature; 147 | unsigned long data; 148 | 149 | // Shift in 32-bit of data from MAX31855 150 | data = readData(); 151 | 152 | // Strip fault data bits & reserved bit 153 | data = data >> 4; 154 | // Bit-12 is the sign 155 | temperature = (data & 0x000007FF); 156 | 157 | // Check for negative temperature 158 | if (data & 0x00000800) 159 | { 160 | // 2's complement operation 161 | // Invert 162 | data = ~data; 163 | // Ensure operation involves lower 11-bit only 164 | temperature = data & 0x000007FF; 165 | // Add 1 to obtain the positive number 166 | temperature += 1; 167 | // Make temperature negative 168 | temperature *= -1; 169 | } 170 | 171 | // Convert to Degree Celsius 172 | temperature *= 0.0625; 173 | 174 | // If temperature unit in Fahrenheit is desired 175 | if (unit == FAHRENHEIT) 176 | { 177 | // Convert Degree Celsius to Fahrenheit 178 | temperature = (temperature * 9.0/5.0)+ 32; 179 | } 180 | 181 | // Return the temperature 182 | return (temperature); 183 | } 184 | 185 | /******************************************************************************* 186 | * Name: readData 187 | * Description: Shift in 32-bit of data from MAX31855 chip. Minimum clock pulse 188 | * width is 100 ns. No delay is required in this case. 189 | * 190 | * Argument Description 191 | * ========= =========== 192 | * 1. NIL 193 | * 194 | * Return Description 195 | * ========= =========== 196 | * data 32-bit of data acquired from the MAX31855 chip. 197 | * 198 | *******************************************************************************/ 199 | unsigned long MAX31855::readData() 200 | { 201 | int bitCount; 202 | unsigned long data; 203 | 204 | // Clear data 205 | data = 0; 206 | 207 | // Select the MAX31855 chip 208 | digitalWrite(cs, LOW); 209 | 210 | // Shift in 32-bit of data 211 | for (bitCount = 31; bitCount >= 0; bitCount--) 212 | { 213 | digitalWrite(sck, HIGH); 214 | 215 | // If data bit is high 216 | if (digitalRead(so)) 217 | { 218 | // Need to type cast data type to unsigned long, else compiler will 219 | // truncate to 16-bit 220 | data |= ((unsigned long)1 << bitCount); 221 | } 222 | 223 | digitalWrite(sck, LOW); 224 | } 225 | 226 | // Deselect MAX31855 chip 227 | digitalWrite(cs, HIGH); 228 | 229 | return(data); 230 | } -------------------------------------------------------------------------------- /osPID_Firmware/MAX31855_local.h: -------------------------------------------------------------------------------- 1 | #ifndef MAX31855_H 2 | #define MAX31855_H 3 | 4 | #if ARDUINO >= 100 5 | #include "Arduino.h" 6 | #else 7 | #include "WProgram.h" 8 | #endif 9 | 10 | #define FAULT_OPEN 10000 11 | #define FAULT_SHORT_GND 10001 12 | #define FAULT_SHORT_VCC 10002 13 | 14 | enum unit_t 15 | { 16 | CELSIUS, 17 | FAHRENHEIT 18 | }; 19 | 20 | class MAX31855 21 | { 22 | public: 23 | MAX31855(unsigned char SO, unsigned char CS, unsigned char SCK); 24 | 25 | double readThermocouple(unit_t unit); 26 | double readJunction(unit_t unit); 27 | 28 | private: 29 | unsigned char so; 30 | unsigned char cs; 31 | unsigned char sck; 32 | 33 | unsigned long readData(); 34 | 35 | }; 36 | #endif -------------------------------------------------------------------------------- /osPID_Firmware/PID_AutoTune_v0.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************************************** 2 | * Arduino PID AutoTune Library - Version 0.0.0 3 | * by Brett Beauregard brettbeauregard.com 4 | * 5 | * This Library is ported from the AutotunerPID Toolkit by William Spinelli 6 | * (http://www.mathworks.com/matlabcentral/fileexchange/4652) 7 | * Copyright (c) 2004 8 | * 9 | * This Library is licensed under the BSD License: 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions are 12 | * met: 13 | * 14 | * * Redistributions of source code must retain the above copyright 15 | * notice, this list of conditions and the following disclaimer. 16 | * * Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in 18 | * the documentation and/or other materials provided with the distribution 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | * POSSIBILITY OF SUCH DAMAGE. 31 | **********************************************************************************************/ 32 | 33 | #if ARDUINO >= 100 34 | #include "Arduino.h" 35 | #else 36 | #include "WProgram.h" 37 | #endif 38 | 39 | #include "PID_AutoTune_v0_local.h" //renamed to avoid conflict if Autotune library is installed on IDE 40 | 41 | 42 | PID_ATune::PID_ATune(double* Input, double* Output) 43 | { 44 | input = Input; 45 | output = Output; 46 | controlType =0 ; //default to PI 47 | noiseBand = 0.5; 48 | running = false; 49 | oStep = 30; 50 | SetLookbackSec(10); 51 | lastTime = millis(); 52 | 53 | } 54 | 55 | 56 | 57 | void PID_ATune::Cancel() 58 | { 59 | running = false; 60 | } 61 | 62 | int PID_ATune::Runtime() 63 | { 64 | justevaled=false; 65 | if(peakCount>9 && running) 66 | { 67 | running = false; 68 | FinishUp(); 69 | return 1; 70 | } 71 | unsigned long now = millis(); 72 | 73 | if((now-lastTime)<(unsigned long)sampleTime) return false; 74 | lastTime = now; 75 | double refVal = *input; 76 | justevaled=true; 77 | if(!running) 78 | { //initialize working variables the first time around 79 | peakType = 0; 80 | peakCount=0; 81 | justchanged=false; 82 | absMax=refVal; 83 | absMin=refVal; 84 | setpoint = refVal; 85 | running = true; 86 | initCount=0; 87 | outputStart = *output; 88 | *output = outputStart+oStep; 89 | } 90 | else 91 | { 92 | if(refVal>absMax)absMax=refVal; 93 | if(refValsetpoint+noiseBand) *output = outputStart-oStep; 99 | else if (refVal=0;i--) 106 | { 107 | double val = lastInputs[i]; 108 | if(isMax) isMax = refVal>val; 109 | if(isMin) isMin = refVal2) 146 | { //we've transitioned. check if we can autotune based on the last peaks 147 | double avgSeparation = (abs(peaks[peakCount-1]-peaks[peakCount-2])+abs(peaks[peakCount-2]-peaks[peakCount-3]))/2; 148 | if( avgSeparation < 0.05*(absMax-absMin)) 149 | { 150 | FinishUp(); 151 | running = false; 152 | return 1; 153 | 154 | } 155 | } 156 | justchanged=false; 157 | return 0; 158 | } 159 | void PID_ATune::FinishUp() 160 | { 161 | *output = outputStart; 162 | //we can generate tuning parameters! 163 | Ku = 4*oStep/((absMax-absMin)*3.14159); 164 | Pu = (double)(peak1-peak2) / 1000; 165 | } 166 | 167 | double PID_ATune::GetKp() 168 | { 169 | return controlType==1 ? 0.6 * Ku : 0.4 * Ku; 170 | } 171 | 172 | double PID_ATune::GetKi() 173 | { 174 | return controlType==1? 1.2*Ku / Pu : 0.48 * Ku / Pu; // Ki = Kc/Ti 175 | } 176 | 177 | double PID_ATune::GetKd() 178 | { 179 | return controlType==1? 0.075 * Ku * Pu : 0; //Kd = Kc * Td 180 | } 181 | 182 | void PID_ATune::SetOutputStep(double Step) 183 | { 184 | oStep = Step; 185 | } 186 | 187 | double PID_ATune::GetOutputStep() 188 | { 189 | return oStep; 190 | } 191 | 192 | void PID_ATune::SetControlType(int Type) //0=PI, 1=PID 193 | { 194 | controlType = Type; 195 | } 196 | int PID_ATune::GetControlType() 197 | { 198 | return controlType; 199 | } 200 | 201 | void PID_ATune::SetNoiseBand(double Band) 202 | { 203 | noiseBand = Band; 204 | } 205 | 206 | double PID_ATune::GetNoiseBand() 207 | { 208 | return noiseBand; 209 | } 210 | 211 | void PID_ATune::SetLookbackSec(int value) 212 | { 213 | if (value<1) value = 1; 214 | 215 | if(value<25) 216 | { 217 | nLookBack = value * 4; 218 | sampleTime = 250; 219 | } 220 | else 221 | { 222 | nLookBack = 100; 223 | sampleTime = value*10; 224 | } 225 | } 226 | 227 | int PID_ATune::GetLookbackSec() 228 | { 229 | return nLookBack * sampleTime / 1000; 230 | } 231 | 232 | -------------------------------------------------------------------------------- /osPID_Firmware/PID_AutoTune_v0_local.h: -------------------------------------------------------------------------------- 1 | #ifndef PID_AutoTune_v0 2 | #define PID_AutoTune_v0 3 | #define LIBRARY_VERSION 0.0.0 4 | 5 | class PID_ATune 6 | { 7 | 8 | 9 | public: 10 | //commonly used functions ************************************************************************** 11 | PID_ATune(double*, double*); // * Constructor. links the Autotune to a given PID 12 | int Runtime(); // * Similar to the PID Compue function, returns non 0 when done 13 | void Cancel(); // * Stops the AutoTune 14 | 15 | void SetOutputStep(double); // * how far above and below the starting value will the output step? 16 | double GetOutputStep(); // 17 | 18 | void SetControlType(int); // * Determies if the tuning parameters returned will be PI (D=0) 19 | int GetControlType(); // or PID. (0=PI, 1=PID) 20 | 21 | void SetLookbackSec(int); // * how far back are we looking to identify peaks 22 | int GetLookbackSec(); // 23 | 24 | void SetNoiseBand(double); // * the autotune will ignore signal chatter smaller than this value 25 | double GetNoiseBand(); // this should be acurately set 26 | 27 | double GetKp(); // * once autotune is complete, these functions contain the 28 | double GetKi(); // computed tuning parameters. 29 | double GetKd(); // 30 | 31 | private: 32 | void FinishUp(); 33 | bool isMax, isMin; 34 | double *input, *output; 35 | double setpoint; 36 | double noiseBand; 37 | int controlType; 38 | bool running; 39 | unsigned long peak1, peak2, lastTime; 40 | int sampleTime; 41 | int nLookBack; 42 | int peakType; 43 | double lastInputs[100]; 44 | double peaks[10]; 45 | int peakCount; 46 | bool justchanged; 47 | bool justevaled; 48 | int initCount; 49 | double absMax, absMin; 50 | double oStep; 51 | double outputStart; 52 | double Ku, Pu; 53 | 54 | }; 55 | #endif 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /osPID_Firmware/PID_v1.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************************************** 2 | * Arduino PID Library - Version 1.0.1 3 | * by Brett Beauregard brettbeauregard.com 4 | * 5 | * This Library is licensed under a GPLv3 License 6 | **********************************************************************************************/ 7 | 8 | #if ARDUINO >= 100 9 | #include "Arduino.h" 10 | #else 11 | #include "WProgram.h" 12 | #endif 13 | 14 | #include "PID_v1_local.h" //renamed to avoid conflict if PID library is installed on IDE 15 | 16 | /*Constructor (...)********************************************************* 17 | * The parameters specified here are those for for which we can't set up 18 | * reliable defaults, so we need to have the user set them. 19 | ***************************************************************************/ 20 | PID::PID(double* Input, double* Output, double* Setpoint, 21 | double Kp, double Ki, double Kd, int ControllerDirection) 22 | { 23 | PID::SetOutputLimits(0, 255); //default output limit corresponds to 24 | //the arduino pwm limits 25 | 26 | SampleTime = 100; //default Controller Sample Time is 0.1 seconds 27 | 28 | PID::SetControllerDirection(ControllerDirection); 29 | PID::SetTunings(Kp, Ki, Kd); 30 | 31 | lastTime = millis()-SampleTime; 32 | inAuto = false; 33 | myOutput = Output; 34 | myInput = Input; 35 | mySetpoint = Setpoint; 36 | 37 | } 38 | 39 | 40 | /* Compute() ********************************************************************** 41 | * This, as they say, is where the magic happens. this function should be called 42 | * every time "void loop()" executes. the function will decide for itself whether a new 43 | * pid Output needs to be computed 44 | **********************************************************************************/ 45 | void PID::Compute() 46 | { 47 | if(!inAuto) return; 48 | unsigned long now = millis(); 49 | unsigned long timeChange = (now - lastTime); 50 | if(timeChange>=(unsigned long)SampleTime) 51 | { 52 | /*Compute all the working error variables*/ 53 | double input = *myInput; 54 | double error = *mySetpoint - input; 55 | ITerm+= (ki * error); 56 | if(ITerm > outMax) ITerm= outMax; 57 | else if(ITerm < outMin) ITerm= outMin; 58 | double dInput = (input - lastInput); 59 | 60 | /*Compute PID Output*/ 61 | double output = kp * error + ITerm- kd * dInput; 62 | 63 | if(output > outMax) output = outMax; 64 | else if(output < outMin) output = outMin; 65 | *myOutput = output; 66 | 67 | /*Remember some variables for next time*/ 68 | lastInput = input; 69 | lastTime = now; 70 | } 71 | } 72 | 73 | 74 | /* SetTunings(...)************************************************************* 75 | * This function allows the controller's dynamic performance to be adjusted. 76 | * it's called automatically from the constructor, but tunings can also 77 | * be adjusted on the fly during normal operation 78 | ******************************************************************************/ 79 | void PID::SetTunings(double Kp, double Ki, double Kd) 80 | { 81 | if (Kp<0 || Ki<0 || Kd<0) return; 82 | 83 | dispKp = Kp; dispKi = Ki; dispKd = Kd; 84 | 85 | double SampleTimeInSec = ((double)SampleTime)/1000; 86 | kp = Kp; 87 | ki = Ki * SampleTimeInSec; 88 | kd = Kd / SampleTimeInSec; 89 | 90 | if(controllerDirection ==REVERSE) 91 | { 92 | kp = (0 - kp); 93 | ki = (0 - ki); 94 | kd = (0 - kd); 95 | } 96 | } 97 | 98 | /* SetSampleTime(...) ********************************************************* 99 | * sets the period, in Milliseconds, at which the calculation is performed 100 | ******************************************************************************/ 101 | void PID::SetSampleTime(int NewSampleTime) 102 | { 103 | if (NewSampleTime > 0) 104 | { 105 | double ratio = (double)NewSampleTime 106 | / (double)SampleTime; 107 | ki *= ratio; 108 | kd /= ratio; 109 | SampleTime = (unsigned long)NewSampleTime; 110 | } 111 | } 112 | 113 | /* SetOutputLimits(...)**************************************************** 114 | * This function will be used far more often than SetInputLimits. while 115 | * the input to the controller will generally be in the 0-1023 range (which is 116 | * the default already,) the output will be a little different. maybe they'll 117 | * be doing a time window and will need 0-8000 or something. or maybe they'll 118 | * want to clamp it from 0-125. who knows. at any rate, that can all be done 119 | * here. 120 | **************************************************************************/ 121 | void PID::SetOutputLimits(double Min, double Max) 122 | { 123 | if(Min >= Max) return; 124 | outMin = Min; 125 | outMax = Max; 126 | 127 | if(inAuto) 128 | { 129 | if(*myOutput > outMax) *myOutput = outMax; 130 | else if(*myOutput < outMin) *myOutput = outMin; 131 | 132 | if(ITerm > outMax) ITerm= outMax; 133 | else if(ITerm < outMin) ITerm= outMin; 134 | } 135 | } 136 | 137 | /* SetMode(...)**************************************************************** 138 | * Allows the controller Mode to be set to manual (0) or Automatic (non-zero) 139 | * when the transition from manual to auto occurs, the controller is 140 | * automatically initialized 141 | ******************************************************************************/ 142 | void PID::SetMode(int Mode) 143 | { 144 | bool newAuto = (Mode == AUTOMATIC); 145 | if(newAuto == !inAuto) 146 | { /*we just went from manual to auto*/ 147 | PID::Initialize(); 148 | } 149 | inAuto = newAuto; 150 | } 151 | 152 | /* Initialize()**************************************************************** 153 | * does all the things that need to happen to ensure a bumpless transfer 154 | * from manual to automatic mode. 155 | ******************************************************************************/ 156 | void PID::Initialize() 157 | { 158 | ITerm = *myOutput; 159 | lastInput = *myInput; 160 | if(ITerm > outMax) ITerm = outMax; 161 | else if(ITerm < outMin) ITerm = outMin; 162 | } 163 | 164 | /* SetControllerDirection(...)************************************************* 165 | * The PID will either be connected to a DIRECT acting process (+Output leads 166 | * to +Input) or a REVERSE acting process(+Output leads to -Input.) we need to 167 | * know which one, because otherwise we may increase the output when we should 168 | * be decreasing. This is called from the constructor. 169 | ******************************************************************************/ 170 | void PID::SetControllerDirection(int Direction) 171 | { 172 | if(inAuto && Direction !=controllerDirection) 173 | { 174 | kp = (0 - kp); 175 | ki = (0 - ki); 176 | kd = (0 - kd); 177 | } 178 | controllerDirection = Direction; 179 | } 180 | 181 | /* Status Funcions************************************************************* 182 | * Just because you set the Kp=-1 doesn't mean it actually happened. these 183 | * functions query the internal state of the PID. they're here for display 184 | * purposes. this are the functions the PID Front-end uses for example 185 | ******************************************************************************/ 186 | double PID::GetKp(){ return dispKp; } 187 | double PID::GetKi(){ return dispKi;} 188 | double PID::GetKd(){ return dispKd;} 189 | int PID::GetMode(){ return inAuto ? AUTOMATIC : MANUAL;} 190 | int PID::GetDirection(){ return controllerDirection;} 191 | 192 | 193 | -------------------------------------------------------------------------------- /osPID_Firmware/PID_v1_local.h: -------------------------------------------------------------------------------- 1 | #ifndef PID_v1_h 2 | #define PID_v1_h 3 | #define LIBRARY_VERSION 1.0.0 4 | 5 | class PID 6 | { 7 | 8 | 9 | public: 10 | 11 | //Constants used in some of the functions below 12 | #define AUTOMATIC 1 13 | #define MANUAL 0 14 | #define DIRECT 0 15 | #define REVERSE 1 16 | 17 | //commonly used functions ************************************************************************** 18 | PID(double*, double*, double*, // * constructor. links the PID to the Input, Output, and 19 | double, double, double, int); // Setpoint. Initial tuning parameters are also set here 20 | 21 | void SetMode(int Mode); // * sets PID to either Manual (0) or Auto (non-0) 22 | 23 | void Compute(); // * performs the PID calculation. it should be 24 | // called every time loop() cycles. ON/OFF and 25 | // calculation frequency can be set using SetMode 26 | // SetSampleTime respectively 27 | 28 | void SetOutputLimits(double, double); //clamps the output to a specific range. 0-255 by default, but 29 | //it's likely the user will want to change this depending on 30 | //the application 31 | 32 | 33 | 34 | //available but not commonly used functions ******************************************************** 35 | void SetTunings(double, double, // * While most users will set the tunings once in the 36 | double); // constructor, this function gives the user the option 37 | // of changing tunings during runtime for Adaptive control 38 | void SetControllerDirection(int); // * Sets the Direction, or "Action" of the controller. DIRECT 39 | // means the output will increase when error is positive. REVERSE 40 | // means the opposite. it's very unlikely that this will be needed 41 | // once it is set in the constructor. 42 | void SetSampleTime(int); // * sets the frequency, in Milliseconds, with which 43 | // the PID calculation is performed. default is 100 44 | 45 | 46 | 47 | //Display functions **************************************************************** 48 | double GetKp(); // These functions query the pid for interal values. 49 | double GetKi(); // they were created mainly for the pid front-end, 50 | double GetKd(); // where it's important to know what is actually 51 | int GetMode(); // inside the PID. 52 | int GetDirection(); // 53 | 54 | private: 55 | void Initialize(); 56 | 57 | double dispKp; // * we'll hold on to the tuning parameters in user-entered 58 | double dispKi; // format for display purposes 59 | double dispKd; // 60 | 61 | double kp; // * (P)roportional Tuning Parameter 62 | double ki; // * (I)ntegral Tuning Parameter 63 | double kd; // * (D)erivative Tuning Parameter 64 | 65 | int controllerDirection; 66 | 67 | double *myInput; // * Pointers to the Input, Output, and Setpoint variables 68 | double *myOutput; // This creates a hard link between the variables and the 69 | double *mySetpoint; // PID, freeing the user from having to constantly tell us 70 | // what these values are. with pointers we'll just know. 71 | 72 | unsigned long lastTime; 73 | double ITerm, lastInput; 74 | 75 | int SampleTime; 76 | double outMin, outMax; 77 | bool inAuto; 78 | }; 79 | #endif 80 | 81 | 82 | -------------------------------------------------------------------------------- /osPID_Firmware/io.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The osPID Kit comes with swappable IO cards which are supported by different 3 | * device drivers & libraries. For the osPID firmware to correctly communicate with 4 | * your configuration, you must uncomment the appropriate "define" statements below. 5 | * Please take note that only 1 input card and 1 output card can be used at a time. 6 | * List of available IO cards: 7 | * 8 | * Input Cards 9 | * =========== 10 | * 1. TEMP_INPUT_V110: 11 | * Temperature Basic V1.10 with 1 thermistor & 1 type-K thermocouple (MAX6675) 12 | * interface. 13 | * 2. TEMP_INPUT_V120: 14 | * Temperature Basic V1.20 with 1 thermistor & 1 type-K thermocouple 15 | * (MAX31855KASA) interface. 16 | * 3. PROTOTYPE_INPUT: 17 | * Generic prototype card with input specified by user. Please add necessary 18 | * input processing in the section below. 19 | * 20 | * Output Cards 21 | * ============ 22 | * 1. DIGITAL_OUTPUT_V120: 23 | * Output card with 1 SSR & 2 relay output. 24 | * 2. DIGITAL_OUTPUT_V150: 25 | * Output card with 1 SSR & 2 relay output. Similar to V1.20 except LED mount 26 | * orientation. 27 | * 3. PROTOTYPE_OUTPUT: 28 | * Generic prototype card with output specified by user. Please add necessary 29 | * output processing in the section below. 30 | * 31 | * This file is licensed under Creative Commons Attribution-ShareAlike 3.0 32 | * Unported License. 33 | * 34 | *******************************************************************************/ 35 | 36 | // ***** INPUT CARD ***** 37 | //#define TEMP_INPUT_V110 38 | #define TEMP_INPUT_V120 39 | //#define PROTOTYPE_INPUT 40 | 41 | // ***** OUTPUT CARD ***** 42 | //#define DIGITAL_OUTPUT_V120 43 | #define DIGITAL_OUTPUT_V150 44 | //#define PROTOTYPE_OUTPUT 45 | 46 | union { // This Data structure lets 47 | byte asBytes[32]; // us take the byte array 48 | float asFloat[8]; // sent from processing and 49 | } // easily convert it to a 50 | serialXfer; // float array 51 | byte b1,b2; 52 | 53 | #ifdef TEMP_INPUT_V110 54 | #include "max6675_local.h" 55 | const byte thermistorPin = A6; 56 | const byte thermocoupleCS = 10; 57 | const byte thermocoupleSO = 12; 58 | const byte thermocoupleCLK = 13; 59 | byte inputType = 0; 60 | double THERMISTORNOMINAL = 10; 61 | double BCOEFFICIENT = 1; 62 | double TEMPERATURENOMINAL = 293.15; 63 | double REFERENCE_RESISTANCE = 10; 64 | MAX6675 thermocouple(thermocoupleCLK, thermocoupleCS, thermocoupleSO); 65 | 66 | // EEPROM backup 67 | void EEPROMBackupInputParams(int offset) 68 | { 69 | EEPROM.write(offset, inputType); 70 | EEPROM_writeAnything(offset+2,THERMISTORNOMINAL); 71 | EEPROM_writeAnything(offset+6,BCOEFFICIENT); 72 | EEPROM_writeAnything(offset+10,TEMPERATURENOMINAL); 73 | EEPROM_writeAnything(offset+14,REFERENCE_RESISTANCE); 74 | } 75 | 76 | // EEPROM restore 77 | void EEPROMRestoreInputParams(int offset) 78 | { 79 | inputType = EEPROM.read(offset); 80 | EEPROM_readAnything(offset+2,THERMISTORNOMINAL); 81 | EEPROM_readAnything(offset+6,BCOEFFICIENT); 82 | EEPROM_readAnything(offset+10,TEMPERATURENOMINAL); 83 | EEPROM_readAnything(offset+14,REFERENCE_RESISTANCE); 84 | } 85 | 86 | void InitializeInputCard() 87 | { 88 | } 89 | 90 | void InputSerialReceiveStart() 91 | { 92 | } 93 | 94 | void InputSerialReceiveDuring(byte val, byte index) 95 | { 96 | if(index==1) b1 = val; 97 | else if(index<18) serialXfer.asBytes[index-2] = val; 98 | } 99 | 100 | void InputSerialReceiveAfter(int eepromOffset) 101 | { 102 | inputType = b1; 103 | THERMISTORNOMINAL = serialXfer.asFloat[0]; 104 | BCOEFFICIENT = serialXfer.asFloat[1]; 105 | TEMPERATURENOMINAL = serialXfer.asFloat[2]; 106 | REFERENCE_RESISTANCE = serialXfer.asFloat[3]; 107 | EEPROMBackupInputParams(eepromOffset); 108 | } 109 | 110 | void InputSerialSend() 111 | { 112 | Serial.print((int)inputType); 113 | Serial.print(" "); 114 | Serial.print(THERMISTORNOMINAL); 115 | Serial.print(" "); 116 | Serial.print(BCOEFFICIENT); 117 | Serial.print(" "); 118 | Serial.print(TEMPERATURENOMINAL); 119 | Serial.print(" "); 120 | Serial.println(REFERENCE_RESISTANCE); 121 | } 122 | 123 | void InputSerialID() 124 | { 125 | Serial.print(" IID1"); 126 | } 127 | 128 | double readThermistorTemp(int voltage) 129 | { 130 | float R = REFERENCE_RESISTANCE / (1024.0/(float)voltage - 1); 131 | float steinhart; 132 | steinhart = R / THERMISTORNOMINAL; // (R/Ro) 133 | steinhart = log(steinhart); // ln(R/Ro) 134 | steinhart /= BCOEFFICIENT; // 1/B * ln(R/Ro) 135 | steinhart += 1.0 / (TEMPERATURENOMINAL + 273.15); // + (1/To) 136 | steinhart = 1.0 / steinhart; // Invert 137 | steinhart -= 273.15; // convert to C 138 | 139 | return steinhart; 140 | } 141 | 142 | double ReadInputFromCard() 143 | { 144 | if(inputType == 0) return thermocouple.readCelsius(); 145 | else if(inputType == 1) 146 | { 147 | int adcReading = analogRead(thermistorPin); 148 | if ((adcReading == 0) || (adcReading == 1023)) 149 | { 150 | return NAN; 151 | } 152 | else 153 | { 154 | return readThermistorTemp(adcReading); 155 | } 156 | } 157 | } 158 | #endif /*TEMP_INPUT_V110*/ 159 | 160 | #ifdef TEMP_INPUT_V120 161 | #include "MAX31855_local.h" 162 | const byte thermistorPin = A6; 163 | const byte thermocoupleCS = 10; 164 | const byte thermocoupleSO = 12; 165 | const byte thermocoupleCLK = 13; 166 | byte inputType = 0; 167 | double THERMISTORNOMINAL = 10; 168 | double BCOEFFICIENT = 1; 169 | double TEMPERATURENOMINAL = 293.15; 170 | double REFERENCE_RESISTANCE = 10; 171 | MAX31855 thermocouple(thermocoupleSO, thermocoupleCS, thermocoupleCLK); 172 | 173 | // EEPROM backup 174 | void EEPROMBackupInputParams(int offset) 175 | { 176 | EEPROM.write(offset, inputType); 177 | EEPROM_writeAnything(offset+2,THERMISTORNOMINAL); 178 | EEPROM_writeAnything(offset+6,BCOEFFICIENT); 179 | EEPROM_writeAnything(offset+10,TEMPERATURENOMINAL); 180 | EEPROM_writeAnything(offset+14,REFERENCE_RESISTANCE); 181 | } 182 | 183 | // EEPROM restore 184 | void EEPROMRestoreInputParams(int offset) 185 | { 186 | inputType = EEPROM.read(offset); 187 | EEPROM_readAnything(offset+2,THERMISTORNOMINAL); 188 | EEPROM_readAnything(offset+6,BCOEFFICIENT); 189 | EEPROM_readAnything(offset+10,TEMPERATURENOMINAL); 190 | EEPROM_readAnything(offset+14,REFERENCE_RESISTANCE); 191 | } 192 | 193 | void InitializeInputCard() 194 | { 195 | } 196 | 197 | void InputSerialReceiveStart() 198 | { 199 | } 200 | 201 | void InputSerialReceiveDuring(byte val, byte index) 202 | { 203 | if(index==1) b1 = val; 204 | else if(index<18) serialXfer.asBytes[index-2] = val; 205 | } 206 | 207 | void InputSerialReceiveAfter(int eepromOffset) 208 | { 209 | inputType = b1; 210 | THERMISTORNOMINAL = serialXfer.asFloat[0]; 211 | BCOEFFICIENT = serialXfer.asFloat[1]; 212 | TEMPERATURENOMINAL = serialXfer.asFloat[2]; 213 | REFERENCE_RESISTANCE = serialXfer.asFloat[3]; 214 | EEPROMBackupInputParams(eepromOffset); 215 | } 216 | 217 | void InputSerialSend() 218 | { 219 | Serial.print((int)inputType); 220 | Serial.print(" "); 221 | Serial.print(THERMISTORNOMINAL); 222 | Serial.print(" "); 223 | Serial.print(BCOEFFICIENT); 224 | Serial.print(" "); 225 | Serial.print(TEMPERATURENOMINAL); 226 | Serial.print(" "); 227 | Serial.println(REFERENCE_RESISTANCE); 228 | } 229 | 230 | void InputSerialID() 231 | { 232 | Serial.print(" IID2"); 233 | } 234 | 235 | double readThermistorTemp(int voltage) 236 | { 237 | float R = REFERENCE_RESISTANCE / (1024.0/(float)voltage - 1); 238 | float steinhart; 239 | steinhart = R / THERMISTORNOMINAL; // (R/Ro) 240 | steinhart = log(steinhart); // ln(R/Ro) 241 | steinhart /= BCOEFFICIENT; // 1/B * ln(R/Ro) 242 | steinhart += 1.0 / (TEMPERATURENOMINAL + 273.15); // + (1/To) 243 | steinhart = 1.0 / steinhart; // Invert 244 | steinhart -= 273.15; // convert to C 245 | 246 | return steinhart; 247 | } 248 | 249 | double ReadInputFromCard() 250 | { 251 | if(inputType == 0) 252 | { 253 | double val = thermocouple.readThermocouple(CELSIUS); 254 | if (val==FAULT_OPEN|| val==FAULT_SHORT_GND|| val==FAULT_SHORT_VCC)val = NAN; 255 | return val; 256 | } 257 | else if(inputType == 1) 258 | { 259 | int adcReading = analogRead(thermistorPin); 260 | // If either thermistor or reference resistor is not connected 261 | if ((adcReading == 0) || (adcReading == 1023)) 262 | { 263 | return NAN; 264 | } 265 | else 266 | { 267 | return readThermistorTemp(adcReading); 268 | } 269 | } 270 | } 271 | #endif /*TEMP_INPUT_V120*/ 272 | 273 | #ifdef PROTOTYPE_INPUT 274 | /*Include any libraries and/or global variables here*/ 275 | 276 | float flt1_i=0, flt2_i=0, flt3_i=0, flt4_i=0; 277 | byte bt1_i=0, bt2_i=0, bt3_i=0, bt4_i=0; 278 | 279 | void EEPROMBackupInputParams(int offset) 280 | { 281 | EEPROM_writeAnything(offset, bt1_i); 282 | EEPROM_writeAnything(offset+1, bt2_i); 283 | EEPROM_writeAnything(offset+2, bt3_i); 284 | EEPROM_writeAnything(offset+3, bt4_i); 285 | EEPROM_writeAnything(offset+4,flt1_i); 286 | EEPROM_writeAnything(offset+8,flt2_i); 287 | EEPROM_writeAnything(offset+12,flt3_i); 288 | EEPROM_writeAnything(offset+16,flt4_i); 289 | } 290 | 291 | void EEPROMRestoreInputParams(int offset) 292 | { 293 | EEPROM_readAnything(offset, bt1_i); 294 | EEPROM_readAnything(offset+1, bt2_i); 295 | EEPROM_readAnything(offset+2, bt3_i); 296 | EEPROM_readAnything(offset+3, bt4_i); 297 | EEPROM_readAnything(offset+4,flt1_i); 298 | EEPROM_readAnything(offset+8,flt2_i); 299 | EEPROM_readAnything(offset+12,flt3_i); 300 | EEPROM_readAnything(offset+16,flt4_i); 301 | } 302 | 303 | void InitializeInputCard() 304 | { 305 | } 306 | 307 | void InputSerialReceiveStart() 308 | { 309 | } 310 | 311 | void InputSerialReceiveDuring(byte val, byte index) 312 | { 313 | if(index==1) bt1_i = val; 314 | else if(index==2) bt2_i = val; 315 | else if(index==3) bt3_i = val; 316 | else if(index==4) bt4_i = val; 317 | else if(index<22) serialXfer.asBytes[index-5] = val; 318 | } 319 | 320 | void InputSerialReceiveAfter(int eepromOffset) 321 | { 322 | flt1_i = serialXfer.asFloat[0]; 323 | flt2_i = serialXfer.asFloat[1]; 324 | flt3_i = serialXfer.asFloat[2]; 325 | flt4_i = serialXfer.asFloat[3]; 326 | 327 | EEPROMBackupInputParams(eepromOffset); 328 | } 329 | 330 | void InputSerialSend() 331 | { 332 | Serial.print(int(bt1_i)); 333 | Serial.print(" "); 334 | Serial.print(int(bt2_i)); 335 | Serial.print(" "); 336 | Serial.print(int(bt3_i)); 337 | Serial.print(" "); 338 | Serial.print(int(bt4_i)); 339 | Serial.print(" "); 340 | Serial.print(flt1_i); 341 | Serial.print(" "); 342 | Serial.print(flt2_i); 343 | Serial.print(" "); 344 | Serial.print(flt3_i); 345 | Serial.print(" "); 346 | Serial.println(flt4_i); 347 | } 348 | 349 | void InputSerialID() 350 | { 351 | Serial.print(" IID0"); 352 | } 353 | 354 | double ReadInputFromCard() 355 | { 356 | /*your code here*/ 357 | return 0; 358 | } 359 | #endif /*PROTOTYPE_INPUT*/ 360 | 361 | #if defined(DIGITAL_OUTPUT_V120) || defined(DIGITAL_OUTPUT_V150) 362 | byte outputType = 1; 363 | const byte RelayPin = 5; 364 | const byte SSRPin = 6; 365 | //unsigned long windowStartTime; 366 | double outWindowSec = 5.0; 367 | unsigned long WindowSize = 5000; 368 | 369 | void setOutputWindow(double val) 370 | { 371 | unsigned long temp = (unsigned long)(val*1000); 372 | if(temp<500)temp = 500; 373 | outWindowSec = (double)temp/1000; 374 | if(temp!=WindowSize) 375 | { 376 | WindowSize = temp; 377 | } 378 | } 379 | 380 | void EEPROMBackupOutputParams(int offset) 381 | { 382 | EEPROM.write(offset, outputType); 383 | EEPROM_writeAnything(offset+1, WindowSize); 384 | } 385 | void EEPROMRestoreOutputParams(int offset) 386 | { 387 | outputType = EEPROM.read(offset); 388 | EEPROM_readAnything(offset+1, WindowSize); 389 | } 390 | 391 | void InitializeOutputCard() 392 | { 393 | pinMode(RelayPin, OUTPUT); 394 | pinMode(SSRPin, OUTPUT); 395 | } 396 | 397 | void OutputSerialReceiveStart() 398 | { 399 | } 400 | 401 | void OutputSerialReceiveDuring(byte val, byte index) 402 | { 403 | if(index==1) b1 = val; 404 | else if(index<6) serialXfer.asBytes[index-2] = val; 405 | } 406 | 407 | void OutputSerialReceiveAfter(int eepromOffset) 408 | { 409 | if(outputType != b1) 410 | { 411 | if (b1==0)digitalWrite(SSRPin, LOW); 412 | else if(b1==1) digitalWrite( RelayPin,LOW); //turn off the other pin 413 | outputType=b1; 414 | } 415 | outWindowSec = serialXfer.asFloat[0]; 416 | setOutputWindow(outWindowSec); 417 | EEPROMBackupOutputParams(eepromOffset); 418 | } 419 | 420 | void OutputSerialID() 421 | { 422 | Serial.print(" OID1"); 423 | } 424 | 425 | void WriteToOutputCard(double value) 426 | { 427 | unsigned long wind = millis() % WindowSize; // (millis() - windowStartTime); 428 | /*if(wind>WindowSize) 429 | { 430 | wind -= WindowSize; 431 | windowStartTime += WindowSize; 432 | }*/ 433 | unsigned long oVal = (unsigned long)(value*(double)WindowSize/ 100.0); 434 | if(outputType == 0) digitalWrite(RelayPin ,(oVal>wind) ? HIGH : LOW); 435 | else if(outputType == 1) digitalWrite(SSRPin ,(oVal>wind) ? HIGH : LOW); 436 | } 437 | 438 | // Serial send & receive 439 | void OutputSerialSend() 440 | { 441 | Serial.print((int)outputType); 442 | Serial.print(" "); 443 | Serial.println(outWindowSec); 444 | } 445 | #endif /*DIGITAL_OUTPUT_V120 & DIGITAL_OUTPUT_V150*/ 446 | 447 | #ifdef PROTOTYPE_OUTPUT 448 | float flt1_o=0, flt2_o=0, flt3_o=0, flt4_o=0; 449 | byte bt1_o=0, bt2_o=0, bt3_o=0, bt4_o=0; 450 | 451 | void EEPROMBackupOutputParams(int offset) 452 | { 453 | EEPROM_writeAnything(offset, bt1_o); 454 | EEPROM_writeAnything(offset+1, bt2_o); 455 | EEPROM_writeAnything(offset+2, bt3_o); 456 | EEPROM_writeAnything(offset+3, bt4_o); 457 | EEPROM_writeAnything(offset+4,flt1_o); 458 | EEPROM_writeAnything(offset+8,flt2_o); 459 | EEPROM_writeAnything(offset+12,flt3_o); 460 | EEPROM_writeAnything(offset+16,flt4_o); 461 | } 462 | 463 | void EEPROMRestoreOutputParams(int offset) 464 | { 465 | EEPROM_readAnything(offset, bt1_o); 466 | EEPROM_readAnything(offset+1, bt2_o); 467 | EEPROM_readAnything(offset+2, bt3_o); 468 | EEPROM_readAnything(offset+3, bt4_o); 469 | EEPROM_readAnything(offset+4,flt1_o); 470 | EEPROM_readAnything(offset+8,flt2_o); 471 | EEPROM_readAnything(offset+12,flt3_o); 472 | EEPROM_readAnything(offset+16,flt4_o); 473 | } 474 | 475 | void InitializeOutputCard() 476 | { 477 | } 478 | 479 | void OutputSerialReceiveStart() 480 | { 481 | } 482 | 483 | void OutputSerialReceiveDuring(byte val, byte index) 484 | { 485 | if(index==1) bt1_o = val; 486 | else if(index==2) bt2_o = val; 487 | else if(index==3) bt3_o = val; 488 | else if(index==4) bt4_o = val; 489 | else if(index<22) serialXfer.asBytes[index-5] = val; 490 | } 491 | 492 | void OutputSerialReceiveAfter(int eepromOffset) 493 | { 494 | flt1_o = serialXfer.asFloat[0]; 495 | flt2_o = serialXfer.asFloat[1]; 496 | flt3_o = serialXfer.asFloat[2]; 497 | flt4_o = serialXfer.asFloat[3]; 498 | 499 | EEPROMBackupOutputParams(eepromOffset); 500 | } 501 | 502 | void OutputSerialID() 503 | { 504 | Serial.print(" OID0"); 505 | } 506 | 507 | void WriteToOutputCard(double value) 508 | { 509 | } 510 | 511 | // Serial send & receive 512 | void OutputSerialSend() 513 | { 514 | Serial.print(int(bt1_o)); 515 | Serial.print(" "); 516 | Serial.print(int(bt2_o)); 517 | Serial.print(" "); 518 | Serial.print(int(bt3_o)); 519 | Serial.print(" "); 520 | Serial.print(int(bt4_o)); 521 | Serial.print(" "); 522 | Serial.print(flt1_o); 523 | Serial.print(" "); 524 | Serial.print(flt2_o); 525 | Serial.print(" "); 526 | Serial.print(flt3_o); 527 | Serial.print(" "); 528 | Serial.println(flt4_o); 529 | } 530 | #endif /*PROTOTYPE_OUTPUT*/ 531 | -------------------------------------------------------------------------------- /osPID_Firmware/max6675.cpp: -------------------------------------------------------------------------------- 1 | // this library is public domain. enjoy! 2 | // www.ladyada.net/learn/sensors/thermocouple 3 | 4 | #include 5 | #include 6 | #include 7 | #include "max6675_local.h" 8 | 9 | MAX6675::MAX6675(int8_t SCLK, int8_t CS, int8_t MISO) { 10 | sclk = SCLK; 11 | cs = CS; 12 | miso = MISO; 13 | 14 | //define pin modes 15 | pinMode(cs, OUTPUT); 16 | pinMode(sclk, OUTPUT); 17 | pinMode(miso, INPUT); 18 | 19 | digitalWrite(cs, HIGH); 20 | } 21 | double MAX6675::readCelsius(void) { 22 | 23 | uint16_t v; 24 | 25 | digitalWrite(cs, LOW); 26 | _delay_ms(1); 27 | 28 | v = spiread(); 29 | v <<= 8; 30 | v |= spiread(); 31 | 32 | digitalWrite(cs, HIGH); 33 | 34 | if (v & 0x4) { 35 | // uh oh, no thermocouple attached! 36 | return NAN; 37 | //return -100; 38 | } 39 | 40 | v >>= 3; 41 | 42 | return v*0.25; 43 | } 44 | 45 | double MAX6675::readFarenheit(void) { 46 | return readCelsius() * 9.0/5.0 + 32; 47 | } 48 | 49 | byte MAX6675::spiread(void) { 50 | int i; 51 | byte d = 0; 52 | 53 | for (i=7; i>=0; i--) 54 | { 55 | digitalWrite(sclk, LOW); 56 | _delay_ms(1); 57 | if (digitalRead(miso)) { 58 | //set the bit to 0 no matter what 59 | d |= (1 << i); 60 | } 61 | 62 | digitalWrite(sclk, HIGH); 63 | _delay_ms(1); 64 | } 65 | 66 | return d; 67 | } 68 | -------------------------------------------------------------------------------- /osPID_Firmware/max6675_local.h: -------------------------------------------------------------------------------- 1 | // this library is public domain. enjoy! 2 | // www.ladyada.net/learn/sensors/thermocouple 3 | 4 | #if ARDUINO >= 100 5 | #include "Arduino.h" 6 | #else 7 | #include "WProgram.h" 8 | #endif 9 | 10 | class MAX6675 { 11 | public: 12 | MAX6675(int8_t SCLK, int8_t CS, int8_t MISO); 13 | 14 | double readCelsius(void); 15 | double readFarenheit(void); 16 | private: 17 | int8_t sclk, miso, cs; 18 | uint8_t spiread(void); 19 | }; 20 | -------------------------------------------------------------------------------- /osPID_Firmware/osPID_Firmware.ino: -------------------------------------------------------------------------------- 1 | //#define USE_SIMULATION 2 | 3 | #include 4 | #include 5 | #include "AnalogButton_local.h" 6 | #include "PID_v1_local.h" 7 | #include "EEPROMAnything.h" 8 | #include "PID_AutoTune_v0_local.h" 9 | #include "io.h" 10 | 11 | // ***** PIN ASSIGNMENTS ***** 12 | 13 | const byte buzzerPin = 3; 14 | const byte systemLEDPin = A2; 15 | 16 | const byte EEPROM_ID = 2; //used to automatically trigger and eeprom reset after firmware update (if necessary) 17 | 18 | const byte TYPE_NAV=0; 19 | const byte TYPE_VAL=1; 20 | const byte TYPE_OPT=2; 21 | 22 | byte mMain[] = { 23 | 0,1,2,3}; 24 | byte mDash[] = { 25 | 4,5,6,7}; 26 | byte mConfig[] = { 27 | 8,9,10,11}; 28 | byte *mMenu[] = { 29 | mMain, mDash, mConfig}; 30 | 31 | byte curMenu=0, mIndex=0, mDrawIndex=0; 32 | LiquidCrystal lcd(A1, A0, 4, 7, 8, 9); 33 | AnalogButton button(A3, 0, 253, 454, 657); 34 | 35 | unsigned long now, lcdTime, buttonTime,ioTime, serialTime; 36 | boolean sendInfo=true, sendDash=true, sendTune=true, sendInputConfig=true, sendOutputConfig=true; 37 | 38 | bool editing=false; 39 | bool inputOk = true; 40 | bool tuning = false; 41 | 42 | double setpoint=250,input=250,output=50, pidInput=250; 43 | 44 | double kp = 2, ki = 0.5, kd = 2; 45 | byte ctrlDirection = 0; 46 | byte modeIndex = 0; 47 | byte highlightedIndex=0; 48 | 49 | PID myPID(&pidInput, &output, &setpoint,kp,ki,kd, DIRECT); 50 | 51 | double aTuneStep = 20, aTuneNoise = 1; 52 | unsigned int aTuneLookBack = 10; 53 | byte ATuneModeRemember = 0; 54 | PID_ATune aTune(&pidInput, &output); 55 | 56 | 57 | byte curProfStep=0; 58 | byte curType=0; 59 | float curVal=0; 60 | float helperVal=0; 61 | unsigned long helperTime=0; 62 | boolean helperflag=false; 63 | unsigned long curTime=0; 64 | 65 | 66 | /*Profile declarations*/ 67 | const unsigned long profReceiveTimeout = 10000; 68 | unsigned long profReceiveStart=0; 69 | boolean receivingProfile=false; 70 | const int nProfSteps = 15; 71 | char profname[] = { 72 | 'N','o',' ','P','r','o','f'}; 73 | byte proftypes[nProfSteps]; 74 | unsigned long proftimes[nProfSteps]; 75 | float profvals[nProfSteps]; 76 | boolean runningProfile = false; 77 | 78 | 79 | //for devlopment and demo purposes, it's useful to have a 80 | //simulation that can run on the osPID. the problem is 81 | //that is uses memory. rather than have it hogging resources 82 | //when not in use, it's activated using a compile flag. 83 | // this way, it doesn't get compiled during normal circumstances 84 | 85 | #ifdef USE_SIMULATION 86 | double kpmodel = 5, taup = 50, theta[30]; 87 | const double outputStart = 50; 88 | const double inputStart=250; 89 | 90 | void DoModel() 91 | { 92 | // Cycle the dead time 93 | for(byte i=0;i<30;i++) 94 | { 95 | theta[i] = theta[i+1]; 96 | } 97 | // Compute the input 98 | input = (kpmodel / taup) *(theta[0]-outputStart) + (input-inputStart)*(1-1/taup)+inputStart + ((float)random(-10,10))/100; 99 | } 100 | #else 101 | 102 | #endif /*USE_SIMULATION*/ 103 | 104 | 105 | 106 | void setup() 107 | { 108 | Serial.begin(9600); 109 | lcdTime=10; 110 | buttonTime=1; 111 | ioTime=5; 112 | serialTime=6; 113 | //windowStartTime=2; 114 | lcd.begin(8, 2); 115 | 116 | lcd.setCursor(0,0); 117 | lcd.print(F(" osPID ")); 118 | lcd.setCursor(0,1); 119 | lcd.print(F(" v1.70 ")); 120 | delay(1000); 121 | 122 | initializeEEPROM(); 123 | 124 | 125 | 126 | #ifdef USE_SIMULATION 127 | input = inputStart; 128 | for(int i=0;i<30;i++)theta[i] = outputStart; 129 | #else 130 | InitializeInputCard(); 131 | InitializeOutputCard(); 132 | #endif 133 | myPID.SetSampleTime(1000); 134 | myPID.SetOutputLimits(0, 100); 135 | myPID.SetTunings(kp, ki, kd); 136 | myPID.SetControllerDirection(ctrlDirection); 137 | myPID.SetMode(modeIndex); 138 | } 139 | 140 | byte editDepth=0; 141 | void loop() 142 | { 143 | now = millis(); 144 | 145 | if(now >= buttonTime) 146 | { 147 | switch(button.get()) 148 | { 149 | case BUTTON_NONE: 150 | break; 151 | 152 | case BUTTON_RETURN: 153 | back(); 154 | break; 155 | 156 | case BUTTON_UP: 157 | updown(true); 158 | break; 159 | 160 | case BUTTON_DOWN: 161 | updown(false); 162 | break; 163 | 164 | case BUTTON_OK: 165 | ok(); 166 | break; 167 | } 168 | buttonTime += 50; 169 | } 170 | 171 | bool doIO = now >= ioTime; 172 | //read in the input 173 | if(doIO) 174 | { 175 | ioTime+=250; 176 | #ifdef USE_SIMULATION 177 | DoModel(); 178 | pidInput = input; 179 | #else 180 | input = ReadInputFromCard(); 181 | inputOk = !isnan(input); 182 | if(inputOk)pidInput = input; 183 | 184 | #endif /*USE_SIMULATION*/ 185 | } 186 | 187 | 188 | if(tuning) 189 | { 190 | byte val = (aTune.Runtime()); 191 | 192 | if(val != 0) 193 | { 194 | tuning = false; 195 | } 196 | 197 | if(!tuning) 198 | { 199 | // We're done, set the tuning parameters 200 | kp = aTune.GetKp(); 201 | ki = aTune.GetKi(); 202 | kd = aTune.GetKd(); 203 | myPID.SetTunings(kp, ki, kd); 204 | AutoTuneHelper(false); 205 | EEPROMBackupTunings(); 206 | } 207 | } 208 | else 209 | { 210 | if(runningProfile) ProfileRunTime(); 211 | //allow the pid to compute if necessary 212 | if(inputOk) myPID.Compute(); 213 | } 214 | 215 | 216 | 217 | 218 | 219 | if(doIO) 220 | { 221 | //send the output 222 | #ifdef USE_SIMULATION 223 | theta[29] = output; 224 | #else 225 | if(!inputOk) output = 0; // Ensure output is zero when input is invalid 226 | // Send to output card 227 | WriteToOutputCard(output); 228 | #endif /*USE_SIMULATION*/ 229 | 230 | 231 | 232 | } 233 | 234 | if(now>lcdTime) 235 | { 236 | drawLCD(); 237 | lcdTime+=250; 238 | } 239 | if(millis() > serialTime) 240 | { 241 | //if(receivingProfile && (now-profReceiveStart)>profReceiveTimeout) receivingProfile = false; 242 | SerialReceive(); 243 | SerialSend(); 244 | serialTime += 500; 245 | } 246 | } 247 | 248 | 249 | void drawLCD() 250 | { 251 | boolean highlightFirst= (mDrawIndex==mIndex); 252 | drawItem(0,highlightFirst, mMenu[curMenu][mDrawIndex]); 253 | drawItem(1,!highlightFirst, mMenu[curMenu][mDrawIndex+1]); 254 | if(editing) lcd.setCursor(editDepth, highlightFirst?0:1); 255 | } 256 | 257 | void drawItem(byte row, boolean highlight, byte index) 258 | { 259 | char buffer[7]; 260 | lcd.setCursor(0,row); 261 | double val=0; 262 | int dec=0; 263 | int num=0; 264 | char icon=' '; 265 | boolean isNeg = false; 266 | boolean didneg = false; 267 | byte decSpot = 0; 268 | boolean edit = editing && highlightedIndex==index; 269 | boolean canEdit=!tuning; 270 | switch(getMenuType(index)) 271 | { 272 | case TYPE_NAV: 273 | lcd.print(highlight? '>':' '); 274 | switch(index) 275 | { 276 | case 0: 277 | lcd.print(F("DashBrd")); 278 | break; 279 | case 1: 280 | lcd.print(F("Config ")); 281 | break; 282 | case 2: 283 | lcd.print(tuning ? F("Cancel ") : F("ATune ")); 284 | break; 285 | case 3: 286 | if(runningProfile)lcd.print(F("Cancel ")); 287 | else lcd.print(profname); 288 | break; 289 | default: 290 | return; 291 | } 292 | 293 | break; 294 | case TYPE_VAL: 295 | 296 | switch(index) 297 | { 298 | case 4: 299 | val = setpoint; 300 | dec=1; 301 | icon='S'; 302 | break; 303 | case 5: 304 | val = input; 305 | dec=1; 306 | icon='I'; 307 | canEdit=false; 308 | break; 309 | case 6: 310 | val = output; 311 | dec=1; 312 | icon='O'; 313 | canEdit = (modeIndex==0); 314 | break; 315 | case 8: 316 | val = kp; 317 | dec=2; 318 | icon='P'; 319 | break; 320 | case 9: 321 | val = ki; 322 | dec=2; 323 | icon='I'; 324 | break ; 325 | case 10: 326 | val = kd; 327 | dec=2; 328 | icon='D'; 329 | break ; 330 | 331 | default: 332 | return; 333 | } 334 | lcd.print(edit? '[' : (highlight ? (canEdit ? '>':'|') : 335 | ' ')); 336 | 337 | if(isnan(val)) 338 | { //display an error 339 | lcd.print(icon); 340 | lcd.print( now % 2000<1000 ? F(" Error"):F(" ")); 341 | return; 342 | } 343 | 344 | for(int i=0;i=1;i--) 354 | { 355 | if(i==decSpot)buffer[i] = '.'; 356 | else { 357 | if(num==0) 358 | { 359 | if(i>=decSpot-1) buffer[i]='0'; 360 | else if (isNeg && !didneg) 361 | { 362 | buffer[i]='-'; 363 | didneg=true; 364 | } 365 | else buffer[i]=' '; 366 | } 367 | else { 368 | buffer[i] = num%10+48; 369 | num/=10; 370 | } 371 | } 372 | } 373 | lcd.print(buffer); 374 | break; 375 | case TYPE_OPT: 376 | 377 | lcd.print(edit ? '[': (highlight? '>':' ')); 378 | switch(index) 379 | { 380 | case 7: 381 | lcd.print(modeIndex==0 ? F("M Man "):F("M Auto ")); 382 | break; 383 | case 11://12: 384 | 385 | lcd.print(ctrlDirection==0 ? F("A Direc"):F("A Rever")); 386 | break; 387 | } 388 | 389 | break; 390 | default: 391 | return; 392 | } 393 | 394 | //indication of altered state 395 | if(highlight && (tuning || runningProfile)) 396 | { 397 | //should we blip? 398 | if(tuning) 399 | { 400 | if(now % 1500 <500) 401 | { 402 | lcd.setCursor(0,row); 403 | lcd.print('T'); 404 | } 405 | } 406 | else //running profile 407 | { 408 | if(now % 2000 < 500) 409 | { 410 | lcd.setCursor(0,row); 411 | lcd.print('P'); 412 | } 413 | else if(now%2000 < 1000) 414 | { 415 | lcd.setCursor(0,row); 416 | char c; 417 | if(curProfStep<10) c = curProfStep + 48; //0-9 418 | else c = curProfStep + 65; //A,B... 419 | lcd.print(c); 420 | } 421 | } 422 | } 423 | } 424 | 425 | byte getValDec(byte index) 426 | { 427 | switch(index) 428 | { 429 | case 4: 430 | case 5: 431 | case 6: 432 | //case 11: 433 | return 1; 434 | case 8: 435 | case 9: 436 | case 10: 437 | default: 438 | return 2; 439 | } 440 | } 441 | byte getMenuType(byte index) 442 | { 443 | switch(index) 444 | { 445 | case 0: 446 | case 1: 447 | case 2: 448 | case 3: 449 | return TYPE_NAV; 450 | case 4: 451 | case 5: 452 | case 6: 453 | case 8: 454 | case 9: 455 | case 10: 456 | //case 11: 457 | return TYPE_VAL; 458 | case 7: 459 | case 11: //12: 460 | return TYPE_OPT; 461 | default: 462 | return 255; 463 | } 464 | } 465 | 466 | boolean changeflag=false; 467 | 468 | void back() 469 | { 470 | if(editing) 471 | { //decrease the depth and stop editing if required 472 | 473 | editDepth--; 474 | if(getMenuType(highlightedIndex)==TYPE_VAL) 475 | { 476 | if(editDepth==7-getValDec(highlightedIndex))editDepth--; //skip the decimal 477 | } 478 | if(editDepth<3) 479 | { 480 | editDepth=0; 481 | editing= false; 482 | lcd.noCursor(); 483 | } 484 | } 485 | else 486 | { //if not editing return to previous menu. currently this is always main 487 | 488 | 489 | //depending on which menu we're coming back from, we may need to write to the eeprom 490 | if(changeflag) 491 | { 492 | if(curMenu==1) 493 | { 494 | EEPROMBackupDash(); 495 | } 496 | else if(curMenu==2) //tunings may have changed 497 | { 498 | EEPROMBackupTunings(); 499 | myPID.SetTunings(kp,ki,kd); 500 | myPID.SetControllerDirection(ctrlDirection); 501 | } 502 | changeflag=false; 503 | } 504 | if(curMenu!=0) 505 | { 506 | highlightedIndex = curMenu-1; //make sure the arrow is on the menu they were in 507 | mIndex=curMenu-1; 508 | curMenu=0; 509 | mDrawIndex=0; 510 | 511 | } 512 | } 513 | } 514 | 515 | 516 | 517 | double getValMin(byte index) 518 | { 519 | switch(index) 520 | { 521 | case 4: 522 | case 5: 523 | case 6: 524 | // case 11: 525 | return -999.9; 526 | case 8: 527 | case 9: 528 | case 10: 529 | default: 530 | return 0; 531 | } 532 | } 533 | 534 | 535 | double getValMax(byte index) 536 | { 537 | switch(index) 538 | { 539 | case 4: 540 | case 5: 541 | case 6: 542 | //case 11: 543 | return 999.9; 544 | case 8: 545 | case 9: 546 | case 10: 547 | default: 548 | return 99.99; 549 | } 550 | 551 | } 552 | 553 | void updown(bool up) 554 | { 555 | 556 | if(editing) 557 | { 558 | changeflag = true; 559 | byte decdepth; 560 | double adder; 561 | switch(getMenuType(highlightedIndex)) 562 | { 563 | case TYPE_VAL: 564 | decdepth = 7 - getValDec(highlightedIndex); 565 | adder=1; 566 | if(editDepthdecdepth)for(int i=decdepth;imaximum)(*val)=maximum; 595 | else if((*val)0) 620 | { 621 | mIndex--; 622 | mDrawIndex=mIndex; 623 | } 624 | } 625 | else 626 | { 627 | byte limit = 3;// (curMenu==2 ? 4 : 3); 628 | if(mIndexhelperTime) 777 | { 778 | setpoint = curVal; 779 | gotonext=true; 780 | } 781 | else 782 | { 783 | setpoint = (curVal-helperVal)*(1-(float)(helperTime-now)/(float)(curTime))+helperVal; 784 | } 785 | } 786 | else if (curType==2) //wait 787 | { 788 | float err = input-setpoint; 789 | if(helperflag) //we're just looking for a cross 790 | { 791 | 792 | if(err==0 || (err>0 && helperVal<0) || (err<0 && helperVal>0)) gotonext=true; 793 | else helperVal = err; 794 | } 795 | else //value needs to be within the band for the perscribed time 796 | { 797 | if (abs(err)>curVal) helperTime=now; //reset the clock 798 | else if( (now-helperTime)>=curTime) gotonext=true; //we held for long enough 799 | } 800 | 801 | } 802 | else if(curType==3) //step 803 | { 804 | 805 | if((now-helperTime)>curTime)gotonext=true; 806 | } 807 | else if(curType==127) //buzz 808 | { 809 | if(now=nProfSteps) 836 | { 837 | curType=0; 838 | helperTime =0; 839 | } 840 | else 841 | { 842 | curType = proftypes[curProfStep]; 843 | curVal = profvals[curProfStep]; 844 | curTime = proftimes[curProfStep]; 845 | 846 | } 847 | if(curType==1) //ramp 848 | { 849 | helperTime = curTime + now; //at what time the ramp will end 850 | helperVal = setpoint; 851 | } 852 | else if(curType==2) //wait 853 | { 854 | helperflag = (curVal==0); 855 | if(helperflag) helperVal= input-setpoint; 856 | else helperTime=now; 857 | } 858 | else if(curType==3) //step 859 | { 860 | setpoint = curVal; 861 | helperTime = now; 862 | } 863 | else if(curType==127) //buzzer 864 | { 865 | helperTime = now + curTime; 866 | } 867 | else 868 | { 869 | curType=0; 870 | } 871 | 872 | 873 | 874 | if(curType==0) //end 875 | { //we're done 876 | runningProfile=false; 877 | curProfStep=0; 878 | Serial.println("P_DN"); 879 | digitalWrite(buzzerPin,LOW); 880 | } 881 | else 882 | { 883 | Serial.print("P_STP "); 884 | Serial.print(int(curProfStep)); 885 | Serial.print(" "); 886 | Serial.print(int(curType)); 887 | Serial.print(" "); 888 | Serial.print((curVal)); 889 | Serial.print(" "); 890 | Serial.println((curTime)); 891 | } 892 | 893 | } 894 | 895 | 896 | 897 | 898 | 899 | const int eepromTuningOffset = 1; //13 bytes 900 | const int eepromDashOffset = 14; //9 bytes 901 | const int eepromATuneOffset = 23; //12 bytes 902 | const int eepromProfileOffset = 35; //136 bytes 903 | const int eepromInputOffset = 172; //? bytes (depends on the card) 904 | const int eepromOutputOffset = 300; //? bytes (depends on the card) 905 | 906 | 907 | void initializeEEPROM() 908 | { 909 | //read in eeprom values 910 | byte firstTime = EEPROM.read(0); 911 | if(firstTime!=EEPROM_ID) 912 | {//the only time this won't be 1 is the first time the program is run after a reset or firmware update 913 | //clear the EEPROM and initialize with default values 914 | for(int i=1;i<1024;i++) EEPROM.write(i,0); 915 | EEPROMBackupTunings(); 916 | EEPROMBackupDash(); 917 | EEPROMBackupATune(); 918 | EEPROMBackupInputParams(eepromInputOffset); 919 | EEPROMBackupOutputParams(eepromOutputOffset); 920 | EEPROMBackupProfile(); 921 | EEPROM.write(0,EEPROM_ID); //so that first time will never be true again (future firmware updates notwithstanding) 922 | } 923 | else 924 | { 925 | EEPROMRestoreTunings(); 926 | EEPROMRestoreDash(); 927 | EEPROMRestoreATune(); 928 | EEPROMRestoreInputParams(eepromInputOffset); 929 | EEPROMRestoreOutputParams(eepromOutputOffset); 930 | EEPROMRestoreProfile(); 931 | } 932 | } 933 | 934 | 935 | 936 | void EEPROMreset() 937 | { 938 | EEPROM.write(0,0); 939 | } 940 | 941 | 942 | void EEPROMBackupTunings() 943 | { 944 | EEPROM.write(eepromTuningOffset,ctrlDirection); 945 | EEPROM_writeAnything(eepromTuningOffset+1,kp); 946 | EEPROM_writeAnything(eepromTuningOffset+5,ki); 947 | EEPROM_writeAnything(eepromTuningOffset+9,kd); 948 | } 949 | 950 | void EEPROMRestoreTunings() 951 | { 952 | ctrlDirection = EEPROM.read(eepromTuningOffset); 953 | EEPROM_readAnything(eepromTuningOffset+1,kp); 954 | EEPROM_readAnything(eepromTuningOffset+5,ki); 955 | EEPROM_readAnything(eepromTuningOffset+9,kd); 956 | } 957 | 958 | void EEPROMBackupDash() 959 | { 960 | EEPROM.write(eepromDashOffset, (byte)myPID.GetMode()); 961 | EEPROM_writeAnything(eepromDashOffset+1,setpoint); 962 | EEPROM_writeAnything(eepromDashOffset+5,output); 963 | } 964 | 965 | void EEPROMRestoreDash() 966 | { 967 | modeIndex = EEPROM.read(eepromDashOffset); 968 | EEPROM_readAnything(eepromDashOffset+1,setpoint); 969 | EEPROM_readAnything(eepromDashOffset+5,output); 970 | } 971 | 972 | void EEPROMBackupATune() 973 | { 974 | EEPROM_writeAnything(eepromATuneOffset,aTuneStep); 975 | EEPROM_writeAnything(eepromATuneOffset+4,aTuneNoise); 976 | EEPROM_writeAnything(eepromATuneOffset+8,aTuneLookBack); 977 | } 978 | 979 | void EEPROMRestoreATune() 980 | { 981 | EEPROM_readAnything(eepromATuneOffset,aTuneStep); 982 | EEPROM_readAnything(eepromATuneOffset+4,aTuneNoise); 983 | EEPROM_readAnything(eepromATuneOffset+8,aTuneLookBack); 984 | } 985 | 986 | void EEPROMBackupProfile() 987 | { 988 | EEPROM_writeAnything(eepromProfileOffset, profname); 989 | EEPROM_writeAnything(eepromProfileOffset + 8, proftypes); 990 | EEPROM_writeAnything(eepromProfileOffset + 24, profvals); 991 | EEPROM_writeAnything(eepromProfileOffset + 85, proftimes); //there might be a slight issue here (/1000?) 992 | } 993 | 994 | void EEPROMRestoreProfile() 995 | { 996 | EEPROM_readAnything(eepromProfileOffset, profname); 997 | EEPROM_readAnything(eepromProfileOffset + 8, proftypes); 998 | EEPROM_readAnything(eepromProfileOffset + 24, profvals); 999 | EEPROM_readAnything(eepromProfileOffset + 85, proftimes); //there might be a slight issue here (/1000?) 1000 | } 1001 | 1002 | /******************************************** 1003 | * Serial Communication functions / helpers 1004 | ********************************************/ 1005 | 1006 | boolean ackDash = false, ackTune = false; 1007 | union { // This Data structure lets 1008 | byte asBytes[32]; // us take the byte array 1009 | float asFloat[8]; // sent from processing and 1010 | } // easily convert it to a 1011 | foo; // float array 1012 | 1013 | // getting float values from processing into the arduino 1014 | // was no small task. the way this program does it is 1015 | // as follows: 1016 | // * a float takes up 4 bytes. in processing, convert 1017 | // the array of floats we want to send, into an array 1018 | // of bytes. 1019 | // * send the bytes to the arduino 1020 | // * use a data structure known as a union to convert 1021 | // the array of bytes back into an array of floats 1022 | void SerialReceive() 1023 | { 1024 | 1025 | // read the bytes sent from Processing 1026 | byte index=0; 1027 | byte identifier=0; 1028 | byte b1=255,b2=255; 1029 | boolean boolhelp=false; 1030 | 1031 | while(Serial.available()) 1032 | { 1033 | byte val = Serial.read(); 1034 | if(index==0){ 1035 | identifier = val; 1036 | Serial.println(int(val)); 1037 | } 1038 | else 1039 | { 1040 | switch(identifier) 1041 | { 1042 | case 0: //information request 1043 | if(index==1) b1=val; //which info type 1044 | else if(index==2)boolhelp = (val==1); //on or off 1045 | break; 1046 | case 1: //dasboard 1047 | case 2: //tunings 1048 | case 3: //autotune 1049 | if(index==1) b1 = val; 1050 | else if(index<14)foo.asBytes[index-2] = val; 1051 | break; 1052 | case 4: //EEPROM reset 1053 | if(index==1) b1 = val; 1054 | break; 1055 | case 5: //input configuration 1056 | if (index==1)InputSerialReceiveStart(); 1057 | InputSerialReceiveDuring(val, index); 1058 | break; 1059 | case 6: //output configuration 1060 | if (index==1)OutputSerialReceiveStart(); 1061 | OutputSerialReceiveDuring(val, index); 1062 | break; 1063 | case 7: //receiving profile 1064 | if(index==1) b1=val; 1065 | else if(b1>=nProfSteps) profname[index-2] = char(val); 1066 | else if(index==2) proftypes[b1] = val; 1067 | else foo.asBytes[index-3] = val; 1068 | 1069 | break; 1070 | case 8: //profile command 1071 | if(index==1) b2=val; 1072 | break; 1073 | default: 1074 | break; 1075 | } 1076 | } 1077 | index++; 1078 | } 1079 | 1080 | //we've received the information, time to act 1081 | switch(identifier) 1082 | { 1083 | case 0: //information request 1084 | switch(b1) 1085 | { 1086 | case 0: 1087 | sendInfo = true; 1088 | sendInputConfig=true; 1089 | sendOutputConfig=true; 1090 | break; 1091 | case 1: 1092 | sendDash = boolhelp; 1093 | break; 1094 | case 2: 1095 | sendTune = boolhelp; 1096 | break; 1097 | case 3: 1098 | sendInputConfig = boolhelp; 1099 | break; 1100 | case 4: 1101 | sendOutputConfig = boolhelp; 1102 | break; 1103 | default: 1104 | break; 1105 | } 1106 | break; 1107 | case 1: //dashboard 1108 | if(index==14 && b1<2) 1109 | { 1110 | setpoint=double(foo.asFloat[0]); 1111 | //Input=double(foo.asFloat[1]); // * the user has the ability to send the 1112 | // value of "Input" in most cases (as 1113 | // in this one) this is not needed. 1114 | if(b1==0) // * only change the output if we are in 1115 | { // manual mode. otherwise we'll get an 1116 | output=double(foo.asFloat[2]); // output blip, then the controller will 1117 | } // overwrite. 1118 | 1119 | if(b1==0) myPID.SetMode(MANUAL);// * set the controller mode 1120 | else myPID.SetMode(AUTOMATIC); // 1121 | EEPROMBackupDash(); 1122 | ackDash=true; 1123 | } 1124 | break; 1125 | case 2: //Tune 1126 | if(index==14 && (b1<=1)) 1127 | { 1128 | // * read in and set the controller tunings 1129 | kp = double(foo.asFloat[0]); // 1130 | ki = double(foo.asFloat[1]); // 1131 | kd = double(foo.asFloat[2]); // 1132 | ctrlDirection = b1; 1133 | myPID.SetTunings(kp, ki, kd); // 1134 | if(b1==0) myPID.SetControllerDirection(DIRECT);// * set the controller Direction 1135 | else myPID.SetControllerDirection(REVERSE); // 1136 | EEPROMBackupTunings(); 1137 | ackTune = true; 1138 | } 1139 | break; 1140 | case 3: //ATune 1141 | if(index==14 && (b1<=1)) 1142 | { 1143 | 1144 | aTuneStep = foo.asFloat[0]; 1145 | aTuneNoise = foo.asFloat[1]; 1146 | aTuneLookBack = (unsigned int)foo.asFloat[2]; 1147 | if((!tuning && b1==1)||(tuning && b1==0)) 1148 | { //toggle autotune state 1149 | changeAutoTune(); 1150 | } 1151 | EEPROMBackupATune(); 1152 | ackTune = true; 1153 | } 1154 | break; 1155 | case 4: //EEPROM reset 1156 | if(index==2 && b1<2) EEPROM.write(0,0); //eeprom will re-write on next restart 1157 | break; 1158 | case 5: //input configuration 1159 | InputSerialReceiveAfter(eepromInputOffset); 1160 | sendInputConfig=true; 1161 | break; 1162 | case 6: //ouput configuration 1163 | OutputSerialReceiveAfter(eepromOutputOffset); 1164 | sendOutputConfig=true; 1165 | break; 1166 | case 7: //receiving profile 1167 | 1168 | if((index==11 || (b1>=nProfSteps && index==9) )) 1169 | { 1170 | if(!receivingProfile && b1!=0) 1171 | { //there was a timeout issue. reset this transfer 1172 | receivingProfile=false; 1173 | Serial.println("ProfError"); 1174 | EEPROMRestoreProfile(); 1175 | } 1176 | else if(receivingProfile || b1==0) 1177 | { 1178 | if(runningProfile) 1179 | { //stop the current profile execution 1180 | StopProfile(); 1181 | } 1182 | 1183 | if(b1==0) 1184 | { 1185 | receivingProfile = true; 1186 | profReceiveStart = millis(); 1187 | } 1188 | 1189 | if(b1>=nProfSteps) 1190 | { //getting the name is the last step 1191 | receivingProfile=false; //last profile step 1192 | Serial.print("ProfDone "); 1193 | Serial.println(profname); 1194 | EEPROMBackupProfile(); 1195 | Serial.println("Archived"); 1196 | } 1197 | else 1198 | { 1199 | profvals[b1] = foo.asFloat[0]; 1200 | proftimes[b1] = (unsigned long)(foo.asFloat[1] * 1000); 1201 | Serial.print("ProfAck "); 1202 | Serial.print(b1); 1203 | Serial.print(" "); 1204 | Serial.print(proftypes[b1]); 1205 | Serial.print(" "); 1206 | Serial.print(profvals[b1]); 1207 | Serial.print(" "); 1208 | Serial.println(proftimes[b1]); 1209 | } 1210 | } 1211 | } 1212 | break; 1213 | case 8: 1214 | if(index==2 && b2<2) 1215 | { 1216 | if(b2==1) StartProfile(); 1217 | else StopProfile(); 1218 | 1219 | } 1220 | break; 1221 | default: 1222 | break; 1223 | } 1224 | } 1225 | 1226 | 1227 | // unlike our tiny microprocessor, the processing ap 1228 | // has no problem converting strings into floats, so 1229 | // we can just send strings. much easier than getting 1230 | // floats from processing to here no? 1231 | void SerialSend() 1232 | { 1233 | if(sendInfo) 1234 | {//just send out the stock identifier 1235 | Serial.print("\nosPID v1.70"); 1236 | InputSerialID(); 1237 | OutputSerialID(); 1238 | Serial.println(""); 1239 | sendInfo = false; //only need to send this info once per request 1240 | } 1241 | if(sendDash) 1242 | { 1243 | Serial.print("DASH "); 1244 | Serial.print(setpoint); 1245 | Serial.print(" "); 1246 | if(isnan(input)) Serial.print("Error"); 1247 | else Serial.print(input); 1248 | Serial.print(" "); 1249 | Serial.print(output); 1250 | Serial.print(" "); 1251 | Serial.print(myPID.GetMode()); 1252 | Serial.print(" "); 1253 | Serial.println(ackDash?1:0); 1254 | if(ackDash)ackDash=false; 1255 | } 1256 | if(sendTune) 1257 | { 1258 | Serial.print("TUNE "); 1259 | Serial.print(myPID.GetKp()); 1260 | Serial.print(" "); 1261 | Serial.print(myPID.GetKi()); 1262 | Serial.print(" "); 1263 | Serial.print(myPID.GetKd()); 1264 | Serial.print(" "); 1265 | Serial.print(myPID.GetDirection()); 1266 | Serial.print(" "); 1267 | Serial.print(tuning?1:0); 1268 | Serial.print(" "); 1269 | Serial.print(aTuneStep); 1270 | Serial.print(" "); 1271 | Serial.print(aTuneNoise); 1272 | Serial.print(" "); 1273 | Serial.print(aTuneLookBack); 1274 | Serial.print(" "); 1275 | Serial.println(ackTune?1:0); 1276 | if(ackTune)ackTune=false; 1277 | } 1278 | if(sendInputConfig) 1279 | { 1280 | Serial.print("IPT "); 1281 | InputSerialSend(); 1282 | sendInputConfig=false; 1283 | } 1284 | if(sendOutputConfig) 1285 | { 1286 | Serial.print("OPT "); 1287 | OutputSerialSend(); 1288 | sendOutputConfig=false; 1289 | } 1290 | if(runningProfile) 1291 | { 1292 | Serial.print("PROF "); 1293 | Serial.print(int(curProfStep)); 1294 | Serial.print(" "); 1295 | Serial.print(int(curType)); 1296 | Serial.print(" "); 1297 | switch(curType) 1298 | { 1299 | case 1: //ramp 1300 | Serial.println((helperTime-now)); //time remaining 1301 | 1302 | break; 1303 | case 2: //wait 1304 | Serial.print(abs(input-setpoint)); 1305 | Serial.print(" "); 1306 | Serial.println(curVal==0? -1 : float(now-helperTime)); 1307 | break; 1308 | case 3: //step 1309 | Serial.println(curTime-(now-helperTime)); 1310 | break; 1311 | default: 1312 | break; 1313 | 1314 | } 1315 | 1316 | } 1317 | 1318 | } 1319 | 1320 | 1321 | 1322 | 1323 | 1324 | 1325 | 1326 | 1327 | 1328 | --------------------------------------------------------------------------------