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. No comments yet.
  1. No trackbacks yet.