Readme updated, sketch added and PDFs
parent
d600ad7808
commit
48224a88bc
@ -1,37 +1,110 @@
|
||||
# Introduction
|
||||
|
||||
The temperature control lab is an application of feedback control with an Arduino, an LED, two heaters, and two temperature sensors that is mainly based in the `APMonitor TCLab` project. The heater power output is adjusted to maintain a desired temperature setpoint. Thermal energy from the heater is transferred by conduction, convection, and radiation (phenomena) to the temperature sensor. Heat is also transferred away from the device to the surroundings by the heat-sink.
|
||||
The Temperature Control Laboratory (TCLab) is an application of feedback control with an Arduino, an LED, two heaters, and two temperature sensors that are mainly based on the `APMonitor TCLab` project by Jeffrey Kantor and Carl Sandrock.
|
||||
|
||||
This TCLab consists of three main parts: hardware, Arduino sketch, and the `TCLab` library.
|
||||
|
||||
**The following guide provides step-by-step instructions to design and build from scratch your own TCLab version that can communicate with Python and Jupyter Notebook.**
|
||||
|
||||
## TCLab hardware
|
||||
|
||||
The TCLab is a modular, portable, and inexpensive solution for hands-on process control learning. The heat output produced by the transistor is adjusted by modulating (PWM) the current flow to each of the two devices. Then, thermistors measure the temperatures by the Arduino's ADCs.
|
||||
|
||||
The energy from the transistor output is transferred by conduction and convection to the temperature sensor. Heat transfer dynamics provide rich opportunities to implement single and multivariable control systems, modeling, and system identification applications. The TClab is integrated into a small PCB, which can be mounted to any Arduino or Arduino-compatible microcontroller.
|
||||
|
||||
The next Figure shows the TCLab version distributed by Jeffrey Kantor and Carl Sandrock.
|
||||
|
||||
![](http://apmonitor.com/pdc/uploads/Main/tclab_transparent.png)
|
||||
|
||||
This project is a resource for model identification, controller development, and machine learning applications. It is a pocket-sized lab with software in Python, MATLAB, and Simulink for the purpose of reinforcing control theory for students.
|
||||
The device is a resource for model identification, controller development, and machine learning applications. It is a pocket-sized lab with Python software for the purpose of reinforcing control theory for students.
|
||||
|
||||
# Portable Temperature Control Lab for Learning Process Control
|
||||
|
||||
This lab teaches principles of system dynamics and control. In particular, this lab reinforces:
|
||||
### TCLab General Schematic
|
||||
|
||||
- Dynamic modeling with balance equations
|
||||
- The difference between manual and automatic control
|
||||
- Step tests to generate dynamic data
|
||||
- Fitting dynamic data to a First Order Plus Dead Time (FOPDT) model
|
||||
- Obtaining parameters for PID control from standard tuning rules
|
||||
- Tuning the PID controller to improve performance
|
||||
Below is the TCLab main schematic to attach to the Arduino UNO board.
|
||||
|
||||
![](./schematic.png)
|
||||
|
||||
![](http://apmonitor.com/pdc/uploads/Main/tclab_schematic.png)
|
||||
The board considers the following components:
|
||||
|
||||
|
||||
| Quantity | Description | Value | Package | Notes |
|
||||
|----------|---------------|----------|------------|-------|
|
||||
| 2 | TIP31C | | TO-220 | |
|
||||
| 3 | Resistance | 470 Ohms | 1206 | |
|
||||
| 2 | TMP36GT | | TO-92 | |
|
||||
| 1 | LED | | 1206 | |
|
||||
| 1 | Barrel jack | | | |
|
||||
| 1 | 10 pins 0.1" | | pin header | |
|
||||
| 2 | 8 pins 0.1 in | | pin header | |
|
||||
| 1 | 6 pins 0.1 in | | pin header | |
|
||||
|
||||
You will find the complete project with the PCB layout in the open project at [Upverter]().
|
||||
|
||||
**The TIP31C and TMP36 data-sheets are in this repository**
|
||||
|
||||
## Arduino Sketch
|
||||
|
||||
The Arduino Sketch (TCLab sketch) is a set of methods that supports the Temperature Control Lab when downloaded and installed on a compatible Arduino device. The sketch is used in conjunction with the compatible Python library `TCLab` for programmable control of the Temperature Control Lab with Python.
|
||||
|
||||
|
||||
### Hardware setup
|
||||
|
||||
1. Plug the Temperature Control Laboratory board in an Arduino UNO or Leonardo. Connect your computer to the Arduino via the USB connection. Plug the DC power adapter into the wall and attach it to the power input to the TCLab board.
|
||||
|
||||
2. Install Arduino Drivers if needed. For most users, there will be no need. If you are using Windows 10, the Arduino board should connect with no additional drivers. Mac OS users may need to install a serial driver for Arduino Uno clones. A suitable driver can be found [here](https://github.com/adrianmihalko/ch340g-ch34g-ch34x-mac-os-x-driver) for clones using the CH340G, CH34G or CH34X chipset.
|
||||
|
||||
3. Install Arduino Firmware:
|
||||
* Download and install the Arduino IDE application.
|
||||
* Download the file TCLab-sketch.ino located in this repository in the folder with the same name.
|
||||
* Open the Arduino IDE application. Select the Arduino board type from `Tools -> Board -> Arduino Board -> Arduino Uno` or your compatible board, and finally verify the port connection.
|
||||
* Compile and upload `TCLab-sketch.ino`
|
||||
|
||||
4. Test
|
||||
|
||||
To confirm the board's proper operation, use the serial monitor (located under the tools menu of the Arduino IDE). Select serial monitor, set the baud rate to 9600, and line endings to 'newline.' If the firmware is operating correctly, enter the command
|
||||
```
|
||||
LED 100
|
||||
```
|
||||
|
||||
will cause the LED to flash at 100% power for 10 seconds.
|
||||
|
||||
|
||||
|
||||
## TCLab library
|
||||
|
||||
First, install the Python TCLab library by using:
|
||||
```
|
||||
pip3 install tclab
|
||||
```
|
||||
|
||||
then open a Python IDE or execute the next code in Jupyter Notebook.
|
||||
```
|
||||
import tclab
|
||||
with tclab.TCLab() as lab:
|
||||
print(lab.T1)
|
||||
```
|
||||
|
||||
if everything was done properly, you should receive a message back indicating the temperature of transistor 1 `T1`:
|
||||
```
|
||||
Connecting to TCLab
|
||||
TCLab Firmware Version 1.2.1 on NHduino connected to port XXXX
|
||||
21.54
|
||||
TCLab disconnected successfully.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Main project objective
|
||||
|
||||
The `TC-Lab` project will be used for the students as a portable temperature controller system to implement several kind of controllers. Thus, the student should develop the next tasks to reach the final goal (main objective):
|
||||
The `TC-Lab` project will be used for the students as a portable temperature controller system to implement several kinds of controllers. Thus, the student should develop the next tasks to reach the final goal (main objective):
|
||||
|
||||
- Develop the basic schematic
|
||||
- Develop the PCB for the `TC-Lab`
|
||||
- Test the `TC-Lab` in the Arduino platform
|
||||
- Acquire temperature data from heater
|
||||
- Acquire temperature data from the heaters
|
||||
- Develop a regressor and a classifier
|
||||
|
||||
|
||||
![](./tclab-block.png)
|
||||
![](http://apmonitor.com/pdc/uploads/Main/tclab_schematic.png)
|
||||
|
||||
![](./pcb.png)
|
||||
|
@ -0,0 +1,358 @@
|
||||
/*
|
||||
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();
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 150 KiB |
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue