Introduction.
For controlling my TV and separate audio amplifier, I want to make a control that powers-up/down both devices with one single button. Also, I want to keep the posibility to change the volume of my amplifier.
As I stream my TV content via my tablet, I don't need other functions of both remote control devices, so I want to replace them with one single remote controller.
I know, you can buy very cheap a remote controller that you can configure as you like, but I want to make this on my own,
as I learn by doing this project.
Infrared controls.
Remote controls are standardized thru the RC5 and RC6 protocol by Philips, in my case I did some quick and dirty coding.
I didn't looked for the specific commands, I just sniffed the signals from the remote controls with my logic analyzer and the microcontroller reproduces the commands with an exact match in time.
When you connect a TSOP (this is an IR receiver IC) to you oscilloscope or logic analyzer, you wil get a beautiful pulsetrain, nevertheless, this is NOT what the IR led of the remote control device is sending, it are the pulses already filtered from the base/carrier frequency. This frequency depends on the brand, generally this is around 38kHz. When you buy a TSOP, the numbers/type behind specifies the base frequency for which frequency they are made/will filter of what they receive. So the microcontroller needs to not only give a pulsetrain, but also send it with a base frequency of 38kHz (in my case).
Those frequency's can go from 30kHz to 56kHz,depending on the brand.
Sleep and count the sheep.
As my microcontroller will be powered with battery's I try to be as efficient as possible to prevent consuming too much battery power.Therefore I will put the controller in sleep when no commands are send. The Atmega32 has three external interrupt input's, so prefect for my use, as my controller wakes up on those inputs.
The hardware.
I used AVR studio 7 to do the programming, with an AVR dragon to program the AVR with JTAG.
The AVR is a breakout board from Olimex, M32, the Atmega32, little bit overkill :-).
An external crystal is needed as the sleep function requires an external crystal. I have connected also two led's:
-One to blink at first power up/startup. (to give me time to program the MCU, as my Jtag is disabled as the MCU goes into sleep) This blink status is only when you for example replaces the battery's, then you know the MCU is ok and started.
When the user presses buttons, this led will never blink again to prevent wasting energy.
-One IR led that will send the signals.
I will use also 3x AA battery's, direct on the controller, as a voltage regulator will also consume energy for nothing.
The housing is recuperated from another defective device.
IR remote control
/*
* GccApplication1.c
*
* Created: 29/12/2018 19:30:53
* Author : TDCBYTE
*/
#ifndef F_CPU
#define F_CPU 4000000UL // 4 MHz clock speed
#endif
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/io.h>
volatile int statusTX1; //global variable, when used in a ISR, it MUST be volatile!!
volatile int statusTX2; //global variable, when used in a ISR, it MUST be volatile!!
volatile int statusTX3; //global variable, when used in a ISR, it MUST be volatile!!
double TX1counter;
double TX2counter;
double TX3counter;
double SleepTimer;
int main(void)
{
statusTX1 = 0; //volatile for not loosing data at interrupt
statusTX2 = 0;
statusTX3 = 0;
TX1counter = 0;
TX2counter = 0;
TX3counter = 0;
SleepTimer = 0;
//setup
DDRC = 0b00000011; //Makes PORTC-0 and PORTC -1 as Output, all other pins input
PORTC = 0xfc; // Makes PORC2-7 pull up enabled, it's better to activate the pull up's on floating input pins.
DDRB = 0x00; //Makes PORTB as input
PORTB = 0b11111111; // enable pullups on pins 8 to 13
DDRD = 0b00000011; // set pins 2 to 7 as inputs, leaves 0 & 1 (RX & TX) as is
PORTD = 0b11111100; // enable pullups on pins 2 to 7, leave pins 0 and 1 alone
GICR = 0b11100000; // enable interupt pins INT0,1 and2
MCUCR = 0b01110000; //
//0 - SLEEP BIT, will be set in the program
//1 - Sleep mode, power down = 0
//2- Sleep mode, power down = 1
//3- Sleep mode, power down = 0
//4 - Interrupt on INT1 low level - SLEEP function doesn't work on edge triggered interrupts.
//5- Interrupt on INT1 low level
//6- Interrupt on INT0 low level
//7- Interrupt on INT0 low level
//show MCU startup with blinky led, this provides also the time to reprogram the MCU via the JTAG interface.
fastblink();
fastblink();
slowblink();
slowblink();
fastblink();
fastblink();
sei(); //ensure interrupts enabled so we can wake up again
while(1) //infinite loop
{
//Here the main loop
//sei(); //ensure interrupts enabled so we can wake up again
//only go to sleep after x cycles.
if (SleepTimer == 10000 )
{
SleepTimer = 0; //reset sleeptimer before going to sleep
MCUCSR |=(1<<JTD); //disable JTAG interface, we LEAVE the blinky at startup of the MCU, so we can always used JTAG again for programming/updates
MCUCSR |=(1<<JTD);
MCUCR = 0b00100000; // To level triggered input, otherwise we cannot come out of sleepmode
sei();
sleep_mode(); //this function will do the following: sleep enable, sleep cpu en sleep disable
//MCUCR = 0b00101010; //back to edge triggered interrupt, to prevent overflow stack if button is pressed/to much interrupts
}
if (statusTX1==0 & statusTX2 == 0 & statusTX3==0)//only when no other job's are currently running, timeout to go to sleep
{
SleepTimer = SleepTimer +1;
}
//MCUCSR &=(0<<JTD); //enable JTAG interface, we LEAVE the blinky at startup of the MCU, so we can always used JTAG again for programming/updates
//MCUCSR &=(0<<JTD); //this was only for debugging
//tv on off
if (statusTX1 == 1)//send request by interrupt
{
cli();
IRcommand1();
statusTX1 = 2; //start counter, function locked, interrupt cannot request another send action
}
if (statusTX1 == 2) // function in waiting procedure
{
TX1counter = TX1counter + 1 ;
if ( TX1counter == 6500)//timer by cycles, without blocking the whole program or other buttons
{
statusTX1 = 0; //reset function, again possible to send command
TX1counter = 0;//reset counter
sei(); //Enable global interrupts
}
}
//audio on off
if (statusTX2 == 1)//send request by interrupt
{
cli();
IRcommand2();
statusTX2 = 2; //start counter, function locked, interrupt cannot request another send action
}
if (statusTX2 == 2) // function in waiting procedure
{
TX2counter = TX2counter + 1 ;
if ( TX2counter == 6500)
{
statusTX2 = 0; //reset function, again possible to send command
TX2counter = 0;//reset counter
sei(); //Enable global interrupts
}
}
//this is INT2, only edge trigger interrupt, so we will use this for set decoder on/off
if (statusTX3 == 1)//send request by interrupt
{
cli();
IRcommand3();
statusTX3 = 2; //start counter, function locked, interrupt cannot request another send action
}
if (statusTX3 == 2) // function in waiting procedure
{
TX3counter = TX3counter + 1 ;
if ( TX3counter == 6500) //longer delay
{
statusTX3 = 0; //reset function, again possible to send command
TX3counter = 0;//reset counter
sei(); //Enable global interrupts
}
}
}
}
void fastblink()
{
PORTC = PORTC | (1 << 0); //Turns ON LED
_delay_ms(300); //300 milisecond delay
PORTC = PORTC & (0 << 0);//Turns OFF LED on PORTC0
_delay_ms(300); //300 milisecond delay
PORTC = PORTC | (1 << 0); //Turns ON LED
_delay_ms(300); //300 milisecond delay
PORTC = PORTC & (0 << 0); //Turns OFF LED on PORTC0
_delay_ms(300); //300 milisecond delay
}
void slowblink()
{
PORTC = PORTC | (1 << 0); //Turns ON LED
_delay_ms(1000); //1 second delay
PORTC = PORTC & (0 << 0);//Turns OFF LED on PORTC0
_delay_ms(1000); //1 second delay
PORTC = PORTC | (1 << 0); //Turns ON LED
_delay_ms(1000); //1 second delay
PORTC = PORTC & (0 << 0); //Turns OFF LED on PORTC0
_delay_ms(1000); //1 second delay
}
void IRcommandTV_ONOFF()
{
//TV ON / OFF
IR_ON();
IR_modulator(173);
IR_OFF();
_delay_us(4500);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(575);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(575);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(575);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(575);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(575);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(575);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(575);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(575);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(575);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(575);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(575);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(575);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(575);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(575);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(575);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(575);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(575);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(575);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22);
IR_OFF();
_delay_ms(50); //! 50ms delay after sending stream
}
//new fom 31/10/2020
void IRcommandDECODER_ONOFF()
{
IR_ON();
IR_modulator(348); //every 26µs
IR_OFF();
_delay_us(4529);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(1669);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(560);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(560);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(560);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(560);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(560);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(560);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(560);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(560); //8
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
//einde eerste stream
_delay_us(1670);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(1670);
//Stream off 1670µs
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(560);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(560);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(560);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(560);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(560);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(560);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(560);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(560);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(560);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(560);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_us(1670);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
_delay_ms(42); //42.48ms
IRcommandDECODER_ONOFF_ENDSTREAM();
_delay_ms(97);
IRcommandDECODER_ONOFF_ENDSTREAM();
_delay_ms(97);
IRcommandDECODER_ONOFF_ENDSTREAM();
_delay_ms(97);
IRcommandDECODER_ONOFF_ENDSTREAM();
_delay_ms(97);
IRcommandDECODER_ONOFF_ENDSTREAM();
_delay_ms(97);
IRcommandDECODER_ONOFF_ENDSTREAM();
_delay_ms(97);
IRcommandDECODER_ONOFF_ENDSTREAM();
_delay_ms(97);
IRcommandDECODER_ONOFF_ENDSTREAM();
_delay_ms(97);
IRcommandDECODER_ONOFF_ENDSTREAM();
_delay_ms(97);
IRcommandDECODER_ONOFF_ENDSTREAM();
}
void IRcommandDECODER_ONOFF_ENDSTREAM()
{
IR_ON();
IR_modulator(348); //every 26µs
IR_OFF();
_delay_us(2233);
IR_ON();
IR_modulator(22); //every 26µs
IR_OFF();
}
void IRcommandAUDIO_ONOFF()
{
IR_modulator(346);
_delay_us(4400);
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SS();//1
IRYAMAHA_SS();
IRYAMAHA_SS();
IRYAMAHA_SS();
IRYAMAHA_SS();
IRYAMAHA_SS();//6
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_SL();
_delay_ms(39);
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
}
void IRcommandAUDIO_VOLUP()
{
IR_modulator(346);
_delay_us(4400);
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SS();
IRYAMAHA_SS();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SS();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
}
void IRcommandAUDIO_VOLDOWN()
{
IR_modulator(346);
_delay_us(4400);
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SS();
IRYAMAHA_SS();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SS();
IRYAMAHA_SS();
IRYAMAHA_SS();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SS();
IRYAMAHA_SS();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_SL();
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
IRYAMAHA_ENDSTEAM();
}
void IRYAMAHA_SL()
{
IR_modulator(23);
_delay_us(1630);
}
void IRYAMAHA_SS()
{
IR_modulator(23);
_delay_us(530);
}
void IRYAMAHA_ENDSTEAM()
{
IR_modulator(346);
_delay_us(2200);
IR_modulator(23);
_delay_ms(95);
}
void IR_modulator(uint16_t cycles)
{
// when using this routine, every cycles given must be considered as 26us
//generate the 37.85kHz base frequency
uint16_t IRcycles = 0;
//seperate reset en counter for more -time-efficient code
for (;IRcycles < cycles;)
{
IRcycles = IRcycles + 1;
IR_ON();
_delay_us(10);
IR_OFF();
_delay_us(10);
}
}
void IRcommand1()
{
IRcommandTV_ONOFF();
IRcommandTV_ONOFF();
IRcommandTV_ONOFF();
//IRcommandAUDIO_VOLUP();
}
void IRcommand2()
{
IRcommandAUDIO_ONOFF();
//IRcommandAUDIO_VOLDOWN();
}
void IRcommand3()
{
IRcommandDECODER_ONOFF();
//IRcommandTV_ONOFF();
//IRcommandTV_ONOFF();
//IRcommandTV_ONOFF();
//Delay for Yamaha receiver, otherwise only the TV will be started
//_delay_ms(200);
//IRcommandAUDIO_ONOFF();
}
void IR_ON()
{
PORTC = PORTC | (1 << 1); //Turns ON LED on PORTC1
}
void IR_OFF()
{
PORTC = PORTC & (0 << 1);//Turns OFF LED on PORTC1
}
ISR(INT0_vect)
{
if (statusTX1==0 )//only when function is free, we can activate the send command
{
statusTX1= 1;
}
cli();
}
ISR (INT1_vect)
{
if (statusTX2 == 0 )//only when function is free, we can activate the send command
{
statusTX2 = 1;
}
cli();
}
ISR (INT2_vect)
{
if (statusTX3==0 )//only when function is free, we can activate the send command
{
statusTX3=1;
}
cli();
}