{"id":836,"date":"2021-01-28T22:27:19","date_gmt":"2021-01-28T22:27:19","guid":{"rendered":"http:\/\/socmaker.com\/?p=836"},"modified":"2021-01-28T22:28:31","modified_gmt":"2021-01-28T22:28:31","slug":"3dp-heating-part-3","status":"publish","type":"post","link":"https:\/\/socmaker.com\/?p=836","title":{"rendered":"3DP: Heating Part 3"},"content":{"rendered":"\n<p>Ugh.  My last post is good in some parts, bad in others.  Reworked!<\/p>\n\n\n\n<p>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.  <\/p>\n\n\n\n<p>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.<\/p>\n\n\n\n<p>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:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>typedef struct myPidControls {\n    double Input;\n    double Output;\n\n    \/\/Define the aggressive and conservative Tuning Parameters\n    double aggKp, aggKi, aggKd;\n    double consKp, consKi, consKd;    \n    \n    int16_t *PWM;\n    int16_t CurrentSlot;\n    int16_t MaxSlotCount;\n    int16_t PWM_Value;\n    int16_t PWM_Sum;\n    int16_t offset;\n    double  OffsetMultiplier; \/\/ amount to bend the knee when temperature has reached kneeTemperature\n                                \/\/ Can be 1.0,  0.9,  0.7, etc.  Lower number bends knee more.\n    \n    \/\/double lastGap;\n    double kneeTemperature; \/\/ temperature at which we should start the running sum over.\n    \n} MY_PID_CONTROLS;\n\ntypedef struct temperatureProtectionStruct {\n    uint32_t                MaximumNumberOfMilliSecondsToWaitWhileMaintaining;    \/\/ 20000 \n    uint32_t                TemperatureHysteresisDegreesCWhileMaintaining;      \/\/ 2   \n        \/\/ when starting heater\n    uint32_t                TemperatureRampUpMillisecondsWatchPeriod;       \/\/ 20000\n    uint32_t                TemperatureRampUpIncreaseDegreesC;        \/\/ 2\n        \/\/ temperatures\n    double                  LastCurrentTemperature;\n    uint32_t                lastMilliseconds;\n    double                  lastDesiredTemperature;\n    enum temperatureStates  lastTemperatureState; \/\/ off or not off\n\n} TEMPERATURE_PROTECTION;\n\ntypedef struct temperatureControlStruct {\n    double                  CurrentTemperature;\n\n    double                  DesiredTemperature;\n    enum temperatureStates  TemperatureState;\n    MY_PID_CONTROLS         PIDControls;\n    PID_TYPE                PID;\n    TEMPERATURE_PROTECTION  TemperatureProtection;\n    char                    HeaterASCIIidString&#91;4];\/\/ \"0\\0xa\"\n} TEMPERATURE_CONTROL;\n\nextern TEMPERATURE_CONTROL gBedTempControl;\nextern TEMPERATURE_CONTROL gE0TempControl;<\/code><\/pre>\n\n\n\n<p>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):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/-------------------------------------------------------------\n\/\/ Watch to make sure the temperature is changing the right amounts\n\/\/-------------------------------------------------------------\nvoid handleTemperatureWatch(TEMPERATURE_CONTROL *pTempControl,TaskMonitor_t *TaskMonitorPtr){\n    TEMPERATURE_PROTECTION *pTP = \n                     &amp;pTempControl-&gt;TemperatureProtection;\n    uint32_t elapsedTime = \n       getElapsedTimeInMilliseconds(pTP-&gt;lastMilliseconds);\n    double tempDiff;\n    \n    \n    if (pTempControl-&gt;TemperatureState != tempOffState){        \n        \/\/ if we are in the \"maintaining\" mode, then \n        if (pTempControl-&gt;TemperatureState ==  tempMaintainedState){\n            tempDiff = pTempControl-&gt;DesiredTemperature - pTempControl-&gt;CurrentTemperature;\n            if (tempDiff &lt; 0.0) \n             tempDiff = -tempDiff; \/\/ simple absolute()function\n            \n          \/\/ if temperature is out of our range\n          if (tempDiff &gt; \n           pTP-&gt;TemperatureHysteresisDegreesCWhileMaintaining){\n                \/\/ if it has been out of our range too long, do a kill()\n                if (elapsedTime &gt; \n       pTP-&gt;MaximumNumberOfMilliSecondsToWaitWhileMaintaining){\n                    GCodePrintString(HEATING_FAILED_STRING);\n                    GCodePrintString(\n                            pTempControl-&gt;HeaterASCIIidString);\n                    GCodePrintString(PRINTER_HALTED_STRING);\n                    TaskDelay(100,TaskMonitorPtr);\/\/ let messages clear out\n                    vDoFullStop(pdTRUE,TaskMonitorPtr);\/\/ halt printing\n                }else {\n                    return;\n                }\n            } else {\n                \/\/ refresh last time, temperature within range\n                pTP-&gt;lastMilliseconds=getSystemTimeInMs();   \n            }            \n            return; \/\/ finished with \"temp Maintained'\n        }\n    \n        \n        \/\/ if temperature is ramping up, then temperature rise must be occurring\n        if (pTempControl-&gt;TemperatureState ==  tempRampUpState){\n            \/\/ if we do not have a \"last\" temperature (is 0), then grab the current temperature\n            if (pTP-&gt;LastCurrentTemperature==0.0){\n                pTP-&gt;LastCurrentTemperature=pTempControl-&gt;CurrentTemperature;\n            }\n\n            \/\/ if the temperature has not gone up the increase, and time is past watchpoint\n            \/\/ then is error.\n            tempDiff =  pTempControl-&gt;CurrentTemperature - \n                                pTP-&gt;LastCurrentTemperature;            \n            if (tempDiff &lt; 0.0) tempDiff = -tempDiff; \/\/ simple absolute()function\n            \n            \/\/ if temperature has not increased enough\n            if (tempDiff &lt; \n                 pTP-&gt;TemperatureRampUpIncreaseDegreesC ){\n                \/\/ then is enough time has passed.\n                if (elapsedTime &gt; \n                pTP-&gt;TemperatureRampUpMillisecondsWatchPeriod){\n                    \/\/ then is an error\n                    GCodePrintString(HEATING_FAILED_STRING);\n                    GCodePrintString(\n                        pTempControl-&gt;HeaterASCIIidString);\n                    GCodePrintString(PRINTER_HALTED_STRING);\n                    TaskDelay(100,TaskMonitorPtr);\/\/ let messages clear out\n                    vDoFullStop(pdTRUE,TaskMonitorPtr);\/\/ halt printing            \n                }else {\n                    \/\/ refresh last time, temperature within range\n                    pTP-&gt;lastMilliseconds=getSystemTimeInMs();   \n                }                \n                return;\n            } else {\n                \/\/ pick up current temperature\n                pTP-&gt;LastCurrentTemperature = \n                             pTempControl-&gt;CurrentTemperature;\n            }\n        }\n    }\n        \n    \/\/ if temperature is ramping down (or turned off), then temperature fall must be occurring\n    if ( pTempControl-&gt;TemperatureState ==  tempRampDownState \n        || pTempControl-&gt;TemperatureState ==  tempOffState  ){\n        \/\/ if we do not have a \"last\" temperature (is 0), then grab the current temperature\n        if (pTP-&gt;LastCurrentTemperature==0.0){\n            pTP-&gt;LastCurrentTemperature=\n                             pTempControl-&gt;CurrentTemperature;\n        }\n\n        \/\/ if the temperature has not gone down any, and time is past watchpoint\n        \/\/ then is error.\n        tempDiff =  pTempControl-&gt;CurrentTemperature - pTP-&gt;LastCurrentTemperature;            \n        \n        \/\/ if temperature has not decreased enough\n        if (tempDiff &gt;= 0 ){\n            \/\/ then is enough time has passed. (use same as maintained)\n            if (elapsedTime &gt; \n       pTP-&gt;MaximumNumberOfMilliSecondsToWaitWhileMaintaining){\n                \/\/ if we are close to room temperature, ignore.\n                if (pTempControl-&gt;CurrentTemperature &lt;= 30.0 )\n                    return;\n                \/\/ then is an error\n                GCodePrintString(HEATING_FAILED_STRING);\n                GCodePrintString(\n                         pTempControl-&gt;HeaterASCIIidString);\n                GCodePrintString(PRINTER_HALTED_STRING);\n                TaskDelay(100,TaskMonitorPtr);\/\/ let messages clear out\n                vDoFullStop(pdTRUE,TaskMonitorPtr);\/\/ halt printing            \n        }else {\n            \/\/ refresh last time, temperature within range\n            pTP-&gt;lastMilliseconds=getSystemTimeInMs();   \n        }            \n            return;\n        }\n    }   \n}<\/code><\/pre>\n\n\n\n<p>The Bed temperature control task now looks simpler. It only calls the PID and PWM routines.  Its&#8217; only job is to turn the heater on or off, and regulate the PWM based on the PID decisions.  Its&#8217;s initialization sets all the variables in the TEMPERATURE_CONTROL structure based on settings in the Configuration.h style files:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/*-----------------------------------------------------------*\/\n\/\/-----------------------------------------------------------------------------\n\/\/ task that monitors the bed temperature a few times per second\n\/\/ It uses the PID values to control the PWM\n\/\/-----------------------------------------------------------------------------\nstatic portTASK_FUNCTION( vBedTemperatureTask, pvParameters ){\n\n static   TaskMonitor_t *TaskMonitor = &amp;TaskMonitorArray&#91;BED_TEMPERATURE_TASK];\n    \n    (void) pvParameters;\n    \n    \n        \/\/ initialize constants\n    gBedTempControl.PIDControls.aggKp=4, \n        gBedTempControl.PIDControls.aggKi=0.2, \n        gBedTempControl.PIDControls.aggKd=1;\n    \/\/gBedTempControl.PID.consKp=1, gBedTempControl.PID.consKi=0.05, gBedTempControl.PID.consKd=0.25;\n    gBedTempControl.PIDControls.consKp=1, \n        gBedTempControl.PIDControls.consKi=0.05, \n        gBedTempControl.PIDControls.consKd=0.25;\n    \n    gBedTempControl.PIDControls.PWM = BedPWM;\n    gBedTempControl.PIDControls.MaxSlotCount = BED_PWM_SLOTS;\n    gBedTempControl.PIDControls.kneeTemperature = BED_TEMPERATURE_KNEE_FOR_PID_CONTROL;\n    gBedTempControl.PIDControls.OffsetMultiplier = BED_TEMPERATURE_PID_KNEE_SLOPE_MULTIPLIER;\n    \n    \n    #ifdef ENABLE_THERMAL_PROTECTION_BED\n     gBedTempControl.TemperatureProtection.MaximumNumberOfMilliSecondsToWaitWhileMaintaining = THERMAL_PROTECTION_BED_TIME_IN_SECONDS*1000;       \/\/ 20 \n     gBedTempControl.TemperatureProtection.TemperatureHysteresisDegreesCWhileMaintaining = THERMAL_PROTECTION_BED_TEMPERATURE_HYSTERESIS_DEGREES_C;      \/\/ 2   \n        \/\/ when starting heater\n     gBedTempControl.TemperatureProtection.TemperatureRampUpMillisecondsWatchPeriod=THERMAL_BED_START_HEAT_WATCH_PERIOD*1000;       \/\/ 20\n     gBedTempControl.TemperatureProtection.TemperatureRampUpIncreaseDegreesC= THERMAL_BED_START_TEMPERATURE_INCREASE_DEGREES_C;        \/\/ 2\n    #endif\n    \n    PID_init(   &amp;gBedTempControl.PID,\n                gBedTempControl.PIDControls.Input, \n                gBedTempControl.PIDControls.Output, \n                gBedTempControl.DesiredTemperature,\n                gBedTempControl.PIDControls.consKp, \n                gBedTempControl.PIDControls.consKi,\n                gBedTempControl.PIDControls.consKd, \n                BED_TEMPERATURE_MONITOR_RATE_IN_MILLISECONDS,\n                P_ON_M,  \/\/ specify proportional on input reading\n                DIRECT);\n    \n    PID_SetMode(&amp;gBedTempControl.PID,AUTOMATIC);\n\n    for(;;) {\n        \n        vTaskDelay(pdMS_TO_TICKS(BED_TEMPERATURE_MONITOR_RATE_IN_MILLISECONDS));\/\/Default: 20 times per second\n        TaskMonitor-&gt;runCounter++;\n        \n        if (gBedTempControl.TemperatureState == tempOffState) {\/\/ if bed temperature not on\n            #if USE_HARDWARE_PWM_FOR_BED_HEATER\n                PWM_BedHeater_WriteCompare(0);\/\/ 0 output, heater off\n            #else    \n                PinWrite(BED_HEATER_PIN,LOW);\/\/ turn heater off\n            #endif\n             gBedTempControl.PIDControls.offset=0; \/\/ when turn off, reset the flag\n            continue;\/\/ don't execute code below, go to for(;;)\n        }\n                \n        \/\/ we are \"ON\".  \n        #if USE_HARDWARE_PWM_FOR_BED_HEATER                \n         PWM_BedHeater_WriteCompare(\n                          (int8_t)gBedTempControl.PID.Output);\n        #else\n            applySoftwarePWM(&amp;gBedTempControl.PID, \n                   &amp;gBedTempControl.PID,BED_HEATER_PIN);\n        #endif\n    \n    } \n}<\/code><\/pre>\n\n\n\n<p>There is also a state machine controlling the temperature status.  The states are as follows:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>enum temperatureStates {\n    tempOffState=0\n    ,tempRampUpState        \/\/ turned on, ramp up\n    ,tempReachedState       \/\/ reached\n    ,tempMaintainedState    \/\/ keep here\n    ,tempRampDownState      \/\/ turned off, ramp down\n    ,tempFailState          \/\/ machine must be killed\n    \n};\n<\/code><\/pre>\n\n\n\n<p>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:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/------------------------------------------------------------\n\/\/ determine which state the heater is in\n\/\/------------------------------------------------------------\nvoid handleStateCalculation(TEMPERATURE_CONTROL *pTempControl){\n                \n    if (pTempControl-&gt;DesiredTemperature ) {\n        \/\/ temp going up\n        if (pTempControl-&gt;CurrentTemperature &lt; pTempControl-&gt;DesiredTemperature ){\n            pTempControl-&gt;TemperatureState = tempRampUpState;\n        } else {\n            \/\/ current temperature == to desired (with a small window)\n            if (pTempControl-&gt;CurrentTemperature &gt;=  pTempControl-&gt;DesiredTemperature -1.0\n                &amp;&amp; pTempControl-&gt;CurrentTemperature &lt;=  pTempControl-&gt;DesiredTemperature+1.0 ){\n                    pTempControl-&gt;TemperatureState = tempMaintainedState;\n                } else {\n                    \/\/ temp going down\n                    pTempControl-&gt;TemperatureState = tempRampDownState;\n                }                            \n        }                \n    } else {\n        \/\/ desired temperature is 0, turn off\n        pTempControl-&gt;TemperatureState = tempOffState;\n    }\n}<\/code><\/pre>\n\n\n\n<p>The task that watches the heating devices and calculates the controls for the devices follows:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/------------------------------------------------------------\n\/\/ task that calculates the bed and extruder temperatures a few times per second\n\/\/------------------------------------------------------------\nstatic portTASK_FUNCTION( vTemperatureCalculateTask, pvParameters ){\n static   TaskMonitor_t *TaskMonitorPtr = &amp;TaskMonitorArray&#91;TEMPERATURE_CALC_TASK];\n\n\n    (void) pvParameters;\n    \n    startADC();\n    \n    for(;;) {\n        \/\/ wait enough time for the A\/D sequencer to finish                \n        vTaskDelay(pdMS_TO_TICKS(2));\/\/Default: around 100 times per second (will vary)\n\n        TaskMonitorPtr-&gt;runCounter++;\n        \n        handleADCReadings();\/\/ create the temperatures\n        \n        \/\/================================================\n        \/\/ BED\n        \/\/================================================            \n        \/\/ Now calculate the temperatures. Plug results into the PID structures\n        calculateBedTemperature();\n        handleStateCalculation(&amp;gBedTempControl);\n        \/\/ only run the controller if is heating\n        if (gBedTempControl.TemperatureState != tempOffState) {\n\n        gBedTempControl.PID.Setpoint = \n            gBedTempControl.DesiredTemperature;\n        gBedTempControl.PID.Input = \n             gBedTempControl.CurrentTemperature;\n\n            PID_Compute(&amp;gBedTempControl.PID);\n\n            #ifdef ENABLE_THERMAL_PROTECTION_BED\n             handleTemperatureWatch(&amp;gBedTempControl,TaskMonitorPtr);\n            #endif\n        }\n        \/\/================================================\n        \/\/ EXTRUDER\n        \/\/================================================            \n        \n        calculateExtruderTemperature(); \n        handleStateCalculation(&amp;gE0TempControl);\n        if (gE0TempControl.TemperatureState != tempOffState){\n        gE0TempControl.PID.Setpoint = \n                 gE0TempControl.DesiredTemperature;\n\n            gE0TempControl.PID.Input = gE0TempControl.CurrentTemperature;\n            PID_Compute(&amp;gE0TempControl.PID);\n            #ifdef ENABLE_THERMAL_PROTECTION_EXTRUDER\n            handleTemperatureWatch(&amp;gE0TempControl);\n            #endif\n        }            \n       \n    } \n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Next Time<\/h4>\n\n\n\n<p>I will be testing this code and making it work.  It may delay the next post as I work out the kinks.<\/p>\n\n\n\n<p>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!  <\/p>\n\n\n\n<p>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.<\/p>\n\n\n\n<p>Enjoy! <\/p>\n\n\n\n<p><\/p>\n\n\n\n<p> <\/p>\n","protected":false},"excerpt":{"rendered":"<p>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 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-836","post","type-post","status-publish","format-standard","hentry","category-blogposts"],"_links":{"self":[{"href":"https:\/\/socmaker.com\/index.php?rest_route=\/wp\/v2\/posts\/836","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/socmaker.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/socmaker.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/socmaker.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/socmaker.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=836"}],"version-history":[{"count":5,"href":"https:\/\/socmaker.com\/index.php?rest_route=\/wp\/v2\/posts\/836\/revisions"}],"predecessor-version":[{"id":842,"href":"https:\/\/socmaker.com\/index.php?rest_route=\/wp\/v2\/posts\/836\/revisions\/842"}],"wp:attachment":[{"href":"https:\/\/socmaker.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=836"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/socmaker.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=836"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/socmaker.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=836"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}