-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathtemp-control.rst.long
More file actions
271 lines (172 loc) · 9.1 KB
/
temp-control.rst.long
File metadata and controls
271 lines (172 loc) · 9.1 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
Temperature controller
======================
Feedback control
----------------
With a constant duty cycle, the Peltier cell moves heat from the cold side to the hot side. Heat is then slowly removed from the hot side by the heat-sink, and dissipated through `convection <https://en.wikipedia.org/wiki/Convection>`_. For this reason, the cold side temperature of the Peltier cell becomes colder than the room average temperature. Increasing the duty cycle further reduces the cold side temperature. However, you do not have much control on the actual temperature of the cold side. That depends on a number of factors. Even if you spend time to estimate the relationship between duty cycle and cold side temperature (at steady state), that relationship will dependent on the room average temperature, airflow within the heat sink, humidity, and many other factors.
To overcome these limitations and control precisely the cold side temperature you need feedback. Feedback is effective against uncertainties. The idea is simple: if the cold side temperature is above the desidred temperature, then the PWM duty cycle must be increased to remove more heat. If the cold side temperature is below the desired temperature, then the PWM duty cycle must be set to zero to reduce the extraction of heat. This basic `feedback <https://en.wikipedia.org/wiki/Control_theory>`_ approach, summarized in the figure below, is extensively used in industry.
.. figure:: https://upload.wikimedia.org/wikipedia/commons/2/24/Feedback_loop_with_descriptions.svg
:scale: 50 %
:alt: Feedback loop
Feedback loop. Source: Wikipedia
In our setting, the "system" is the Peltier cell and the "sensor" is the temperature sensor. In principle, the microcontroller can computes the difference between the cold side measured temperature and the desired/reference temperature, and proportionally adjusts the duty cycle to reach the desired temperature.
Feedback control algorithm
--------------------------
The code below implements a feedback control algorithm. Sensor reading and serial comunication are as in the previous code. A new Ticker variable is introduce to trigger updates of the PWM duty cycle at constant interval of times. The function ``update_PWM()`` adapts the PWM duty cycle accordingly to the difference between current and desired cold side temperatures. The function implement the simplest form of feedback control: proportional feedback.
.. code-block:: c
#include "mbed.h"
#include "stdint.h"
// *** PWM driver: pins and variables
PwmOut peltierpwm(PA_7);
DigitalOut DIR(D5);
Ticker dT_output;
volatile int write_output = 0;
// *** Temperature sensor: pins and variables
#define LM75_REG_TEMP (0x00) // Temperature Register
#define LM75_REG_CONF (0x01) // Configuration Register
#define LM75_ADDR (0x90) // LM75 address
I2C i2c(I2C_SDA, I2C_SCL); //D14 and D15
Ticker dT_input;
volatile int read_input = 0;
// *** Serial communication: variables
Serial pc(SERIAL_TX, SERIAL_RX);
Ticker dT_serial;
volatile int update_serial = 0;
// *** Interrupt functions
void sensing() {
read_input = 1;
}
void actuation() {
write_output = 1;
}
void serial_com() {
update_serial = 1;
}
// *** General functions
float read_temperature() {
// Read temperature register
char data_write[2];
char data_read[2];
data_write[0] = LM75_REG_TEMP;
i2c.write(LM75_ADDR, data_write, 1, 1); // no stop
i2c.read(LM75_ADDR, data_read, 2, 0);
// Calculate temperature value in Celcius
int16_t i16 = (data_read[0] << 8) | data_read[1];
// Read data as twos complement integer so sign is correct
float temperature = i16 / 256.0;
// Return temperature
return temperature;
}
void update_PWM(float temperature) {
// Read temperature register
float ref = 30;
float kp = 0.3;
float duty_cycle;
duty_cycle = kp*(ref-temperature);
if (duty_cycle <= 0) {
peltierpwm.write(0.0f);
}
if (duty_cycle >= 0.50) {
peltierpwm.write(0.50f);
}
if (duty_cycle >= 0 && duty_cycle <= 0.50) {
peltierpwm.write(duty_cycle);
}
}
int main() {
//*** temperature sensing configuration
//Sensor configuration
char data_write[2];
data_write[0] = LM75_REG_CONF;
data_write[1] = 0x02;
i2c.write(LM75_ADDR, data_write, 2, 0);
//variables
float temperature = 0;
//*** PWM drive configuration
DIR = 1;
peltierpwm.period_us(1000);
peltierpwm.write(0.0f);
printf("pwm set to %.2f %%\n", peltierpwm.read());
//*** Interrupt configuration
dT_input.attach(sensing, 0.01);
dT_output.attach(actuation, 0.01);
dT_serial.attach(serial_com, 0.25);
while(1) {
if (read_input == 1) {
read_input = 0;
temperature = read_temperature();
}
if (write_output == 1) {
write_output = 0;
update_PWM(temperature);
}
if (update_serial == 1) {
update_serial = 0;
pc.printf("Pwm set to %.2f, Temperature = %.3f\r\n ",peltierpwm.read() * 100, temperature, ref);
}
}
}
Feedback control algorithm in detail
------------------------------------
Let's discuss only the new elements.
.. code-block:: c
// *** PWM driver: pins and variables
PwmOut peltierpwm(PA_7);
DigitalOut DIR(D5);
Ticker dT_output;
volatile int write_output = 0;
In this code, the ticker variable ``dT_output`` is used to trigger an interrupt at constant intervals of time. You will see that, as a consequence of the interrupt, the variable ``write_output`` is set to 1. This will trigger an update of the duty cycle driving the Peltier cell.
.. code-block:: c
void actuation() {
write_output = 1;
}
The function ``actuation()`` is called when the ``dT_output`` ticker triggers an interrupt. The function changes the variable ``write_output`` to $1$.
.. code-block:: c
void update_PWM(float temperature) {
// Read temperature register
float ref = 30;
float kp = 0.3;
float duty_cycle;
duty_cycle = kp*(ref-temperature);
if (duty_cycle <= 0) {
peltierpwm.write(0.0f);
}
if (duty_cycle >= 0.50) {
peltierpwm.write(0.50f);
}
if (duty_cycle >= 0 && duty_cycle <= 0.50) {
peltierpwm.write(duty_cycle);
}
}
The function ``update_PWM()`` adapts the duty cycle driving the Peltier cell:
- ``ref`` is the desired temperature. This is set by the user.
- ``duty_cycle = kp*(ref-temperature)`` adjusts the duty cycle proportionally to the difference between the reference temperature ``ref`` and the actual measured temperature ``temperature``. The proportional gain ``kp`` can be adjusted by the user.
The rest of the code normalizes the duty_cycle within safety bounds, for compatibility with the physical limits of Peltier cell and MAX14870 driver:
- if ``duty_cycle <= 0``, the measured temperature is already below the reference temperature and the best action is to turn off the Peltier cell by setting ``peltierpwm.write(0.0f)``;
- if ``duty_cycle >= 0.50`` then the actual duty cycle is normalized to the maximum value ``peltierpwm.write(0.50f)`` to avoid large currents within the Peltier cell.
The initial part of the main code is used for initialization of the controller. For instance,
.. code-block:: c
//*** PWM drive configuration
EN = 1;
peltierpwm.period_us(1000);
peltierpwm.write(0.0f);
printf("pwm set to %.2f %%\n", peltierpwm.read());
sets the initial PWM duty cycle at 0. Also
.. code-block:: c
dT_output.attach(actuation, 0.01);
defines a recurrent interrupt every 0.01 seconds, which call the function ``actuation()``.
Finally, within the main while loop, the code
.. code-block:: c
if (write_output == 1) {
write_output = 0;
update_PWM(temperature);
}
triggers an update of the PWM duty cycle whenever the variable ``write_output`` is detected equal to 1. After that, ``write_output`` is set to 0, in preparation for the next interrupt.
Actuation and sensing are updated every 0.01 seconds, a very fast rate. Serial updates to the user are just four each second, a much slower rate (enough for monitor reading).
Task
----
- what is the difference between small and large proportional gain kp?
- Set the reference temperature through buttons.
- The `proportional control <https://en.wikipedia.org/wiki/Proportional_control>`_ you have implemented in the code above is non optimal for temperature control: it works but it is not precise. There is always a small error near the desired temperature. The best approach is the so called `proportional + integral control <https://en.wikipedia.org/wiki/PID_controller>`_. The role of integral control is to estimate the exact amount of energy that the system needs at steady state to keep the temperature at the exact desired value (no small error). If you have mastered this lecture so far and you want to explore a more challanging control algorithm, please read the linked wikipedia page and add an integral component to your proportional controller. A few additional resources can be found here:
`PID Cookbook Mbed <https://os.mbed.com/cookbook/PID>`_
`What is a PID Controller? <https://www.youtube.com/watch?v=sFqFrmMJ-sg>`_
`What are PID Tuning Parameters? <https://www.youtube.com/watch?v=1ImhKwpSmuc>`_