# Precise On/Off control for IR LED

My goal is to be able to send/receive IR messages in my custom format and send messages ~10 bytes long. My message object will get serialized down to individual bits, which are transmitted by turning the IR LED on/off at specific timings of my choosing to represent logical ones and zeros. From what I've seen, an IR LED can be turned on/off in either a while loop or as a PWM signal to transmit the message. I'm under the assumption the only thing gained by using a PWM signal is that the work there in the timer callback will run seemingly in parallel with other code. For simplicity, I opted for the while loop approach.

The issue I am seeing is either one of two. Either the receiver is not reading the timings right, or the transmitter is not turning the LED on/off as fast as I would expect.

In the code below, the IR LED is turned on for 600 microseconds, and off for 300. (Eventually, my custom messages will be built up using something along these lines.) However, as you can see in the Sample Outputs below the receiver is not seeing the IR signal as on or off for anywhere near that amount of time.

Not sure if this is a setup issue, or something else. The two boards I'm using are both Arduino Pro ATmega328p 3.3v boards. For the board sending the IR signal, Pin 6 goes to a 330 Ohm resistor, then to the LED, then to ground. The IR Receiver on the other board has no resistors, just direct connections to pins. When reading the IR signals, I hold the receiver a few centimeters away from the LED.

Since the eventual goal is to get this written in C++, I'm trying to avoid as many Arduino specific APIs as possible.

My IR LEDs and IR Receivers are from Amazon at these links: https://www.amazon.com/gp/product/B007R9UN68/ref=ppx_yo_dt_b_asin_title_o09__o00_s00?ie=UTF8&psc=1 https://www.amazon.com/gp/product/B00LQP68DQ/ref=ppx_yo_dt_b_asin_title_o08__o00_s00?ie=UTF8&psc=1

I've looked online and haven't found definitive answers to some questions. My questions are:

1. Is this a viable setup? Should this work? Or am I all wrong in my assumptions and need to look some other way of doing this?
2. Is my assumption about turning the LED on/off with a while loop vs. a PWM signal true? Or should I remove the loop concept and only use a PWM signal.
3. I see everyone say the carrier frequency should be the same for the LED and Receiver. Is there something for that I need to setup? As in, is there some code I need to write to do this? Or is the hardware made that way, and as long as they match up I'm good to go?

IR Transmit Code:

#define IRpin          6

void setup() {
pinMode(IRpin, OUTPUT);
}

void loop() {
//LED On
PORTD |= (1 << IRpin);
delayMicroseconds(600);

//LED Off
PORTD &= ~(1 << IRpin);
delayMicroseconds(300);
}


IR Receive Code (taken from an online example, only modifications are to output text):

#define IRpin_PIN      PIND
#define IRpin          2

// the maximum pulse we'll listen for - 65 milliseconds is a long time
#define MAXPULSE 65000
#define RESOLUTION 20

uint16_t pulses[300][2];  // pair is high and low pulse
uint8_t currentpulse = 0; // index for pulses we're storing

void setup(void) {
Serial.begin(9600);
}

void loop(void) {
uint16_t highpulse, lowpulse;  // temporary storage timing
highpulse = lowpulse = 0; // start out with no pulse length

while (IRpin_PIN & (1 << IRpin)) {
// count off another few microseconds
highpulse++;
delayMicroseconds(RESOLUTION);

// If the pulse is too long, we 'timed out' - either nothing
// was received or the code is finished, so print what
// we've grabbed so far, and then reset
if ((highpulse >= MAXPULSE) && (currentpulse != 0)) {
printpulses();
currentpulse = 0;
return;
}
}
// we didn't time out so lets stash the reading
pulses[currentpulse][0] = highpulse;

// same as above
while (! (IRpin_PIN & _BV(IRpin))) {
// pin is still LOW
lowpulse++;
delayMicroseconds(RESOLUTION);
if ((lowpulse >= MAXPULSE)  && (currentpulse != 0)) {
Serial.println("Breaking - Hit max Pulse Low");
printpulses();
currentpulse = 0;
return;
}
}
pulses[currentpulse][1] = lowpulse;

// we read one high-low pulse successfully, continue!
currentpulse++;
}

void printpulses(void) {
uint32_t totalMicroseconds = 0;
for (uint8_t i = 0; i < currentpulse - 1; i++) {
Serial.print("On(");
Serial.print(pulses[i][1] * RESOLUTION , DEC);
Serial.print(");");
Serial.println("");
Serial.print("   Off(");
Serial.print(pulses[i + 1][0] * RESOLUTION , DEC);
Serial.println(");");

totalMicroseconds += pulses[i][1] * RESOLUTION;
totalMicroseconds += pulses[i + 1][0] * RESOLUTION;

if (i % 8 == 0) {
Serial.println("");
}
}

Serial.print("On(");
Serial.print(pulses[currentpulse - 1][1] * RESOLUTION, DEC);
Serial.print(");");
totalMicroseconds += pulses[currentpulse - 1][1] * RESOLUTION;

Serial.println("");
Serial.print("Total Microseconds: ");
Serial.println(totalMicroseconds, DEC);
}


Sample Outputs:

Ready to decode IR!
On(20);
Off(62064);

On(0);
Off(12060);
On(360);
Off(1860);
On(440);
Off(8940);
On(20);
Off(15460);
On(80);
Off(25672);
On(40);
Total Microseconds: 127016
On(40);
Off(6740);

On(300);
Off(6740);
On(20);
Off(8520);
On(40);
Off(6740);
On(1540);
Off(15720);
On(40);
Off(34860);
On(20);
Total Microseconds: 81320

On(3000);
Off(35384);

On(40);
Off(14180);
On(1220);
Off(54440);
On(980);
Total Microseconds: 109244

On(200);
Total Microseconds: 200
On(20);
Off(29160);

On(60);
Off(26700);
On(60);
Off(22880);
On(20);
Off(2928);
On(20);
Total Microseconds: 81848

• you asked this question previously arduino.stackexchange.com/questions/60602/… .... why are you double posting – jsotola Jan 17 at 1:26
• Rather than link to Amazon, you should link to the actual datasheets of the products. If you looked at these you would see that they are not compatible. – Elliot Alderson Jan 17 at 1:30
• I wasn't sure if this was a better place to post this. After a few days the original post didn't get any responses. I figured this might be more of an electronics question anyway. – ProgrammerAl Jan 17 at 1:30
• What range and data rate ? IRDA or modulated carrier are different approaches that work best – Sunnyskyguy EE75 Jan 17 at 1:54

No, this is not a viable approach. The IR receiver you selected is not a simple photodiode or phototransistor. The receiver expects to see IR light that is modulated at 38kHz, so your simple on-off switching of the LED will not work.

• This answer has already been super helpful in leading me where to look next. Thank you! The concept of modulating makes sense to me now (I think). A pulse that I send at 38kHz means I'll turn the LED on/off at a rate of 38,000 times a second for the receiver to receive an On signal for that whole second. Or, on for 38 μs, then off for 38 μs, and repeat. So going back to my original example, if I want a logical 1 bit to be 600 μs high and 300 μslow, I would turn the LED on then off every 38 μsfor a total of 600 μs, then leave it off for 300 μs. Is that about right? – ProgrammerAl Jan 17 at 1:59
• No, at 38kHz you need a total pulse period of 26.32 μs...that's on for 13.16 μs and low for 13.16 μs. You should be within 5% of 38kHz for best results. The 600/300 μs timing looks to me like it would meet the datasheet requirements, but just barely. – Elliot Alderson Jan 17 at 2:38
• Thanks. I'm really new to this, but I think you got me on the right direction. I'll look over the datasheet again and change my timing to be better within the range the receiver can work with. – ProgrammerAl Jan 17 at 2:40
• You can use a photodiode, DC_cancelled so as to tolerate sunlight and 60Hz, to feed into a LM567 tone decoder with its amplifiers and Phase Locked Loop. Go read the datasheet. – analogsystemsrf Jan 17 at 2:41

You better stick with the data sheet with synchronous pulse modulation. 38kHz

• All bursts must be phase synchronous @ 38kHz .
• you may not be driving it at the optimum push-pull impedance + current levels. i.e. don't use open collector/drain.
• the preamble should be symmetrical even then the received pulse width varies with intensity. (fig 4) Otherwise, it's worse.
• the 60ms burst sequence gives the AGC enough time to recover from dark levels over 4 orders of magnitude in current levels. This is a tradeoff with range and error rate.

https://www.vishay.com/docs/82491/tsop382.pdf The circuit uses AGC on the preamble to set the peak level. The BPF has a Q of 10 or 10% BW at half power.

You can drive the LEDs in series with 100mA Abs Max steady or 200 mA pk. if 5mm, 20mA is wrong. Use a push-pull driver with current limiting R. A 38kHz Clk and D FF clk & data is easy in HW for synchronous Tx.

One last thing is with 10% BW or 3.8kZ the rise time = 0.35/BW = 92 us but this only occurs at high intensity. When the dark level is a low (dark) current source the response time slows down so your 300us gap will stretch out when the signal range is extended. This dark level is detected and set with the brightness level to determine the optimal data slice threshold, so the burst length ON and OFF needs to be equal for optimum performance.

You can roll your own protocol or use a std optimized for range. As long as you understand the tradeoffs

• There are tricks to get faster data rates but you have to define your path and emitter power 1st. – Sunnyskyguy EE75 Jan 17 at 2:44