Using Timer Interrupts to Generate 38kHz on IR LED with Flora / ATmega32u4

Hi y’all -
I’m working on a project using Adafruit Floras to pulse IR LEDs at 38kHZ for IR transmission. The LED pulses will be sensed by IR phototransistors connected to other Arduinos. By varying the length of the IR blasts, I want each LED to send a different signal, so I can trigger various activities on the receiving Arduinos, based on the position/direction of the person wearing the Flora(s).

The main problem I’m having is generating a reliable 38kHz carrier wave on the IR LED. Right now I’m using code based on delayMicroseconds() in order to guesstimate the 26.32 microseconds needed for a full cycle at 38kHz.
Here’s that code:

while (microsecs > 0) {
   // 38 kHz is about 13 microseconds high and 13 microseconds low
   digitalWrite(IRledPin, HIGH);  // this takes about ? microseconds to happen
   delayMicroseconds(4);         // hang out for some microseconds, change this if not working
   digitalWrite(IRledPin, LOW); 
   delayMicroseconds(4);         // hang out for some more microseconds
   // so 26 microseconds altogether
   microsecs -= 26;
  }

That code works! But I think it would work better if I were using interrupts to get really accurate timing.
I’ve done some research, but am running into trouble since the Flora uses the ATMega32u4 and runs at 8MHz and has a somewhat confusing (to me!) pinout. I’m new to Arduino (and electronics) and C / AVR programming is quite a bit over my head still.

Here’s my paltry attempt at a sketch using interrupts - it doesn’t seem to do anything with the IR led, however and I’m not sure why.

#define CODE 2000 //length of "mark" in usecs (1 cycle of IR mark @ 38khz ==  26.32 usecs // CODE x 0.038 = # of cycles)

int irLedPin = 3; //IR on Pin 3
float codeCyclesFloat = 0; //exact # of cycles for each burst
unsigned int codeCyclesInt = 0; //approx. # of cycles for each burst (for faster math)

//storage variables
boolean irLedBurst = 0; //toggles between IR "bursts" and "breaks"
volatile boolean toggleIR = 1; //toggles HIGH/LOW @ 38kHz IR pulse
volatile int irBurstTimer = 0;//counts the IR pulses (irBurstTimer x 2 == # of full HIGH/LOW cycles) (irBurstTimer x 13.16 == # of microseconds)

void setup(){
  
  pinMode(irLedPin, OUTPUT);
  
  cli();
  
  //set timer1 (16-bit) interrupt at 76000 Hz  (i.e. 38kHz x 2 for full wave)
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 38khz increments
  OCR1A = 104;// = ((8*10^6) / (76000*1)) - 1 [must be <65536] ... that is: (8MHz / (76Khz*prescaler) - 1 [count begins with 0]
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS10 bit for no prescaler
  TCCR1B |= (1 << CS10);
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);
  
  sei();
  
  //figure out # of cycles from CODE
  codeCyclesFloat = (float)CODE / 26.32; //derive # of cycles per burst from CODE
  Serial.print("codeCyclesFloat = ");
  Serial.print(codeCyclesFloat, 10);
  Serial.print("\n\r");
  codeCyclesInt = (int)codeCyclesFloat;
  Serial.print("codeCyclesInt = ");
  Serial.print(codeCyclesInt, DEC);
  Serial.print("\n\r");
  

}

void loop(){
  //program here
  Serial.print("Sending 10 bursts of IR code: ");
  Serial.print(CODE);
      
  for (int i=0;i<10;i++){
    
    //send burst of CODE usecs, and delay appropriately after each burst
    
    //burst:
    burstIR(codeCyclesInt);
    
    //delay:
    if (CODE < 1780) {
      //delay for >= 10 cycles, per Vishay TSOP 38338 datasheet
      delayMicroseconds(300);
    } else {
      //delay for 7x CODE, per datasheet
      delayMicroseconds(7 * CODE);
    }
  }
}

void burstIR(int burstLength) {
  // we'll count until we reach the number of cycles we are told to burst for (burstLength)
  
  //turn on IR LED pulse (in ISR)
  irLedBurst = true;
  
  while (irBurstTimer < (burstLength / 2)) {
   //burst // irBurstTimer is incremented in half-cycles
  }
  
  //turn off IR LED pulse (in ISR)
  irLedBurst = false;
  
  //make sure IR LED is off
  digitalWrite(irLedPin, LOW);
  
  
}

ISR(TIMER1_COMPA_vect) {
//timer1 interrupt @ 76kHz toggles pin 2 (irLedPin) if irLedBurst boolean is true
//generates pulse wave of frequency 76kHz/2 = 38kHz (takes two cycles for full wave - toggle high then toggle low)
//also increments irBurstTimer variable

  //FIND OUT HOW TO HARDWARE TOGGLE PIN 3 ON FLORA... Arduino Forum
  //Simply Toggle Pin rather than Digital Write
  if (irLedBurst){
    //if there should be an IR pulse, cycle IR
      digitalWrite(irLedPin, toggleIR);
      toggleIR = !toggleIR;
  }
  irBurstTimer++;
  //increment the timer (counts half-cycles == +13.16us)
}

I would like to change this so that the ISR toggles the IR LED pin directly rather than using digitalWrite (actually the pin is connected to a transistor hooked into Vbatt to power the LED with a lot of amperage). I also need to count a certain number of microseconds or cycles in order to figure out how long to send the IR blasts so that different LEDs are identifiable. I’m not sure if incrementing a counter in the ISR is the best way to do that.

Any ideas on what I might be doing wrong? The LED doesn’t seem to light up at all. Any advice or resources on interrupts would be welcome to. I’ve tried to do my homework but at this point things are over my head.

Thanks!

Why not just use an ATtiny85 to generate the 38khz ?

This link explains how to generate a 38kHz IR carrier.

...R

By varying the length of the IR blasts, I want each LED to send a different signal, so I can trigger various activities on the receiving Arduinos, based on the position/direction of the person wearing the Flora(s).
The main problem I’m having is generating a reliable 38kHz carrier wave on the IR LED.

Criteria
Freq = 38khz (period = 16.26 uS)
Duty Cycle = VARIABLE

This sketch is for an ATtiny85 (NOT AN UNO) and generates a PWM signal at PB0 (IC pin-5) 38khz that varies the duty cycle based on the analog input at ADC1 (IC pin-7)
The ATtiny85 is a $3 8-pin IC that can be programmed by an UNO in Arduino as ISP programming mode.
I tested it with a scope and the frequency is 38khz with a duty cycle that varies 10% to 90%.
You can comment out the analog input code lines and uncomment out the FOR loop which continuosly varies the duty cycle as an example. The duty cycle is set by the    OCR0A =1475 ;   // CTC Compare value (count up to 1475) //  
statement and in Setup and then later varied by the analog input MAP statement. (and also by the FOR loop)
The ATtiny85 is an 8-pin IC.
The board file for THIS sketch is 16 Mhz Internal PLL , 4.3V BOD
You have to use Burn Bootloader to set the fuses otherwise it will not run at 16 Mhz.
The analog input is applied to IC pin-7 which is ADC1 . The 38khz PWM is output at IC Pin-5 (PB0)
If you look at the attached scope photos, you can see that in all three cases of duty cycle , the period remains about 16 uS.
If you want to learn how to program an ATtiny85 with an UNO there are numberous tutorials on the web, most notably the MIT one.

There are some Instructables as well.
Keep in mind there are several things that have to be correct besides the wiring.
The board file.
The “cores” file (which you have to download in a zip file)
The UNO has to have ArduinoISP sketch loaded before switching the boards to the ATtiny85 board file.

 // backlight.c
// for NerdKits with ATmega168
// mrobbins@mit.edu
#define F_CPU 16000000
 #include <Arduino.h>
 #include <WProgram.h>
int analogPin = 1;   // potentiometer connected to analog pin ADC1 (IC pin-7)
int freq=0;
int  freqAdj=0;
void setup()
{
    pinMode(analogPin,INPUT);
  
  DDRB |= bit (PB0);     //  Set pin PB0 as output
  
  // stop Timer 0
  TCCR0A = 0;
  TCCR0B = 0;
  TCNT0 = 0;
  
  // set up Timer 0
  TCCR0A |= bit (COM0A1);   // Toggle OC0A/OC0B on Compare Match Table 11-2)
                            // Table 11-2. Compare Output Mode, non-PWM Mode ,ie: 50% duty cycle)
  TCCR0B |= bit (CS00);     // /8 prescaler (Table 11-6: )
  TCCR0A |= bit (WGM01);   // 
  TCCR0A |= bit (WGM00);   // Start Timer 0 in CTC mode (Table 11-5)
  OCR0A =1475 ;   // CTC Compare value (count up to 1475) // 
}

void loop()
{
  int  freqAdj = analogRead(analogPin);   // read the input pin
  freqAdj = map(freqAdj, 100, 1023, 1400, 1500);
    OCR0A=freqAdj;
    
 // FOR LOOP EXAMPLE 
   // for (int i=1400;i<1451;i++)
   //  {
    //   OCR0A = i;
    //   delay(30);
  //   }
  //  for (int i=1450;i>1401;i--)
   //  {
   //    OCR0A = i;
   //     delay(30);
   //  }
   
//int dutycycle  = map(freqAdj, 1450, 1500, 10,100);
}

This sketch can be made to work on a Pro-Mini or an UNO. using some different code.

One more thing. I shouldn’t have to mention this but in case it hasn’t occurred to you, you can make another pin an OUTPUT , like ADC2 or ADC3 and connect THAT pin as a PWM OUTPUT to the analog pin used in this sketch, and use analogWrite statements to generate the voltage instead of a pot, but you need to add an RC filter consisting of a 2.8k to 4.7k ohm resistor and at least a 1uF cap to smooth the PWM and convert it to an ACTUAL steady state analog value instead of a PWM switching signal. Why you would do this instead of just changing the OCR0A value with code I don’t know but you should know that it is possible IF you choose to do it. I only mention it because the PWM signal does not NEED to come from the ATtiny85. You can use your Flora (or an UNO) to generate it using the same filter method. (FYI).

If you want to send a constant frequency / constant duty cycle pulse train, you could do it either using a hardware timer to generate it directly, or to trigger a timer interrupt which runs your interrupt handler to do it. I'm not familiar with the Adafruit Floras and I don't know what standard Arduino it is compatible with, but the PWM library makes it easy to configure the analog outputs to run at different frequencies for the first approach, and the TimerOne library makes it easy to configure timed interrupts for the second approach. If either of these libraries were compatible with your Arduino, they would give you a fairly simple solution.

By varying the length of the IR blasts, I want each LED to send a different signal, so I can trigger various activities on the receiving Arduinos, based on the position/direction of the person wearing the Flora(s).
The main problem I'm having is generating a reliable 38kHz carrier wave on the IR LED.

I interpreted the OP's statement above to mean:

Criteria
Freq = 38khz (period = 16.26 uS)
Duty Cycle = VARIABLE

Hi -
Thanks for all the great advice. I'll look into the ATtiny85 for the future - right now I'm looking to stick with the Flora directly since that's what I have on hand and it's easily sewable (this is a wearable project). I believe the Flora is closest to a Leonardo due to the Atmega32u4 - but it runs at 8MHz.

The TimerOne library looks like it may do the trick for this one. I'll report back if I get it working.

tylerrr:
The TimerOne library looks like it may do the trick for this one. I'll report back if I get it working.

The code I linked to in Reply #2 is very simple and doesn't need any library.

...R

Robin2:
This link explains how to generate a 38kHz IR carrier.

…R

Thanks! I basically did this and got it (pretty much) functional. A few things tripped me up in that post, at first - mainly the sbi() and cbi() - which now I realize are just set bit and clear bit. I had to go into the ATMega32u4 datasheet to figure out how to set Compare Output Mode on Timer1 to toggle 0C1A (Pin 9 on the Flora) - now I see that the Serial IR Comm works using Compare Output Mode on Timer2, so pretty much the same idea.

If anyone is interested, here’s the tutorial that helped me understand AVR C Programming and bitwise operations.

That tutorial + this tutorial on timer interrupts + the ATMega32u4 datasheet got me the following code:

#define CODE 2000 //length of IR "burst" in usecs 

int irLedPin = 9; //IR on Pin 3

//counters for micros()
unsigned long time0 = 0;
unsigned long time1 = 0;

void setup(){
  
  pinMode(irLedPin, OUTPUT);
  
  cli(); //turn off interrupts
  
  //set timer1 (16-bit) interrupt at 76000 Hz  (i.e. 38kHz x 2 for full square wave)
  TCCR1A = 0;// set entire TCCR1A (Timer 1 Control Register A) register to 0
  TCCR1B = 0;// same for TCCR1B (Timer 1 Control Register B)
  TCNT1  = 0;//initialize Timer 1 counter value to 0
  // set compare match register for 38khz increments
  OCR1A = 104;// = (8MHz / (76Khz*prescaler) - 1 [value must be <65536 for 16bit]
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS10 bit for no prescaler
  TCCR1B |= (1 << CS10);
  // enable timer compare interrupt
  //TIMSK1 |= (1 << OCIE1A);
  
  //enable toggle OC1A (Flora Pin 9) on compare match
  TCCR1A |= (1 << COM1A0);
  
  sei(); //turn interrupts back on
  
  //set time0 to current micros()
  time0 = micros();
}

void loop(){
  
  time1 = micros();
  
  //if CODE microseconds have elapsed, turn off Pin 9 and pause for a break
  if ((time1 - time0) >= CODE){
     
     //disable compare output mode, Pin 9 now accessible
     TCCR1A &= ~(1 << COM1A0);
     
     //make sure LED is off
     digitalWrite(irLedPin, LOW);
     
     //tell the serial monitor the code was sent
     Serial.print("sent pulse for ");
     Serial.print((time1 - time0), DEC);
     Serial.print("microseconds, according to ");
     Serial.print(CODE, DEC);
     Serial.print(" CODE");
     Serial.print("\n\r");
     
     //delay 20ms between bursts
     delay(20);
     
     //reset time0 to current micros()
     time0 = micros();
     
     //re-enable compare output mode
     TCCR1A |= (1 << COM1A0);

  } 
}

The only problem is that my IR receiver is now reporting the signal received for 1600 microsecs rather than 2000. That may be my receiver code however. The transmitter is reporting bursts of 2000 microseconds almost perfectly.