// *** TemperatureControl *** // This example expands the previous ArduinoController example. The PC will now send a start command to the controller, // the controller will now start measuring the temperature and controlling the heater. The controller will also start // sending temperature and heater steer data which the PC will plot in a chart. With a slider we can set the goal // temperature, which will make the PID library on the controller adjust the setting of the heater. // To use this example without having a thermocouple or heating element, it comes with a simulated boiler // In order to use the simulator, disable the #define REAL_HEATER //#define REAL_HEATER; #ifdef REAL_HEATER #include <Adafruit_MAX31855.h> #else #include <HeaterSim.h> #endif #include <CmdMessenger.h> #include <PID_v1.h> #include <DoEvery.h> // Attach a new CmdMessenger object to the default Serial port CmdMessenger cmdMessenger = CmdMessenger(Serial); const int heaterPwmInterval = 300; // PWM cycle duration const int measureInterval = 100; // Interval between measurements DoEvery tempTimer(measureInterval); DoEvery pidTimer(heaterPwmInterval); // PID settings double pidP = 1500; double pidI = 25; double pidD = 0; // Thermocouple pins int thermoDO = 3; int thermoCS = 4; int thermoCLK = 5; // Solid State switch pin const int switchPin = 4; bool acquireData = false; // Logging start/stop flag bool controlHeater = false; // Heater start/stop flag long startAcqMillis = 0; double CurrentTemperature = 20; // Measured temperature double goalTemperature = 20; // Goal temperature bool heaterOn = false; // Initial binary heater state double heaterSteerValue = 0; // Initial normalized heater value // Initialize thermocouple library #ifdef REAL_HEATER Adafruit_MAX31855 thermocouple(thermoCLK, thermoCS, thermoDO); #else HeaterSim heaterSim(20); // Heater is placed in ambient temperature of 20 degrees Celsius #endif // Initialize PID library PID pid(&CurrentTemperature, &heaterSteerValue, &goalTemperature,pidP,pidI,pidD,DIRECT); // This is the list of recognized commands. These can be commands that can either be sent or received. // In order to receive, attach a callback function to these events enum { // Commands kRequestId , // Command to request application ID kSendId , // Command to send application ID kAcknowledge , // Command to acknowledge a received command kError , // Command to message that an error has occurred kStartLogging , // Command to request logging start kStopLogging , // Command to request logging stop kPlotDataPoint , // Command to request data-point plotting kSetGoalTemperature , // Command to set the goal temperature KSetStartTime , // Command to set the new start time for the logger }; // Commands we send from the PC and want to receive on the Arduino. // We must define a callback function in our Arduino program for each entry in the list below. void attachCommandCallbacks() { // Attach callback methods cmdMessenger.attach(OnUnknownCommand); cmdMessenger.attach(kRequestId, OnRequestId); cmdMessenger.attach(kStartLogging, OnStartLogging); cmdMessenger.attach(kStopLogging, OnStopLogging); cmdMessenger.attach(kSetGoalTemperature, OnSetGoalTemperature); cmdMessenger.attach(KSetStartTime, OnSetStartTime); } // ------------------ C A L L B A C K S ----------------------- // Called when a received command has no attached function void OnUnknownCommand() { cmdMessenger.sendCmd(kError,"Command without attached callback"); } void OnRequestId() { cmdMessenger.sendCmd(kSendId,"TemperatureControl"); } // Callback function that responds that Arduino is ready (has booted up) void OnArduinoReady() { cmdMessenger.sendCmd(kAcknowledge,"Arduino ready"); } // Start data acquisition void OnStartLogging() { // Start data acquisition acquireData = true; cmdMessenger.sendCmd(kAcknowledge,"Start Logging"); } // Stop data acquisition void OnStopLogging() { acquireData = false; cmdMessenger.sendCmd(kAcknowledge,"Stop Logging"); } // Callback function that sets the goal temperature void OnSetGoalTemperature() { // Read led state argument, interpret string as float float newTemperature = cmdMessenger.readBinArg<float>(); // Make sure that the argument is valid before we change // the goal temperature if (cmdMessenger.isArgOk()) { goalTemperature = newTemperature; // Enable heater control (was disabled at initialization) controlHeater = true; // Send acknowledgment back to PC cmdMessenger.sendBinCmd(kAcknowledge,goalTemperature); } else { // Error in received goal temperature! Send error back to PC cmdMessenger.sendCmd(kError,"Error in received new goal temperature"); } } // Callback function that sets the start time void OnSetStartTime() { // Read led state argument, interpret string as float float startTime = cmdMessenger.readBinArg<float>(); // Make sure that the argument is valid before we change if (cmdMessenger.isArgOk()) { unsigned long milis = millis(); // translate time in seconds to time in milliseconds wrt to internal clock; startAcqMillis = (unsigned long)((float)startTime*1000.0f); if (startAcqMillis > milis) { startAcqMillis = milis; } startAcqMillis = milis- startAcqMillis; } } // ------------------ M A I N ---------------------- // Setup function void setup() { // Listen on serial connection for messages from the pc // 115200 is typically the maximum speed for serial over USB // Serial.begin(115200); // Many bluetooth breakout boards run on 9600 at default speed // The Serial setting below should match this Serial.begin(9600); // Do not print newLine at end of command, // in order to reduce data being sent cmdMessenger.printLfCr(false); //initialize timers tempTimer.reset(); pidTimer.reset(); //initialize the PID variables pid.SetOutputLimits(0,heaterPwmInterval); // Read the current temperature #ifdef REAL_HEATER CurrentTemperature= thermocouple.readCelsius(); #else CurrentTemperature = heaterSim.GetTemp(); #endif //prepare PID port for writing pinMode(switchPin, OUTPUT); //turn the PID on and set to automatic pid.SetMode(AUTOMATIC); // Set pid sample time to the measure interval pid.SetSampleTime(measureInterval); // Attach my application's user-defined callback methods attachCommandCallbacks(); // Send the status to the PC that says the Arduino has booted cmdMessenger.sendCmd(kAcknowledge,"Arduino has started!"); } // Loop function void loop() { // Process incoming serial data, and perform callbacks cmdMessenger.feedinSerialData(); // Every 100 ms, update the temperature if(tempTimer.check()) measure(); // Update the PID timer. pidTimer.check(); // Check if are controlling the heater if (controlHeater) { //compute new PID parameters pid.Compute(); //Control the heater using Pulse Width Modulation heaterPWM(); } } // Measure temperature in boiler void measure() { if (acquireData) { // Calculate time float seconds = (float) (millis()-startAcqMillis) /1000.0 ; // Measure temperature #ifdef REAL_HEATER CurrentTemperature= thermocouple.readCelsius(); // measure with thermocouple #else CurrentTemperature = heaterSim.GetTemp(); // measure temperature in simulated boiler #endif // Send data to PC cmdMessenger.sendCmdStart(kPlotDataPoint); cmdMessenger.sendCmdBinArg((float)seconds); // Time cmdMessenger.sendCmdBinArg((float)CurrentTemperature); // Measured temperature cmdMessenger.sendCmdBinArg((float)goalTemperature); // Goal temperature cmdMessenger.sendCmdBinArg((float)((double)heaterSteerValue/(double)heaterPwmInterval)); // normalized heater steer value cmdMessenger.sendCmdBinArg((bool)heaterOn); // On / off state during PWM cycle cmdMessenger.sendCmdEnd(); } } void SetHeaterState(bool heaterOn) { // Turn heater, connected to relay at pin switchPin digitalWrite(switchPin,heaterOn?HIGH:LOW); } // Set binary heater state void heaterPWM() { // Switch heater on or off, based on moment in the PWM cycle heaterOn = pidTimer.before(heaterSteerValue); #ifdef REAL_HEATER SetHeaterState(heaterOn); // Turn on heater of boiler #else heaterSim.SetHeaterState(heaterOn); // Turn on heater of simulated boiler #endif }