Infrared Remote Recording
Note: To re-create this project to this point, go back to the post PSoC USB UART: A Debugging Tool and work from there. If you are starting out with PSOC, go back to the post PSoC 101 and a PSOC Creator Trick
In this post I will show the fairly involved process of reading infrared (IR) light signals. Infrared light has a frequency just below the normal red color human eyes can see. Interestingly enough, mosquitoes, vampire bats, bed bugs, some snakes, and some beetles can see infrared. So can some electronics. People can feel infrared if it is strong enough. We call this strong infrared signal “heat.” Today’s electronics can sense much weaker infrared signals.
The typical television and air conditioner remote you hold in your hand transmits its commands to the controlled device using infrared (heat) signals, turning the IR light on and off. It modulates the light using a specific pattern of on and off blinks.
However, turning a light on and off is not enough. In the typical room there are so many IR sources (i.e. human bodies) that simply receiving typical IR signals simply does not work. The solution is simple: The IR light is commonly modulated using a 38 kHz to 42 kHz frequency. The IR receivers have a filter that only accepts IR signals that turn on and off at that frequency.
This pulsed IR signal is then blinked over longer period of times (in milliseconds) in definite ON / OFF patterns. By using the presence of light to indicate a 1 and absence to indicate a 0, an encoded pattern can be sent.
According to the information on the Internet (which is always true (not!)), the IR transmitters emit light using Manchester encoding. (You can find out more about Manchester encoding here: Wikipedia: Manchester Encoding.
It is my experience that Air Conditioner manufacturers generally follow the Manchester guidelines. However, during testing, the finding of “generally” caused it to be impossible for me to decode the Manchester signals and recreate them for all A/C units. I had to resort to recording the signals in the time domain and playing it back the same way. To do this, I used a commercial off the shelf part, the TSOP38238.
The TSOP38238
Vishay sells the TSOP38238 IR receiver through various vendors, including Mouser. (You can find the cost (around $1.00) and data sheet for this part here: TSOP28238.) This device does a good job of receiving IR signals, and turning them into 1’s and 0’s without the 38 kHz signal chop. You get a “pure” signal, which can be fed into your project.
Adding the TSOP38238 to the CY8CKIT-059
You can easily add the IR receiver to your project on your breadboard. Plug it in as shown:
The layout of VDD and GND on the CY8CKIT-059 matches the needs of the IR receiver exactly. You do not need any additional parts (like resistors, etc.) to receive signals into the PSOC.
Now For Wiring The PSOC Internals.
Examine the image below carefully, and add the components as shown. (You can right-click on the image and choose to show in a new tab in order to see details better.)
Notes: 1) The TSOP38238 package was taken from the data sheet, saved as a JPEG and loaded into the image tool (on the left side bar in the schematic editor). 2) I used an LHT00SU1 logic analyzer on this project while I was debugging it. (I was testing it at the time. The Saleae clone also works. See the post Intermission: Debugging Tips.
Component Info
The Sync_2 component is a “Sync” from Digital / Utility components. The CNTR_IR_Capture is the Counter [v3.0] from the Digital / Functions components.
The Glitch Filter is from Digital / Utility.
The IR receiver is pieced together from various “External” components in the right hand pane (resistor, transistor, Photo Transistor). Note: When you use the “Wire” command on the editor with external components, it draws blue dotted lines.
I added various ISR’s from the data sheet during testing. You can choose to enable them to do different things with firmware. The P1_5_CaptureISR_Orange was originally toggled with firmware to show when the processor entered the ISR.
Configuration
The configuration of the components is as follows. (see the following images and configure your project the same way)
Pin Labeling and Assignments
I labeled the pins with the wire colors from the logic analyzer so I could remember how I hooked it up from one weekend to the next. The actual PSOC pins used for the Logic analyzer are not extremely important; they were easy to get to and not used by any other devices at the time. This hi-lights one of the greatest strengths of the PSOC with PSOC Creator: almost any output can be routed to almost any pin. (With Modus Toolbox, Cypress has taken a step or two backwards and removed some of these features. Hopefully that will be fixed in the future.)
Pin Configuration
Configuring the pins is simple. Only configure the first tab, the “Pins” tab. Leave the rest of the configuration tabs as default.
After doing this work, head over to the “Pins” under “Design Wide Resources” and assign them as follows:
Note that the name of the pin tells you where to assign it to on the PSOC. This also makes it easier to work with in the future.
Intermediate Step: Build the Project
You want to build the project now. This build will create references that are needed to make your code easier to write. (It will allow PSOC Creator to give you hints for possible code completion.)
Now add the following files to your project:
captureIR.h
#ifndef _CAPTUREIR_H_
#define _CAPTUREIR_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>
#define ENABLE_RECORDING 1
void init_capture();
void handle_capture();
extern int16 captureIRData;
// predefined enums
enum equipEnum {
FRIDGIDAIRE_ID=0
,GE_ID=1
};
enum captureTypesEnum {
nullType=0
,upType
,downType
,powerType
,heatType
,coolType
,energySaverType
,fanOnlyType
,fanAutoType
,fanFasterType
,fanSlowerType
,modeType // select next operating mode
,numberOfTypes
};
extern enum captureTypesEnum captureType;// playIR.c
#define MAX_NUMBER_OF_TRANSITIONS_IN_ONE_IR_MESSAGE 256
extern uint32 CaptureTimes[MAX_NUMBER_OF_TRANSITIONS_IN_ONE_IR_MESSAGE];
extern uint8 capCounter;// is 256 maximum so will wrap if captures get out of hand
extern uint16 SendTimes[MAX_NUMBER_OF_TRANSITIONS_IN_ONE_IR_MESSAGE];// scratchpad (i'm lazy)
typedef struct ir_capture_struct {
enum captureTypesEnum
CaptureType;
uint8 NumberOfSpacesAllocatedInThisCapture; // NumberOfTransitions can be less than this.
uint8 NumberOfTransitions;
uint16 TransitionTimes[512];// unknown number, is a set of transitions
} IR_CAPTURE;
extern IR_CAPTURE CapturedIR;
extern enum captureTypesEnum captureType;
#define IR_CAPTURE_SIZE(irc_ptr) (sizeof(IR_CAPTURE)+irc_ptr->NumberOfTransitions*sizeof(uint16)-1)
#endif
/* [] END OF FILE */
captureIR.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 captureIRData;
#if ENABLE_RECORDING
//--------------------------------------------------
// MAX_NUMBER_OF_TRANSITIONS_IN_ONE_IR_MESSAGE = 256
//--------------------------------------------------
uint32 CaptureTimes[MAX_NUMBER_OF_TRANSITIONS_IN_ONE_IR_MESSAGE];
uint8 capCounter;
IR_CAPTURE CapturedIR;
enum captureTypesEnum captureType;
//--------------------------------------------------
// save the width of the IR pulses to memory
//--------------------------------------------------
CY_ISR(CaptureISR){
// toggle the ISR flag if using logic analyzer
P1_5_CaptureISR_Orange_Write(!P1_5_CaptureISR_Orange_Read());
int16_t regVal=CNTR_IR_Capture_ReadStatusRegister();
if (regVal & CNTR_IR_Capture_STATUS_OVERFLOW){
capCounter=0;
return;
}
// divide reading by 2 to convert to microseconds
if (regVal & CNTR_IR_Capture_STATUS_CAPTURE
&&
regVal & CNTR_IR_Capture_STATUS_FIFONEMP)
CaptureTimes[capCounter++]= CNTR_IR_Capture_ReadCapture()>>1;
while (CNTR_IR_Capture_ReadStatusRegister()
& CNTR_IR_Capture_STATUS_FIFONEMP)
CNTR_IR_Capture_ReadCapture();
}
#endif
//--------------------------------------------------
// do calculations for relative times, save in memory
//--------------------------------------------------
void updateIRCapture() {
int16 index;
uint16 result;
for (index =1; index < capCounter; index++){
result = CaptureTimes[index]-CaptureTimes[index-1];
CapturedIR.TransitionTimes[index-1]=result;
}
CapturedIR.CaptureType =captureType;
CapturedIR.NumberOfTransitions = capCounter;
}
//--------------------------------------------------
//--------------------------------------------------
void init_capture(){
#if ENABLE_RECORDING
isr_Capture_StartEx(CaptureISR);
CNTR_IR_Capture_Start();
#endif
}
//--------------------------------------------------
// run the state machine for capturing ir data
//--------------------------------------------------
void handle_capture() {
switch (captureIRData){
// wait for something external to transition state
case 0: case 2:
break;
// reset counter, start capture
case 1:
CNTR_IR_Capture_Stop();
CNTR_IR_Capture_WriteCounter(0);
CNTR_IR_Capture_Enable();
capCounter=0;
captureIRData=2;
break;
// finish capture of IR
case 3:
CNTR_IR_Capture_Stop();
captureIRData=0;
// for now, the following done in user interface code
// updateIRCapture();// save calculated data
break;
}
}
/* [] END OF FILE */
Update main.h:
#include <project.h>
#include <stdio.h>
#include "parseCommands.h"
#include "USBSerial.h"
#include "ds18b20.h"
#include "Milliseconds.h"
#include "Leds.h"
#include "captureIR.h"
Update main.c:
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
for(;;)
{
BlinkLed1(100); // blink 10 times per second (every 100ms)
//======================
// usb serial port
//======================
if ((bufNum=handleUSBSerial())>=0){
USBRxPtr=&USBRxBuffer[bufNum];
// handle possible command
parse((char*)(USBRxPtr->msg),(uint16*)&(USBRxPtr->size));
}
temperature=getTemperatureF(&TemperatureReadingAvailable);
if (TemperatureReadingAvailable) {
// write your code here to do something
}
//===========================
// if the user has chosen to
// read the I/R controller
// the following will do it
//===========================
handle_capture();
}
}
Update parseCommands.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"
#include "Version.h"
char outMsg[64];
char commandBuffer[64];
int16_t commandBufferIndex;
int16_t debugPrint;
//-------------------------------------------
//-------------------------------------------
void init_parse() {
uint16_t c=1;
parse((char *)"v",&c);// show sign on message
}
int16 handleDoCapture(){
//extern int16 captureIRData;
switch(commandBuffer[1]){
case 'T': case 't':
printMessage("\n\rNow press IR button for function capture ");
switch (commandBuffer[2]){
case 'u':case'U':
captureType=upType;
printMessage("of UP button.\n\r");
break;
case 'd':case'D':
captureType=downType;
printMessage("of DOWN button.\n\r");
break;
case 'P':case'p':
captureType=powerType;
printMessage("of POWER button.\n\r");
break;
case 'h':case 'H':
captureType=heatType;
printMessage("of HEAT select button.\n\r");
break;
case 'c':case 'C':
captureType=heatType;
printMessage("of COOL select button.\n\r");
break;
case 'f': case 'F':
captureType=fanOnlyType;
break;
case 'm': case 'M':
captureType=modeType;
break;
default:
printMessage("\n\rCommand not understood\n\r");
return 1;
}
printMessage("Hit Enter to stop capture.>");
captureIRData=1;
return 0;// don't print signon message
case 'd': case 'D':
return 2;
default:
printMessage("\n\rCommand Not Understood\n\r");
return 1;// print signon
}// switch commandbuffer[1]
}
void PrintDump() {
int32 result;
int16 index,index1;
sprintf(outMsg,"Captured %d readings\n\r",capCounter);
printMessage(outMsg);
index1=0;
for (index =0; index < capCounter; index++){
result = CapturedIR.TransitionTimes[index];
sprintf(outMsg,"%2d : %5ld ",index,result);
if (++index1>9){
index1=0;
printMessage("\n\r");
}
printMessage(outMsg);
}
printMessage("\n\rabsolute:\n\r-----\n\r");
index1=0;
for (index =0; index < capCounter; index++){
result = CaptureTimes[index];
sprintf(outMsg,"%2d : %5ld ",index,result);
if (++index1>5){
index1=0;
printMessage("\n\r");
}
printMessage(outMsg);
}
}
//-------------------------------------------
//-------------------------------------------
void parse(char *RXBuffer, uint16_t *rxCountp) {
int16_t rxCount = *rxCountp;
static int16_t bufferIndex;
*rxCountp=0;// reset indicating we consumed buffer
char c;
for (bufferIndex =0 ; bufferIndex < rxCount; bufferIndex++) {
c=commandBuffer[commandBufferIndex++]=RXBuffer[bufferIndex];
printChar(c);
if (c != '\r')
continue;
printChar('\n');
commandBufferIndex=0;
// we have received data from usb. look at it and determine what to do
switch(commandBuffer[0]) {
case 'c':
case 'C':{
switch(handleDoCapture()){
case 2:
PrintDump();
case 1:
goto printSignon;
default:
break;
}
}break;
case 0:
case '\n':
case '\r':{
uint16 result;
int16 index;
if (captureIRData==2){
captureIRData=3;
printMessage("\n\rCapture stopped.\n\r");
// save the calculated relative time values here.
for (index =1; index <= capCounter; index++){
result = CaptureTimes[index]-CaptureTimes[index-1];
// store relative reading
CapturedIR.TransitionTimes[index-1]=result;
}
PrintDump();
}
}
default:
case 'v':
case 'V':
printSignon:
printMessage("\n\n\r--------\n\r");
printMessage(VERSION_STRING);
printMessage("commands: (press enter after command to execute)\n\r---------\n\r");
printMessage(" CT[]-- Capture Type []==U , D, P, H, C, F , M (for GE) \n\r"
" (up, down, power, heat, cool, fan only, Mode)\n\r");
printMessage(" CD -- Dump Capture Readings\n\r");
printMessage(" D -- Toggle Debug Print\n\r");
printMessage(" T -- Print Current Temperature\n\r");
printMessage(" V -- Version / help\n\r");
printMessage("--\n\renter command>");
return;
case 'd': case 'D':
debugPrint=!debugPrint;
printMessage("Debug print now ");
if (debugPrint)
printMessage("on\n\r");
else
printMessage("off\n\r");
goto printSignon;
break;
case 't': case 'T':{
extern float temperature;
sprintf(outMsg,"\n\rTemperature=%f\n\r",temperature);
printMessage(outMsg);
goto printSignon;
}break;
}//switch(commandBuffer
}//for()
return;
}
/* [] END OF FILE */
Build and Test
If I have copied everything correctly, and your part is correct, you should be able to build and put onto the PSOC. Once that is done, you will have a new menu options on your USB debug port.
To capture readings for temperature up, enter CTU <enter> on your keyboard, press the IR temperature up button as it is pointing to the IR receiver, and then press the <enter> key again. You will get a printout of the number of transitions it saw, along with absolute and relative readings.
My experience is you should try this several times, aiming for the smallest number of transitions. In the case of one remote for GE, the smallest number was 76 transitions, but sometimes was 114 or 152 transitions. Later, when playing back the IR data through an infrared LED, I found the 76 transition number was the correct capture, though the others worked as well.
Use a logic analyzer to see the working inside the PSOC by looking at the signals brought out on the pins as you exercise a remote in front of the receiver. By changing internal pin wiring, you can use your logic analyzer to see what is happening at the various components and apply fixes.
Last Words
As I was originally working with this, I found that there were occasional glitches on saving and printing IR captures. I believe that is because I originally did not have enough memory set aside for storage of the processed waveform. The code worked, but had issues.
Sometimes you do not set aside enough memory for storing data into arrays. This leads to everything working *mostly* ok, but with odd quirks. Keep your eyes open for those kinds of issues!
Next time I expect to explain in some detail how the IR receiver works. After that, the IR transmitter will be described and published.
Enjoy!