The Batteryless Energy Harvesting Remote Control can be taught Infra-red codes and then repeat them. This post explains the way in which this was achieved.
The circuit uses an Infra-red demodulator. The commands from an existing device are fed into the demodulator. The output of the demodulator is connected to an input pin on the MCU. The software is rather simple; it “guesses” which protocol is being sent. This is done by measuring the mark and space time of the received signal and applying a set of rules to determine the protocol. The flowchart below outlines the basic process.
The code utilizes the programmable interval timer (PIC) to determine the on and off time for the sample.
PIT_configure(); PIT_setInterupt(1); PIT_resetVal(); while(irDetector()); //wait until an IR signal is detected. PIT_start(); //Start the programmable interval timer. do { while(irDetector()) //Start with on time { if(PIT_readValS() > 5) break; //Break after 5 seconds of not changing. } diff_times[i] = PIT_readVal(); //Store current counter value. if(PIT_readValS() > 5) break; PIT_resetVal(); //Reset to 0. if(diff_times[i] > largest) largest = diff_times[i]; //Set new largest time where appropriate i++; while(!irDetector()) //Repeat for off time { if(PIT_readValS() > 5) break; } diff_times[i] = PIT_readVal(); if(PIT_readValS() > 5) break; PIT_resetVal(); if(diff_times[i] > largest) largest = diff_times[i]; i++; } while(i < SAMPLE_LENGTH); num_samples = i; PIT_setInterupt(0); PIT_stop(); PIT_powerDown();
The time of the initial high and initial low is then determined, as is the mark and space time:
j=0; for(i=0;i<num_samples;i++) { if(diff_times[i] >= (largest-(largest/100))) { start_locat[j] = i; if(j>0) { temp+= (start_locat[j] - start_locat[j-1]); } j++; if(j>4) break; } } average_bits = start_locat[0]/2; start_time_high = diff_times[1]; //determine the initial time high start_time_low = diff_times[2]; //determine the initial time low temp = 5; j = 25; mark_time = largest; for(i=temp;i<=j;i++) { if(diff_times[i] < mark_time) mark_time = diff_times[i]; //determine mark time. if(diff_times[i] > space_time) space_time = diff_times[i]; //determine space time. }
A different decoding algorithm is required for each type of protocol. The data is decoded by looking at each time difference and comparing it with the documented mark space ratios for the protocol determined. Some protocols are based on Manchester coding in which a slightly different decoding process is required. Example of decoding algorithms can be seen below:
/*----------------------RC-5 DECODE---------------------------------*/ if((start_time_high >= 700 && start_time_high <= 950) &&(start_time_low >= 700 && start_time_low <= 950) &&(mark_time >= 700 && mark_time <= 950) ) { device_learnt = RC5; mark_time = 889; space_time = 1778; j=1; j=0; for(i=1;i<start_locat[0];i++) { if(diff_times[i] >= (700) && diff_times[i] <= (950)) { data_code |= (0x1<<j); j++; } else if(diff_times[i] >= (1400) && diff_times[i] <= (1900)) { data_code |= (0x0<<j); j++; } else { break; } } } /*----------------------NEC/LG/JVC DECODE---------------------------------*/ else if((start_time_high >= 8000 && start_time_high <= 10000) &&(start_time_low >= 1500 && start_time_low <= 5000) &&(mark_time >= 450 && mark_time <= 600) &&(space_time >= 1400 && space_time <= 2500)) { mark_time = 560; space_time = 1690; j=0; for(i=4;i<67;i+=2) { if(diff_times[i] >= (mark_time-(mark_time*0.25)) && diff_times[i] <= (mark_time+(mark_time*0.25))) { data_code |= (0x1<<j); j++; } else if(diff_times[i] >= (space_time-(space_time*0.25)) && diff_times[i] <= (space_time+(space_time*0.25))) { data_code |= (0x0<<j); j++; } else { break; } } if(j==16) device_learnt = JVC; else device_learnt = NEC; } /*----------------------SONY DECODE---------------------------------*/ else if((start_time_high >= 2300 && start_time_high <= 2500) &&(start_time_low >= 500 && start_time_low <= 700) &&(mark_time >= 500 && mark_time <= 700) &&(space_time >= 1100 && space_time <= 1300)) { mark_time = 600; space_time = 1200; j=0; for(i=start_locat[0]+3;j<20;i+=2) { if(diff_times[i] >= (mark_time-(mark_time*0.25)) && diff_times[i] <= (mark_time+(mark_time*0.25))) { data_code |= (0x1<<j); j++; } else if(diff_times[i] >= (space_time-(space_time*0.25)) && diff_times[i] <= (space_time+(space_time*0.25))) { data_code |= (0x0<<j); j++; } else { break; } } if(j==12) device_learnt = SONY_12; else if(j==15) device_learnt = SONY_15; else device_learnt = SONY_20; } /*----------------------SAMSUNG DECODE---------------------------------*/ else if((start_time_high >= 4400 && start_time_high <= 4600) &&(start_time_low >= 4400 && start_time_low <= 4600) &&(mark_time >= 450 && mark_time <= 550) &&(space_time >= 1500 && space_time <= 1700)) { device_learnt = SAMSUNG; mark_time = 560; space_time = 1690; j=0; for(i=start_locat[0]+4;j<32;i+=2) { if(diff_times[i] >= (mark_time-(mark_time*0.25)) && diff_times[i] <= (mark_time+(mark_time*0.25))) { data_code |= (0x1<<j); j++; } else if(diff_times[i] >= (space_time-(space_time*0.25)) && diff_times[i] <= (space_time+(space_time*0.25))) { data_code |= (0x0<<j); j++; } else { break; } } } else { device_learnt = UNKNOWN; }
To replay the learned Infra-red data, an IR LED, which was connected to a PWM pin on the MCU, was used. Because the protocol has been determined, we can look up the correct mark space times and start conditions that need to be transmitted. This has the advantage of reducing any error encountered when learning the infra-red codes.
/*---------------REPLAY NEC/LG-----------------*/ if(command.device_id==NEC) { PIT_resetVal(); PIT_start(); PWM_for_time(9000); Delay_usRun(4500); for(i=0;i<32;i++) { PWM_for_time(560); bit_mask = 1 << i; bit = command.ir_code & bit_mask; bit >>= i; if(bit == 0x0) { Delay_usRun(1690); } else if (bit == 0x1) { Delay_usRun(560); } } PWM_for_time(560); while(PIT_readVal() < 110000); for(i=0;i<5;i++) { PIT_resetVal(); PIT_start(); PWM_for_time(9000); Delay_usRun(2250); PWM_for_time(560); while(PIT_readVal() < 110000); } } /*---------------REPLAY JVC-----------------*/ if(command.device_id==JVC) { PIT_resetVal(); PIT_start(); PWM_for_time(8400); Delay_usRun(4200); while(TSI_button_held()) { for(i=0;i<16;i++) { PWM_for_time(526); bit_mask = 1 << i; bit = command.ir_code & bit_mask; bit >>= i; if(bit == 0x0) { Delay_usRun(1574); } else if (bit == 0x1) { Delay_usRun(524); } } PWM_for_time(526); while(PIT_readVal() < 55000); PIT_resetVal(); PIT_start(); } } /*---------------REPLAY SAMSUNG-----------------*/ if(command.device_id==SAMSUNG) { PIT_resetVal(); PIT_start(); PWM_for_time(4500); Delay_usRun(4500); PWM_for_time(560); for(i=0;i<32;i++) { bit_mask = 1 << i; bit = command.ir_code & bit_mask; bit >>= i; if(bit == 0x0) { Delay_usRun(1690); } else if (bit == 0x1) { Delay_usRun(560); } PWM_for_time(560); } while(PIT_readVal() < 108000); } /*-----------REPLAY RC5-----------------*/ if(command.device_id==RC5) { PIT_resetVal(); PIT_start(); j=0; for(i=0;j<27;i++) { bit_mask = 1 << i; bit = command.ir_code & bit_mask; bit >>= i; if(bit == 0x1) { PWM_for_time(889); j++; } else if (bit == 0x0) { PWM_for_time(1778); j+=2; } i++; bit_mask = 1 << i; bit = command.ir_code & bit_mask; bit >>= i; if(bit == 0x1) { Delay_usRun(889); j++; } else if (bit == 0x0) { Delay_usRun(1778); j+=2; } } while(PIT_readVal() < 114000); }
The IR LED needs to be pulsed at 38KHz for the mark time and turned off for the space time. The way this was achieved is shown in the code below:
/*------------------------------------------------------------------------------ PWM the IR led at 38Khz for a time given in us *------------------------------------------------------------------------------*/ /*__INLINE*/ void PWM_for_time(int time_us) { uint32_t curTicks; curTicks = PIT_readVal(); while ((PIT_readVal() - curTicks) < (time_us)) { FPTD->PDOR |= (1UL << 7); Delay_usRun(11); FPTD->PDOR &= ~(1UL << 7); Delay_usRun(11); } FPTD->PDOR &= ~(1UL << 7); }
The codes need to be stored in a more permanent place than RAM in case the remote control is powered off. Using EEPROM, which usually connects to the Microcontroller via I2C or SPI would have increased the power consumption of the circuit. Instead flash memory was used. Carrying out a procedure called in application programming allows you to save data into the flash memory. More information on writing data to flash can be found in Writing Data To Flash During Program Execution With The Freescale KL25z Series.
The next development step was Batteryless Energy Harvesting Remote Control Power Regulation With The ARM Cortex-M0+