Example #1
0
class ReflowStateMachine(object):
    # Reflow profiles
    LEADED_PROFILE = 'leaded'
    LEAD_FREE_PROFILE = 'leadfree'

    # Constants
    TEMPERATURE_ROOM = 50
    SENSOR_SAMPLING_TIME = 1000
    SOAK_TEMPERATURE_STEP = 5
    SOAK_MICRO_PERIOD = 9000

    def __init__(self, reflowProfile, thermocouple=None, relay=None, lcd=None):
        self.__reflowProfile = None

        if (reflowProfile == self.LEAD_FREE_PROFILE):
            self.__reflowProfile = ReflowLeadFreeProfile()
        else:
            self.__reflowProfile = ReflowLeadedProfile()

        self.__reflowOvenPidContext = PIDContext(_input=0.0,
                                                 _output=0.0,
                                                 _setpoint=0.0)
        self.__reflowOvenPid = PID(self.__reflowOvenPidContext,
                                   Kp=self.__reflowProfile.PID_KP_PREHEAT,
                                   Ki=self.__reflowProfile.PID_KI_PREHEAT,
                                   Kd=self.__reflowProfile.PID_KD_PREHEAT,
                                   direction=PID.DIRECT)

        self.__thermocouple = thermocouple
        self.__relay = relay
        self.__lcd = lcd
        self.__windowSize = 2000
        self.__windowStartTime = datetime.now()
        self.__nextCheck = datetime.now()
        self.__nextRead = datetime.now()
        self.__timerSoak = 0.0
        self.__reflowState = ReflowState.REFLOW_STATE_IDLE
        self.__reflowStatus = ReflowStatus.REFLOW_STATUS_OFF
        self.__timerSeconds = 0.0

    def Reflow(self):
        reflowCycleComplete = False
        now = datetime.now()
        while (reflowCycleComplete == False):
            # Time to read the thermocouple?
            if (datetime.now() > self.__nextRead):
                # Read thermocouple next sampling period
                self.__nextRead += timedelta(
                    milliseconds=self.SENSOR_SAMPLING_TIME)
                # Read current temperature
                try:
                    self.__reflowOvenPidContext.Params[
                        PIDContext.Input] = self.__thermocouple.ReadCelsius()
                except Exception as e:
                    # Thermocouple error
                    self.__reflowState = ReflowState.REFLOW_STATE_ERROR
                    self.__reflowStatus = ReflowStatus.REFLOW_STATUS_OFF

            if (datetime.now() > self.__nextCheck):
                # Check the Input within the next second
                self.__nextCheck += timedelta(milliseconds=1000)
                # If reflow process is ongoing
                if (self.__reflowStatus == ReflowStatus.REFLOW_STATUS_ON):
                    self.__timerSeconds += 1

                if (self.__lcd is not None):
                    self.__lcd.Clear()
                    self.__lcd.Print(ReflowState.Messages[self.__reflowState])
                    self.__lcd.SetCursor(0, 1)

                    if (self.__reflowState == ReflowState.REFLOW_STATE_ERROR):
                        self.__lcd.Print("No thermocouple connected!")
                    else:
                        self.__lcd.Print(
                            str(self.__reflowOvenPidContext.Params[
                                PIDContext.Input]) + "C ")

            # Reflow oven controller state machine
            if (self.__reflowState == ReflowState.REFLOW_STATE_IDLE):
                if (self.__reflowOvenPidContext.Params[PIDContext.Input] >=
                        self.TEMPERATURE_ROOM):
                    self.__reflowState = ReflowState.REFLOW_STATE_TOO_HOT
                else:
                    # Intialize seconds timer for serial debug information
                    self.__timerSeconds = 0
                    # Initialize PID control window starting time
                    self.__windowStartTime = datetime.now()
                    # Ramp up to minimum soaking temperature
                    self.__reflowOvenPidContext.Params[
                        PIDContext.
                        SetPoint] = self.__reflowProfile.TEMPERATURE_SOAK_MIN
                    # Tell the PID to range between 0 and the full window size
                    self.__reflowOvenPid.SetOutputLimits(
                        0.0, self.__windowSize)
                    self.__reflowOvenPid.SetSampleTime(
                        self.__reflowProfile.PID_SAMPLE_TIME)
                    # Turn the PID on
                    self.__reflowOvenPid.SetMode(PID.AUTOMATIC)
                    # Proceed to preheat stage
                    self.__reflowState = ReflowState.REFLOW_STATE_PREHEAT

            elif (self.__reflowState == ReflowState.REFLOW_STATE_PREHEAT):
                self.__reflowStatus = ReflowStatus.REFLOW_STATUS_ON
                # If minimum soak temperature is achieved
                if (self.__reflowOvenPidContext.Params[PIDContext.Input] >=
                        self.__reflowProfile.TEMPERATURE_SOAK_MIN):
                    # Chop soaking period into smaller sub-periods
                    self.__timerSoak = datetime.now() + timedelta(
                        milliseconds=self.SOAK_MICRO_PERIOD)
                    # Set less agressive PID parameters for soaking ramp
                    self.__reflowOvenPid.SetTunings(
                        Kp=self.__reflowProfile.PID_KP_SOAK,
                        Ki=self.__reflowProfile.PID_KI_SOAK,
                        Kd=self.__reflowProfile.PID_KD_SOAK)
                    # Ramp up to first section of soaking temperature
                    self.__reflowOvenPidContext.Params[
                        PIDContext.
                        SetPoint] = self.__reflowProfile.TEMPERATURE_SOAK_MIN + self.SOAK_TEMPERATURE_STEP
                    # Proceed to soaking state
                    self.__reflowState = ReflowState.REFLOW_STATE_SOAK

            elif (self.__reflowState == ReflowState.REFLOW_STATE_SOAK):
                # If micro soak temperature is achieved
                if (datetime.now() > self.__timerSoak):
                    self.__timerSoak = (
                        datetime.now() +
                        timedelta(milliseconds=self.SOAK_MICRO_PERIOD))
                    # Increment micro setpoint
                    self.__reflowOvenPidContext.Params[
                        PIDContext.SetPoint] += self.SOAK_TEMPERATURE_STEP
                    if (self.__reflowOvenPidContext.Params[PIDContext.SetPoint]
                            > self.__reflowProfile.TEMPERATURE_SOAK_MAX):
                        # Set agressive PID parameters for reflow ramp
                        self.__reflowOvenPid.SetTunings(
                            Kp=self.__reflowProfile.PID_KP_REFLOW,
                            Ki=self.__reflowProfile.PID_KI_REFLOW,
                            Kd=self.__reflowProfile.PID_KD_REFLOW)
                        # Ramp up to first section of reflow temperature
                        self.__reflowOvenPidContext.Params[
                            PIDContext.
                            SetPoint] = self.__reflowProfile.TEMPERATURE_REFLOW_MAX
                        # Proceed to reflowing state
                        self.__reflowState = ReflowState.REFLOW_STATE_REFLOW

            elif (self.__reflowState == ReflowState.REFLOW_STATE_REFLOW):
                # We need to avoid hovering at peak temperature for too long
                # Crude method that works like a charm and safe for the components
                if (self.__reflowOvenPidContext.Params[PIDContext.Input] >=
                    (self.__reflowProfile.TEMPERATURE_REFLOW_MAX - 5)):
                    # Set PID parameters for cooling ramp
                    self.__reflowOvenPid.SetTunings(
                        Kp=self.__reflowProfile.PID_KP_REFLOW,
                        Ki=self.__reflowProfile.PID_KI_REFLOW,
                        Kd=self.__reflowProfile.PID_KD_REFLOW)
                    # Ramp down to minimum cooling temperature
                    self.__reflowOvenPidContext.Params[
                        PIDContext.
                        SetPoint] = self.__reflowProfile.TEMPERATURE_COOL_MIN
                    # Proceed to cooling state
                    self.__reflowState = ReflowState.REFLOW_STATE_COOL

            elif (self.__reflowState == ReflowState.REFLOW_STATE_COOL):
                # If minimum cool temperature is achieved
                if (self.__reflowOvenPidContext.Params[PIDContext.Input] <=
                        self.__reflowProfile.TEMPERATURE_COOL_MIN):
                    # Turn off reflow process
                    self.__reflowStatus = ReflowStatus.REFLOW_STATUS_OFF
                    # Proceed to reflow Completion state
                    self.__reflowState = ReflowState.REFLOW_STATE_COMPLETE

            elif (self.__reflowState == ReflowState.REFLOW_STATE_COMPLETE):
                # Reflow process ended
                self.__reflowState = ReflowState.REFLOW_STATE_IDLE
                # Exit the state machine loop
                reflowCycleComplete = True

            elif (self.__reflowState == ReflowState.REFLOW_STATE_TOO_HOT):
                # If oven temperature drops below room temperature
                if (self.__reflowOvenPidContext.Params[PIDContext.Input] <
                        self.TEMPERATURE_ROOM):
                    # Ready to reflow
                    self.__reflowState = ReflowState.REFLOW_STATE_IDLE

            elif (self.__reflowState == ReflowState.REFLOW_STATE_ERROR):
                # Exit the state machine loop
                reflowCycleComplete = True

            # PID computation and relay control
            if (self.__reflowStatus == ReflowStatus.REFLOW_STATUS_ON):
                now = datetime.now()
                self.__reflowOvenPid.Compute()
                if ((now - self.__windowStartTime) >
                        timedelta(milliseconds=self.__windowSize)):
                    # Time to shift the Relay Window
                    self.__windowStartTime += timedelta(
                        milliseconds=self.__windowSize)
                if (timedelta(milliseconds=self.__reflowOvenPidContext.Params[
                        PIDContext.Output]) > (now - self.__windowStartTime)):
                    self.__relay.SwitchRelay(RelayInterface.ON)
                else:
                    self.__relay.SwitchRelay(RelayInterface.OFF)
            else:
                self.__relay.SwitchRelay(RelayInterface.OFF)
Example #2
0
#!/usr/bin/python
from pid import PID, PIDContext
from datetime import datetime, timedelta

SetPoint = 260.0
Input = 1.0
Output = 0.0
WindowSize = 3000.0
WindowSizeTimeDelta = timedelta(milliseconds=WindowSize)
WindowStartTime = datetime.now()

pidContext = PIDContext(Input, Output, SetPoint)
pid = PID(pidContext, 0.2, 0.5, 0.1, PID.DIRECT)
pid.SetOutputLimits(0.0, WindowSize)
pid.SetMode(PID.AUTOMATIC)

while pidContext.Params[PIDContext.Input] < SetPoint:
    now = datetime.now()
    pid.Compute()

    print(pidContext.Params.__str__())

    output = pidContext.Params[PIDContext.Output]

    if (now - WindowStartTime > WindowSizeTimeDelta):
        WindowStartTime += WindowSizeTimeDelta

    pidContext.Params[PIDContext.Input] += 1

    if (timedelta(milliseconds=output) > now - WindowStartTime):
        print("Relay ON")