0
\$\begingroup\$

Trying to create a PID controller for temperature control using a Peltier element, I decided to take it step by step and create first the Proportional and then the Integral and the Derivative algorithms.

The whole system is based on a raspberry-pi zero which drives with the help of a Syren 10A Driver a Peltier element.

As you have understood (since I talked about PIDs) I want to set a target temperature and reach it as close as possible.

Initially, I tried a bang-bang method to do that but the results weren't so hopeful. I had a constant error around +/- 5 degrees. The code I used was simple enough and it follows:

import pigpio
import time
import os
from w1thermsensor import W1ThermSensor

pi = pigpio.pi()

FAN_PIN = 5
PLTR_PIN = 18


def FAN_ON( fanPIN ):
    print("[on] FAN")
    pi.write( fanPIN, 1 )

def FAN_OFF( fanPIN ):
    print("[off] FAN")
    pi.write( fanPIN, 0 )

def PLTR_OFF( pltrPIN ):
    print("[off] PELTIER")
    pi.set_servo_pulsewidth( pltrPIN, 1500 )

def PLTR_ON( pltrPIN ):
    print("[on] PELTIER")
    pi.set_servo_pulsewidth( pltrPIN, 2000 )
    #pi.set_servo_pulsewidth( pltrPIN, 1600 )

def PLTR_REV( pltrPIN ):
    print("[reverse] PELTIER")
    pi.set_servo_pulsewidth( pltrPIN, 1000 )

def GET_TEMP():
    sensor = W1ThermSensor()
    temp   = sensor.get_temperature()
    return temp


try:
    print("System is starting...")
    FAN_ON( FAN_PIN )
    PLTR_OFF( PLTR_PIN )

    target_temp = 55

    PLTR_ON( PLTR_PIN )
    time.sleep(3)
    PLTR_OFF( PLTR_PIN )
    time.sleep(4)

    while True:
        error = GET_TEMP() - target_temp
        print( "Error: ", error, "Temp: ", GET_TEMP() )

        if (error > 10):
            PLTR_OFF( PLTR_PIN )
            time.sleep(15)
        elif (error <= 10 and error >= 0):
            PLTR_OFF( PLTR_PIN )
            time.sleep(7)
            PLTR_ON( PLTR_PIN )
            time.sleep(1)
        elif (error < -10):
            PLTR_OFF( PLTR_PIN )
            time.sleep(2)
            PLTR_ON( PLTR_PIN )
            time.sleep(3)
        elif (error >= -10 and error <= 0):
            PLTR_OFF( PLTR_PIN )
            time.sleep(0)
            PLTR_ON( PLTR_PIN )
            time.sleep(0)

except KeyboardInterrupt:
    FAN_OFF( FAN_PIN )
    PLTR_OFF( PLTR_PIN )
    pi.stop()

So then I thought to give a try to the PIDs. So I start coding the first part. The Proportional one and here is the code:

from time import sleep
from w1thermsensor import W1ThermSensor
import pigpio

pi = pigpio.pi()


def GET_TEMP():
    sensor = W1ThermSensor()
    temp   = sensor.get_temperature()
    return temp

def PLTR_CTRL( PWM_VALUE ):
    pi.set_servo_pulsewidth( PLTR_PIN, PWM_VALUE )

def FAN_CTRL( FAN_VALUE ):
    pi.write( FAN_PIN, FAN_VALUE )

# PIN CONFIGURATION
PLTR_PIN    = 18
FAN_PIN     = 5

# PARAMETERS CONFIGURATION
SAMPLE_TIME = 1
PWM_ON      = 2000
PWM_OFF     = 1500
PWM_REVERSE = 1000
TARGET_TEMP = 40

KP          = 0.02

output      = 1

# MAIN PROGRAM STARTS HERE

# INITIALLY OPEN THE FAN
FAN_CTRL(1)
# AND THE PELTIER
PLTR_CTRL( PWM_ON )

try:

    while True:

        error = TARGET_TEMP - GET_TEMP()
        output += error * KP
        # Turn it in a value between 0 an 1
        output = max(min(1, output),0)

        if ( output >= 0.5 ):
            PLTR_CTRL( PWM_ON )
        else:
            PLTR_CTRL( PWM_OFF )

        print("Temperature: ", GET_TEMP(), "\tError: ", error, "\t\t\tOutput: ", output)
        sleep(SAMPLE_TIME)


except KeyboardInterrupt:
    PLTR_CTRL( PWM_OFF )
    FAN_CTRL(0)
    pi.stop()

The problem here is that I cannot convert the output variable into a meaningful value for Peltier's control. I mean, I tried to split the values of the output into the half and open the Peltier for > 0.5 and close it otherwise. But that idea didn't really work because 0.5 output values can actually occur from either negative or positive error.

Here is a sample output:

Temperature:  22.937    Error:  17.375                  Output:  1
Temperature:  26.437    Error:  14.875                  Output:  1
Temperature:  31.125    Error:  10.375                  Output:  1
Temperature:  35.937    Error:  5.438000000000002       Output:  1
Temperature:  39.875    Error:  1.375                   Output:  1
Temperature:  43.437    Error:  -2.375                  Output:  0.9525
Temperature:  46.625    Error:  -5.561999999999998      Output: 0.8412600000000001
Temperature:  49.5      Error:  -8.625                  Output:  0.6687600000000001
Temperature:  51.875    Error:  -11.125                 Output:  0.4462600000000001
Temperature:  52.0      Error:  -12.311999999999998     Output:  0.20002000000000014
Temperature:  50.062    Error:  -10.811999999999998     Output:  0
Temperature:  46.875    Error:  -7.936999999999998      Output:  0
Temperature:  43.437    Error:  -4.5                    Output:  0
Temperature:  40.125    Error:  -1.125                  Output:  0
Temperature:  37.062    Error:  2.0                     Output:  0.04
Temperature:  34.5      Error:  4.688000000000002       Output:  0.13376000000000005
Temperature:  32.125    Error:  7.125                   Output:  0.27626000000000006
Temperature:  30.187    Error:  9.25                    Output:  0.46126000000000006
Temperature:  28.625    Error:  10.875                  Output:  0.67876
Temperature:  29.812    Error:  11.062999999999999      Output:  0.90002
Temperature:  33.312    Error:  7.938000000000002       Output:  1
Temperature:  37.375    Error:  3.9380000000000024      Output:  1
Temperature:  41.812    Error:  -0.375                  Output:  0.9925

If anyone has and idea on how to give Peltier meaning to ther output values I would appreceate it.

\$\endgroup\$
  • \$\begingroup\$ Peltier coolers don't like on/off drive -- they're very inefficient in that mode. It looks like your driver module will take a range of drive commands. I would take the output of your controller, scale it to fit the range of drive commands of your driver, and send that. \$\endgroup\$ – TimWescott Nov 16 '18 at 18:21
  • \$\begingroup\$ You should also check to see if you're exceeding the current at which the Peltier still cools. The thermal flux into a Peltier's cold side is something like \$\phi=k_1 i - k_2 i^2\$ (I don't know the proper terms for \$k_1\$ and \$k_2\$, you should get the idea). The first term is the actual thermoelectric effect; the second term is plain old resistive heating. If you're driving current in excess of \$\frac{k_1}{k_2}\$ then you're just heating up your "cold" side. \$\endgroup\$ – TimWescott Nov 16 '18 at 18:25
  • \$\begingroup\$ How exactly would you scale it to fit the range? I assume that by the range you mean the 1000 - 2000usec. Can you give me a simple example? \$\endgroup\$ – J. Doe Nov 16 '18 at 18:27
  • \$\begingroup\$ So you're treating it as a servo? If you have a variable \$x\$, how would you generate a pulse that's 1000\$\mu\$s when \$x = 0\$, 2000\$\mu\$s when \$x = 1\$, and varies linearly with x in between? \$\endgroup\$ – TimWescott Nov 16 '18 at 18:31
  • \$\begingroup\$ You mean something like NewValue = (((PowerValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin where OldMin = 0 , OldMax = 1, NewMin = 1500, NewMax = 2000 and PowerValue = power ? Note that I excluded the 1000usec pulse becasue it reverses the polarity and alters the hot side of peltier into cold. So I kept only the range 1500 - 2000. \$\endgroup\$ – J. Doe Nov 17 '18 at 14:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.