-
Notifications
You must be signed in to change notification settings - Fork 86
Expand file tree
/
Copy pathTemperatureControl.ino
More file actions
295 lines (232 loc) · 9.18 KB
/
Copy pathTemperatureControl.ino
File metadata and controls
295 lines (232 loc) · 9.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
// *** 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
// NOTE: If you used a package manager to download CmdMessenger library,
// make sure have also fetched these libraries:
//
// * PID
// * Adafruit_MAX31855 (not necessary in simulated mode)
//
// A package that includes all referenced libraries can be found at:
// https://github.com/thijse/Zipballs/blob/master/CmdMessenger/CmdMessenger.zip?raw=true
#include <CmdMessenger.h>
#include <utility/HeaterSim.h>
//#define REAL_HEATER;
#ifdef REAL_HEATER
#include <Adafruit_MAX31855.h>
#else
#include <utility/HeaterSim.h>
#endif
#include <PID_v1.h>
#include <utility/DoEvery.h>
// Attach a new CmdMessenger object to the default Serial port
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
kWatchdog , // Command to request 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(kWatchdog, OnWatchdogRequest);
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 OnWatchdogRequest()
{
// Will respond with same command ID and Unique device identifier.
cmdMessenger.sendCmd(kWatchdog, "77FAEDD5-FAC8-46BD-875E-5E9B6D44F85C");
}
// 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
}