Page 1 of 1

CP - PID controller for reflow hot-plate

Posted: Mon Nov 22, 2021 8:24 pm
by slight
Hello community,
is there anywhere out there a example of how to implement / what library to use for PID controller applications in CircuitPython?
i want to try to create a reflow soldering hot-plate controller :-)

i did not find any PID example in the learn search..

my next try would be the simple-pid library - as it is written in pure python and has no external dependencies..

please let me know all ideas / tips you have :-)

sunny greetings
stefan

Re: CP - PID controller for reflow hot-plate

Posted: Wed Nov 24, 2021 12:37 pm
by mikeysklar
stefan,

Because CircuitPython / MicroPython and Python are all so close if you can find an example in one it probably can easily be moved to another. Take a look at this simple micropython PID library.

https://github.com/B3AU/micropython/blob/master/PID.py

Code: Select all

__author__ = 'beau'
import pyb
class PID:
    """
    Discrete PID control
    """

    def __init__(self,input_fun,output_fun, P=3., I=0.01, D=0.0):

        self.Kp=P
        self.Ki=I
        self.Kd=D

        self.I_value = 0
        self.P_value = 0
        self.D_value = 0

        self.I_max=100.0
        self.I_min=0

        self.set_point=0.0

        self.prev_value = 0

        self.output = 0

        self.output_fun = output_fun
        self.input_fun = input_fun

        self.last_update_time = pyb.millis()


    def update(self):

        if pyb.millis()-self.last_update_time > 500:
            """
            Calculate PID output value for given reference input and feedback
            """
            current_value = self.input_fun()
            self.error = self.set_point - current_value
            print ('temp '+str(current_value))
            print ('SP'+str(self.set_point))

            self.P_value = self.Kp * self.error
            self.D_value = self.Kd * ( current_value-self.prev_value)


            lapsed_time = pyb.millis()-self.last_update_time
            lapsed_time/=1000. #convert to seconds
            self.last_update_time = pyb.millis()





            self.I_value += self.error * self.Ki

            if self.I_value > self.I_max:
                self.I_value = self.I_max
            elif self.I_value < self.I_min:
                self.I_value = self.I_min

            self.output = self.P_value + self.I_value - self.D_value

            if self.output<0:
                self.output = 0.0
            if self.output>100:
                self.output = 100.0

            print("Setpoint: "+str(self.set_point))
            print("P: "+str(self.P_value))
            print("I: "+str(self.I_value))
            print("Output: "+str(self.output))
            print ()

            self.output_fun(self.output/100.0)

            self.last_update_time=pyb.millis()

Re: CP - PID controller for reflow hot-plate

Posted: Tue Nov 30, 2021 10:17 pm
by adafruit2
tbh hot plates are so slow you dont need PID - just heat to 220-240 *C then turn off the heat. :)
https://learn.adafruit.com/ez-make-oven

Re: CP - PID controller for reflow hot-plate

Posted: Wed Dec 01, 2021 7:00 am
by slight
thanks @adafruit2!
that is a very useful recourse... ;-)
seems i only have to port it to my PyBadge (my main controller)
and see what happens...

i have some ceramic heating elements - i think they could be way faster than normal hotplates...

i will let you know!

sunny greetings
stefan

Re: CP - PID controller for reflow hot-plate

Posted: Mon Jan 10, 2022 1:37 pm
by slight
hello again :-)

i made some progress -
and basics are working now

my pid implementation is based on these sources:
my result:
https://github.com/s-light/cp_reflow_co ... ain/pid.py

Code: Select all

#!/usr/bin/env python3
# coding=utf-8

# SPDX-FileCopyrightText: Copyright (c) 2021 Stefan Krüger s-light.eu
#
# SPDX-License-Identifier: MIT

"""
simple pid control.

based on
https://github.com/B3AU/micropython/blob/master/PID.py
and
https://www.embeddedrelated.com/showarticle/943.php
and
http://brettbeauregard.com/blog/2011/04/improving-the-beginners-pid-direction/
"""
import time


class PID:
    """
    Simple PID control.

    P: proportional
    I: integral
    D: derivative
    """

    def __init__(
        self,
        input_fun,
        output_fun,
        *,  # force keyword arguments
        update_intervall=0.1,
        P_gain=0.0,
        I_gain=0.0,
        D_gain=0.0,
        output_min=0.0,
        output_max=100.0,
        debug_out_print=False,
        debug_out_fun=None,
    ):
        self.input_fun = input_fun
        self.output_fun = output_fun
        self.update_intervall = update_intervall

        self.P_gain = P_gain
        self.P_value = 0

        self.I_gain = I_gain
        self.I_value = 0
        self.I_state = 0
        self.I_max = 100.0
        self.I_min = 0

        self.D_gain = D_gain
        self.D_value = 0
        self.D_state = 0

        self.output_min = output_min
        self.output_max = output_max

        self.debug_out_print = debug_out_print
        self.debug_out_fun = debug_out_fun
        self.set_point = 0.0
        self.output = 0

        self.last_update_time = time.monotonic()

    def _update(self, current_value):
        # calculate the proportional term
        self.P_value = self.P_gain * self.error

        # calculate the integral state with appropriate limiting
        self.I_state += self.error
        # Limit the integrator state if necessary
        if self.I_state > self.I_max:
            self.I_state = self.I_max
        elif self.I_state < self.I_min:
            self.I_state = self.I_min

        # calculate the integral term
        self.I_value = self.I_gain * self.I_state

        # calculate the derivative term
        self.D_value = self.D_gain * (current_value - self.D_state)
        self.D_state = current_value

        # calculate output
        self.output = self.P_value + self.I_value - self.D_value

        # limit output
        if self.output < self.output_min:
            self.output = self.output_min
        if self.output > self.output_max:
            self.output = self.output_max

        if self.debug_out_fun or self.debug_out_print:
            debug_out = (
                # "set_point: {set_point}\n"
                "P_value: {P_value: > 7.2f}  "
                "I_value: {I_value: > 7.2f}  "
                "D_value: {D_value: > 7.2f}  "
                "output: {output: > 7.2f}  "
                "".format(
                    # set_point=self.set_point,
                    P_value=self.P_value,
                    I_value=self.I_value,
                    D_value=self.D_value,
                    output=self.output,
                )
            )
            if self.debug_out_fun:
                self.debug_out_fun(debug_out)
            if self.debug_out_print:
                print(debug_out, end="")

        return self.output / 100.0

    def update(
        self,
        *,  # force keyword arguments
        current_value=None,
        set_point=None,
        error=None,
    ):
        """Calculate PID output value."""
        output = None
        elapsed_time = time.monotonic() - self.last_update_time
        if elapsed_time > self.update_intervall:
            if not current_value:
                current_value = self.input_fun()
            if set_point:
                self.set_point = set_point
            if not error:
                self.error = self.set_point - current_value
                if self.debug_out_fun or self.debug_out_print:
                    debug_out = (
                        "current_value: {current_value: > 7.2f}  "
                        "set_point: {set_point: > 7.2f}  "
                        "error: {error: > 7.2f}  "
                        "".format(
                            current_value=current_value,
                            set_point=self.set_point,
                            error=self.error,
                        )
                    )
                    if self.debug_out_fun:
                        self.debug_out_fun(debug_out)
                    if self.debug_out_print:
                        print(debug_out, end="")
            else:
                self.error = error
            output = self._update(current_value)
            self.output_fun(output)
            self.last_update_time = time.monotonic()
            if self.debug_out_print:
                print()
        return output


my full CircuitPythone reflow controller can be found at
https://github.com/s-light/cp_reflow_controller

and all the documentation at these both places: sunny greetings
stefan