NEC Protocol Decoding with STM32 Part 3: Decoding algorithm

In the previous guide (here), we saw how to configure the required peripherals. In this guide we shall see how to develop the algorithm required to decode the data.

In this guide, we shall cover the following:

  • Decoding algorithm.
  • Code.
  • Demo

8. Decoding Algorithm:

In general, the protocol has the following:

  • Leader code
  • First address
  • Second address
  • First command
  • Second command

In Leader code, there is AGC pulse which is 9.4 milliseconds, hence we can detect this as following once the external interrupt is generated:

We start of by declare a variable to store the pulse length and read the current timer value (which should be zero) as following:

uint16_t len = 0;
len = read_nec_timer();   

Then switch the decode state:

  • Start of AGC pulse
  • End of AGC pulse
switch (decoding_state)
	  {
	  case AGC_START:
	    start_nec_timer();                          // Start 9 ms pulse measurement
	    decoding_state = AGC_END;                   // Begin decoding
	    return;

	  case AGC_END:
	    reset_nec_timer();                          // Reset timer
	    if ((len > MIN_9_MS) && (len < MAX_9_MS))   // ... 9 ms AGC
	    {
	      decoding_state = SHORT_PAUSE;             // then begin a 4,5 ms..
	      return;                                   // ..pulse measurement
	    }
	    break;

Then move the to long pause which is about 4.5 milliseconds

	  case SHORT_PAUSE:
	    reset_nec_timer();                          // Reset timer
	    if ((len > MIN_4_5_MS) && (len < MAX_4_5_MS))
	    {
	      decoding_state = NEC_ADDR_COMM;           // Begin system adress decode
	      return;
	    }
	    break;

Then first address:

case NEC_ADDR_COMM:
	    if ((len > MIN_560_US) && (len < MAX_560_US) &&
	       (bitn == MAX_BITS) &&
	       ((system & inv_system) == 0) &&
	       ((command & inv_command) == 0))
	    {
	      decoding_state = LONG_PAUSE_1;            // Begin a long pause measurement
	      reset_nec_timer();                        // Reset timer
	      bitn = DECODE_IS_OK;
	      return;
	    }
	    else
	    {
	      decoding_state = NEC_BITS_DEC;
	      return;
	    }
	    break;

Decoding the actual data:

If the pulse length is about 2.25 milliseconds, then data is 1.

If the pulse is about 1.25, then the data is 0.

	  case NEC_BITS_DEC:                            // Bits receiving
	    reset_nec_timer();                          // Reset timer
	    decoding_state = NEC_ADDR_COMM;
	    bitn++;

	    if ((len > MIN_2_25_MS) && (len < MAX_2_25_MS))
	    {
	      one_is_received();                        // Received bit is "1"
	      return;
	    }
	    else if ((len > MIN_1_125_MS) && (len < MAX_1_125_MS))
	    {
	      zero_is_received();                       // Received bit is "0"
	      return;
	    }
	    break;

For adding zero and 1:

void one_is_received(void)
{
  if (bitn < START_OF_COM)
  {
    if (bitn < START_OF_INV_SYS)     // Receive system address bits
    {
      system = ((system >> 1) | 0x80);
    }
    else
    {
      inv_system = ((inv_system >> 1) | 0x80);
    }
  }
  else
  {
    if (bitn < START_OF_INV_COM)     // Receive command bits
    {
      command = ((command >> 1) | 0x80);
    }
    else
    {
      inv_command = ((inv_command >> 1) | 0x80);
    }
  }
}


/* This functon stores the received "zero" bits
 *
 */
void zero_is_received(void)
{
  if (bitn < START_OF_COM)
  {
    if (bitn < START_OF_INV_SYS)   // Receive system address bits
    {
      system = ((system >> 1) & 0x7F);
    }
    else
    {
      inv_system = ((inv_system >> 1) & 0x7F);
    }
  }
  else
  {
    if (bitn < START_OF_INV_COM)   // Receive command bits
    {
      command = ((command >> 1) & 0x7F);
    }
    else
    {
      inv_command = ((inv_command >> 1) & 0x7F);
    }
  }
}

The rest of the algorithm (mostly identical to the first):

case NEC_ADDR_COMM:
	    if ((len > MIN_560_US) && (len < MAX_560_US) &&
	       (bitn == MAX_BITS) &&
	       ((system & inv_system) == 0) &&
	       ((command & inv_command) == 0))
	    {
	      decoding_state = LONG_PAUSE_1;            // Begin a long pause measurement
	      reset_nec_timer();                        // Reset timer
	      bitn = DECODE_IS_OK;
	      return;
	    }
	    else
	    {
	      decoding_state = NEC_BITS_DEC;
	      return;
	    }
	    break;

	  case NEC_BITS_DEC:                            // Bits receiving
	    reset_nec_timer();                          // Reset timer
	    decoding_state = NEC_ADDR_COMM;
	    bitn++;

	    if ((len > MIN_2_25_MS) && (len < MAX_2_25_MS))
	    {
	      one_is_received();                        // Received bit is "1"
	      return;
	    }
	    else if ((len > MIN_1_125_MS) && (len < MAX_1_125_MS))
	    {
	      zero_is_received();                       // Received bit is "0"
	      return;
	    }
	    break;

	  case LONG_PAUSE_1:
	    reset_nec_timer();                          // Reset timer
	    if ((len > MIN_40_MS) && (len < MAX_40_MS)) // P
	    {                                           // A
	      decoding_state = REPEAT_AGC;              // U
	      return;                                   // S
	    }                                           // E
	    break;

	  case REPEAT_AGC:
	    reset_nec_timer();                          // Reset timer
	    if ((len > MIN_9_MS) && (len < MAX_9_MS))
	    {                                           // R
	      decoding_state = REPEAT_SPACE;            // E
	      return;                                   // P
	    }                                           // E
	    break;                                      // A
	                                                // T
	  case REPEAT_SPACE:
	    reset_nec_timer();                          // Reset timer
	    if ((len > MIN_2_25_MS) && (len < MAX_2_25_MS))
	    {                                           // C
	      decoding_state = REPEAT_BURST;            // O
	      return;                                   // D
	    }                                           // E
	    break;

	  case REPEAT_BURST:
	    reset_nec_timer();                          // Reset timer
	    if ((len > MIN_560_US) && (len < MAX_560_US))
	    {                                           // R
	      decoding_state = LONG_PAUSE_2;            // E
	      rep_code++;                               // P
	      return;                                   // E
	    }                                           // A
	    break;                                      // T
	                                                // .
	  case LONG_PAUSE_2:                            // .
	    reset_nec_timer();                          // Reset timer
	    if ((len > MIN_98_MS) && (len < MAX_98_MS))
	    {                                           // C
	      decoding_state = REPEAT_AGC;              // O
	      return;                                   // D
	    }                                           // E
	    break;

	  default:
	    reset_nec_timer();                          // Reset timer
	    decoding_state = FAULT;                     // Indicate an error
	    dec_ready = 0;
	    break;
	  }

For NEC timer start and reset:


void reset_nec_timer(void)
{
	TIM2->CNT=0;
	TIM2->EGR|=TIM_EGR_UG;
}

uint16_t read_nec_timer(void)
{
  return TIM2->CNT; // Get the pulse len
}

void start_nec_timer(void)
{
  TIM2->CR1|=TIM_CR1_CEN;
  TIM2->CNT=0;
  TIM2->EGR|=TIM_EGR_UG;
}



void stop_nec_timer(void)
{
	TIM2->CR1&=~TIM_CR1_CEN;
}

For reading the data from the main

uint8_t decode_state()
{

	if((bitn==DECODE_IS_OK)){return decode_success;}
	else {return decode_failed;}
	return 0;
}

uint8_t get_system_state()
{
  return system;
}


uint8_t read_command()
{
	return decoded_data;

}

Then inside the header file, we shall add the following:

#define decode_success 1
#define decode_failed  0

uint8_t decode_state();
uint8_t read_command();
uint8_t get_system_state();

enum decoding_state {
						AGC_START,
						AGC_END,
						SHORT_PAUSE,
						NEC_ADDR_COMM,
						NEC_BITS_DEC,
						LONG_PAUSE_1,
						REPEAT_AGC,
						REPEAT_SPACE,
						REPEAT_BURST,
						LONG_PAUSE_2,
						FAULT
					};

#define         SYSTEM                     0
#define         FAULT                      0xFF
#define         MAX_BITS                   32
#define         START_OF_INV_SYS           9
#define         START_OF_COM               17
#define         START_OF_INV_COM           25
#define         DECODE_IS_OK               0x55

In the main function:

extern void SysClockConfig(void);
#include "stm32f4xx.h"
#include "delay.h"
#include "math.h"
#include "stdlib.h"
#include "NEC.h"
#include "stdio.h"
#include "oled.h"
#include "i2c.h"





int main(void)
	{

	SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /*Enable floating point unit*/
	SysClockConfig();
	systick_init_ms(64000000);
	NEC_Decoder_Init();
	SSD1306_Init();
	SSD1306_GotoXY(0,0);
	SSD1306_Puts("hello world",&Font_11x18,1);
	SSD1306_UpdateScreen();
	delay(2000);
	SSD1306_Clear();

	while(1)
		{
			if(decode_state()==decode_success)
				{
					if(get_system_state()){
					uint8_t data=read_command();
					char data_str[20];
					sprintf(data_str,"Code=0x%x",data);
					SSD1306_Clear();
					SSD1306_GotoXY(0,0);
					SSD1306_Puts(data_str,&Font_11x18,1);
					SSD1306_UpdateScreen();}


				}


		}

	}

9. Code:

You may download the entire source code from here:

10. Demo:

Happy coding 🙂

Add Comment

Your email address will not be published. Required fields are marked *