3DP: Heating Part 3

Ugh. My last post is good in some parts, bad in others. Reworked!

In order to reuse as much code as possible, I blended several structures into a larger one, and refactored the code. I also added a temperature monitor to stop the printer in case there is a temperature problem with heating.

If the temperature does not rise fast enough, or if the temperature does not drop fast enough, there is a problem. The best thing to do is to stop the machine.

The new structure to monitor a heating device (will help control Bed, Extruder, or Chamber). There is the PID control section (the PID code has not changed), a Temperature protection section, and an identifier for the heater. This code is not yet tested, so will change:

typedef struct myPidControls {
    double Input;
    double Output;

    //Define the aggressive and conservative Tuning Parameters
    double aggKp, aggKi, aggKd;
    double consKp, consKi, consKd;    
    
    int16_t *PWM;
    int16_t CurrentSlot;
    int16_t MaxSlotCount;
    int16_t PWM_Value;
    int16_t PWM_Sum;
    int16_t offset;
    double  OffsetMultiplier; // amount to bend the knee when temperature has reached kneeTemperature
                                // Can be 1.0,  0.9,  0.7, etc.  Lower number bends knee more.
    
    //double lastGap;
    double kneeTemperature; // temperature at which we should start the running sum over.
    
} MY_PID_CONTROLS;

typedef struct temperatureProtectionStruct {
    uint32_t                MaximumNumberOfMilliSecondsToWaitWhileMaintaining;    // 20000 
    uint32_t                TemperatureHysteresisDegreesCWhileMaintaining;      // 2   
        // when starting heater
    uint32_t                TemperatureRampUpMillisecondsWatchPeriod;       // 20000
    uint32_t                TemperatureRampUpIncreaseDegreesC;        // 2
        // temperatures
    double                  LastCurrentTemperature;
    uint32_t                lastMilliseconds;
    double                  lastDesiredTemperature;
    enum temperatureStates  lastTemperatureState; // off or not off

} TEMPERATURE_PROTECTION;

typedef struct temperatureControlStruct {
    double                  CurrentTemperature;

    double                  DesiredTemperature;
    enum temperatureStates  TemperatureState;
    MY_PID_CONTROLS         PIDControls;
    PID_TYPE                PID;
    TEMPERATURE_PROTECTION  TemperatureProtection;
    char                    HeaterASCIIidString[4];// "0\0xa"
} TEMPERATURE_CONTROL;

extern TEMPERATURE_CONTROL gBedTempControl;
extern TEMPERATURE_CONTROL gE0TempControl;

There is a new function called handleTemperatureWatch(). That function only determines if the printer shutdown routines should be called (is called Kill() in most 3D printers). This function is just a rough draft, but here it is. Note that by using long variables names, I get to read what the function is doing even without comments. Note the watch function does only that. This coding style that uses state machines allows you to think linearly and only about the problem at hand. (Note: I did some formatting in this cut/n/paste for the narrow code window):

//-------------------------------------------------------------
// Watch to make sure the temperature is changing the right amounts
//-------------------------------------------------------------
void handleTemperatureWatch(TEMPERATURE_CONTROL *pTempControl,TaskMonitor_t *TaskMonitorPtr){
    TEMPERATURE_PROTECTION *pTP = 
                     &pTempControl->TemperatureProtection;
    uint32_t elapsedTime = 
       getElapsedTimeInMilliseconds(pTP->lastMilliseconds);
    double tempDiff;
    
    
    if (pTempControl->TemperatureState != tempOffState){        
        // if we are in the "maintaining" mode, then 
        if (pTempControl->TemperatureState ==  tempMaintainedState){
            tempDiff = pTempControl->DesiredTemperature - pTempControl->CurrentTemperature;
            if (tempDiff < 0.0) 
             tempDiff = -tempDiff; // simple absolute()function
            
          // if temperature is out of our range
          if (tempDiff > 
           pTP->TemperatureHysteresisDegreesCWhileMaintaining){
                // if it has been out of our range too long, do a kill()
                if (elapsedTime > 
       pTP->MaximumNumberOfMilliSecondsToWaitWhileMaintaining){
                    GCodePrintString(HEATING_FAILED_STRING);
                    GCodePrintString(
                            pTempControl->HeaterASCIIidString);
                    GCodePrintString(PRINTER_HALTED_STRING);
                    TaskDelay(100,TaskMonitorPtr);// let messages clear out
                    vDoFullStop(pdTRUE,TaskMonitorPtr);// halt printing
                }else {
                    return;
                }
            } else {
                // refresh last time, temperature within range
                pTP->lastMilliseconds=getSystemTimeInMs();   
            }            
            return; // finished with "temp Maintained'
        }
    
        
        // if temperature is ramping up, then temperature rise must be occurring
        if (pTempControl->TemperatureState ==  tempRampUpState){
            // if we do not have a "last" temperature (is 0), then grab the current temperature
            if (pTP->LastCurrentTemperature==0.0){
                pTP->LastCurrentTemperature=pTempControl->CurrentTemperature;
            }

            // if the temperature has not gone up the increase, and time is past watchpoint
            // then is error.
            tempDiff =  pTempControl->CurrentTemperature - 
                                pTP->LastCurrentTemperature;            
            if (tempDiff < 0.0) tempDiff = -tempDiff; // simple absolute()function
            
            // if temperature has not increased enough
            if (tempDiff < 
                 pTP->TemperatureRampUpIncreaseDegreesC ){
                // then is enough time has passed.
                if (elapsedTime > 
                pTP->TemperatureRampUpMillisecondsWatchPeriod){
                    // then is an error
                    GCodePrintString(HEATING_FAILED_STRING);
                    GCodePrintString(
                        pTempControl->HeaterASCIIidString);
                    GCodePrintString(PRINTER_HALTED_STRING);
                    TaskDelay(100,TaskMonitorPtr);// let messages clear out
                    vDoFullStop(pdTRUE,TaskMonitorPtr);// halt printing            
                }else {
                    // refresh last time, temperature within range
                    pTP->lastMilliseconds=getSystemTimeInMs();   
                }                
                return;
            } else {
                // pick up current temperature
                pTP->LastCurrentTemperature = 
                             pTempControl->CurrentTemperature;
            }
        }
    }
        
    // if temperature is ramping down (or turned off), then temperature fall must be occurring
    if ( pTempControl->TemperatureState ==  tempRampDownState 
        || pTempControl->TemperatureState ==  tempOffState  ){
        // if we do not have a "last" temperature (is 0), then grab the current temperature
        if (pTP->LastCurrentTemperature==0.0){
            pTP->LastCurrentTemperature=
                             pTempControl->CurrentTemperature;
        }

        // if the temperature has not gone down any, and time is past watchpoint
        // then is error.
        tempDiff =  pTempControl->CurrentTemperature - pTP->LastCurrentTemperature;            
        
        // if temperature has not decreased enough
        if (tempDiff >= 0 ){
            // then is enough time has passed. (use same as maintained)
            if (elapsedTime > 
       pTP->MaximumNumberOfMilliSecondsToWaitWhileMaintaining){
                // if we are close to room temperature, ignore.
                if (pTempControl->CurrentTemperature <= 30.0 )
                    return;
                // then is an error
                GCodePrintString(HEATING_FAILED_STRING);
                GCodePrintString(
                         pTempControl->HeaterASCIIidString);
                GCodePrintString(PRINTER_HALTED_STRING);
                TaskDelay(100,TaskMonitorPtr);// let messages clear out
                vDoFullStop(pdTRUE,TaskMonitorPtr);// halt printing            
        }else {
            // refresh last time, temperature within range
            pTP->lastMilliseconds=getSystemTimeInMs();   
        }            
            return;
        }
    }   
}

The Bed temperature control task now looks simpler. It only calls the PID and PWM routines. Its’ only job is to turn the heater on or off, and regulate the PWM based on the PID decisions. Its’s initialization sets all the variables in the TEMPERATURE_CONTROL structure based on settings in the Configuration.h style files:

/*-----------------------------------------------------------*/
//-----------------------------------------------------------------------------
// task that monitors the bed temperature a few times per second
// It uses the PID values to control the PWM
//-----------------------------------------------------------------------------
static portTASK_FUNCTION( vBedTemperatureTask, pvParameters ){

 static   TaskMonitor_t *TaskMonitor = &TaskMonitorArray[BED_TEMPERATURE_TASK];
    
    (void) pvParameters;
    
    
        // initialize constants
    gBedTempControl.PIDControls.aggKp=4, 
        gBedTempControl.PIDControls.aggKi=0.2, 
        gBedTempControl.PIDControls.aggKd=1;
    //gBedTempControl.PID.consKp=1, gBedTempControl.PID.consKi=0.05, gBedTempControl.PID.consKd=0.25;
    gBedTempControl.PIDControls.consKp=1, 
        gBedTempControl.PIDControls.consKi=0.05, 
        gBedTempControl.PIDControls.consKd=0.25;
    
    gBedTempControl.PIDControls.PWM = BedPWM;
    gBedTempControl.PIDControls.MaxSlotCount = BED_PWM_SLOTS;
    gBedTempControl.PIDControls.kneeTemperature = BED_TEMPERATURE_KNEE_FOR_PID_CONTROL;
    gBedTempControl.PIDControls.OffsetMultiplier = BED_TEMPERATURE_PID_KNEE_SLOPE_MULTIPLIER;
    
    
    #ifdef ENABLE_THERMAL_PROTECTION_BED
     gBedTempControl.TemperatureProtection.MaximumNumberOfMilliSecondsToWaitWhileMaintaining = THERMAL_PROTECTION_BED_TIME_IN_SECONDS*1000;       // 20 
     gBedTempControl.TemperatureProtection.TemperatureHysteresisDegreesCWhileMaintaining = THERMAL_PROTECTION_BED_TEMPERATURE_HYSTERESIS_DEGREES_C;      // 2   
        // when starting heater
     gBedTempControl.TemperatureProtection.TemperatureRampUpMillisecondsWatchPeriod=THERMAL_BED_START_HEAT_WATCH_PERIOD*1000;       // 20
     gBedTempControl.TemperatureProtection.TemperatureRampUpIncreaseDegreesC= THERMAL_BED_START_TEMPERATURE_INCREASE_DEGREES_C;        // 2
    #endif
    
    PID_init(   &gBedTempControl.PID,
                gBedTempControl.PIDControls.Input, 
                gBedTempControl.PIDControls.Output, 
                gBedTempControl.DesiredTemperature,
                gBedTempControl.PIDControls.consKp, 
                gBedTempControl.PIDControls.consKi,
                gBedTempControl.PIDControls.consKd, 
                BED_TEMPERATURE_MONITOR_RATE_IN_MILLISECONDS,
                P_ON_M,  // specify proportional on input reading
                DIRECT);
    
    PID_SetMode(&gBedTempControl.PID,AUTOMATIC);

    for(;;) {
        
        vTaskDelay(pdMS_TO_TICKS(BED_TEMPERATURE_MONITOR_RATE_IN_MILLISECONDS));//Default: 20 times per second
        TaskMonitor->runCounter++;
        
        if (gBedTempControl.TemperatureState == tempOffState) {// if bed temperature not on
            #if USE_HARDWARE_PWM_FOR_BED_HEATER
                PWM_BedHeater_WriteCompare(0);// 0 output, heater off
            #else    
                PinWrite(BED_HEATER_PIN,LOW);// turn heater off
            #endif
             gBedTempControl.PIDControls.offset=0; // when turn off, reset the flag
            continue;// don't execute code below, go to for(;;)
        }
                
        // we are "ON".  
        #if USE_HARDWARE_PWM_FOR_BED_HEATER                
         PWM_BedHeater_WriteCompare(
                          (int8_t)gBedTempControl.PID.Output);
        #else
            applySoftwarePWM(&gBedTempControl.PID, 
                   &gBedTempControl.PID,BED_HEATER_PIN);
        #endif
    
    } 
}

There is also a state machine controlling the temperature status. The states are as follows:

enum temperatureStates {
    tempOffState=0
    ,tempRampUpState        // turned on, ramp up
    ,tempReachedState       // reached
    ,tempMaintainedState    // keep here
    ,tempRampDownState      // turned off, ramp down
    ,tempFailState          // machine must be killed
    
};

The heating device is either off, ramping up, station keeping, ramping down, or failed. The following code determines which state the heating device is in. That is all this code does:

//------------------------------------------------------------
// determine which state the heater is in
//------------------------------------------------------------
void handleStateCalculation(TEMPERATURE_CONTROL *pTempControl){
                
    if (pTempControl->DesiredTemperature ) {
        // temp going up
        if (pTempControl->CurrentTemperature < pTempControl->DesiredTemperature ){
            pTempControl->TemperatureState = tempRampUpState;
        } else {
            // current temperature == to desired (with a small window)
            if (pTempControl->CurrentTemperature >=  pTempControl->DesiredTemperature -1.0
                && pTempControl->CurrentTemperature <=  pTempControl->DesiredTemperature+1.0 ){
                    pTempControl->TemperatureState = tempMaintainedState;
                } else {
                    // temp going down
                    pTempControl->TemperatureState = tempRampDownState;
                }                            
        }                
    } else {
        // desired temperature is 0, turn off
        pTempControl->TemperatureState = tempOffState;
    }
}

The task that watches the heating devices and calculates the controls for the devices follows:

//------------------------------------------------------------
// task that calculates the bed and extruder temperatures a few times per second
//------------------------------------------------------------
static portTASK_FUNCTION( vTemperatureCalculateTask, pvParameters ){
 static   TaskMonitor_t *TaskMonitorPtr = &TaskMonitorArray[TEMPERATURE_CALC_TASK];


    (void) pvParameters;
    
    startADC();
    
    for(;;) {
        // wait enough time for the A/D sequencer to finish                
        vTaskDelay(pdMS_TO_TICKS(2));//Default: around 100 times per second (will vary)

        TaskMonitorPtr->runCounter++;
        
        handleADCReadings();// create the temperatures
        
        //================================================
        // BED
        //================================================            
        // Now calculate the temperatures. Plug results into the PID structures
        calculateBedTemperature();
        handleStateCalculation(&gBedTempControl);
        // only run the controller if is heating
        if (gBedTempControl.TemperatureState != tempOffState) {

        gBedTempControl.PID.Setpoint = 
            gBedTempControl.DesiredTemperature;
        gBedTempControl.PID.Input = 
             gBedTempControl.CurrentTemperature;

            PID_Compute(&gBedTempControl.PID);

            #ifdef ENABLE_THERMAL_PROTECTION_BED
             handleTemperatureWatch(&gBedTempControl,TaskMonitorPtr);
            #endif
        }
        //================================================
        // EXTRUDER
        //================================================            
        
        calculateExtruderTemperature(); 
        handleStateCalculation(&gE0TempControl);
        if (gE0TempControl.TemperatureState != tempOffState){
        gE0TempControl.PID.Setpoint = 
                 gE0TempControl.DesiredTemperature;

            gE0TempControl.PID.Input = gE0TempControl.CurrentTemperature;
            PID_Compute(&gE0TempControl.PID);
            #ifdef ENABLE_THERMAL_PROTECTION_EXTRUDER
            handleTemperatureWatch(&gE0TempControl);
            #endif
        }            
       
    } 
}

Next Time

I will be testing this code and making it work. It may delay the next post as I work out the kinks.

I expect to unveil the schematic for a CY8CKit-059 to Ramps 1.4 adapter board. With that board, 3D printing will be available to PSOC users. Just add a Ramps 1v4 board electronics and a 3D printer frame, some software, and start working. Hah!

I have updated the board for SPI LCD and possibly SD Card. Once that board is in and the SPI LCD (is called a 12864 display for 3D aficionados) is working, I will start posting some of that code.

Enjoy!

Add a Comment

Your email address will not be published.