# Converting the output of a Proportional Control System to a meaningful drive of a Peltier

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.

• 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. – TimWescott Nov 16 '18 at 18:21
• 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. – TimWescott Nov 16 '18 at 18:25
• 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? – J. Doe Nov 16 '18 at 18:27
• 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? – TimWescott Nov 16 '18 at 18:31
• 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. – J. Doe Nov 17 '18 at 14:55