Hello!
My project aim is to convert my infrared remote control for Canon HF11 camcorder to RF 433MHz using RX470-4 RF receiver, WL102-341 transmitter and ATTiny85. I got the IR codes from the original remote control of the camcorder and assembled a remote control using ATTiny85, using modified code from Technoblogy - IR Remote Wand . First I attached an IR led to the remote control and fine tuned the protocol intervals until I got almost perfect signal. ATTiny85's oscillator had to be calibrated using serial method. The camcorder reacts perfectly to the IR version of the remote control.
Next I attached to that ATTiny85 remote control output the RF sender module.The RF receiver was hooked to and Arduino Nano using IR Remote receiver sketch. Every IR code transmitted over the 433MHz RF was received just fine, but the receiver also grabbed a lot of noise (on purpose I deactivated the NEC protocol, to be able to see the raw codes).
Next I connected an IR led and a resistor of 120 ohms to data output of the RF receiver and tested with the camcorder. Note that both the remote control and the receiver were powered from the same 3.7V Li-Po battery. The RF range was over 25cm (length of the power cables) but the IR signal is very weak, the camcorder responds only if the distance from the IR led to the camcorder's detector is less then 2cm. If the remote and the receiver were powered from different Li-Po batteries (both charged and checked) the RF range dropped to max 1 cm and IR range dropped below 0.5 cm.
To boost the infrared signal I replicated this project IR to RF Converter Circuit Diagram, only the receiver part, and it's not working, even if the IR Led and IR receiver are touching each other. I used 3.3V (Arduino), 3.7V (battery) and 5V (Arduino and DC-DC 3.7 to 5V step-up module) for the receiver circuit with no results. The RF receiver go nuts from the moment I flip the second battery's switch to on ( a lot of noise). I found no way to increase the range of the pair RF emitter-receiver when powered from different batteries.
I also tried the RF pair using a Tiny85 combined with the receiver, library RCSwitch, with custom 8 bit protocol to shrink the RF transmission duration. All buttons work fine except for Zoom In and Zoom Out, that are moving in visible steps and ruin everything. I measured the interval between two IR messages for zoom and it's over 39 ms (as in the NEC protocol) and very variable.
Please help me to increase the range of infrared output of the first version of RF to IR remote, using only one ATTiny85 with the RF sender.
Code for the remote control IR/RF
/* IR Remote Wand v2 with Mode from Reset - see http://www.technoblogy.com/show?25TN
David Johnson-Davies - www.technoblogy.com - 13th May 2018
ATtiny85 @ 1 MHz (internal oscillator; BOD disabled)
CC BY 4.0
Licensed under a Creative Commons Attribution 4.0 International license:
http://creativecommons.org/licenses/by/4.0/
*/
#include <avr/sleep.h>
#include <avr/io.h>
#define EXT_RESET (1 << EXTRF) //external reset flag, valoare 1 pentru external Reset
volatile unsigned char count __attribute__((section(".noinit"))); // Not cleared on RESET - sigur nu e int?
// IR transmitter **********************************************
// Buttons
const int S1 = 4; // Rec - Play
const int S2 = 3; // Display - Rewind
const int S3 = 5; // Reset - Mode
const int S4 = 2; //ZoomIn - Stop
const int S5 = 0; //ZoomOut - Fast-Fwd
const int LED = 1; // IR LED output
// Pin change interrupt service routine
ISR (PCINT0_vect) {
int in = PINB;
if (count % 2 == 0) {
if ((in & 1<<S1) == 0) {
SendCanonP(0xFC03, 0xE383); //Rec
Pulse(0, 9800); //avoid bounce on Rec
}
else if ((in & 1<<S2) == 0) SendCanonP(0xB14E, 0xE383); //ExtDisplay
else if ((in & 1<<S4) == 0) {
while (digitalRead(S4) == LOW) SendCanonP(0xE31C, 0xE383); //ZoomIn active while button pressed
}
else if ((in & 1<<S5) == 0) {
while (digitalRead(S5) == LOW) SendCanonP(0xE21D, 0xE383); //ZoomOut active while button pressed
}
} else {
if ((in & 1<<S1) == 0) SendCanonP(0xDB24, 0xE383); //Enter
else if ((in & 1<<S2) == 0) SendCanonP(0xDD22, 0xE383); ///Right
else if ((in & 1<<S4) == 0) SendCanonP(0xDC23, 0xE383); //Left
else if ((in & 1<<S5) == 0) SendCanonP(0xE817, 0xE383); //Stop
}
}
const int top = 25; // 1000000/26 = 38.5kHz
const int match = 12; // 50% duty cycle
// Set up Timer/Counter1 to output PCM on OC1A (PB1)
void SetupPCM () {
TCCR1 = 1<<PWM1A | 3<<COM1A0 | 1<<CS10; // Inverted PWM output on OC1A divide by 1
OCR1C = top; // 38.5kHz
OCR1A = top; // Keep output low
}
// Generate count cycles of carrier followed by gap cycles of gap
void Pulse (int count, int gap) {
OCR1A = match; // Generate pulses
for (int i=0; i<2; i++) {
for (int c=0; c<count; c++) { //wait for period = count x nr de microsec pt overflow (overflow se produce la (top+1) microsecunde, adica la 26 microsec )
while ((TIFR & 1<<TOV1) == 0); // asteapta pana la overflow
TIFR = 1<<TOV1;
}
count = gap; // Generate gap
OCR1A = top; //stop PWM
}
}
void SendCanonP(unsigned long raw_code2, unsigned long raw_code1) { //unde Hex 48biti e format din raw_code1||raw_code_2, 38KHz
const int HeaderUp = 350; //Arduino nu poate trimite mai mult de 32 de biti odata, Tiny are probleme si cu 32
const int HeaderGap = 173; //parametrii ATTiny = parametrii Arduino in milisecunde (micro/1000) x frecventa semnalului; pentru Canon nu se potrivesc valorile prezise teoretic
const int Bit1Up = 21; // valoarea count = timp[microsecunde]/(top+0.5) empiric
const int Bit1Gap = 62;
const int Bit0Up = 21;
const int Bit0Gap = 21;
TCNT1 = 0; // Start counting from 0
Pulse(HeaderUp, HeaderGap); //trimite header-ul
for (int Bit = 0; Bit<16; Bit++) {
if (raw_code1 & (unsigned long) 1<<Bit) {
Pulse(Bit1Up, Bit1Gap);
} else {
Pulse(Bit0Up, Bit0Gap);
}
}
for (int Bit = 0; Bit<16; Bit++) {
if (raw_code2 & (unsigned long) 1<<Bit) {
Pulse(Bit1Up, Bit1Gap);
} else {
Pulse(Bit0Up, Bit0Gap);
}
}
Pulse(Bit0Up, 1568); //end of transmission 0 bit and gap between transmissions; 1532 working with Zoom
}
// Setup demo **********************************************
void setup() {
OSCCAL = 93; // valabil numai pentru vechiul microcontroller, urmatorul trebuie calibrat cu metoda Serial
// Numara de cate ori a fost resetat microcontroller-ul
if ((MCUSR & EXT_RESET) != 0) {
//MCU Status Register provides information on which reset source caused an MCU Reset.
// If External RESET then increment count and output to pins
MCUSR = MCUSR & ~EXT_RESET;
++count;
} else {
// Else, set count to zero ; nu trebuie sa numere reset de alta cauza in afara de butonul reset
count = 0;
}
// poti inlocui pinMode cu DDRB, mai rapid, dar pierzi portabilitatea, va merge numai pe ATTiny85
pinMode(LED, OUTPUT);
pinMode(S1, INPUT_PULLUP); //PULLUP
pinMode(S2, INPUT_PULLUP);
pinMode(S4, INPUT_PULLUP);
pinMode(S5, INPUT_PULLUP);
SetupPCM();
// Configure pin change interrupts to wake on button presses
PCMSK = 1<<S1 | 1<<S2 | 1<<S4 | 1<<S5;
GIMSK = 1<<PCIE; // Enable interrupts
GIFR = 1<<PCIF; // Clear interrupt flag
// Disable what we don't need to save power
ADCSRA &= ~(1<<ADEN); // Disable ADC
PRR = 1<<PRUSI | 1<<PRADC; // Turn off clocks to unused peripherals
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
// Send S3 code on reset
//Send('R', 0x0013, 0x000C);
}
// Stay asleep and just respond to interrupts
void loop() {
sleep_enable();
sleep_cpu();
}
// DDRB |= 1<<DDB0; would be equivalent to pinMode(0, OUTPUT);
Code for ATtiny85 receiver, using modified RCSwitch library, with support for two extra camcorders (Sony and Panasonic), bad Zoom
/*
Simple example for receiving
https://github.com/sui77/rc-switch/
*/
//modificat pentru 8MHz, cu prescaler 1/8
//#include <avr/sleep.h>
#include <avr/io.h>
const int LED_PIN = 1;
// RF receiver pe PB2
const int S1 = 4; // Mode
const int S2 = 3; // Display - Rewind
const int S3 = 0; // Change Camera
static const uint8_t button_pins[] = {S1,S2,S3};
const int buttonInterval = 750;
bool mode = true; // mod recording
int count = 1; // numarul camerei 1 - Sony; 2 - Canon; 3 - Panasonic
unsigned long currentMillis = 0; //timpul actual
unsigned long previousMillis; //timpul anterior
#include <RCSwitch.h>
RCSwitch mySwitch = RCSwitch();
const int top = 25; // 1000000/26 = 38.5kHz
const int match = 19; // approx. 25% mark/space ratio
// Set up Timer/Counter1 to output PCM on OC1A (PB1)
void SetupPCM () {
TCCR1 = 1<<PWM1A | 3<<COM1A0 | 1<<CS12; // Inverted PWM output on OC1A divide by 1; prescaler 1/8 pt 8MHz
OCR1C = top; // 38.5kHz
OCR1A = top; // Keep output low
}
// Generate count cycles of carrier followed by gap cycles of gap
void Pulse (int count, int gap) {
OCR1A = match; // Generate pulses
for (int i=0; i<2; i++) {
for (int c=0; c<count; c++) {
while ((TIFR & 1<<TOV1) == 0);
TIFR = 1<<TOV1;
}
count = gap; // Generate gap
OCR1A = top;
}
}
void SendSony(int NumberBits, unsigned long raw_code, int numrepeats, int transmgap) { //rutina pentru trimiterea codurilor Sony, 40KHz
const int HeaderUp = 96; //parametrii ATTiny = parametrii Arduino in milisecunde (micro/1000) x frecventa semnalului
const int HeaderGap = 24;
const int Bit1Up = 48;
const int Bit1Gap = 24;
const int Bit0Up = 24;
const int Bit0Gap = 24;
for (int repeat=0; repeat<numrepeats; repeat++) { //telecomanda trimite fiecare cod de 5 ori
Pulse(HeaderUp, HeaderGap);
for (int Bit = 0; Bit<NumberBits; Bit++) {
if (raw_code & (unsigned long) 1<<Bit) {
//Serial.print('1');
Pulse(Bit1Up, Bit1Gap);
} else {
//Serial.print('0');
Pulse(Bit0Up, Bit0Gap);
}
}
Pulse(0, transmgap); //gap between transmissions
}
}
void SendCanonP(unsigned long raw_code2, unsigned long raw_code1) { //unde Hex 48biti e format din raw_code1||raw_code_2, 38KHz
const int HeaderUp = 350; //Arduino nu poate trimite mai mult de 32 de biti odata, Tiny are probleme si cu 32
const int HeaderGap = 173; //parametrii ATTiny = parametrii Arduino in milisecunde (micro/1000) x frecventa semnalului; pentru Canon nu se potrivesc valorile prezise teoretic
const int Bit1Up = 21; // valoarea count = timp[microsecunde]/(top+0.5) empiric
const int Bit1Gap = 62;
const int Bit0Up = 21;
const int Bit0Gap = 21;
TCNT1 = 0; // Start counting from 0
Pulse(HeaderUp, HeaderGap); //trimite header-ul
for (int Bit = 0; Bit<16; Bit++) {
if (raw_code1 & (unsigned long) 1<<Bit) {
Pulse(Bit1Up, Bit1Gap);
} else {
Pulse(Bit0Up, Bit0Gap);
}
}
for (int Bit = 0; Bit<16; Bit++) {
if (raw_code2 & (unsigned long) 1<<Bit) {
Pulse(Bit1Up, Bit1Gap);
} else {
Pulse(Bit0Up, Bit0Gap);
}
}
Pulse(Bit0Up, 906); //end of transmission 0 bit and gap between transmissions; 1568 working with Zoom
}
void SendPanasonic(unsigned long raw_code2, unsigned long raw_code1) { //unde Hex 48biti e format din raw_code1||raw_code_2, 37KHz
const int HeaderUp = 138; //Arduino nu poate trimite mai mult de 32 de biti odata ; 128
const int HeaderGap = 67; //parametrii ATTiny = parametrii Arduino in milisecunde (micro/1000) x frecventa semnalului; 64
const int Bit1Up = 16;
const int Bit1Gap = 48;
const int Bit0Up = 16;
const int Bit0Gap = 16;
TCNT1 = 0; // Start counting from 0
Pulse(HeaderUp, HeaderGap); //trimite header-ul
for (int Bit = 0; Bit<24; Bit++) {
if (raw_code1 & (unsigned long) 1<<Bit) {
Pulse(Bit1Up, Bit1Gap);
} else {
Pulse(Bit0Up, Bit0Gap);
}
}
for (int Bit = 0; Bit<24; Bit++) {
if (raw_code2 & (unsigned long) 1<<Bit) {
Pulse(Bit1Up, Bit1Gap);
} else {
Pulse(Bit0Up, Bit0Gap);
}
}
Pulse(Bit0Up, 2256); //end of transmission 0 bit and gap between transmissions; 2924 prev
}
void setup() {
OSCCAL = 104; // (1MHz 3.3V)
mySwitch.enableReceive(0); // Receiver on interrupt 0 => that is PB2
pinMode(LED_PIN,OUTPUT); //IR LED
pinMode(S1, INPUT_PULLUP); //PULLUP
pinMode(S2, INPUT_PULLUP);
pinMode(S3, INPUT_PULLUP);
SetupPCM();
// Configure pin change interrupts to wake on button presses
// PCMSK = 1<<S1 | 1<<S2 | 1<<S5;
// GIMSK = 1<<PCIE; // Enable interrupts
// GIFR = 1<<PCIF; // Clear interrupt flag
// Disable what we don't need to save power
//ADCSRA &= ~(1<<ADEN); // Disable ADC
//PRR = 1<<PRUSI | 1<<PRADC; // Turn off clocks to unused peripherals
// set_sleep_mode(SLEEP_MODE_PWR_DOWN);
}
void checkPush(int pinNumber) {
int pushed = digitalRead(pinNumber); // read input value
if (pushed == LOW) {
if ((pinNumber == S1) && ( mode == true )) mode = false; //schimba Mode
else if ((pinNumber == S1) && ( mode == false )) mode = true; //schimba Mode
else if (pinNumber == S2){
if ((count==1) && (mode==true)){ //Sony
noInterrupts();
SendSony(12,0x3DA, 5, 778); //Display
SendSony(15,0x6C9A, 5, 778); //adaugat ZoomIn pentru Debug
interrupts();
} else if ((count==1) && (mode==false)) {
noInterrupts();
SendSony(12,0x39C, 5, 778); //Fast Fwd
interrupts();
} else if ((count==2) && (mode==true)){ //Canon
noInterrupts();
SendCanonP(0xB14E, 0xE383); //Display
interrupts();
} else if ((count==2) && (mode==false)){
noInterrupts();
SendCanonP(0xDD22, 0xE383); //Right
interrupts();
} else if ((count==3) && (mode==true)){ //Panasonic
noInterrupts();
SendPanasonic(0xF5728, 0x702002); //Display
SendPanasonic(0x3A6228, 0x702002); //adaugat ZoomIn pentru debug
interrupts();
} else if ((count==3) && (mode==false)){
noInterrupts();
SendPanasonic(0x8BD229, 0x702002); //Right
interrupts();
}
}
else if (pinNumber == S3) { //Change Camera
count++;
if (count > 3) count = 1;
}
pushed = !pushed;
previousMillis = currentMillis;
}
// else
// digitalWrite(ledPin, HIGH); // turn LED ON
}
void loop() {
if (mySwitch.available()) {
if (mySwitch.getReceivedValue() == 254) { //Button A
if ((count==1) && (mode==true)){ //Sony
noInterrupts();
SendSony(15,0x5C99, 5, 778); //Rec
interrupts();
} else if ((count==1) && (mode==false)) {
noInterrupts();
SendSony(12,0x39A, 5, 778); //Play
interrupts();
} else if ((count==2) && (mode==true)){ //Canon
noInterrupts();
SendCanonP(0xFC03, 0xE383); //Rec
interrupts();
} else if ((count==2) && (mode==false)){
noInterrupts();
SendCanonP(0xDB24, 0xE383); //Set
interrupts();
} else if ((count==3) && (mode==true)){ //Panasonic
noInterrupts();
SendPanasonic(0x94CC28, 0x702002); //Rec
interrupts();
} else if ((count==3) && (mode==false)){
noInterrupts();
SendPanasonic(0x5828, 0x702002); //Enter
interrupts();
}
} else if (mySwitch.getReceivedValue() == 250){ //Button B
if ((count==1) && (mode==true)){ //Sony
noInterrupts();
SendSony(15,0x6C9B, 5, 778); //Zoom Out, 5, 778
interrupts();
} else if ((count==1) && (mode==false)) {
noInterrupts();
SendSony(12,0x398, 5, 778); //Stop
interrupts();
} else if ((count==2) && (mode==true)){ //Canon
noInterrupts();
SendCanonP(0xE21D, 0xE383); //Zoom Out
interrupts();
} else if ((count==2) && (mode==false)){
noInterrupts();
SendCanonP(0xE817, 0xE383); //Stop
interrupts();
} else if ((count==3) && (mode==true)){ //Panasonic
noInterrupts();
SendPanasonic(0x3B6328, 0x702002); //Zoom Out
interrupts();
} else if ((count==3) && (mode==false)){
noInterrupts();
SendPanasonic(0x580028, 0x702002); //Stop
interrupts();
}
} else if (mySwitch.getReceivedValue() == 251){ //Button C
if ((count==1) && (mode==true)){ //Sony
noInterrupts();
SendSony(15,0x6C9A, 5, 778); //Zoom In 778
interrupts();
} else if ((count==1) && (mode==false)) {
noInterrupts();
SendSony(12,0x39B, 5, 778); //Rewind
interrupts();
} else if ((count==2) && (mode==true)){ //Canon
noInterrupts();
SendCanonP(0xE31C, 0xE383); //Zoom In
interrupts();
} else if ((count==2) && (mode==false)){
noInterrupts();
SendCanonP(0xDC23, 0xE383); //Left
interrupts();
} else if ((count==3) && (mode==true)){ //Panasonic
noInterrupts();
SendPanasonic(0x3A6228, 0x702002); //Zoom In
interrupts();
} else if ((count==3) && (mode==false)){
noInterrupts();
SendPanasonic(0x8AD329, 0x702002); //Left
interrupts();
}
} else if (mySwitch.getReceivedValue() == 249){ //Button D
if ((count==1) && (mode==true)){ //Sony
noInterrupts();
SendSony(12,0x3DA, 5, 778); //Display
interrupts();
} else if ((count==1) && (mode==false)) {
noInterrupts();
SendSony(12,0x39C, 5, 778); //Fast Fwd
interrupts();
} else if ((count==2) && (mode==true)){ //Canon
noInterrupts();
SendCanonP(0xB14E, 0xE383); //Display
interrupts();
} else if ((count==2) && (mode==false)){
noInterrupts();
SendCanonP(0xDD22, 0xE383); //Right
interrupts();
} else if ((count==3) && (mode==true)){ //Panasonic
noInterrupts();
SendPanasonic(0xF5728, 0x702002); //Display
interrupts();
} else if ((count==3) && (mode==false)){
noInterrupts();
SendPanasonic(0x8BD229, 0x702002); //Right
interrupts();
}
}
mySwitch.resetAvailable();
}
// for(int i=0 ; i<3 ; i++) {
// checkPush(button_pins[i]);
// }
currentMillis = millis(); //prevent bouncing
if((currentMillis - previousMillis) >= buttonInterval){
for(int i=0 ; i<3 ; i++) {
checkPush(button_pins[i]);
}
}
}
Code for the ATTiny85 sender using RCSwitch library
/* IR Remote Wand v2
modified to send RF signal through RCSwitch library */
#include <avr/sleep.h>
#include <avr/io.h>
#include <RCSwitch.h>
RCSwitch mySwitch = RCSwitch();
// IR transmitter **********************************************
// Buttons
const int S1 = 0; // Zoom In
const int S2 = 1; // Display - Rewind
const int S3 = 3; // Reset - Mode
const int S4 = 4; //ZoomIn - Stop
// Pin change interrupt service routine
ISR (PCINT0_vect) {
int in = PINB;
if ((in & 1<<S1) == 0) { //Rec 254
while (digitalRead(S1) == LOW) {
mySwitch.send(251, 8); //Cod Zoom In - buton bun
delayMicroseconds(4400);
}
}
else if ((in & 1<<S4) == 0) { //ExtDisplay 249
mySwitch.send(249, 8);
} //ZoomIn activ cat 251
else if ((in & 1<<S3) == 0) {
while (digitalRead(S3) == LOW) {
mySwitch.send(250, 8); //Cod Zoom Out
delayMicroseconds(4400);
} //ZoomIn activ cat este butonul apasat
} //ZoomOut activ cat este butonul apasat 250
else if ((in & 1<<S2) == 0) {
mySwitch.send(254, 8); // cod Rec
}
}
// Setup demo **********************************************
void setup() {
OSCCAL = 93; // numai pentru vechiul ATTiny85
mySwitch.enableTransmit(1); // transmite pe PB2
// poti inlocui pinMode cu DDRB, mai rapid, dar pierzi portabilitatea, va merge numai pe ATTiny85
pinMode(S1, INPUT_PULLUP); //PULLUP
pinMode(S2, INPUT_PULLUP);
pinMode(S3, INPUT_PULLUP);
pinMode(S4, INPUT_PULLUP);
// Configure pin change interrupts to wake on button presses
PCMSK = 1<<S1 | 1<<S2 | 1<<S3 | 1<<S4;
GIMSK = 1<<PCIE; // Enable interrupts
GIFR = 1<<PCIF; // Clear interrupt flag
// Disable what we don't need to save power
ADCSRA &= ~(1<<ADEN); // Disable ADC
PRR = 1<<PRUSI | 1<<PRADC; // Turn off clocks to unused peripherals
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
// Send S3 code on reset
//Send('R', 0x0013, 0x000C);
}
// Stay asleep and just respond to interrupts
void loop() {
sleep_enable();
sleep_cpu();
}
// DDRB |= 1<<DDB0; would be equivalent to pinMode(0, OUTPUT);
RF transmitter (3.7V only) - working also with IR led on PB1
If powered from the same battery (3.7V), RF range is good, infrared range is less than 1cm
If powered from different batteries RF range is about 8mm (distance between the antenas at which I still have a positive response from the camcorder) and IR range si still about 1 cm.
The same RF remote, with the RCSwitch code and Atiny85 + receiver powered from 3.7V Li-Po batteries has a RF range over 5m and IR range over 50cm. Unfortunately the total duration of RF message+IR message are more than Canon's gap between the IR messages, of 39ms, and break the Zoom.
Any suggestion will be highly appreciated. What is the problem with the different power source and Li battery noise?
I still have to add capacitors for power source filtering.