Control loops

  • To understand the principles of integrating a sensor and an actuator into a unified control system, it's best to start by attempting to maintain a constant temperature using a temperature sensor and a heating element. The principles learned from this example can be applied with equal success to control the position of a motor, the water level in a reservoir, or any other domain that involves both measuring the current state and controlling its change using electronic devices.

    The illustration shows why such systems are called control loops.

    In the case of a thermostat in a house, a desired temperature is set - let's call it the control or set temperature. The temperature sensor measures the actual temperature. The difference between the actual temperature and the set temperature determines the error. Then the controller receives this error to calculate the power supplied to the heater.
    Control circuit (thermostat example)
    image.png (27.94 KiB)
    Control circuit (thermostat example) Viewed 4738 times
    In the simplest scenario, the heater is either turned on or off. If the temperature error is positive (meaning the set temperature is higher than the actual temperature), it means the house is cold enough to turn on the heater. Conversely, if the actual temperature rises above the set temperature, it means the house is warm enough to turn off the heater.

    In the section "Experiment: How Well Does an On/Off Thermostat Work?" to create a simple thermostat, we will use Arduino in combination with a resistor as the heater and a digital temperature sensor. This same equipment will be used when exploring more precise methods of controlling an actuator.
  • In this experiment, a simple temperature control system using on/off control of a heater is implemented using an Arduino board and the DS18B20 digital temperature sensor integrated circuit. A 100-ohm resistor, physically attached to the DS18B20 IC, serves as the heating element.

    Commands for setting the desired temperature will be sent through the serial monitor window, and the read temperature will also be displayed on this monitor. The temperature readings can be copied and pasted into a spreadsheet to create graphs that show the quality of temperature regulation.

    The compact size and close proximity of components allow experiments with the temperature controller without the need for large and slow-to-change objects. The schematic of this experiment is shown in the figure below, and please note the clip securely attaching the resistor to the temperature sensor IC.
    Schematic diagram of an experiment to create a thermostatic control system based on heater on/off switching using an Arduino assembly
    image.png (198.19 KiB)
    Schematic diagram of an experiment to create a thermostatic control system based on heater on/off switching using an Arduino assembly Viewed 4738 times
    Components

    For this experiment working with either Arduino or Raspberry Pi (regardless of the board used), you will need the following components:
    - IC1 - DS18B20 Digital Thermometer IC
    - R1 - 4.7k ohm Resistor
    - R2 - 1k ohm Resistor
    - R3 - 100 ohm 0.25W Resistor
    - Q1 - Composite Transistor MПC14
    - 400-Point Breadboard
    - Jumper Wires
    - Small Clip

    If you don't have a small clip, you can use a regular plastic one to avoid accidentally short-circuiting the resistor or IC pins.

    Sometimes the DS18B20 IC is available together with a 4.7k-ohm resistor. Additionally, it is often sold in a waterproof casing. For the experiment, it is advisable to use the "bare" IC (looking like a regular transistor), but if you plan to continue working and create a real thermostat, the waterproof sensor version is ideal for the project discussed further in the "Project: Beverage Cooler Thermostat" section.

    Circuit Diagram of the Experiment

    The schematic diagram for our experiment is shown in the figure below. It consists of two parts: the DS18B20 temperature sensor, which requires a pull-up resistor R1 with a 4.7k-ohm rating, and the heating section, with transistor Q1 placed at its center. Resistor R2 limits the current flowing into the transistor's base, while resistor R3 serves as the heating element.
    Schematic diagram of an on/off based thermostat experiment
    image.png (67.35 KiB)
    Schematic diagram of an on/off based thermostat experiment Viewed 4738 times
    DS18B20 IC
    The DS18B20 IC is a highly useful compact device. Its value lies not only in its accuracy (±0.5 degrees Celsius) but also in its ability to create a chain of similar devices using just one Arduino or Raspberry Pi pin.


    Instructions for connecting multiple DS18B20 sensors and addressing them individually can be found in Miles Burton's Arduino program at: https://milesburton.com/Dallas_Temperat ... ol_Library

    When extracting data from multiple devices on a Raspberry Pi, make sure to use different identifiers when opening the device files.

    Breadboard Layout of the Experiment

    The circuit assembled on a breadboard for conducting the experiment is shown in the figure below.
    Circuit assembled on breadboard for thermostat based on on/off switch
    image.png (166.06 KiB)
    Circuit assembled on breadboard for thermostat based on on/off switch Viewed 4738 times
    The heating resistor R3 is placed immediately behind the flat side of the DS18B20 sensor so that they can be firmly pressed together with a small clip.

    Two Arduino pins are involved in the circuit: Pin D9 is used to control the transistor, switching the power to the resistor acting as the heater on and off, while Pin D2 serves as the input for the digital sensor signal.

    Make sure to place the transistor and DS18B20 IC correctly, towards the right edge of the breadboard.
    The resistor and the DS18B20 sensor are held together by a small clothespin
    image.png (235.09 KiB)
    The resistor and the DS18B20 sensor are held together by a small clothespin Viewed 4738 times
    Program

    The DВ18B20 microchip operates on a bus called 1-Wire. To use this microchip as a sensor for Arduino, you need to download and install two Arduino libraries into your Arduino IDE environment.

    First, download the OneWire library (http://www.pjrc.com/teensy/arduino libraries/OneWire.zip). Extract the downloaded archive, and you will find a directory named "OneWire." Install this directory into your Arduino environment.

    To download the second library specifically for the DS18B20 microchip, visit the page https://github.com/milesburton/Arduino- ... ol-Library and click the Download ZIP button. After unpacking the archive, you will have a folder named "dallas-temperature-control." Before moving it to your libraries directory, rename it to "Dallas Temperature."

    Installing Arduino Libraries
    The Arduino IDE environment includes many pre-installed libraries, but sometimes there's a need to download a library from the Internet that is not included in the IDE by default. This process is straightforward: libraries are usually downloaded in the form of a ZIP archive that needs to be unpacked into a separate directory. The name of this directory should match the library's name, but there are cases where the downloaded directory may not have the exact same name, especially when downloading from GitHub.

    After renaming the directory (if necessary), you should move it to your "libraries" directory, which is located within your Arduino directory. The Arduino IDE should have automatically created the "arduino" directory inside your "Documents" directory - this is where Arduino stores its sketches.

    When creating the very first library, before moving the downloaded library into the "libraries" directory (which doesn't exist yet), you should create a directory with that name within the "arduino" directory.

    After installing a new library, you will need to restart the Arduino IDE for it to be recognized.

    Arduino sketch for this project:

    Code: Select all

    #include <OneWire.h>
    #include <DallasTemperature.h>
    
    const int tempPin = 2;     // (1)
    const int heatPin = 9;
    const long period = 1000;  // (2)
    
    OneWire oneWire(tempPin);  // (3)
    DallasTemperature sensors(&oneWire);
    
    float setTemp = 0.0;       // (4)
    long lastSampleTime = 0;
    
    void setup() {  
      pinMode(heatPin, OUTPUT);
      Serial.begin(9600);
      Serial.println("t30 - sets the temperature to 30");
      sensors.begin();        // (5)
    }
    
    void loop() { 
      if (Serial.available()) {     // (6)
        char command = Serial.read();
        if (command == 't') {
          setTemp = Serial.parseInt();
          Serial.print("Set Temp=");
          Serial.println(setTemp);
        }
      }
      long now = millis();         // (7)
      if (now > lastSampleTime + period) {
        lastSampleTime = now;
        float measuredTemp = readTemp();    // (8)
        float error = setTemp - measuredTemp;
        Serial.print(measuredTemp);
        Serial.print(", ");
        Serial.print(setTemp);
        if (error > 0) {                    // (9)
            digitalWrite(heatPin, HIGH);
            Serial.println(", 1");
        }
        else {
          digitalWrite(heatPin, LOW);
          Serial.println(", 0");
        }
      }
    }
    
    float readTemp() {     // (10)
      sensors.requestTemperatures(); 
      return sensors.getTempCByIndex(0);  
    }
    
    Let's clarify some points in the sketch by breaking it down step by step, using the line markup provided in the comments:

    1. The code begins with defining constants for the temperature reading pin (tempPin) and the heater control pin (heatPin).

    2. The constant period is used to set the time interval between consecutive temperature readings. The DS18B20 microchip isn't particularly fast, and this interval can be as long as 750 ms, so it's advisable to choose a value for period that is not less than 750.

    3. Both libraries, DallasTemperature and OneWire, require variables to be defined for access.

    4. The setTemp variable holds the current set temperature, and the lastSampleTime variable is used to keep track of the time of the last temperature reading.

    5. Initialization of the DallasTemperature library.

    6. The first part of the loop() function checks for consecutive commands coming from the serial monitor window. In reality, there is only one possible command ('t'), followed by the desired temperature setting in degrees Celsius. After setting a new value for setTemp, it is echoed back to the serial monitor window for confirmation.

    7. The second half of loop() first checks if it's time for a new reading, i.e., if the specified time interval in milliseconds has elapsed.

    8. The temperature is measured, and the deviation is calculated. The values of measuredTemp and setTemp are then written to the serial monitor window.

    9. If the deviation is greater than zero, the heater is turned on, and the digit 1 appears at the end of the line in the monitor window, indicating that the heater is powered. Otherwise, if the deviation is zero, the heater is turned off, and the digit 0 is displayed.

    10. Since multiple DS18B20 sensors can be connected to the same input pin, the requestTemperature command requests all connected DS18B20 microchips (in this case, only one) to measure the temperature and send the result. Then, the getTempCByIndex function is called to retrieve the read temperature value from the first and only connected DS18B20 microchip.

    Loading and Running the Program

    Load the program and open the serial monitor window. You should see a continuous stream of temperature readings (note that the DS18B20 microchip is designed to work in degrees Celsius, so throughout this chapter, I'll be using temperature units in degrees Celsius):

    Code: Select all

    t30 - sets the temperature to 30 degrees
    21.75, 0.00, 0
    21.69, 0.00, 0
    21.75, 0.00, 0 
    21.69, 0.00, 0
    21.75, 0.00, 0
    In the three displayed columns, you can see the measured temperature, the set temperature (in this case, 0), and the heater power indicator (0 or 1).

    Enter the command "t30" to set the desired temperature to 30°C. The temperature will immediately start rising because the heater will turn on, as indicated by the digit in the third column:

    21.75, 0.00, 0
    21.75, 0.00, 0
    Set temperature: 30.00
    21.75, 30.00, 1
    21.75, 30.00, 1
    21.81, 30.00, 1
    21.94, 30.00, 1
    22.06, 30.00, 1

    When the temperature reaches 30°C, the heater should turn off, and you'll observe a cycle of heater on/off as the temperature fluctuates near 30°C:

    29.87, 30.00, 1
    29.94, 30.00, 1
    29.94, 30.00, 1
    30.00, 30.00, 0
    30.06, 30.00, 0
    30.12, 30.00, 0
    30.06, 30.00, 0.
    29.94, 30.00, 1
    29.81, 30.00, 1
    29.75, 30.00, 1

    Congratulations! You've created a thermostat that can be useful for various applications. However, you may notice that the temperature fluctuates around the set value of 30 degrees. To significantly improve the results, you can use the previously mentioned control technology called Proportional-Integral-Derivative (PID) control. While the name PID may sound complicated and mathematical, you can simplify the entire process by following a few basic rules.
    plotting the temperature graph of a simple thermostat operating on an on/off basis
    image.png (57.44 KiB)
    plotting the temperature graph of a simple thermostat operating on an on/off basis Viewed 4738 times
    As you can see, the temperature fluctuates above and below the set point by almost half a degree. We can significantly improve our results by using the previously mentioned control technology called proportional-integral-differential control (PID). Although the very name PID suggests the use of complex mathematical calculations, the whole work can be simplified by following a few basic rules.
  • Using a transistor to toggle power on and off allows achieving a high-speed switching process and harnessing Pulse Width Modulation (PWM) technology. However, for certain systems, frequent power cycling of a device is highly undesirable. This can apply to household heaters that require time to start generating heat after being powered on. Furthermore, controlling them using electromechanical switches and similar devices in constant on/off mode can shorten the heater's lifespan.

    To prevent excessively rapid switching, it is necessary to set a minimum time during which the device cannot be turned off or introduce hysteresis.
    image.png
    image.png (62.62 KiB)
    Viewed 4738 times
    Applied to a thermostat, hysteresis means having not just one but two temperature thresholds, one of which is higher than the other by a fixed amount. If the temperature drops below the lower threshold, the heater turns on, but it only turns off when the temperature exceeds the upper threshold. Thus, the natural inertia of the system is used to introduce a delay in the switching process.
  • Turning off the heater when the temperature sensor indicates that the heater is hotter than the set temperature and turning it on when it's cooler than that leads to a system where the temperature "oscillates" around the set value.

    For more precise temperature control, proportional-integral-derivative (PID) control should be used.

    Instead of simply turning the heater on and off, a PID controller regulates the output power of the heater (or another actuator) taking into account three factors: proportional, integral, and derivative.

    [Warning]
    [In this section, we will dive deeper into the theory. Questions about PID control are quite common, and one of the main goals I've chosen is to explain the principles and applications of PID control.]

    Proportional (P)

    Using only the P-part of the PID controller and ignoring the other two - I and D, can yield quite good results on many systems. Therefore, on every new system, it's advisable to start with only the P-part and see how acceptable the results turn out.

    Proportional control means that the power supplied to the heater is proportional to the error. The greater the error, i.e., the lower the actual temperature compared to the set temperature, the more power is supplied to the heater. As the actual temperature approaches the set temperature, the power decreases, resulting in the actual temperature not exceeding the set one as much as shown in the "temperature graph of a simple thermostat working on an on/off basis." Depending on the system, it will likely still exceed the set temperature, but not as much as it would with simple on/off control. This process is somewhat akin to approaching a stop sign while driving – you begin to slow down in advance, not right at the sign itself.

    If the actual temperature is higher than the set temperature, the error will be negative, and this should result in reducing the power to the heater. If the heater were a Peltier element, you could use an H-bridge to send current through it in the opposite direction and turn it from a heater into a cooler. However, in practice, unless the ambient temperature significantly differs from the set temperature, there's no need to do that. Instead, you can simply let the system naturally cool down (or heat up).

    The discrepancy used in calculating the output power in the case of a thermostat is the temperature difference. Expressing it in degrees Celsius, if the set temperature is 30°C and the measured temperature is 25°C, then the discrepancy is 5°C (30°C - 25°C). If a Pulse Width Modulation (PWM) scheme is used with an Arduino for setting the output, the output will range from 0 to 255. Therefore, directly setting the discrepancy value (5) would result in providing too little power to the heater, which is likely insufficient to raise the temperature to 30°C. Hence, to obtain the value of the applied output power, the discrepancy is multiplied by a number called kp (sometimes referred to as "gain" as well). Changing kp determines how quickly the temperature will rise to the set value. With a low kp value, the temperature may never reach the desired level, but with a very high kp value, the system will behave similarly to a simple on/off temperature controller, causing the actual temperature to oscillate around the set temperature. The figure below illustrates how different values of kp affect the behavior of an idealized system.
    Effect of kp (gain) on the output in a proportional controller
    image.png (53.31 KiB)
    Effect of kp (gain) on the output in a proportional controller Viewed 4738 times
    Here, it can be observed that if the value of kp is too low, the temperature may never reach the desired level, or it may take too long to do so. On the other hand, if the value of kp is too high, oscillations will occur at the output, and their magnitude will not decrease. The appropriate value of kp will quickly adjust the output to the desired level, perhaps with a slight overshoot, and then the oscillations will quickly settle to an acceptably low level.

    Finding the suitable value of kp is referred to as system tuning. Tuning of PID control will be discussed further.

    Returning to the example with a set temperature of 30°C but an actual temperature of 25°C, in this case, it is still desirable for the heater to dissipate the maximum power, i.e., a PWM duty cycle of 255 (100%). If a value of 50 is chosen for kp, then a discrepancy of 5 units will lead to the following output power:

    Output = Discrepancy x kp = 5 x 50 = 250

    Once the system is only 1°C away from the set temperature, the output power will decrease to the following value:

    Output = Discrepancy x kp = 1 x 50 = 50

    This power may be sufficient or insufficient for the system to reach the desired temperature. Systems vary, so the controller requires tuning.

    Integral (I)

    If achieving the required precision of power control through proportional control is not possible, calculations may require the addition of a value I to calculate the output power using the following formula:

    Output = Discrepancy x kp + I x ki.

    Here, a new constant ki is introduced, scaling our new mysterious property called "I" (let's reveal the secret - it's precisely Integral), and the output power is calculated by adding this integral component to the previously discussed proportional component. This type of controller is called a PI controller (PI, or sometimes P + I, don't confuse it with "Pi" in Raspberry Pi). Both the use of a purely proportional controller and a PI controller sometimes yield quite acceptable results, eliminating the need to add the third component of the PID controller, D.

    The integral component is calculated by maintaining the current sum of discrepancies with each temperature measurement. When the discrepancy is positive (heating), the I-component will continue to increase, exceeding the value of the initial response. Reduction will only start when the discrepancy becomes negative after exceeding the set temperature.

    Once the actual temperature reaches the set temperature, the I-component has a calming effect, smoothing out temperature changes to better maintain the temperature at the desired value.

    The only problem with the I-component is that it provides a large boost as the temperature rises, potentially leading to a significant temperature overshoot, after which time is required for it to decrease to the set temperature and stabilize.

    Derivative (D)

    In practice, the D-component is often not used in real control systems because the benefits of using it to reduce overshoots are outweighed by the complexity of tuning.

    When adding the D-component to the control program to counteract overshoot effects, the output power is calculated using the following formula:

    Output = Discrepancy x kp + I x ki + D x kd.

    The D-component measures how quickly the discrepancy changes between each temperature measurement, and therefore, it somewhat predicts where the temperature is heading.

    PID Controller Tuning

    Tuning a PID controller involves finding values for kp, ki, and kd that make the system behave appropriately. I recommend simplifying things and using only PI control, setting kd to zero. Then, you only need to tune two parameters. In fact, this simplification eases the tuning process for most systems, although it slightly increases the time spent on tuning.

    In the section "Experiment: Thermostatic PID Controller," I will guide you through a trial-and-error method that works well for the temperatures the controller deals with. The rest of the section will focus on the most popular method of PID tuning known as the Ziegler-Nichols method. This method significantly streamlines the process of obtaining values for kp, ki, and kd, reducing it to a series of experiments followed by straightforward calculations.

    Ziegler-Nichols tuning starts with setting ki and kd to zero in order for the controller to work in proportional mode. Then, the kp value is gradually increased until the output power starts to oscillate. The kp value at which this happens is called ku.

    After determining the value of ku, you need to measure the number of oscillations per second (oscillation period, referred to as pu).
    determination of the number of vibrations per second (oscillation period)
    image.png (33.49 KiB)
    determination of the number of vibrations per second (oscillation period) Viewed 4738 times
    To obtain it, you should plot the power readings on the load, as was done in the experiment in the section "Experiment: How Good Is a Thermostat Based on On/Off Control?"

    Then, you can calculate the values for kp, ki, and kd:

    kp = 0.6 x ku
    ki = (2 x ku) / pu
    kd = (ku x pu) / 8


    If you are using a PI controller, it is better to use the following calculations:


    ku = 0.45 x ku
    ki = (1.2 x kp) / pu
    kd = 0


    You can find an article on the Ziegler-Nichols method in Wikipedia (http://en.wikipedia.org/wiki/PID_controller).
  • PID Controller Experiment

    In this experiment, we will explore the operation of a PID controller using both Arduino and Raspberry Pi. The program for the PID controller using Arduino is taken from the library, while the program for the PID controller using Raspberry Pi will need to be manually typed.

    Equipment

    This experiment uses the exact same equipment as the experiment in the section "Experiment: How Good is an On/Off Thermostat?"

    Arduino Program

    In this experiment, a ready-to-use PID library is employed (https://github.com/br3ttb/Arduino-PID-Library/), which you can download and install. You can find complete documentation for this library on the Arduino website (http://playground.arduino.cc/Code/PIDLibrary).

    The sketch used in the Arduino experiment is as follows:

    Code: Select all

    #include <OneWire.h>
    #include <DallasTemperature.h>
    #include <PID_v1.h>
    
    const int tempPin = 2;
    const int heatPin = 9;
    const long period = 1000; // >750
    
    double kp = 0.0;     // (1)
    double ki = 0.0;
    double kd = 0.0;
    
    
    OneWire oneWire(tempPin);
    DallasTemperature sensors(&oneWire);
    
    double setTemp = 0.0;
    double measuredTemp = 0.0;
    double outputPower = 0.0;     // (2)
    long lastSampleTime = 0;
    
    PID myPID(&measuredTemp, &outputPower, &setTemp, kp, ki, kd, DIRECT); // (3)
    
    void setup() {
      pinMode(heatPin, OUTPUT);
      Serial.begin(9600);
      Serial.println("t30 - sets the temperature to 30");
      Serial.println("k50 20 10 - sets kp, ki and kd respectively");
      sensors.begin();
      myPID.SetSampleTime(1000); // (4)
      myPID.SetMode(AUTOMATIC);  
    }
    
    void loop() { 
      checkForSerialCommands();  // (5)
      
      long now = millis();
      if (now > lastSampleTime + period) { // (6)
        lastSampleTime = now;
        measuredTemp = readTemp();
        myPID.Compute();
        analogWrite(heatPin, outputPower);
      
        Serial.print(measuredTemp);  // (7)
        Serial.print(", ");
        Serial.print(setTemp);
        Serial.print(", ");
        Serial.println(outputPower);
      }
    }
    
    void checkForSerialCommands() {  // (8)
        if (Serial.available()) {
        char command = Serial.read();
        if (command == 't') {
          setTemp = Serial.parseFloat();
          Serial.print("Set Temp=");
          Serial.println(setTemp);
        }
        if (command == 'k') {
          kp = Serial.parseFloat();
          ki = Serial.parseFloat();
          kd = Serial.parseFloat();
          myPID.SetTunings(kp, ki, kd);
          Serial.print("Set Constants kp=");
          Serial.print(kp);
          Serial.print(" ki=");
          Serial.print(ki);
          Serial.print(" kd=");
          Serial.println(kd);
        }
      }
    }
    
    double readTemp() {
      sensors.requestTemperatures(); 
      return sensors.getTempCByIndex(0);  
    }
    
    
    NOTE
    The code related to reading temperature data from DS18B20 is no different from the code used in the experiment from the section "Experiment: How Good is an On/Off Thermostat?" Therefore, we will focus on the code related to PID control here.


    1. Scaling coefficients for the P, I, and D components of the output are determined using three variables: kp, ki, and kd. The use of variables is because they can be adjusted using commands in the port monitor during program execution. The double data type is used instead of float, partly because it allows for more precise numbers than floats, and also because this data type is expected by the library.

    2. The variable outputPower will contain the PWM duty cycle ranging from 0 to 255.

    3. A variable myPID is defined to access the PID library's code. Note that the first three parameters when creating a variable for accessing the PID library are the names of the variables measuredTemp, outputPower, and setTemp, preceded by the prefix &. This is a C language technique that allows the PID library to modify the values of these variables, even if they are not part of the library itself. If you want additional information about this C language feature (pointers in C), refer to the Tutorials Point material (http://www.tutorialspoint.com/cprogramm ... inters.htm). The last parameter (DIRECT) sets the PID operation to direct mode, meaning that the output will be proportional to the error and not inverted when using this library. By default, the output range for PWM is set from 0 to 255.

    4. The sampling time needs to be set to 1 second (1000 ms). PID calculations are initiated by setting the mode to AUTOMATIC.

    5. Now the check for incoming serial commands is in its own function, which stops the loop() cycle, making the code easier to read. It was previously too verbose.

    6. When it's time for the next temperature reading, it is read into the measuredTemp variable, and then the PID library is instructed to update its calculations (myPID.Compute). This automatically updates the outputPower value, which is then used to set the PWM duty cycle on the pin used to control the resistor heater.

    7. All values are printed in the port monitor window because they are needed for plotting graphs and monitoring the controller's operation.

    8. The checkForSerialCommands function checks for the command to set the temperature, just like in the experiment from the section "Experiment: How Good is an On/Off Thermostat?". However, it also checks for the k command, followed by three numbers: kp, ki, kd, setting the tuning parameters in case of receiving this command.

    Uploading and Running the Program

    The equipment and program designed for this experiment provide us with everything we need to conduct an experiment with a PID controller. We can change the set temperature and the three values of kp, ki, and kd and observe their influence on the output. In our case, reasonable results can be obtained with a PI controller, so you can simply set the value of kd to 0.

    So, upload the program to Arduino and open the serial monitor window.

    Image

    Controller tuning takes time. You'll need to record the data and create graphs based on it to see how the system behaves. Let's start by finding a suitable value for kp. Let's try values of 50, 500, and 5000 just to get an idea.

    First, set the tuning parameters by entering the following values in the serial monitor window: k50 0 0

    As a result, kp will be set to 50, and ki and kd will be set to zero. If you prefer, you can enter values with decimal parts (e.g., k30.0 0.0 0.0). In both cases, the numbers will be converted to floating-point format.

    Now, let's set the desired temperature to 30°C (I chose this value as it exceeds the ambient temperature by approximately 7 or 8 degrees): t30

    The temperature should start rising, which will be displayed on the screen, showing three columns: the actual temperature, the set temperature, and the PWM output value (ranging from 0 to 255):

    25.06, 30.00, 246.88
    25.19, 30:00, 240.63
    25.31, 30.00, 234.38
    25.44, 30.00, 228.13

    Note that the PWM output value is displayed as a floating-point number, so it has digits after the decimal point. When using the analogWrite function, this value should be truncated to an integer in the range of 0 to 255.

    As you can see, the PWM duty cycle value in the last column starts decreasing almost immediately with a kp value of 50. This means that kp is too low, but continue collecting data for a few more minutes and then transfer them to a text file and import that file into a spreadsheet program. I started copying data when the temperature reached 25°C. Plot a temperature graph based on the data, and you should get a similar picture as shown below:

    Image

    It seems that with a kp value of 50, the temperature will never reach 30°C. Set the temperature to 0°C (t0 in the serial monitor window) and wait for the system to cool down. Then, repeat the procedure starting with kp values of 500 and then 5000. The results obtained at these three kp values are shown below.

    As we suspected, a kp value of 50 is too low, 500 is more appropriate, but the set temperature is still not reached. With a kp value of 5000, the system behaves like an on/off thermostat. It can be assumed that a kp value of 700 will be suitable, especially if the system gets a boost in temperature rise by adding a value for the I component.

    In the Ziegler-Nichols PID controller tuning method, ki is calculated using the following formula:

    ki = (1.2 × kp) / pu

    We estimated that a kp value of 700 would be suitable, and from the graph, it appears that the value of pu is approximately 15 seconds, suggesting a ki value of 56.

    Image
    Image

    Set another set of data for Arduino with kp=700 and ki=56. The results obtained from this configuration are shown in the figure above, compared to the results of pure proportional control with kp set to 700. The Y-axis is slightly stretched to visualize the results of PI control.

    Once the PI control curve reaches the set temperature, it fluctuates by a little over 0.1 degrees. The temperature changes in steps, not smoothly, because the DS18B20 microchip has a fixed step size.

    With additional experimentation, you could perhaps further improve the results, but it may not be necessary.
  • In this project, we will add thermostat control to the project from the section "Project: Beverage Cooler" so that beverages can be cooled more accurately to the desired temperature (as shown in the diagram below). In the future, the project will be further developed by adding a display showing the set and actual temperatures.
    Image

    Equipment

    This project is based on the project from the section "Project: Beverage Cooler," but with the addition of an Arduino board and a DS18B20 temperature sensor to the operational cycle. Therefore, if you have not yet worked on that project, please follow the instructions for its implementation provided in the section.

    Components

    In this project, the following components will be needed to work with Arduino:

    R1 - 4.7 Ohm Resistor;
    R2 - 1 kOhm Resistor;
    R3 - 270 Ohm Resistor;
    R4 - 10 kOhm Trimmer Resistor;
    Sealed temperature sensor DS18B20;
    Q1 - MOSFET transistor FQP30N06L;
    LED1 - Green LED;
    Thermoelectric cooling device with Peltier elements and two fans with a current consumption of no more than 4A;
    Adapter with a round socket and screw clamps;
    Power supply (12V 5A);
    Bi-directional terminal block;
    Large juice container;

    The sealed temperature sensor DS18B20 contains the same microchip as used in the experiments from the section "Experiment: How good is a thermostat based on on/off switching?" and from the section "Experiment: Thermostatic PID controller," except that it comes in a convenient waterproof capsule with long wires that can be connected directly to the prototype board.

    If a more powerful Peltier element is needed than the one listed, use a more powerful power supply to ensure that its maximum allowable current exceeds the current consumed by the element. Provide at least a half-ampere excess for the fans and another half-ampere just in case.

    Project scheme

    The schematic diagram of this project is shown in the image below. In the left part of the diagram, a trimmer resistor R4, also known as a potentiometer, is shown. The movable contact of the potentiometer is connected to pin A0, representing an analog input of the Arduino. The position of the potentiometer knob on pin A0 sets the voltage, which is measured by the Arduino and then used to set the desired cooler temperature.

    Potentiometers

    The component called a potentiometer should be familiar to you from volume controls of a radio receiver or amplifier. It has a knob that rotates almost a full turn.

    The area around R4 in the image above shows how the potentiometer is used as an input device to Arduino: the top contact of the potentiometer is connected to the 5V line, and the bottom contact is connected to ground, while the voltage on the middle contact of the potentiometer will vary from 0 to 5V depending on the knob position.


    The right part of the diagram in the image below is very similar to the experiment schematic in the section "Experiment: How good is a thermostat based on on/off switching?", except that a powerful MOSFET transistor FQP30N06L is used instead of the low-power transistor MPSA14. This transistor is capable of switching a current of 4A or more to the cooler, and its heating allows it to operate without a heatsink.
    Image

    Project assambly

    Assuming you have already implemented the project from the section "Project: Beverage Cooler," to implement this project, you only need to perform the following additional steps.

    Step 1: Adding the Temperature Sensor
    The physical construction of the cooler remains exactly the same as in the project from the section "Project: Beverage Cooler," but now you will add a temperature sensor, which should be placed at the bottom of the container - under the glasses or bottles being cooled on top of it (as shown in the diagram below). In this case, I simply attached the sensor to the bottom of the container with adhesive tape, but it is better to securely attach it in place.
    Image

    Step 2: Assembling the Circuit on a Breadboard
    The diagram below shows the assembled circuit on a breadboard used for the project, along with the connections of various project components.

    Place the components on the breadboard, ensuring the correct connection of the MOSFET transistor and LED. The temperature sensor has four wires in its cable. The wires with red and black insulation are connected respectively to the common power line (VCC) and ground (GND), and the wire with yellow insulation is the digital output of the sensor. The fourth wire does not need to be connected anywhere.

    I attached a small piece of paper to the potentiometer knob, creating a primitive scale with markings to display the set cooler temperature.
    Image

    Step 3: Connecting the Cooler
    The cooler has three pairs of wires: two for the fans and one for the Peltier element itself. To simplify the connection of the cooler, a bi-directional terminal block is used, allowing the cooler to be connected to the breadboard with just two wires (as shown in the diagram below).
    Image

    Step 4: Connecting the Power Supply
    The adapter with a round socket and screw clamps can be connected to the breadboard using "male-to-male" jumper wires. While this option may be acceptable when using high-quality jumper wires with a relatively large cross-section, many jumper wires contain thin conductors that can heat up significantly from the current passing through them, especially when dealing with several amperes. This in itself does not pose a problem as long as these wires do not get too hot, instead of just being warm. However, this means that not all of the 12V will reach the Peltier element, and it will take longer for the cooler to enter its working mode.

    To connect the breadboard to the power adapter, you can use a single-core insulated wire with a large cross-section instead of simple jumper wires. The same applies to the connecting wires leading to the cooler.

    Arduino program

    Using a PID controller for a beverage cooler may be considered excessive. However, in this case, the question boils down to the program, so there are no additional costs expected for using a "fancy" algorithm to maintain beverages in a chilled state.

    The program for this project is largely similar to the one used in the experiments from the section "Experiment: How good is a thermostat based on on/off switching?" and from the section "Experiment: Thermostatic PID controller," including all the code for interfacing with the temperature sensor DS18B20. Therefore, to understand this code, you should refer back to the descriptions of the mentioned experiments.

    Code: Select all

    #include <OneWire.h>
    #include <DallasTemperature.h>
    #include <PID_v1.h>
    
    const double minTemp = 0.0;  //1
    const double maxTemp = 20.0;
    const float tempOKMargin = 0.5; 
    
    double kp = 1500;     //2
    double ki = 50.0;
    double kd = 0.0;
    
    const int tempPin = 2;
    const int coolPin = 9;
    const int ledPin = 10;   //3    
    const int potPin = A0;
    const long period = 1000; // >750
    
    OneWire oneWire(tempPin);
    DallasTemperature sensors(&oneWire);
    
    double setTemp = 0.0;
    double measuredTemp = 0.0;
    double outputPower = 0.0;     
    long lastSampleTime = 0;
    
    PID myPID(&measuredTemp, &outputPower, &setTemp, kp, ki, kd, REVERSE); //4
    
    void setup() {
      pinMode(coolPin, OUTPUT);
      pinMode(ledPin, OUTPUT);
      Serial.begin(9600);
      sensors.begin();
      myPID.SetSampleTime(1000); // (4)
      myPID.SetMode(AUTOMATIC);  
    }
    
    void loop() { //5
      long now = millis();         
      if (now > lastSampleTime + period) {
          checkTemperature();
          lastSampleTime = now;
      }
      setTemp = readSetTempFromPot(); //6
    }
    
    void checkTemperature() {      //7
      measuredTemp = readTemp();  
      Serial.print(measuredTemp);
      Serial.print(", ");
      Serial.print(setTemp);
      Serial.print(", ");
      Serial.println(outputPower);
      myPID.Compute();
      analogWrite(coolPin, outputPower);
      float error = setTemp - measuredTemp;//8
      if (abs(error) < tempOKMargin) {        
        digitalWrite(ledPin, HIGH);
      }
      else {
        digitalWrite(ledPin, LOW);
      }
    }
    
    double readSetTempFromPot() {   //9
      int raw = analogRead(potPin);
      double temp = map(raw, 0, 1023, minTemp, maxTemp);
      return temp;
    } 
    
    double readTemp() {
      sensors.requestTemperatures(); 
      return sensors.getTempCByIndex(0);  
    }
    Let's clarify some points in the program step by step, using the line annotations provided in the comments:

    1. The temperature range set by the potentiometer is determined by two constants: minTemp and maxTemp. The variable tempOKMargin defines the value above or below the set temperature that the actual temperature can have before the green LED turns off.
    2. A relatively high value is set for kp to ensure that the cooler turns on and off more precisely. This is mainly done to eliminate the dull sound of the fan motors when they are powered at low output power levels. Instead, the fans can be powered separately to run continuously, and only regulate the power to the Peltier element.
    3. Additional contacts are defined for the LED and potentiometer.
    4. The PID controller is initialized in REVERSE mode instead of DIRECT(as before) because adding output power will lower, not raise, the temperature.
    5. In the main loop, the passage of a one-second interval is checked, and the checkTemperaturefunction is called as needed to turn the cooler on and off.
    6. Each cycle (which should occur several times per second) calls the readsetTempFromPot function to set the setTempvariable based on the potentiometer position.
    7. The checkTemperature function measures the temperature, reads the data obtained, and then updates the PID controller. This function also writes the read data to the serial monitor window, allowing you to adjust the cooler or track its operation. Arduino does not need to be connected via the USB port since it receives power through its Vin pin, but if it is connected via the USB port, the output data can be viewed on the serial monitor window.
    8. The rest of this function includes turning on the LED if the measured temperature is within an acceptable deviation from the set temperature, determined by the tempOKMargin constant. The abs function (absolute value) removes the minus sign from a number.
    9. The code for converting the potentiometer position into a value between minTemp and maxTemp. The raw analog read (values ranging from 0 to 1023) is stored in the variable raw. Then, the map function is called to convert the read value into the desired temperature range (see the section below for more details).

    MAP FUNCTION USED IN ARDUINO
    When controlling devices with Arduino or Raspberry Pi, there is often a need to convert a number from one range of values to another range of values.

    For example, on an Arduino analog input with a range of values from 0 to 1023, if you need to map this range to a temperature range, such as from 0 to 20, you can simply divide the number by 51.15 (i.e., 1023/20). Then, 1023 will be converted to 1023/51.15 = 20.

    The task becomes more complex when both ranges do not start at zero. This is where the built-in map function in Arduino can be useful. As shown below, it takes five parameters to convert a number from the range of 0 to 1023 to a range of 20 to 40
    map(value, 0, 1023, 20, 40);
    Here, the first parameter is the value to be converted, the second and third parameters define the range of the existing value, and the fourth and fifth define the range in which to obtain the corresponding value (in this case, the range from 20 to 40).

    In the Python language, there is no built-in range function, but it can be easily created and then used in your program. It should look something like this:

    Code: Select all

    def map(value, from_low, from_high, to_low, to_high):
        from_range = from_high - from_low
        to_range = to_high - to_low 
        scale_factor = from / to_range
        return to_low + (value / scale_factor)
    Then you can call this function in Python with the same parameters as its Arduino counterpart. For example: map(510, 0, 1023, 20, 40)

    As a result, the function will return the value 30, which is the midpoint value for the range from 20 to 40, just as the value 510 is roughly in the middle of the range from 0 to 1023.