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
#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 "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.
{
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.
GeSHi Error: GeSHi could not find the language makefile (using path /hp/aa/ad/zd/www/asurowiki/pmwiki/cookbook/geshi/geshi/) (code 2)
Weblinks
- c't-Bot Wiki - Fernbedienübersicht
- www.spettel.de - AVRGCC Code für RC5 IR-Fernbedinungen
- Roboternetz Wissen - RC5-Decoder für ATMega
- www.sprut.de - Elektronik: IR-Fernbedienung, RC-5
- www.heise.de/ct - c't 23/2005, S. 222: Zeitschaltuhr mit Netzwerkanschluss