It's all in the timing!

Configuring the EFM32 to produce the correct timing for the InfraRed signals is easy.

All the fiddly work has been done and hidden from the developer.

All the developer needs to do is include and call upon the Clock Management Unit (CMU) API from the EFM library.

For a 1MHz inbuilt Oscillator Frequency we use,



To derive the carrier frequency we can allocate and configure Timer 0 to produce a 50% duty cycle by counting the desired number of 1MHz cycles for 38-41kHz;

  • Total Timer Time
  • Active Portion Time


The calculation is 1,000,000/38000 is about 26 so we can use 26 for the Total Timer Period and half that 13 for the active portion time.

This is done in code using;

  CMU_ClockEnable(cmuClock_TIMER0, true);                              // Enable Timer0 Clock

  TIMER0->ROUTE = TIMER_ROUTE_LOCATION_LOC3 |  TIMER_ROUTE_CC0PEN;     // Route Timer 0 CC0 output to LOC3(which is PD1)

  TIMER0->CC[0].CTRL = TIMER_CC_CTRL_MODE_PWM;                         // Configure as PWM Generator

  TIMER0->CC[0].CCV  = 13;                                             // Active Timer Time

  TIMER0->TOP        = 26;                                             // Total Portion Time


We require another timer to produce desired lengths carrier frequency bursts. This is done by Timer1.

It is configured to 1 microsecond resolution by using;

  TIMER1->TOP = length_us;

  while (!(TIMER1->IF & TIMER_IF_OF));                                 // Wait configured time


  // Stop Timer
  TIMER1->CNT = 0;
  TIMER1->IFC = ~0;


All easy stuff.


The NEC IR protocol for TVs

This is a very simple protocol.

This protocol consists of sending a sequence of 38kHz carrier bursts in a known fashion that is understood by the remote device.

The protocol is asynchronous meaning that IR command can be sent at anytime and is not synchonised to any other signal such as a clock signal.

The TV waits for a Start of Sequence burst to commence decoding the incoming signal.


This is defined as a 9ms active carrier burst followed by a 4ms inactive pause.

Logical bits are defined as;

  • '0' - 562.5uS active burst followed by a 562.5uS inactive pause
  • '1' - 562.5uS active burst followed by a 1675uS inactive pause

(or thereabouts - There are some slight timing variations around.)


web volume up.bmp

Since the bit transmission times are different in duration to keep the datagrams identical in duration the datagram has an equal number of logical '0' bits and logical '1' bits.

This is done by sending the desired byte value followed by its one's complement value ensuring an equal number of '1' and '0's.


The data bits are arranged in 8 bit bytes and are sent LSB (Least Significant Bit) first.


The actual datagram sent to the TV is nothing more than;

  • Start Condition
  • DeviceID
  • Command
  • A trailing '0' bit to signify the end of datagram
  • An inactive period to make the total time equal 108mS before sending another datagram.

With one exception - the repeat command which is just;

  • a 9ms burst
  • followed by a 2.2ms inactive period
  • with a trailing '0' bit
  • and inactive time to make up 108mS transmission time.


The DeviceID is an 8 bit value assigned to the remotely controlled device by the Manufacturer. For my TV it is 0x50.

The Command is an 8 bit value representing the requested command. The actual function assigned to the Command varies from manufacturer to manufacturer

For my TV the commands include;

     0x17 = Power

     0x15 = Volume Down

     0x12 = Volume Up


This was determined by observing the output waveforms on an oscilloscope.

It was also an opportunity to ensure that the output of the EFM matched the original remote.

From observation the specified timings were slightly different. The code was tuned to match it.


Sending the bits is easy. All that is done is the PWM output is turned on and off as desired using programmed combinations of;

    onModulation(8520); Delay(4150);   // Send Header

    onModulation(560);  Delay(450);    // Send '0'

    onModulation(560);  Delay(1527);   // Send '1'

to formulate the valid datagrams.


The provided parameters are in microseconds.