Home > AVR, Electronics, Programming, Uncategorized > RC5 device addressees and commands are partly “standardized”

RC5 device addressees and commands are partly “standardized”

During my attempt to program an Atmel micro-controller to act as a remote controller sending RC5 commands, the original remote suddenly stopped sending the correct Standby code to the device and I noticed device ID part of the RC5 frame has changed from 0x10 into 0x11. I accidentally switched the original remote to a mode for a different type of appliance! I realized that after I found a nice PDF from Freescale semiconductor showing a table with common device IDs and command IDs.

It is interesting to know these IDs are at least partly “standardized”. This PDF can be found here and maybe it will help someone else too. I don’t like linking to remote content as it can be moved or deleted easily – especially in this case – I couldn’t find this PDF on the official Freescale site.

So then I found out how to switch the original remote into the proper device mode again. :P

Example RC5 sending code for Atmega328P

If you are into coding a RC5 remote, you can use my work-in-progress code for Atmel Atmega328p as a starting point. RC5 sending code is partly based on Arduino IR library. I found out exact meaning of the timer setup bits, commented it more, added explicit device ID and command ID and RC5x support.

PB5 is used for debug LED output (Arduino Nano uses it for internal LED too), PB1 is PWM output for IR LED and PD2 is used as an external interrupt input to wake the device up from sleep mode (connecting it to low level wakes the device up). The command codes are for Marantz SR series btw…

#ifndef F_CPU
#define F_CPU 16 * 1000000UL // 16 MHz clock speed
#endif

// Atmega 328p by default
#ifdef VSCODE
#include <avr/iom328p.h>
#endif

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>

#define RC5_T1 889
#define RC5_RPT_LENGTH 46000

void mark()
{
  TCCR1A |= _BV(COM1A1); // enable PWM
  _delay_us(RC5_T1);
}

void space()
{
  TCCR1A &= ~(_BV(COM1A1)); // disable PWM
  _delay_us(RC5_T1);
}

// send numbits of LSB of data
void rc5_send_raw(unsigned long data, unsigned numbits) {
  for (unsigned long mask = 1UL << (numbits-1); mask; mask >>= 1) {
    if (data & mask)  {
      // 1 is space, then mark
      space(); 
      mark();
    } else {
      // 0 is mark, then space
      mark();
      space();
    }
  }
}

void rc5_send(unsigned char device, char subdev, unsigned char command)
{
  static unsigned long toggle = 0;
  
  toggle = !toggle;

  // two starting ones
  mark();
  space();
  mark();

  // toggle
  rc5_send_raw(toggle, 1);

  // 5 device address bits
  rc5_send_raw(device, 5);

  // RC5x ?
  if (subdev >= 0) {
    space();
    space();
    space();
    space();

    // 6 subdevice address bits
    rc5_send_raw(subdev, 6);
  }

  // 6 command bits
  rc5_send_raw(command, 6);

  // always end with the LED off
  space();
}

void blink()
{
  PORTB ^= _BV(PORTB5);
  _delay_ms(100);
  PORTB ^= _BV(PORTB5);
  _delay_ms(100);
  PORTB ^= _BV(PORTB5);
  _delay_ms(300);
  PORTB ^= _BV(PORTB5);
  _delay_ms(300);
}

int main(void)
{
  // PD2 input with pullup for INT0
  DDRD &= ~_BV(DDD2);
  PORTD |= _BV(PORTD2);

  DDRB |= _BV(DDB5); // led output

  // PB1 (D9/OC1A) as IR LED OUT
  DDRB |= _BV(DDB1);

  // TIMER1 for PWM
  const uint16_t khz = 36;
  const uint16_t pwmval = F_CPU / 2000 / khz;
  // PWM, Phase Correct
  //  top=ICR1
  //  update OCR1x (output compare register) at TOP
  //  TOV1 (Timer/Counter Overflow Flag) set at BOTTOM
  TCCR1A = _BV(WGM11);
  TCCR1B = _BV(WGM13) | _BV(CS10); // CS10 without other CSx => no prescaling
  ICR1 = pwmval;

  // specifies duty percentage in <0-ICR1> range
  OCR1A = pwmval / 3; // Output compare of 1A oc unit, compares with TCNT1

  // INT0 on low level (00)
  EICRA = 0x00;

  // sleep mode
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);

  while (1) {
    blink();

    rc5_send(0x10, 0x25, 0x2A); // night mode toggle (RC5x type)
    // rc5_send(0x10, -1, 0xC); // pwr
    // rc5_send(0x10, -1, 0x10); // vol+
    // rc5_send(0x10, -1, 0x11); // vol-
    // _delay_ms(1000);

    // enable external INT0 to wake up
    EIMSK |= _BV(INT0);

    // sleep_mode() has a possible race condition so let's use these macros
    sleep_enable();
    sei(); // turn interrupts on
    sleep_cpu();
    sleep_disable();

    // we want single external interrupt to wake up only
    EIMSK &= ~_BV(INT0);
    cli();
  }
}

Categories: AVR, Electronics, Programming, Uncategorized Tags:
  1. Lucas
    May 19th, 2022 at 18:29 | #1

    Heya!
    I’m an engineering student, and I was studying about RC5 and I found your program and it helped me a lot. But I have a question about this function below:

    void rc5_send_raw(unsigned long data, unsigned numbits)

    I didn’t understand this function, could you please explain how it works?

  2. k3a
    May 24th, 2022 at 19:18 | #2

    @Lucas
    Hi & sorry for my delay in the response.
    It is not as difficult as it may seem at the first sight. The purpose of it is to transmit an integer value `data` with the length of `numbits` bits by sending the most significant bit (MSB) first. It is using bitwise operations (bit shifts and AND) for that. First it initiates a bit mask by doing `1UL << (numbits-1)`. If numbits is 8 (one byte) then it starts with 0b00000001 and shifts this right-most '1' bit 7 positions to the left to create 0b10000000 bit number. Then it uses the mask and bit 'and' operation to check if that bit is set in the `data` value at the same position and transmits space/mark accordingly. The next iteration just updates the mask by shifting it one bit to the right and continues as long as there is still some bit set in the mask. The iteration ends once 1 is shifted to the right to create 0b00000000 (effectively 0 decimal). Hope it helped!

  3. Victor Duarte
    May 27th, 2022 at 18:40 | #3

    Hello!
    The RC5 protocol have 14 bits, I don’t understand why are we using, for example, the number “0x10252A” which have 24 bits, could you please help me to get that? I think it’s from subdev, but I don’t understand that too, thanks!

  4. Victor Duarte
    May 27th, 2022 at 18:46 | #4

    @Victor Duarte
    And I’m also having trouble with the command, could you make an example to code FF22DD set up a LED? Thanks again!!!

  5. k3a
    May 27th, 2022 at 19:38 | #5

    @Victor Duarte
    This is either Marantz-specific addition or I messed up the RC5x implementation :P that’s why there is “RC5 ?” comment. It seems RC5x should be 16bit as well, just the second starting bit is re-purposed as an inverted sixth most significant command bit, see [1]. Sorry I don’t know but the code based on this code has worked for me for years (its job was to listen for NEC commands and convert them to RC5, i.e. so I could do some operations on RC5 Marantz receiver by using a NEC LG TV remote). It could happen that this “subdev” was actually creating a beginning of a proper RC5x command! I used 711 for RC5_T1 in it, though.

    You should probably remove the subdev part altogether. And maybe there should be space() after the second starting bit (I am not sure, the code worked for me as-is so I didn’t care).

    If it doesn’t work for you, ensure you have proper resistor for the LED according to its specification and that the output goes to the proper pin (it should be PB1).
    For RC5 16bit commands you are expected to call rc5_send(device, -1, command) where device is 5-bit and command is 6-bit number.

    You can also take a look to the code which did the conversion job for me [2]. RC5 code in it is very similar, it just uses platformio as the dev environment.
    Hopefully that will help more. It is proof-of-concept quality code, it is a good starting point but you may need to tweak it to meet your needs. :)

    [1] http://lirc.sourceforge.net/remotes/rc-5/RC-5-extented
    [2] https://files.k3a.me/p/nec2rcfive.zip

  6. Victor Duarte
    May 27th, 2022 at 20:56 | #6

    @k3a
    Thanks for the help!!

  1. No trackbacks yet.

deadly laser