{"id":481,"date":"2020-07-10T19:26:34","date_gmt":"2020-07-10T19:26:34","guid":{"rendered":"http:\/\/socmaker.com\/?p=481"},"modified":"2020-07-17T20:16:12","modified_gmt":"2020-07-17T20:16:12","slug":"temperature-control-part-1","status":"publish","type":"post","link":"https:\/\/socmaker.com\/?p=481","title":{"rendered":"Temperature Control Part 1"},"content":{"rendered":"\n<p>If you have been following since the beginning, you now have a breadboard with a CY8CKIT-059, a temperature sensor, and an IR Led driven by a Darlington Transistor or an FET.  You also have a UART over the USB that can be connected to using TeraTerm or Putty under Windows, or natively on the Mac or Linux platforms.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Temperature Control <\/h4>\n\n\n\n<p>The temperature control algorithm is complex to think about, but simple in implementation.  At first I thought I needed to send the temperature down signal to the A\/C once a minute if it was too high, and vice versa if too low.<\/p>\n\n\n\n<p>After experimentation, I realized that would not work for me.   I settled on driving the temperature all the way down to the minimum if too hot, and drive it all the way up to maximum if too cold.  If the temperature is &#8220;just right&#8221; there is no action taken.<\/p>\n\n\n\n<p>The reason for driving the control on the A\/C one direction or another is due to the fact the A\/C unit does not control the temperature where I want it controlled.  It controls the temperature at the unit, which is usually much different than the temperature elsewhere in the room.  So, its control is over-ridden by driving to top and bottom of its temperature control range.<\/p>\n\n\n\n<p>Once a minute the PSOC code samples the temperature and makes the decision again.  This is due to the fact that the unit is not directly wired to the control unit:  IR signals can get lost.  After much usage, I am starting to lean towards sending IR signals managing the temperature every 3 minutes rather than every minute:  The beeping of the unit adjusting the temperature can be annoying.   With the source code, you can decide.<\/p>\n\n\n\n<p>The temperature controller uses a state machine.  By doing this, complex if-then-else statements go away, and the code is easy to go to and understand.  In each state, simple decisions are made and simple steps are executed.  <\/p>\n\n\n\n<p>Once a state is finished, the state machine variable is changed to take the machine to the next state to execute.  With this approach, hanging in a single state can be done with no issues, until you are ready for the next stae.<\/p>\n\n\n\n<p>Add the following code, modifying main.h to look like the following:<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">main.h:<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>#ifndef _MAIN_H_\n    #define _MAIN_H_\n\/* ========================================\n * Copyright Wade Maxfield, 2020\n * All Rights Reserved\n * LICENSED SOFTWARE.\n *  Under the GPL v3 license\n * This license does not override previous licenses\n * Some information may be Proprietary to \n * Cypress (http:\/\/www.cypress.com) for their\n * PSoC 5LP\u00ae--Cypress Semiconductor and\n * only usable on their devices.\n * PROPERTY OF Wade Maxfield.\n * Commercial license available\n * ========================================\n*\/  \n#include &lt;project.h>\n#include &lt;stdio.h>\n#include \"parseCommands.h\"\n#include \"USBSerial.h\"\n#include \"ds18b20.h\"\n#include \"Milliseconds.h\"\n#include \"Leds.h\"\n#include \"captureIR.h\"\n#include \"playIR.h\"\n#include \"TemperatureControl.h\"\n    \n#endif\n<\/code><\/pre>\n\n\n\n<p> Modify main.c to initialize and call our temperature control algorithms.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">main.c<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>\/* ========================================\n * Copyright Wade Maxfield, 2020\n * All Rights Reserved\n * LICENSED SOFTWARE.\n *  Under the GPL v3 license\n * This license does not override previous licenses\n * Some information may be Proprietary to \n * Cypress (http:\/\/www.cypress.com) for their\n * PSoC 5LP\u00ae--Cypress Semiconductor and\n * only usable on their devices.\n * PROPERTY OF Wade Maxfield.\n * Commercial license available\n * ========================================\n*\/\n#include \"main.h\"\n\nint16_t TemperatureReadingAvailable;\nfloat temperature;\nint main(void)\n{\n    \/\/ the following two variables are for USBSerial\/Command Parser\n    int16 bufNum;\n    USBSerialMessage *USBRxPtr;\n\n    init_milliseconds();\n    init_leds();\n    \n    CyGlobalIntEnable; \/* Enable global interrupts. *\/\n\n    initUSBSerial();\/\/ setup the USB serial interface.\n    init_parse();\n    init_capture();\/\/ set up the I\/R Capture hardware\n    init_playIR(); \/\/ initialize the IR playback\n    init_TemperatureControl();\n   for(;;)\n    {\n        handle_playIR(); \/\/ handles scheduling of IR commands\n        handle_TemperatureControl();\n        \n        BlinkLed1(100); \/\/ blink 5 times per sec (100ms on\/100ms off)\n\n        \/\/======================\n        \/\/ usb serial port\n        \/\/======================\n        if ((bufNum=handleUSBSerial())>=0){\n            USBRxPtr=&amp;USBRxBuffer&#91;bufNum];\n           parse((char*)(USBRxPtr->msg),(uint16*)&amp;(USBRxPtr->size)); \/\/ handle possible command   \n        }\n        \n        temperature=getTemperatureF(&amp;TemperatureReadingAvailable); \n        if (TemperatureReadingAvailable) {\n             \/\/ write your code here to do something\n        }\n\n        \/\/===========================\n        \/\/ if the user has chosen to \n        \/\/ read the I\/R controller.\n        \/\/===========================\n        handle_capture();\n        \n        handle_playIR(); \/\/ handles scheduling of ir commands\n\n    }\n}\n\n\/* &#91;] END OF FILE *\/\n<\/code><\/pre>\n\n\n\n<p>Add a line to Milliseconds.h to reference a down count variable:<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Milliseconds.h<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>#ifndef MILLISECONDS_H_\n    #define MILLISECONDS_H_\n\/* ========================================\n * Copyright Wade Maxfield, 2020\n * All Rights Reserved\n * LICENSED SOFTWARE.\n *  Under the GPL v3 license\n * This license does not override previous licenses\n * Some information may be Proprietary to \n * Cypress (http:\/\/www.cypress.com) for their\n * PSoC 5LP\u00ae--Cypress Semiconductor and\n * only usable on their devices.\n * PROPERTY OF Wade Maxfield.\n * Commercial license available\n * ========================================\n*\/\n#include &lt;project.h>\n    \nextern volatile uint16 ms,seconds, minutes, hours;    \nextern volatile uint32 milliseconds;\n    \/\/ the following flags can be used to start things on 1 millisecond boundaries\nextern int16 FlagLED1,timeFlag1,timeFlag2,playIRFlag;\nvolatile int16 MsLedCounter;\n\nextern int16 msCounter1; \/\/ used for down counter, stop a 0    \nvoid init_milliseconds();\n\n#endif\n\/* &#91;] END OF FILE *\/\n<\/code><\/pre>\n\n\n\n<p>Modify Milliseconds.c to drive msCounter1 to 0 and then stop:<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Milliseconds.c:<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>\/* ========================================\n * Copyright Wade Maxfield, 2020\n * All Rights Reserved\n * LICENSED SOFTWARE.\n *  Under the GPL v3 license\n * This license does not override previous licenses\n * Some information may be Proprietary to \n * Cypress (http:\/\/www.cypress.com) for their\n * PSoC 5LP\u00ae--Cypress Semiconductor and\n * only usable on their devices.\n * PROPERTY OF Wade Maxfield.\n * Commercial license available\n * ========================================\n*\/\n#include \"main.h\"\n\n\/\/ the volatile keyword makes sure compiler does not\n\/\/ re-use the ARM's internal register value, but gets it\n\/\/ fresh from memory\nvolatile uint32 milliseconds;\nvolatile uint16 ms,seconds, minutes, hours;\nvolatile int16 MsLedCounter;\nint16 timeFlag1,timeFlag2,playIRFlag;\nvolatile int16 msCounter1;\n\nCY_ISR(MillisecondInterrupt) {\n    milliseconds++;\n    MsLedCounter++;\n    timeFlag1=timeFlag2=playIRFlag=1; \/\/ set the millisecond flags\n\n    if ( msCounter1>0){\n        msCounter1--;\n    }\n\n    if (++ms>=1000){\n        ms=0;\n        if (++seconds>=60){\n            seconds=0;\n            if (++minutes >=60) {\n                minutes=0;\n                if (++hours>=24){\n                    hours=0;\n                }\n            }\n        }\n    }\n        \n}\n\nvoid init_milliseconds() {\n    isr_1ms_StartEx(MillisecondInterrupt);\n}\n\n\/* &#91;] END OF FILE *\/\n<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">TemperatureControl.h:<\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>#ifndef _TEMP_CTL_H_\n#define _TEMP_CTL_H_\n\/* ========================================\n * Copyright Wade Maxfield, 2020\n * All Rights Reserved\n * LICENSED SOFTWARE.\n *  Under the GPL v3 license\n * This license does not override previous licenses\n * Some information may be Proprietary to \n * Cypress (http:\/\/www.cypress.com) for their\n * PSoC 5LP\u00ae--Cypress Semiconductor and\n * only usable on their devices.\n * PROPERTY OF Wade Maxfield.\n * Commercial license available\n * ========================================\n*\/\n#include \"main.h\"\n\nvoid init_TemperatureControl();\nvoid handle_TemperatureControl();\n    \n#define HEAT 1\n#define COOL 0\n\nextern float coolTemp;\nextern float heatTemp;\nextern int16 onOff; \/\/ if YES (1) is on.  control the aircon\nextern int16 operatingMode; \/\/ if 1, heat, if 0 cool\nextern int16 recordingFinished;\nextern int16 manualMode ; \/\/ 1== manual, 0== automatic\nextern int32 decisionTimer;\n\nextern float temperature;\nextern enum equipEnum CurrentEquipmentID;\n \n#endif\n\n\/* &#91;] END OF FILE *\/<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">TemperatureControl.c:<\/h4>\n\n\n\n<p>Note: All of the &#8220;send To Nextion&#8221; functions are commented out for now:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/* ========================================\n * Copyright Wade Maxfield, 2020\n * All Rights Reserved\n * LICENSED SOFTWARE.\n *  Under the GPL v3 license\n * This license does not override previous licenses\n * Some information may be Proprietary to \n * Cypress (http:\/\/www.cypress.com) for their\n * PSoC 5LP\u00ae--Cypress Semiconductor and\n * only usable on their devices.\n * PROPERTY OF Wade Maxfield.\n * Commercial license available\n * ========================================\n*\/\n#include \"main.h\"\n\n#define YES 1\n\nfloat temperature=70.0;\/\/ this is set elsewhere\n\nint16 manualMode;\nfloat coolTemp;\nfloat heatTemp; \nint16 operatingMode; \/\/ if 1, heat, if 0 cool\n\n\n\/\/float upperTemperature,lowerTemperature;\nfloat temperatureRange;\nint32 decisionTimer;\nuint16 lastMinutes;\n\nenum ACControlStates {\n    StateTemperatureStart=0\n    ,StateTemperatureTakeReading\n    ,StateTemperatureTooHigh\n    ,StateTemperatureJustRight\n    ,StateTemperatureTooLow\n    ,StateTemperatureTestReading\n    ,StateTemperatureWaitForTimeout\n};\n\nenum ACControlStates ACControlState;\n\n\/\/------------------------------------\n\/\/------------------------------------\nenum ACControlStates processCurrentTemperature(uint16 selectedTemperature){\n  enum ACControlStates stateToRun = StateTemperatureTakeReading;\n    \n    if ( temperature &lt; selectedTemperature -temperatureRange ){\n        stateToRun = StateTemperatureTooLow;\n    } else {\n        if (temperature > selectedTemperature + temperatureRange){\n            stateToRun=StateTemperatureTooHigh;\n        } else {\n            stateToRun = StateTemperatureJustRight;\n        }\n    }\n    \n    return stateToRun;\n}\n\/\/------------------------------------\n\/\/------------------------------------\nvoid init_TemperatureControl(){\n    \n    ACControlState=StateTemperatureStart;\n    temperatureRange = 0.5f;    \n    debugPrint=YES;\/\/ during debug\n    \/\/sendCountDownTextToNextion(decisionTimer);\n}\n\n\/\/------------------------------------\n\/\/ send temperature down command\n\/\/------------------------------------\nvoid sendIRSequence(enum captureTypesEnum cType,enum equipEnum equipID){\n        addPlayIRToQueue(cType,equipID);\n}\n\/\/------------------------------------\n\/\/ send temperature down commands\n\/\/ # was determined after testing\n\/\/------------------------------------\nvoid sendTempDown() {\n    if (debugPrint)\n    printMessage(\"Send Temp DOWN.\\n\\r\");\n\nint16 i;\n    for (i=0; i &lt; 25;i++)\n        sendIRSequence(downType,CurrentEquipmentID);\n}\n\/\/------------------------------------\n\/\/ send 3 temperature up commands\n\/\/ this was determined empirically\n\/\/------------------------------------\nvoid sendTempUp() {\n    if (debugPrint)\n    printMessage(\"Send Temp UP.\\n\\r\");\n\nint16 i;\n    for (i=0; i &lt; 26;i++)\n        sendIRSequence(upType,CurrentEquipmentID);\n}\n\n\nvoid  printCurrentState(){\n    \n    \n}\n\nint16 downPulseCount;\nint16 upPulseCount;\nint16 targetReachStoppedDone;\n\/\/------------------------------------\n\/\/ todo: need to add delta temperature and\n\/\/ microphone to listen to the aircon.\n\/\/------------------------------------\n\/\/  if we are in Manual Mode then exit\n\/\/\n\/\/  if we are in the heat\/cool mode then ---\n\/\/      if the temperature is too low then ---\n\/\/          if we have not changed any setting in \n\/\/               the last 3 minutes then ---\n\/\/                      increment the temperature by 25 pulses\n\/\/          endif ...\n\/\/      endif ...\n\/\/  else --- we are in heat\/cool mode\n\/\/      if the temperature is too high then ---\n\/\/          if we have not changed any setting in \n\/\/              the last 3 minutes then ---\n\/\/                     decrement the temperature by 25 pulses\n\/\/          endif ...\n\/\/      endif ....\n\/\/  endif ...\n\/\/\n\/\/    \n\/\/------------------------------------\nint16_t TemperatureReadingAvailable;\nvoid handle_TemperatureControl(){\n            \n    if (manualMode)\n        return;\n    \n    switch(ACControlState) {\n        case StateTemperatureStart:\n                ACControlState=StateTemperatureTakeReading;\n        break;\n        \n        case StateTemperatureTakeReading:\n            temperature=getTemperatureF(&amp;TemperatureReadingAvailable);\n            if (!TemperatureReadingAvailable)\n                break;\n\n            if (decisionTimer>0)\n                ACControlState = StateTemperatureWaitForTimeout;            \n            else\n                ACControlState = StateTemperatureTestReading;\n        break;\n            \n        case StateTemperatureTestReading:{\n            if (debugPrint){\n                printMessage(\"Testing Temperature\\n\\r\");\n             \/\/   sendDebugTextToNextion(\"Testing\");\n            }\n            float testTemp;\n            if (operatingMode==COOL)\n                testTemp = coolTemp;\n            else\n                testTemp = heatTemp;\n            \n            \n            if (temperature &lt; testTemp-temperatureRange){                        \n                ACControlState= StateTemperatureTooLow;\n                break;\n            } \n            \n            if (temperature > testTemp+temperatureRange){\n                ACControlState = StateTemperatureTooHigh;\n                break;\n            }\n            \n            ACControlState = StateTemperatureJustRight;\n        }break;   \n           \n        \n        case StateTemperatureTooHigh:{\n            if (debugPrint){\n                printMessage(\"Temperature too high.\\n\\r\");\n             \/\/   sendDebugTextToNextion(\"Temp Too High\");\n            }\n\n                \n             \/\/ only play the heat\/cool type once, if we are currently \n            \/\/ frigidaire, as it has those selects\n            \/\/ if we are GE, it uses a round robin, will have to handle that elsewhere\n            if (CurrentEquipmentID == FRIDGIDAIRE_ID){\n                if (debugPrint) printMessage(\"F:\");\n                \n                \/\/ play the heat\/cool setting command\n                if (operatingMode==HEAT)\n                    addPlayIRToQueue(heatType,CurrentEquipmentID);\n                else\n                    addPlayIRToQueue(coolType,CurrentEquipmentID);\n            }else {\n               if (debugPrint) printMessage(\"GE:\");\n             }\n            \n            \/\/ the following code will play the adjustment to the AC unit\n            sendTempDown();\n            decisionTimer=4;\/\/ wait 4 minutes\n            ACControlState = StateTemperatureWaitForTimeout;\n          \/\/  sendCountDownTextToNextion(decisionTimer);\n        }break;\n        \n        case StateTemperatureJustRight:\n           if (debugPrint){\n                printMessage(\"Temperature Good.\\n\\r\");\n               \/\/ sendDebugTextToNextion(\"Temp OK\");\n            }\n            decisionTimer=1; \/\/ wait 1 minute and check again \n         \/\/   sendCountDownTextToNextion(decisionTimer);\n            ACControlState = StateTemperatureWaitForTimeout; \n        break;\n        \n        case StateTemperatureTooLow:{\n            if (debugPrint){\n                printMessage(\"Temperature too Low.\\n\\r\");\n               \/\/ sendDebugTextToNextion(\"Temp Too Low\");\n            }\n\n             \/\/ only play the heat\/cool type once, if we are currently \n            \/\/ frigidaire, as it has those selects\n            \/\/ if we are GE, it uses a round robin, will have to handle that elsewhere\n            if (CurrentEquipmentID == FRIDGIDAIRE_ID){\n                if (debugPrint) printMessage(\"F:\");\n                \n                \/\/ play the heat\/cool setting command\n                if (operatingMode==HEAT)\n                    addPlayIRToQueue(heatType,CurrentEquipmentID);\n                else\n                    addPlayIRToQueue(coolType,CurrentEquipmentID);\n            }else {\n               if (debugPrint) printMessage(\"GE:\");\n             }\n            \n            \/\/ the following code will play the adjustment to the AC unit\n            sendTempUp();\n            decisionTimer=4;\/\/ wait 4 minutes\n            ACControlState = StateTemperatureWaitForTimeout;\n           \/\/ sendCountDownTextToNextion(decisionTimer);\n        }break;\n        \n       \n        case StateTemperatureWaitForTimeout:\n            \/\/ take temperature every 5 seconds\n            if (msCounter1&lt;=0){\n                ACControlState=StateTemperatureTakeReading;\n                msCounter1=5000; \/\/ one time per 5 seconds\n                break;\n            }\n            if (minutes != lastMinutes){\n                lastMinutes = minutes;\n                if (decisionTimer>0){\n              \n                    decisionTimer--;\n                   \/\/ sendCountDownTextToNextion(decisionTimer);\n\n                    if (debugPrint){\n                        printMessage(\"--decisionTimer\\n\\r\");\n                    }\n\n                }\n            }\n            \n            if (decisionTimer>0)\n                break;\n            \n            ACControlState = StateTemperatureTestReading;\n        break;\n    }   \n}\n\/* &#91;] END OF FILE *\/\n<\/code><\/pre>\n\n\n\n<p>You now have a functioning controller of an A\/C unit, but with a serial user interface.  I chose a Nextion display to manage the user interface.  This display is an intelligent 3.2&#8243; display, with EEPROM and an RTC (which I do not yet use).    You can get the display from Amazon (which easily allows returns) at this <a href=\"https:\/\/www.amazon.com\/s?k=nextion+3.2+enhanced&amp;crid=3BP0B6SGRTUEN&amp;sprefix=nextion+3.2+enahn%2Caps%2C164&amp;ref=nb_sb_ss_sc_1_17\">link<\/a>.  I have heard that sometimes getting a display from Australian or Chinese vendors can lead to difficulty in returns should you get a bad unit.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Next Time:<\/h4>\n\n\n\n<p>Next time I hope to concentrate on the PC Board that holds the controlling PSOC, with spots for the Darlington and the resistor for the IR led.<\/p>\n\n\n\n<p>Enoy!<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you have been following since the beginning, you now have a breadboard with a CY8CKIT-059, a temperature sensor, and an IR Led driven by a Darlington Transistor or an FET. You also have a UART over the USB that can be connected to using TeraTerm or Putty under Windows, or natively on the Mac [&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-481","post","type-post","status-publish","format-standard","hentry","category-blogposts"],"_links":{"self":[{"href":"https:\/\/socmaker.com\/index.php?rest_route=\/wp\/v2\/posts\/481","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=481"}],"version-history":[{"count":18,"href":"https:\/\/socmaker.com\/index.php?rest_route=\/wp\/v2\/posts\/481\/revisions"}],"predecessor-version":[{"id":503,"href":"https:\/\/socmaker.com\/index.php?rest_route=\/wp\/v2\/posts\/481\/revisions\/503"}],"wp:attachment":[{"href":"https:\/\/socmaker.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=481"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/socmaker.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=481"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/socmaker.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=481"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}