Raspberry Pi Pico Internal Temperature Sensor Based Fan Speed Control using PID Algorithm with Anti-Windup Logic

Posted

in

by

This system uses the Raspberry pi pico development board which has an RP2040 microcontroller. The RP2040 microcontroller has an internal temperature sensor.

Using its internal temperature sensor I have devised a very simple setup that demonstrates the PID algorithm. Using PID Algorithm control technique I am controlling the fan speed by changing the PWM duty cycle.

Component required:

  1. Raspberry Pi Pico
  2. PC Fan (+12V)
  3. L298n Module
  4. +12V Power supply

Commonly the PC fan changes its speed by PWM signal, I am sending a PWM signal of frequency 29Khz. Most PC fans operate from 25Khz to 30Khz. PWM signal duty cycle can be from 30% to 100%. At duty cycle lower than 30% PC fan won’t start.

We will dive into the code and explain each step of the implementation.

Code Overview:
The code provided is written in MicroPython and utilizes the machine module for hardware interactions. Here’s an overview of the key components and functionalities:

  1. PWM Configuration: The code configures a GPIO pin for Pulse Width Modulation (PWM) and sets the frequency to 29 kHz. PWM is used to control the speed of the fan.
  2. Temperature Sensor Configuration: The internal temperature sensor is configured using the ADC (Analog-to-Digital Converter) module. The conversion factor is calculated to convert the raw ADC readings to temperature values.
  3. PID Algorithm Implementation: The PID algorithm is implemented using proportional (P), integral (I), and derivative (D) control constants. The target temperature is set, and the error between the target temperature and the measured temperature is calculated. The code calculates the proportional, integral, and derivative terms and combines them to obtain the PID value.
  4. Anti-Windup Logic: To prevent windup issues, an anti-windup logic is implemented. It limits the PID value within a valid range to avoid saturation or unstable behavior.
  5. Timer Interrupts: Timer objects are created to trigger callback functions at regular intervals. The 100 ms timer callback updates the PID value and adjusts the fan speed accordingly, while the 1-second timer callback prints relevant information for monitoring and debugging purposes.

Implementation Walkthrough:

  1. PWM and Temperature Sensor Configuration: The code starts by configuring the PWM pin and the internal temperature sensor.
  2. PID Constants and Variables: The proportional control constant (kp), minimum duty cycle value (min_duty_cycle), and target temperature (target_temp) are set. Variables for duty cycle, error, temperature, integral error (i_err), integral count (i_cnt), derivative count (d_cnt), previous error (prev_err), and PID value (PID) are initialized.
  3. Timer Interrupts: Timer objects are created and initialized to trigger the respective callback functions every 100 ms and 1 second.
  4. PID Calculation and Fan Control: In the timer callback function for 100 ms, the raw temperature is converted to a temperature value. The error, integral error, derivative count, proportional count, and PID value are calculated. The PID value is limited to a valid range and converted to an appropriate duty cycle value for the PWM. The duty cycle is applied to control the fan speed.
  5. Monitoring and Debugging: The timer callback function for 1 second prints relevant information such as the PID value, error, duty cycle, temperature, derivative count, integral count, and proportional count.
import machine
import utime

# Configure the GPIO pin for PWM
pwm_pin = machine.Pin(9)
pwm = machine.PWM(pwm_pin)

# Set the PWM frequency to 29 kHz
pwm.freq(29000)

# Configure the internal temperature sensor
sensor_temp = machine.ADC(machine.ADC.CORE_TEMP)
conversion_factor = 3.3 / 65535

# Set the proportional control constants
#target_temp = 25  # Set the desired target temperature
# Set the proportional control constants
#target_temp = 25.63997  # Set the desired target temperature
target_temp = 25.17182  # Set the desired target temperature
#target_temp = 24.70368
#target_temp = 24.23554
#target_temp = 23.76739
#target_temp = 23.29925
kp = (((2**16)-1) * -1)
min_duty_cycle = (((2**16)-1) * 0.3)  # Minimum duty cycle value to start the fan

duty_cycle = 0
error = 0
temperature = 0

# Initialize the timestamp for 1 second intervals
timestamp_1s = utime.time()
p_cnt = 0
i_err = 0
Ki = -200
i_cnt = 0

prev_err = 0
Kd = -2500
d_cnt = 0
PID = 0
# Timer interrupt callback function for 100 ms interval
def timer_callback_100ms(timer):
    global duty_cycle, error, temperature,i_err,i_cnt,prev_err,Kd,d_cnt,p_cnt,PID,Ki

    raw_temp = sensor_temp.read_u16() * conversion_factor
    temperature = (27 - (raw_temp - 0.706) / 0.001721)
    error = target_temp - temperature
    i_err += (error)
    i_cnt = int(i_err * Ki)
    d_cnt = ((error - prev_err) * Kd)/0.1
    p_cnt = error * kp
    PID = (p_cnt + d_cnt + i_cnt)
    if PID >= 65535:
        PID = 65535
    if PID <= 0 :
        PID = 0
    duty_cycle = int(PID)
    
    pwm.duty_u16(duty_cycle)
    '''
    if error >= 0:
        #duty_cycle = max((duty_cycle - int(error * kp)), 0)
        duty_cycle = int(max(( d_cnt + i_cnt + (1 * error * kp)), 0))
        if duty_cycle <= 0:
            duty_cycle = 0
        pwm.duty_u16(duty_cycle)
    elif error < 0:
        duty_cycle = int(min( d_cnt + i_cnt +  error * kp), 65535))
        
        #duty_cycle = min(max((duty_cycle + int(-1 * error * kp)), int(min_duty_cycle)), 65535)
        pwm.duty_u16(int(duty_cycle))
    '''
    prev_err = error

# Timer interrupt callback function for 1 second interval
def timer_callback_1s(timer):
    print(f"pid: {PID}\terror: {error}\tduty: {duty_cycle}\tTemp: {temperature}\td_cnt: {d_cnt}\ti_cnt: {i_cnt}\tp_cnt: {p_cnt}")

# Create timer objects
timer_100ms = machine.Timer()
timer_1s = machine.Timer()

# Configure the timer to trigger the 100 ms callback function every 100 milliseconds
timer_100ms.init(period=100, mode=machine.Timer.PERIODIC, callback=timer_callback_100ms)

# Configure the timer to trigger the 1 second callback function every 1 second
timer_1s.init(period=1000, mode=machine.Timer.PERIODIC, callback=timer_callback_1s)

# Main loop
while True:
    # Add any other desired non-blocking operations here
    
    # Sleep briefly to avoid excessive CPU usage
    utime.sleep(0.01)

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *