You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

359 lines
11 KiB
C++

/*
TCLab Temperature Control Lab Firmware
Jeffrey Kantor, Bill Tubbs, John Hedengren, Shawn Summey
February 2021
This firmware provides a high level interface to the Temperature Control Lab. The
firmware scans the serial port for commands. Commands are case-insensitive. Any
unrecognized command results in sleep model. Each command returns a result string.
A software restart. Returns "Start".
LED float set LED to float for 10 sec. range 0 to 100. Returns actual float
P1 float set pwm limit on heater 1, range 0 to 255. Default 200. Returns P1.
P2 float set pwm limit on heater 2, range 0 to 255. Default 100. Returns P2.
Q1 float set Heater 1, range 0 to 100. Returns value of Q1.
Q2 float set Heater 2, range 0 to 100. Returns value of Q2.
Q1B float set Heater 1, range 0 to 100. Returns value of Q1 as a 32-bit float.
Q2B float set Heater 2, range 0 to 100. Returns value of Q2 as a 32-bit float.
R1 get value of Heater 1, range 0 to 100
R2 get value of Heater 2, range 0 to 100
SCAN get values T1 T2 Q1 Q1 in line delimited values
T1 get Temperature T1. Returns value of T1 in °C.
T2 get Temperature T2. Returns value of T2 in °C.
T1B get Temperature T1. Returns value of T1 in °C as a 32-bit float.
T2B get Temperature T2. Returns value of T2 in °C as a 32-bit float.
VER get firmware version string
X stop, enter sleep mode. Returns "Stop"
Limits on the heater power can be configured with the constants below.
Status is indicated by LED1 on the Temperature Control Lab. Status conditions are:
LED1 LED1
Brightness State
---------- -----
dim steady Normal operation, heaters off
bright steady Normal operation, heaters on
dim blinking High temperature alarm on, heaters off
bright blinking High temperature alarm on, heaters on
The Temperature Control Lab shuts down the heaters if it receives no host commands
during a timeout period (configure below), receives an "X" command, or receives
an unrecognized command from the host.
Constants are used to configure the firmware.
Changelog ordered by Semantic Version
1.0.1 first version included in the tclab package
1.1.0 added R1 and R2 commands to read current heater values
changed heater values to units of percent of full power
added P1 and P2 commands to set heater power limits
changed readCommand to avoid busy states
changed simplified LED status model
1.2.0 added LED command
1.2.1 fixed reset heater values on close
added version history
1.2.2 changed version string for better display by TCLab
1.2.3 changed baudrate to from 9600 to 115200
1.3.0 added SCAN function
added board type in version string
1.4.0 changed Q1 and Q2 to float from int
1.4.1 fixed missing Serial.flush() at end of command loop
1.4.2 fixed bug with X command
1.4.3 deprecated use of Arduino IDE Version < 1.0.0
1.5.0 removed webusb
1.6.0 changed temperature to average 10 measurements to reduce noise
2.0.0 added binary communications.
added T1B and T2B commands return 32-bit float
added Q1B and Q2B commands return 32-bit float confirmation of heater setting
added calculation to use 1.75 AREF to match TMP36 voltage range
2.0.1 added updates to Notre Dame and BYU versions of this firmware
changed version history to standard change log practices
*/
#include "Arduino.h"
// determine board type
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
String boardType = "Arduino Uno";
#elif defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__)
String boardType = "Arduino Leonardo/Micro";
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
String boardType = "Arduino Mega";
#else
String boardType = "Unknown board";
#endif
// Enable debugging output
const bool DEBUG = false;
// constants
const String vers = "2.0.1"; // version of this firmware
const long baud = 115200; // serial baud rate
const char sp = ' '; // command separator
const char nl = '\n'; // command terminator
// pin numbers corresponding to signals on the TC Lab Shield
const int pinT1 = 0; // T1
const int pinT2 = 2; // T2
const int pinQ1 = 3; // Q1
const int pinQ2 = 5; // Q2
const int pinLED1 = 9; // LED1
// temperature alarm limits
const int limT1 = 50; // T1 high alarm (°C)
const int limT2 = 50; // T2 high alarm (°C)
// LED1 levels
const int hiLED = 60; // hi LED
const int loLED = hiLED/16; // lo LED
// global variables
char Buffer[64]; // buffer for parsing serial input
int buffer_index = 0; // index for Buffer
String cmd; // command
float val; // command value
int ledStatus; // 1: loLED
// 2: hiLED
// 3: loLED blink
// 4: hiLED blink
long ledTimeout = 0; // when to return LED to normal operation
float LED = 100; // LED override brightness
float P1 = 200; // heater 1 power limit in units of pwm. Range 0 to 255
float P2 = 100; // heater 2 power limit in units in pwm, range 0 to 255
float Q1 = 0; // last value written to heater 1 in units of percent
float Q2 = 0; // last value written to heater 2 in units of percent
int alarmStatus; // hi temperature alarm status
boolean newData = false; // boolean flag indicating new command
int n = 10; // number of samples for each temperature measurement
void readCommand() {
while (Serial && (Serial.available() > 0) && (newData == false)) {
int byte = Serial.read();
if ((byte != '\r') && (byte != nl) && (buffer_index < 64)) {
Buffer[buffer_index] = byte;
buffer_index++;
}
else {
newData = true;
}
}
}
// for debugging with the serial monitor in Arduino IDE
void echoCommand() {
if (newData) {
Serial.write("Received Command: ");
Serial.write(Buffer, buffer_index);
Serial.write(nl);
Serial.flush();
}
}
// return average of n reads of thermister temperature in °C
inline float readTemperature(int pin) {
float degC = 0.0;
for (int i = 0; i < n; i++) {
degC += analogRead(pin) * 0.322265625 - 50.0; // use for 3.3v AREF
//degC += analogRead(pin) * 0.170898438 - 50.0; // use for 1.75v AREF
}
return degC / float(n);
}
void parseCommand(void) {
if (newData) {
String read_ = String(Buffer);
// separate command from associated data
int idx = read_.indexOf(sp);
cmd = read_.substring(0, idx);
cmd.trim();
cmd.toUpperCase();
// extract data. toFloat() returns 0 on error
String data = read_.substring(idx + 1);
data.trim();
val = data.toFloat();
// reset parameter for next command
memset(Buffer, 0, sizeof(Buffer));
buffer_index = 0;
newData = false;
}
}
void sendResponse(String msg) {
Serial.println(msg);
}
void sendFloatResponse(float val) {
Serial.println(String(val, 3));
}
void sendBinaryResponse(float val) {
byte *b = (byte*)&val;
Serial.write(b, 4);
}
void dispatchCommand(void) {
if (cmd == "A") {
setHeater1(0);
setHeater2(0);
sendResponse("Start");
}
else if (cmd == "LED") {
ledTimeout = millis() + 10000;
LED = max(0, min(100, val));
sendResponse(String(LED));
}
else if (cmd == "P1") {
P1 = max(0, min(255, val));
sendResponse(String(P1));
}
else if (cmd == "P2") {
P2 = max(0, min(255, val));
sendResponse(String(P2));
}
else if (cmd == "Q1") {
setHeater1(val);
sendFloatResponse(Q1);
}
else if (cmd == "Q1B") {
setHeater1(val);
sendBinaryResponse(Q1);
}
else if (cmd == "Q2") {
setHeater2(val);
sendFloatResponse(Q2);
}
else if (cmd == "Q2B") {
setHeater1(val);
sendBinaryResponse(Q2);
}
else if (cmd == "R1") {
sendFloatResponse(Q1);
}
else if (cmd == "R2") {
sendFloatResponse(Q2);
}
else if (cmd == "SCAN") {
sendFloatResponse(readTemperature(pinT1));
sendFloatResponse(readTemperature(pinT2));
sendFloatResponse(Q1);
sendFloatResponse(Q2);
}
else if (cmd == "T1") {
sendFloatResponse(readTemperature(pinT1));
}
else if (cmd == "T1B") {
sendBinaryResponse(readTemperature(pinT1));
}
else if (cmd == "T2") {
sendFloatResponse(readTemperature(pinT2));
}
else if (cmd == "T2B") {
sendBinaryResponse(readTemperature(pinT2));
}
else if (cmd == "VER") {
sendResponse("TCLab Firmware " + vers + " " + boardType);
}
else if (cmd == "X") {
setHeater1(0);
setHeater2(0);
sendResponse("Stop");
}
else if (cmd.length() > 0) {
setHeater1(0);
setHeater2(0);
sendResponse(cmd);
}
Serial.flush();
cmd = "";
}
void checkAlarm(void) {
if ((readTemperature(pinT1) > limT1) or (readTemperature(pinT2) > limT2)) {
alarmStatus = 1;
}
else {
alarmStatus = 0;
}
}
void updateStatus(void) {
// determine led status
ledStatus = 1;
if ((Q1 > 0) or (Q2 > 0)) {
ledStatus = 2;
}
if (alarmStatus > 0) {
ledStatus += 2;
}
// update led depending on ledStatus
if (millis() < ledTimeout) { // override led operation
analogWrite(pinLED1, LED);
}
else {
switch (ledStatus) {
case 1: // normal operation, heaters off
analogWrite(pinLED1, loLED);
break;
case 2: // normal operation, heater on
analogWrite(pinLED1, hiLED);
break;
case 3: // high temperature alarm, heater off
if ((millis() % 2000) > 1000) {
analogWrite(pinLED1, loLED);
} else {
analogWrite(pinLED1, loLED/4);
}
break;
case 4: // high temperature alarm, heater on
if ((millis() % 2000) > 1000) {
analogWrite(pinLED1, hiLED);
} else {
analogWrite(pinLED1, loLED);
}
break;
}
}
}
// set Heater 1
void setHeater1(float qval) {
Q1 = max(0., min(qval, 100.));
analogWrite(pinQ1, (Q1*P1)/100);
}
// set Heater 2
void setHeater2(float qval) {
Q2 = max(0., min(qval, 100.));
analogWrite(pinQ2, (Q2*P2)/100);
}
// arduino startup
void setup() {
analogReference(EXTERNAL);
while (!Serial) {
; // wait for serial port to connect.
}
Serial.begin(baud);
Serial.flush();
setHeater1(0);
setHeater2(0);
ledTimeout = millis() + 1000;
}
// arduino main event loop
void loop() {
readCommand();
if (DEBUG) echoCommand();
parseCommand();
dispatchCommand();
checkAlarm();
updateStatus();
}