Generating a 36kHz carrier signal for use in a IR LED array using UNO

Hey fellas.

My goal is to create an array of infrared LED's to communicate the current time from my DS3231 RTC to another board. The goal is to reach a range of up to 30 meters.

My IR receiver will be the TSOP31236, which demodulates a 36kHz signal.

I plan to power the array with a separate battery pack, consisting of AA-batteries.
Furthermore I plan to use the MOSFET IPB123N10N3 G as my switch for the signal.

However at first I will try to send the signal with only one (1) LED, and scale it up from there.
You have to crawl before you can walk.

However I am unsure of how two modulate my signal at 36kHz.

In this tutorial he uses tone() to generate the carrier signal, which is then used in conjunction with a BJT to encode the signal.

I recreated this circuit and I was able to send and receive my signal.
However as far as I can see, this system i harder to scale up, since the LED is powered directly by the Arduino, and if used in conjunction with batteries, it would drain them quickly, since current goes through the BJT when not used in the LED. (Might be wrong about this).

However inspired by this tutorial, I created the following circuit as a way to send my signal;


(I am sorry for my simple paint circuit, I hope you can make sense of it)

With the components:
Varta AA Battery
BC547A BJT
TSAL6200, 940nm, IR LED
IPB123N10N3 G, MOSFET
Arduino Uno

and with the following code;

#include <Time.h>
#include <DS3232RTC.h>
#include <SoftwareSerial.h>
#define cr_pin 9 //To use in the carrier signal

// Using pin10 and pin11 for serial, but inverted
SoftwareSerial mySerial (10, 11, true);

void setup() {
       //To ensure the MOSFET is in ON-stage
       pinMode(11, OUTPUT);

       // Start the serial port
       mySerial.begin(4800);
       
       //Start the carrier signal at 36kHz
       tone(cr_pin, 36000);
       
       // RTC stuff
       setSyncProvider(RTC.get);
}
void loop() {
      
      
      if (hour() < 10)//added code to give leading zeros
        mySerial.print("0");
      mySerial.print(hour());
      
      
      if (minute() < 10)//added code to give leading zeros
        mySerial.print("0");
      mySerial.print(minute());
      
      
      if (second() < 10)//added code to give leading zeros
        mySerial.print("0");
      mySerial.println(second());
      
      //One second delay
      delay (1000);
  }

The idea with this is using tone() on pin9 in conjunction with the BJT to create the carrier signal at 36kHz.

Then I use pin11 in conjunction with my MOSFET to encode my signal on the carrier.
The reason for the inverted logic was to keep the MOSFET off when not sending a signal, to save battery power.

However as I tried to power this circuit, things started to smell funky, and I quickly turned it off again. At this point I am unsure of what component(s) I fried, if any. Probably the BJT if I should guess.

I must have made wrong calculations and/or decisions along the way.

So can the circuit be tweaked, or is this solution completely idiotic?

If this circuit is unusable, how could I instead modulate and send my signal, so it can be received by the TSOP31236, and with the circuit being 'upscale-able' by adding more LED's and batteries as necessary?

Sorry for the long post and questions, and thank you, if you took the time to read it all. : )

This code is from an RFID project but is what you want here. It is setting the PWM output to the right frequency.

void setCoilOutput(){  // sets pin 3 going at the RF field rate
  pinMode(3, OUTPUT);
  TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); // Just enable output on Pin 3 and disable it on Pin 11
  TCCR2B = _BV(WGM22) | _BV(CS22);
  OCR2A = 127; // defines the frequency 109 = 145 KHz, 119 = 133.3 KHz, 127 = 125 KHz
  OCR2B = 64;  // deines the duty cycle - Half the OCR2A value for 50%
  TCCR2B = TCCR2B & 0b00111000 | 0x1; // select a prescale value of 1:1
}

This is the OP's circuit:-


A 10R current limiting resistor is way too low, make it 120R at the lowest. And you should use a PNP transistor to top switch not a NPN and the resistors on the base are wrong. But those errors probbly reduced the amount of smoke you produced.

You MUST have grounds connected
What battery do you use?
Move the NPN transistor next to the MOSFET to use low side switching for both.

Test your components to see if all are OK. You should be able to see IR light with a phone camera as a blue/purple when looking directly on the LED.

Using @Grumpy_Mike's code, you need only one transistor (or mosfet) and this would be on pin 3.
If you were prepared to use a 220R current limiting resistor for the led, you don't need any transistors at all (but, depending on the LED, you may not then get 30M range)

You'd have to make modifications to the code to change the prescaler and the value of OCR2A to get a 36kHz carrier.
You could switch the carrier on and off by simply using pinMode(3, OUTPUT) or pinMode(3, INPUT) respectively. There are also more elegant ways of doing this using bit manipulation on the timer registers.

Edit

Here it is for a 36kHz carrier and a function to switch the carrier off and on.

// global
const uint8_t pinOC2B = 3 ;

// in setup() . . .

pinMode(pinOC2B, OUTPUT);
// set up timer 2 (pin 3)
// TCCR2A set in function osc() ;

TCCR2B = _BV(WGM22) | _BV(CS21);
OCR2A = (( F_CPU / 1000000L )  * 55 ) / 16 ;  // 36.4kHz
OCR2B = OCR2A / 2 ;  // 50% duty cycle



void osc( bool oscOn ) {
 /*
 * Controls timer2. Initial configuration in setup() with prescaler 1:8.
 * oscOn = true. Delivers a X kHz 50% duty cycle signal on OC2B (pin D3), otherwise D3 held low
 *
 */
 if ( oscOn == false )  {
 // switch off
 TCCR2A = 0 ;
 digitalWrite( pinOC2B , LOW ) ; // ensure pin is not left HIGH
 }
 else {
 // switch on
 TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); //  Mode 7 (fast PWM),  Output to pin D3 (OC2B)
 }
}

Is this a static IR LED setup?
Then optics on that ±17 degree wide-angle LED should be used instead of an array.

Or use two narrow beam (<=7 degrees) LEDs in series, directly driven from the pin (no transistors).
Two narrow-beam LEDs with 40mA peak current (20mA average) should do 30m outside.
See this setup.

Connect a series circuit of two LEDs and a 47ohm CL resistor between the PWM pin and a switching pin.
The PWM pin continuously toggles @36kHz, and the other (output) pin can switch the LED on/off (data).

const byte IR_LEDpin = 11;

void setup() {
  pinMode (IR_LEDpin, OUTPUT);
  TCCR2A = _BV (COM2A0) | _BV(WGM21);
  TCCR2B = _BV (CS20);
  OCR2A =  219; // 219 = ~36kHz
}

void loop() {
  // other code
}

You can use a circuit like this for transmitting. The 18 Ohm resistor is for a 100 mA high power IR LED. Use around 100 Ohms for a standard 20 mA LED.

Thank you all for your time and help so far! I really appreciate it.

Reply to Grumpy_Mike and 6v6gt

Grumpy_Mike:
This code is from an RFID project but is what you want here. It is setting the PWM output to the right frequency.

(CODE)

This is the OP's circuit:-
(PICTURE)
A 10R current limiting resistor is way too low, make it 120R at the lowest. And you should use a PNP transistor to top switch not a NPN and the resistors on the base are wrong. But those errors probbly reduced the amount of smoke you produced.

6v6gt:
Using @Grumpy_Mike's code, you need only one transistor (or mosfet) and this would be on pin 3.
If you were prepared to use a 220R current limiting resistor for the led, you don't need any transistors at all (but, depending on the LED, you may not then get 30M range)

You'd have to make modifications to the code to change the prescaler and the value of OCR2A to get a 36kHz carrier.
You could switch the carrier on and off by simply using pinMode(3, OUTPUT) or pinMode(3, INPUT) respectively. There are also more elegant ways of doing this using bit manipulation on the timer registers.

Here it is for a 36kHz carrier and a function to switch the carrier off and on.
(CODE)

Thank you both! Using the PWM registers 'directly' is not something I have done before, but I have tried to read up on it using this tutorial, as to have a better understanding on what is happening. Still I have some follow-up questions, I hope you can/will answer.

Since I didn't have a PNP-transistor at hand, I decided to try and ditch the top-switching, and just use a single MOSFET as suggested by 6v6gt, which I will then try to control with pin3.

Since I was lucky to loan an oscilloscope, I am able to look at the output signal from pin3, before I connect the whole circuit.

As such my current code is;

#include <Time.h>
#include <DS3232RTC.h>
#include <SoftwareSerial.h>
const uint8_t pinOC2B = 3 ;

SoftwareSerial mySerial (2, 3);



void setup() {


       // Start the serial port
       mySerial.begin(4800);

       pinMode(pinOC2B, OUTPUT);
       // set up timer 2 (pin 3)
       // TCCR2A set in function osc() ;

       TCCR2B = _BV(WGM22) | _BV(CS21);
       OCR2A = (( F_CPU / 1000000L )  * 55 ) / 16 ;  // 36.4kHz
       OCR2B = OCR2A / 2 ;  // 50% duty cycle
       
       // RTC stuff
       setSyncProvider(RTC.get);
}

void osc( bool oscOn ) {
 /*
 * Controls timer2. Initial configuration in setup() with prescaler 1:8.
 * oscOn = true. Delivers a X kHz 50% duty cycle signal on OC2B (pin D3), otherwise D3 held low
 *
 */
 if ( oscOn == false )  {
 // switch off
 TCCR2A = 0 ;
 digitalWrite( pinOC2B , LOW ) ; // ensure pin is not left HIGH
 }
 else {
 // switch on
 TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); //  Mode 7 (fast PWM),  Output to pin D3 (OC2B)
 }
}


void loop() {
      
      osc(true);
      
      if (hour() < 10)//added code to give leading zeros
        mySerial.print("0");
      mySerial.print(hour());
      
      if (minute() < 10)//added code to give leading zeros
        mySerial.print("0");
      mySerial.print(minute());
      
      if (second() < 10)//added code to give leading zeros
        mySerial.print("0");
      mySerial.println(second());

      osc(false);
      //One second delay
      delay (1000);
  }

What I intended to happen was the following,

  1. Using osc(true), to turn the carrier on.
  2. Using mySerial.print() on pin 3, to mix my serial signal with the carrier.
  3. using osc(false) to turn the carrier off.

Where I would expect the output signal to look something likes this;


(But at 36kHz instead, and with varying bits)

But instead I have the following output signal;

So it seems like my serial signal is not encoded on the carrier.
Is it not possible to use serial.print() on pin 3, when pin3 is set to oscillate? Or have I made another mistake somewhere?
Otherwise I guess I have to mix the signals in the circuit, as I previously tried.

Reply to Smajdalf

Smajdalf:
You MUST have grounds connected
What battery do you use?
Move the NPN transistor next to the MOSFET to use low side switching for both.

Test your components to see if all are OK. You should be able to see IR light with a phone camera as a blue/purple when looking directly on the LED.

Thank you!
I use the following battery.
I think I might have let too much current run through the BJT transistor, and I then tried a lowside configuration, but without success.

Reply to Wawa

Wawa:
Is this a static IR LED setup?
Then optics on that ±17 degree wide-angle LED should be used instead of an array.

Or use two narrow beam (<=7 degrees) LEDs in series, directly driven from the pin (no transistors).
Two narrow-beam LEDs with 40mA peak current (20mA average) should do 30m outside.
See this setup.

Connect a series circuit of two LEDs and a 47ohm CL resistor between the PWM pin and a switching pin.
The PWM pin continuously toggles @36kHz, and the other (output) pin can switch the LED on/off (data).

(CODE)

Unfortunately the setup will be quite dynamic, and I therefore think an array of LED's, will be best as to achieve better coverage.
But an optical solution is quite interesting! I might to try that in a future project. But thank you a lot for your suggestion!

Reply to jremington

jremington:
You can use a circuit like this for transmitting. The 18 Ohm resistor is for a 100 mA high power IR LED. Use around 100 Ohms for a standard 20 mA LED.
(IMAGE)

That circuits looks good! If I can get my serial signal and carrier mixed on an eventual output signal from pin3, I think i will use that or something similar. Thank you a lot too!

OK. I see what you are trying to do.

I have something similar in that I use an ESP8266 NTP clock to synchronise several autonomous ATmega328p clocks. However, I use the NEC IR protocol and a library similar to the standard Arduino IR library to send the data (date/time) to the receiving clocks.

You appear to be using the 36kHz carrier to send over a standard serial protocol. I'll be very curious to see how far you get, but it could work OK.

Your transmitter code will not work as it is because the carrier has to switched on an off with each bit (including start/stop bits) that is transmitted via the (software) serial interface. You seem to be switching the carrier on, then attempting to transmit hours, minutes and seconds, then switching the carrier off. Further, defining the same pin, that is pin 3 as both the software serial TX pin and the oscillator output pin will not achieve what you want because, irrespective of what the pin has been set to by software serial, the oscillator will always "win". You are effectively "OR-ing" the two outputs, whereas you need to "AND" them.

So it does now look like using a transistor to "AND" the 36kHz carrier on pin 3 and the output of the software serial defined TX pin may be the easiest way of controlling the LED.

But what you could try, just as an experiment is to connect the led (with a series resistor >= 100R ) between pin 3, the carrier output, and the (a different) software serial TX pin and then use the oscilloscope across the LED to see the waveform. Just simply try sending a few characters over the serial connection.

It may also be possible to set a pin change interrupt on the pin defined as the TX pin and use that to switch the carrier on and off, but that would need some experimentation. Further, I am not sure that the pin change interrupt is triggered if the pin is defined as an output pin and written to internally.

Is it not possible to use serial.print() on pin 3, when pin3 is set to oscillate?

Correct it is not possible without some hardware or some rewriting of the software serial library.

OK. I said I was very curious if this would work, that is transmitting standard serial over an IR carrier but using a TSOP IR receiver instead of a photodiode, so I satisfied my curiosity by trying it out and it was indeed successful. I drove the led directly from the TX part’s oscillator pin (but via a 100R series resistor), that is, I did not use a transistor.

I used 300 Baud and tested only over a short distance. To scale up the power supplied to the led, the circuit in post #5 looks good if you are using 3.3 volts.

The RX part is trivial. The TX part uses an external interrupt, driven by the TX pin, to switch the carrier bursts on and off. The connections are documented in the code, including the required jumper.

The SoftwareSerial library could not be used for the TX part here, because it interferes with interrupts, so I used the hardware serial port.

TX Part

/* TX part - 
 *  To use on Uno etc. :
 *  
 * jumper pins 1 and 2
 * connect an IR led with series resistor between pin 3 and ground
 * Set up for 36kHz. Change OCR2A for other carrier frequencies.
 * 
 * 
 * Generate a carrier on pin3 (OC2B)
 * Generate hardware serial on pin 1 (TX) 
 * Jumper TX (pin 1) to INT0 (pin 2)
 * Detect interupt caused by setial TX activity 
 * switch carrier on/off to match the serial activity
 * 
 * Notes:
 * Does not work with standard SoftwareSerial.h which suppresses interrupts for too long
 * Therefore we use the hardware serial.
 * 
 * Author: 6V6GT  17.06.2020
 */

const uint8_t ledPin = 13 ;
const uint8_t pinOC2B = 3 ;
const uint8_t intPin0 = 2 ;


void osc( bool oscOn ) {
  /*
    Controls timer2. Initial configuration in setup() with prescaler 1:8.
    oscOn = true. Delivers a X kHz 50% duty cycle signal on OC2B (pin D3), otherwise D3 held low
  */
  if ( oscOn == false )  {
    // switch off
    TCCR2A = 0 ;
    digitalWrite( pinOC2B , LOW ) ; // ensure pin is not left HIGH
  }
  else {
    // switch on
    TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); //  Mode 7 (fast PWM),  Output to pin D3 (OC2B)
  }
}


void extIntCallBack()  
{
 
  // if ( !( pinD & 0b00000100 ))   // pin2 direct pin manipulation test D2
 
  if ( ! digitalRead( intPin0 ) ) 
  {
    osc( true ) ;
    digitalWrite(ledPin, HIGH);
  }
  else {
    osc( false ) ;
    digitalWrite(ledPin, LOW);
  }

}



void setup() {
  Serial.begin( 300 ) ;
  pinMode( intPin0, INPUT ) ;
  pinMode( pinOC2B, OUTPUT);
  pinMode( ledPin, OUTPUT);
  
  attachInterrupt( digitalPinToInterrupt( intPin0 ), extIntCallBack, CHANGE ) ;

  // set up timer 2 (pin 3)
  // TCCR2A set in function osc() ;
  
  TCCR2B = _BV(WGM22) | _BV(CS21);
  OCR2A = (( F_CPU / 1000000L )  * 55 ) / 16 ;  //  55 = 36.4kHz
  OCR2B = OCR2A / 2 ;  // 50% duty cycle

  osc( false ) ; // oscillator off
  
}

void loop() {
  
  Serial.print( "0123456789" ) ;
  delay( 1000 ) ;
}

RX part:

/*
RX part - 

To use on a Uno etc., connect IR receiver e.g. TSOP3823x output pin to pin 4
Carrier frequency of TX part should match TSOP device

Author: 6V6GT  17.06.2020
*/

#include <SoftwareSerial.h>

const uint8_t ledPin = 13 ;
const uint8_t ssRx = 4 ;
const uint8_t ssTx = 5 ;

SoftwareSerial ss( ssRx, ssTx, false ) ;

void setup() {
  Serial.begin( 115200 ) ;
  ss.begin( 300 ) ;
}

void loop() {
  static uint8_t nChar = 0 ;
  if ( ss.available() != 0 ) {
    char c = ss.read() ;
    Serial.print( c ) ;
    if ( ++nChar >= 40 ) {
      nChar = 0 ;
      Serial.println() ;
    }
  }


}

I revised the transmitter part in post #9 to remove the need for a jumper between the TX pin and the external interrupt 0 pin. It uses an ineresting property of a pin change interrupt, which allows an interrupt to be triggered in software. Writing to an output pin, as Serial does to the TX pin, triggers a pin change interrupt attached to its port.
I tried at 2400 baud (the maximum) and also using a transistor to drive an (unknown) IR led at 70mA and the IR bounces all round the room and "finds" the receiver.

The technique could well be very useful for the simple wireless loading of configuration information to Arduinos, similar to that which the OP intends, since the receiver part is trivial, leveraging the existing Serial communications and not requiring, for example, the installation of an IR library.

Revised TX part (2400 Baud)

/* TX part / Send Serial over IR carrier to TSOP IR Receiver (max 2400 Baud)
 *  
 * To use on Uno etc. :
 *  
 * Connect an IR led with series resistor between pin 3 and ground
 * Set up for 36kHz. Change OCR2A for other carrier frequencies.
 * Ensure serial baud rate set in Serial.begin() matches the receiver part.
 * 
 * 
 * This functions as follows:
 * 
 * A switchable carrier (X kHz) is generated on pin3 (OC2B) which controls the LED
 * A pin change interrupt is attached to the hardware serial TX pin
 * The ISR attached to the interrupt switches the carrier on or off
 *  depending on the state of the TX pin. The carrier is on when TX is low.
 * 
 * 
 * Notes:
 * Does not work with standard SoftwareSerial.h which suppresses interrupts for too long
 * therefore we use the hardware serial.
 * 
 * Credits: 
 * ideas from: https://forum.arduino.cc/index.php?topic=690418.0
 * and https://www.electronicwings.com/arduino/ir-communication-using-arduino-uno
 * 
 * V0_02  pin change interrupt on D1 (TX pin) replaces external interrupt on INT0 and jumper to TX
 * 
 * Author: 6V6GT  18.06.2020
 */

const uint8_t ledPin = 13 ;       // inbuilt led
const uint8_t pinOC2B = 3 ;       // IR LED control
const uint8_t serTx = 1 ;         // HW serial TX pin


void pciSetup(byte pin)
{
  // https://playground.arduino.cc/Main/PinChangeInterrupt/
  *digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin));  // enable pin
  PCIFR  |= bit (digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
  PCICR  |= bit (digitalPinToPCICRbit(pin)); // enable interrupt for the group
}


void osc( bool oscOn ) {
  /*
    Controls timer2. Initial configuration in setup() with prescaler 1:8.
    oscOn = true. Delivers a X kHz 50% duty cycle signal on OC2B (pin D3), otherwise D3 held low
  */
  if ( oscOn == true )  {
    // switch on
    TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20); //  Mode 7 (fast PWM),  Output to pin D3 (OC2B)
  }
  else {
    // switch off
    TCCR2A = 0 ;
    digitalWrite( pinOC2B , LOW ) ; // ensure pin is not left HIGH
  }
}



ISR (PCINT2_vect) // handle pin change interrupt for D0 to D7 here
{
  if ( ! digitalRead( serTx ) )   // D1 - inverted - carrier burst on when TX LOW
  {
    osc( true ) ;
    digitalWrite(ledPin, HIGH);   // show some activity
  }
  else {
    osc( false ) ;
    digitalWrite(ledPin, LOW);
  }
}


void setup() {
  Serial.begin( 2400 ) ;   // max 2400
  pinMode( pinOC2B, OUTPUT);
  pinMode( ledPin, OUTPUT);

  pciSetup( serTx );   // pin change interrupt on TX pin
  
  // set up timer 2 (OC2B - pin 3).  register TCCR2A set in function osc() ; 
  TCCR2B = _BV(WGM22) | _BV(CS21);
  OCR2A = (( F_CPU / 1000000L )  * 52 ) / 16 ;  //  55 = 36.4kHz  52 = 38.4kHz
  OCR2B = OCR2A / 2 ;  // 50% duty cycle

  osc( false ) ; // oscillator off
}

void loop() {
  
  Serial.print( "0123456789 0123456789 0123456789 abcdefghij\n" ) ;
  delay( 1000 ) ;
}