RC 5 Demo C

Einleitung

Ein Programm zum Steuern des ASURO über eine Infrarot Fernbedienung. Dies ist eine verbesserte Version der IRDemo aus dem ASURO Selbsttest. Der Programmcode stammt ursprünglich vom c't-Bot und ist mit geringen Änderungen für den ASURO adaptiert worden. Das Originalprogramm funktioniert leider überhaupt nicht, da eine mir unbekannte Fernbedienung verwendet wurde. In dieser Version kann jede Fernbedienung verwendet werden, die RC5 Codes liefert. Dies sind praktisch alle Universalfernbedienungen.

RC5 Code

RC5 ist eine Erfindung von Philips und Marantz. Wer noch einen alten Philips Fernseher oder Videorecorder besitzt, kann es auch mit der Gerätefernbedienung versuchen. Neuere Geräte von Philips benutzen dagegen den RC6 Code. Dieser ist nicht kompatibel zu RC5. Auch die Fernbedienungen von Hauppauge, die bei deren TV Karten beiliegen, liefern RC5 Code. Wer es mit der Universalfernbedienung probiert sollte den Wahlschalter auf TV oder Videorecorder stellen und als Gerätecode Philips einstellen.

Hauppauge TV Card Fernbedienung Universalfernbedienung

Programm

Der Programmcode aller Beispiel- und Testprogramme kann im Download Bereich heruntergeladen werden, bzw. befinden sich im Examples Ordner der Asuro Lib. Ab der Version 2.70RC3 der Asuro Lib sind die RC5 Funktionen bereits enthalten. Zur Verwendung muß lediglich das Makefile angepaßt werden und die Header-Datei rc.h eingebunden werden. Ein Beispielprojekt befindet sich im Examples Ordner der Asuro Lib unter RC5Test

Programmcode rc5.h

#include <inttypes.h>

#define RC5_TOGGLE  0x0800    /*!< Das RC5-Toggle-Bit */
#define RC5_ADDRESS 0x07C0    /*!< Der Adressbereich */
#define RC5_COMMAND 0x103F    /*!< Der Kommandobereich */

#define RC5_MASK (RC5_COMMAND)

extern volatile uint16_t  RC5data;     /*!< letztes komplett gelesenes RC5-Paket */
extern volatile uint8_t   enableRC5;   /*!< schaltet die RC5 Abfrage ein/aus */

/*!
 * Init RC5
 */

void InitRC5(void);

/*!
 * RC5 Daten lesen
 * @return Wert von ir_data, loescht anschliessend ir_data
 */

uint16_t ReadRC5(void);

/*!
 * RC5 Interrupt Serviceroutine,
 * wird ca. alle 222.2 us aufgerufen
 */

void IsrRC5(void);

Programmcode rc5.c

// ========================================================================
// RC5 Infrarot-Empfaenger
// ========================================================================
#include <avr/io.h>
#include "rc5.h"


// -----------------------------------------------------------------------------
// Timing
// -----------------------------------------------------------------------------
#define IR_SAMPLES_PER_BIT     8   /*!< 8 Samples per Bit */
#define IR_SAMPLES_PER_BIT_EARLY 6  /*!< Flanke fruehestens nach 7 Samples */
#define IR_SAMPLES_PER_BIT_LATE 10  /*!< Flanke spaetestens nach 9 Samples */
#define IR_SAMPLES_PER_BIT_MIN   3  /*!< Flanke vor 3 Samples -> paket verwerfen */
#define IR_PAUSE_SAMPLES       250  /*!< Startbit ist erst nach 200 Samples ohne */
// Pegelaenderung gueltig -- eigentlich muesste
// man rund 500 Samples abwarten (50 x
// Bitzeit), doch weil der Samplezaehler ein
// Byte ist, beschraenken wir uns hier auf ein
// Minimum von 250 Samples

#define IR_PORT   PORTD     /*!< Port D */
#define IR_DDR    DDRD      /*!< DDR of Port D */
#define IR_PINR   PIND      /*!< Port D input */
#define IR_PIN    PD0       /*!< Pin 0 */


static uint8_t     RC5lastsample = 0/*!< zuletzt gelesenes Sample */
static uint8_t     RC5bittimer   = 0/*!< zaehlt die Aufrufe von ir_isr() */

static uint16_t    RC5data_tmp = 0;    /*!< RC5-Bitstream */
static uint8_t     RC5bitcount = 0;    /*!< anzahl gelesener bits */

volatile uint16_t  RC5data = 0;        /*!< letztes komplett gelesenes RC5-paket */
volatile uint8_t   enableRC5 = 0;      /*!< schaltet die RC5 Abfrage ein/aus */

/*!
 * Interrupt Serviceroutine
 * wird alle 222.2us aufgerufen
 */

void IsrRC5 (void)
{
  // sample lesen
  uint8_t sample = 1;

  if ((IR_PINR & (1<<IR_PIN)) != 0)
  {
    sample = 0;
  }

  // bittimer erhoehen (bleibt bei 255 stehen)
  if (RC5bittimer<255)
  {
    RC5bittimer++;
  }

  // flankenerkennung
  if ( RC5lastsample != sample)
  {
    if (RC5bittimer<=IR_SAMPLES_PER_BIT_MIN)
    {
      // flanke kommt zu frueh: paket verwerfen
      RC5bitcount=0;
    }
    else
    {
      // Startbit
      if (RC5bitcount==0)
      {
        if ( (sample==1) && (RC5bittimer>IR_PAUSE_SAMPLES) )
        {
          // Startbit speichern
          RC5data_tmp = 1;
          RC5bitcount++;
        }
        else
        {
          // error
          RC5data_tmp = 0;
        }

        // bittimer-reset
        RC5bittimer = 0;

        // Bits 2..14: nur Flanken innerhalb des Bits beruecksichtigen
      }
      else
      {
        if (RC5bittimer >= IR_SAMPLES_PER_BIT_EARLY)
        {
          if (RC5bittimer<=IR_SAMPLES_PER_BIT_LATE)
          {
            // Bit speichern
            RC5data_tmp = (RC5data_tmp<<1) | sample;
            RC5bitcount++;
          }
          else
          {
            // zu spaet: paket verwerfen
            RC5bitcount = 0;
          }

          // bittimer-reset
          RC5bittimer = 0;
        }
      }
    }

  }
  else
  {
    // keine flanke innerhalb bitzeit?
    if (RC5bittimer > IR_SAMPLES_PER_BIT_LATE)
    {
      // 14 bits gelesen?
      if (RC5bitcount==14)
      {
        RC5data = RC5data_tmp;
      }
      // paket verwerfen
      RC5bitcount = 0;
    }
  }

  // sample im samplepuffer ablegen
  RC5lastsample = sample;


}


/*!
 * IR-Daten lesen
 * @return wert von ir_data, loescht anschliessend ir_data
 */

uint16_t ReadRC5 (void)
{
  uint16_t retvalue = RC5data;
  RC5data = 0;
  return retvalue;
}

/*!
 * Init IR-System
 */

void InitRC5 (void)
{
  IR_DDR  &= ~IR_PIN;   // Pin auf Input
  IR_PORT |= IR_PIN;    // Pullup an
  enableRC5 = 1;
}

Programmcode test.c

Das Testprogramm sendet die empfangenen RC5 Codes über den IR Transceiver an den PC. Das ist praktisch zum Feststellen, welche Taste welchen Code liefert. Entsprechend kann man die gefundenen Codes dann im Test Programm ändern oder neue dazufügen. Bisher werden nur 5 Befehle benutzt, die den ASURO vorwärts, rückwärts, links rechts und stoppen lassen.

#include "asuro.h"
#include "rc5.h"
#include <stdlib.h>

#define DIARWD   0x1008
#define DIAFWD   0x1002
#define DIALEFT  0x1004
#define DIARIGHT 0x1006
#define DIASTOP  0x1029

#define TUNERRWD   0x1021
#define TUNERFWD   0x1020
#define TUNERLEFT  0x1011
#define TUNERRIGHT 0x1010
#define TUNERSTOP  0x1025

#define OFFSET  0x3F
#define STEP    5

int speedLeft,speedRight;

void IRFwd(void)
{
  speedRight += STEP;
  speedLeft  += STEP;
  if (speedLeft < 0 && speedLeft >= -OFFSET) speedLeft = 1;
  if (speedRight < 0 && speedRight >= -OFFSET) speedRight = 1;
  FrontLED(ON);
  BackLED(OFF,OFF);
}

void IRRwd(void)
{
  speedRight -= STEP;
  speedLeft  -= STEP;
  if (speedRight > 0 && speedRight <= OFFSET)  speedRight = -1;
  if (speedLeft > 0 && speedLeft <= OFFSET)  speedLeft = -1;
  FrontLED(OFF);
  BackLED(ON,ON);
}

void IRLeft (void)
{
  speedLeft  -= STEP;
  if (speedLeft > 0 && speedLeft <= OFFSET) speedLeft = -1;
  speedRight += STEP;
  if (speedRight < 0 && speedRight >= -OFFSET) speedRight = 1;
  FrontLED(OFF);
  BackLED(ON,OFF);
}

void IRRight (void)
{
  speedLeft  += STEP;
  if (speedLeft < 0 && speedLeft >= -OFFSET) speedLeft = 1;
  speedRight -= STEP;
  if (speedRight > 0 && speedRight <= OFFSET) speedRight = -1;
  FrontLED(OFF);
  BackLED(OFF,ON);
}


void IRStop(void)
{
  speedRight = speedLeft = 0;
  FrontLED(OFF);
  BackLED(OFF,OFF);
}

int main(void)
{

  static unsigned int cmd;
  unsigned char leftDir = FWD, rightDir = FWD;
  char text[7];

  Init();
  InitRC5();

  SerPrint("RC5 Test\r\n");
  while (1)
  {
    cmd = ReadRC5();
    if (cmd)
    {
      cmd &= RC5_MASK;
      itoa(cmd, text, 16);
      SerPrint(text);
      SerPrint("\r\n");

      switch (cmd)
      {
        case TUNERRWD :
        case DIARWD :
         SerPrint("rwd\r\n");
         IRRwd();
        break;
        case TUNERFWD :
        case DIAFWD :
          SerPrint("fwd\r\n");
          IRFwd();
        break;
        case TUNERLEFT :
        case DIALEFT:
          SerPrint("lft\r\n");
          IRLeft();
        break;
        case TUNERRIGHT :
        case DIARIGHT:
          SerPrint("rgt\r\n");
          IRRight();
        break;
        case TUNERSTOP :
        case DIASTOP :
          SerPrint("stp\r\n");
          IRStop();
        break;
      }
    }
    if (speedLeft > 0 && speedLeft <  OFFSET) speedLeft += OFFSET;
    if (speedLeft < 0 && speedLeft > -OFFSET) speedLeft -= OFFSET;
    if (speedRight > 0 && speedRight <  OFFSET) speedRight += OFFSET;
    if (speedRight < 0 && speedRight > -OFFSET) speedRight -= OFFSET;

    leftDir = rightDir = FWD;
    if (speedLeft < 0)  leftDir = RWD;
    if (speedRight < 0) rightDir = RWD;

    if (speedLeft >   255) speedLeft  =  255;
    if (speedLeft <  -255) speedLeft  = -255;
    if (speedRight >  255) speedRight =  255;
    if (speedRight < -255) speedRight = -255;

    MotorDir(leftDir,rightDir);
    MotorSpeed(abs(speedLeft),abs(speedRight));
    Msleep(100);
  }
  return 0;
}

Anpassungen asuro.c

Die RC5 Empfangsroutine wird vom Timer Interrupt aus alle 222,2µs angesprungen. Dazu reicht es aus, nur auf jeden 8. Timer Interrupt zu reagieren. Damit die RC5 Service Funktion nicht unnötig in allen Projekten eingebunden wird, wird der Aufruf bedingt eingebunden. Dazu dient die #ifdef Abfrage. Der Programmcode zwischen #ifdef und dem #endif wird nur mitübersetzt, wenn RC5_AVAILABLE definiert ist. Definiert wird RC5_AVAILABLE allerdings nicht im Sourcecode, sondern im Makefile. Dadurch gibt es keine Fehlermeldungen wegen undeklarierter Variablen und Funktionen. Da RC5_AVAILABLE in anderen Projekten nicht definiert ist, wird auch die RC5 Service Funktion nicht eingebunden.

SIGNAL (SIG_OVERFLOW2)
{
  TCNT2 += 0x25;
  count36kHz ++;
  if (!count36kHz)
    timebase ++;
#ifdef RC5_AVAILABLE
  if (enableRC5 && !(count36kHz % 8))
    IsrRC5(); // wird alle 222.2us aufgerufen
#endif 
}

Anpassungen Makefile

Das Makefile für das Testprogrammm bedarf einiger Anpassungen. SO muß das zusätzliche Sourcefile rc5.c eingebunden werden und RC5_AVAILABLE definiert werden. Mit der Option -D bei den CFLAGS (Compiler-Optionen) kann man ein Define im Makefile definieren. Dies bewirkt dasselbe wie ein #define RC5_AVAILABLE in einem C-Sourcefile.


# List C source files here. (C dependencies are automatically generated.)
SRC = $(TARGET).c

# If there is more than one source file, append them above, or adjust and
# uncomment the following:
SRC += asuro.c \
       rc5.c

# Optional compiler flags.
#  -g:        generate debugging information (for GDB, or for COFF conversion)
#  -O*:       optimization level
#  -f...:     tuning, see gcc manual and avr-libc documentation
#  -Wall...:  warning level
#  -Wa,...:   tell GCC to pass this to the assembler.
#    -ahlms:  create assembler listing
CFLAGS = -g -O$(OPT) -I../../lib/inc -DRC5_AVAILABLE\
   -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums \
   -Wall -Wstrict-prototypes \
   -Wa,-ahlms=$(<:.c=.lst)

Weblinks