Temperature Control Part 1
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.
Temperature Control
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.
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 “just right” there is no action taken.
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.
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.
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.
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.
Add the following code, modifying main.h to look like the following:
main.h:
#ifndef _MAIN_H_
#define _MAIN_H_
/* ========================================
* Copyright Wade Maxfield, 2020
* All Rights Reserved
* LICENSED SOFTWARE.
* Under the GPL v3 license
* This license does not override previous licenses
* Some information may be Proprietary to
* Cypress (http://www.cypress.com) for their
* PSoC 5LP®--Cypress Semiconductor and
* only usable on their devices.
* PROPERTY OF Wade Maxfield.
* Commercial license available
* ========================================
*/
#include <project.h>
#include <stdio.h>
#include "parseCommands.h"
#include "USBSerial.h"
#include "ds18b20.h"
#include "Milliseconds.h"
#include "Leds.h"
#include "captureIR.h"
#include "playIR.h"
#include "TemperatureControl.h"
#endif
Modify main.c to initialize and call our temperature control algorithms.
main.c
/* ========================================
* Copyright Wade Maxfield, 2020
* All Rights Reserved
* LICENSED SOFTWARE.
* Under the GPL v3 license
* This license does not override previous licenses
* Some information may be Proprietary to
* Cypress (http://www.cypress.com) for their
* PSoC 5LP®--Cypress Semiconductor and
* only usable on their devices.
* PROPERTY OF Wade Maxfield.
* Commercial license available
* ========================================
*/
#include "main.h"
int16_t TemperatureReadingAvailable;
float temperature;
int main(void)
{
// the following two variables are for USBSerial/Command Parser
int16 bufNum;
USBSerialMessage *USBRxPtr;
init_milliseconds();
init_leds();
CyGlobalIntEnable; /* Enable global interrupts. */
initUSBSerial();// setup the USB serial interface.
init_parse();
init_capture();// set up the I/R Capture hardware
init_playIR(); // initialize the IR playback
init_TemperatureControl();
for(;;)
{
handle_playIR(); // handles scheduling of IR commands
handle_TemperatureControl();
BlinkLed1(100); // blink 5 times per sec (100ms on/100ms off)
//======================
// usb serial port
//======================
if ((bufNum=handleUSBSerial())>=0){
USBRxPtr=&USBRxBuffer[bufNum];
parse((char*)(USBRxPtr->msg),(uint16*)&(USBRxPtr->size)); // handle possible command
}
temperature=getTemperatureF(&TemperatureReadingAvailable);
if (TemperatureReadingAvailable) {
// write your code here to do something
}
//===========================
// if the user has chosen to
// read the I/R controller.
//===========================
handle_capture();
handle_playIR(); // handles scheduling of ir commands
}
}
/* [] END OF FILE */
Add a line to Milliseconds.h to reference a down count variable:
Milliseconds.h
#ifndef MILLISECONDS_H_
#define MILLISECONDS_H_
/* ========================================
* Copyright Wade Maxfield, 2020
* All Rights Reserved
* LICENSED SOFTWARE.
* Under the GPL v3 license
* This license does not override previous licenses
* Some information may be Proprietary to
* Cypress (http://www.cypress.com) for their
* PSoC 5LP®--Cypress Semiconductor and
* only usable on their devices.
* PROPERTY OF Wade Maxfield.
* Commercial license available
* ========================================
*/
#include <project.h>
extern volatile uint16 ms,seconds, minutes, hours;
extern volatile uint32 milliseconds;
// the following flags can be used to start things on 1 millisecond boundaries
extern int16 FlagLED1,timeFlag1,timeFlag2,playIRFlag;
volatile int16 MsLedCounter;
extern int16 msCounter1; // used for down counter, stop a 0
void init_milliseconds();
#endif
/* [] END OF FILE */
Modify Milliseconds.c to drive msCounter1 to 0 and then stop:
Milliseconds.c:
/* ========================================
* Copyright Wade Maxfield, 2020
* All Rights Reserved
* LICENSED SOFTWARE.
* Under the GPL v3 license
* This license does not override previous licenses
* Some information may be Proprietary to
* Cypress (http://www.cypress.com) for their
* PSoC 5LP®--Cypress Semiconductor and
* only usable on their devices.
* PROPERTY OF Wade Maxfield.
* Commercial license available
* ========================================
*/
#include "main.h"
// the volatile keyword makes sure compiler does not
// re-use the ARM's internal register value, but gets it
// fresh from memory
volatile uint32 milliseconds;
volatile uint16 ms,seconds, minutes, hours;
volatile int16 MsLedCounter;
int16 timeFlag1,timeFlag2,playIRFlag;
volatile int16 msCounter1;
CY_ISR(MillisecondInterrupt) {
milliseconds++;
MsLedCounter++;
timeFlag1=timeFlag2=playIRFlag=1; // set the millisecond flags
if ( msCounter1>0){
msCounter1--;
}
if (++ms>=1000){
ms=0;
if (++seconds>=60){
seconds=0;
if (++minutes >=60) {
minutes=0;
if (++hours>=24){
hours=0;
}
}
}
}
}
void init_milliseconds() {
isr_1ms_StartEx(MillisecondInterrupt);
}
/* [] END OF FILE */
TemperatureControl.h:
#ifndef _TEMP_CTL_H_
#define _TEMP_CTL_H_
/* ========================================
* Copyright Wade Maxfield, 2020
* All Rights Reserved
* LICENSED SOFTWARE.
* Under the GPL v3 license
* This license does not override previous licenses
* Some information may be Proprietary to
* Cypress (http://www.cypress.com) for their
* PSoC 5LP®--Cypress Semiconductor and
* only usable on their devices.
* PROPERTY OF Wade Maxfield.
* Commercial license available
* ========================================
*/
#include "main.h"
void init_TemperatureControl();
void handle_TemperatureControl();
#define HEAT 1
#define COOL 0
extern float coolTemp;
extern float heatTemp;
extern int16 onOff; // if YES (1) is on. control the aircon
extern int16 operatingMode; // if 1, heat, if 0 cool
extern int16 recordingFinished;
extern int16 manualMode ; // 1== manual, 0== automatic
extern int32 decisionTimer;
extern float temperature;
extern enum equipEnum CurrentEquipmentID;
#endif
/* [] END OF FILE */
TemperatureControl.c:
Note: All of the “send To Nextion” functions are commented out for now:
/* ========================================
* Copyright Wade Maxfield, 2020
* All Rights Reserved
* LICENSED SOFTWARE.
* Under the GPL v3 license
* This license does not override previous licenses
* Some information may be Proprietary to
* Cypress (http://www.cypress.com) for their
* PSoC 5LP®--Cypress Semiconductor and
* only usable on their devices.
* PROPERTY OF Wade Maxfield.
* Commercial license available
* ========================================
*/
#include "main.h"
#define YES 1
float temperature=70.0;// this is set elsewhere
int16 manualMode;
float coolTemp;
float heatTemp;
int16 operatingMode; // if 1, heat, if 0 cool
//float upperTemperature,lowerTemperature;
float temperatureRange;
int32 decisionTimer;
uint16 lastMinutes;
enum ACControlStates {
StateTemperatureStart=0
,StateTemperatureTakeReading
,StateTemperatureTooHigh
,StateTemperatureJustRight
,StateTemperatureTooLow
,StateTemperatureTestReading
,StateTemperatureWaitForTimeout
};
enum ACControlStates ACControlState;
//------------------------------------
//------------------------------------
enum ACControlStates processCurrentTemperature(uint16 selectedTemperature){
enum ACControlStates stateToRun = StateTemperatureTakeReading;
if ( temperature < selectedTemperature -temperatureRange ){
stateToRun = StateTemperatureTooLow;
} else {
if (temperature > selectedTemperature + temperatureRange){
stateToRun=StateTemperatureTooHigh;
} else {
stateToRun = StateTemperatureJustRight;
}
}
return stateToRun;
}
//------------------------------------
//------------------------------------
void init_TemperatureControl(){
ACControlState=StateTemperatureStart;
temperatureRange = 0.5f;
debugPrint=YES;// during debug
//sendCountDownTextToNextion(decisionTimer);
}
//------------------------------------
// send temperature down command
//------------------------------------
void sendIRSequence(enum captureTypesEnum cType,enum equipEnum equipID){
addPlayIRToQueue(cType,equipID);
}
//------------------------------------
// send temperature down commands
// # was determined after testing
//------------------------------------
void sendTempDown() {
if (debugPrint)
printMessage("Send Temp DOWN.\n\r");
int16 i;
for (i=0; i < 25;i++)
sendIRSequence(downType,CurrentEquipmentID);
}
//------------------------------------
// send 3 temperature up commands
// this was determined empirically
//------------------------------------
void sendTempUp() {
if (debugPrint)
printMessage("Send Temp UP.\n\r");
int16 i;
for (i=0; i < 26;i++)
sendIRSequence(upType,CurrentEquipmentID);
}
void printCurrentState(){
}
int16 downPulseCount;
int16 upPulseCount;
int16 targetReachStoppedDone;
//------------------------------------
// todo: need to add delta temperature and
// microphone to listen to the aircon.
//------------------------------------
// if we are in Manual Mode then exit
//
// if we are in the heat/cool mode then ---
// if the temperature is too low then ---
// if we have not changed any setting in
// the last 3 minutes then ---
// increment the temperature by 25 pulses
// endif ...
// endif ...
// else --- we are in heat/cool mode
// if the temperature is too high then ---
// if we have not changed any setting in
// the last 3 minutes then ---
// decrement the temperature by 25 pulses
// endif ...
// endif ....
// endif ...
//
//
//------------------------------------
int16_t TemperatureReadingAvailable;
void handle_TemperatureControl(){
if (manualMode)
return;
switch(ACControlState) {
case StateTemperatureStart:
ACControlState=StateTemperatureTakeReading;
break;
case StateTemperatureTakeReading:
temperature=getTemperatureF(&TemperatureReadingAvailable);
if (!TemperatureReadingAvailable)
break;
if (decisionTimer>0)
ACControlState = StateTemperatureWaitForTimeout;
else
ACControlState = StateTemperatureTestReading;
break;
case StateTemperatureTestReading:{
if (debugPrint){
printMessage("Testing Temperature\n\r");
// sendDebugTextToNextion("Testing");
}
float testTemp;
if (operatingMode==COOL)
testTemp = coolTemp;
else
testTemp = heatTemp;
if (temperature < testTemp-temperatureRange){
ACControlState= StateTemperatureTooLow;
break;
}
if (temperature > testTemp+temperatureRange){
ACControlState = StateTemperatureTooHigh;
break;
}
ACControlState = StateTemperatureJustRight;
}break;
case StateTemperatureTooHigh:{
if (debugPrint){
printMessage("Temperature too high.\n\r");
// sendDebugTextToNextion("Temp Too High");
}
// only play the heat/cool type once, if we are currently
// frigidaire, as it has those selects
// if we are GE, it uses a round robin, will have to handle that elsewhere
if (CurrentEquipmentID == FRIDGIDAIRE_ID){
if (debugPrint) printMessage("F:");
// play the heat/cool setting command
if (operatingMode==HEAT)
addPlayIRToQueue(heatType,CurrentEquipmentID);
else
addPlayIRToQueue(coolType,CurrentEquipmentID);
}else {
if (debugPrint) printMessage("GE:");
}
// the following code will play the adjustment to the AC unit
sendTempDown();
decisionTimer=4;// wait 4 minutes
ACControlState = StateTemperatureWaitForTimeout;
// sendCountDownTextToNextion(decisionTimer);
}break;
case StateTemperatureJustRight:
if (debugPrint){
printMessage("Temperature Good.\n\r");
// sendDebugTextToNextion("Temp OK");
}
decisionTimer=1; // wait 1 minute and check again
// sendCountDownTextToNextion(decisionTimer);
ACControlState = StateTemperatureWaitForTimeout;
break;
case StateTemperatureTooLow:{
if (debugPrint){
printMessage("Temperature too Low.\n\r");
// sendDebugTextToNextion("Temp Too Low");
}
// only play the heat/cool type once, if we are currently
// frigidaire, as it has those selects
// if we are GE, it uses a round robin, will have to handle that elsewhere
if (CurrentEquipmentID == FRIDGIDAIRE_ID){
if (debugPrint) printMessage("F:");
// play the heat/cool setting command
if (operatingMode==HEAT)
addPlayIRToQueue(heatType,CurrentEquipmentID);
else
addPlayIRToQueue(coolType,CurrentEquipmentID);
}else {
if (debugPrint) printMessage("GE:");
}
// the following code will play the adjustment to the AC unit
sendTempUp();
decisionTimer=4;// wait 4 minutes
ACControlState = StateTemperatureWaitForTimeout;
// sendCountDownTextToNextion(decisionTimer);
}break;
case StateTemperatureWaitForTimeout:
// take temperature every 5 seconds
if (msCounter1<=0){
ACControlState=StateTemperatureTakeReading;
msCounter1=5000; // one time per 5 seconds
break;
}
if (minutes != lastMinutes){
lastMinutes = minutes;
if (decisionTimer>0){
decisionTimer--;
// sendCountDownTextToNextion(decisionTimer);
if (debugPrint){
printMessage("--decisionTimer\n\r");
}
}
}
if (decisionTimer>0)
break;
ACControlState = StateTemperatureTestReading;
break;
}
}
/* [] END OF FILE */
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″ 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 link. 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.
Next Time:
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.
Enoy!
Hi Wade,
Thank you for creating a blog. Very interesting articles indeed! Do you have archived project for DS18B20, described in this thread?
/odissey1
I will upload the project near the end of the blog.
I will contact you with the current project.