Dieser Code imlpementiert einen Interrupt-getriebenen RC5-Empfänger.
Auf eine Flanke an einem externen INT hin werden die nachfolgenden Pulslängen gemessen und in einer Struktur bereitgestellt, falls es sich um RC5-Code handelt und die Empfängeradresse übereinstimmt.
Inhaltsverzeichnis
Abgrenzung
Nicht alle Fernbedienungen halten sich genau an die RC5-Spezifikation und haben oft einen mehr oder weniger starken Jitter auf dem Signal. In konstanten Zeitabständen auf den Port zu schauen und anhand des gelesenen Wertes das RC5-Signal aufzubauen, funktioniert daher nicht zuverlässig bei allen Fernbedienungen.
Der Ansatz, den diese Implementierung verfolgt, ist aufwändiger und ergibt ein längeres Programm, hat aber dafür nicht den beschriebenen Nachteil.
Resourcen
Resource | Verbrauch (mit -Os) |
---|---|
I/O | Timer0, 1 Pin für extern INT |
Interrupts | Timer0 Overflow, 1 externer IRQ |
Flash | ~ 0x180 |
SRAM | statisch: 8 Stack: 11 |
Laufzeit, Erhöhung der IRQ-Latenz |
?, aber statisch abschätzbar |
externe Hardware | IR-Empfänger wie TSOP17xx, TSOP18xx, SFH 506-xx o.ä. |
Schaltplan
IR-Empfänger vom Typ TSOP17xx an AVR
R1 ca 10 kΩ |
Weitere IR-Empfänger-ICs sind SFH 506-xx
Interface
#define RC5_INT0 0 #define RC5_INT1 1 #define RC5_ALL 0xff typedef struct { uint8_t code; uint8_t addr; volatile char flip; } rc5_t; extern rc5_t rc5; extern void rc5_init (uint8_t addr);
- void rc5_init (uint8_t addr)
- Initialisiert die Hardware für RC5-Empfang. Akzeptiert wird Code, der an Adresse addr geschickt wird. Falls addr = RC5_ALL bzw Bit 7 von addr gesetzt ist, werden alle Adressen akzeptiert.
Der beteiligte INT-Port wird nicht auf IN geschaltet und das I-Flag in SREG (Global Interrupt Enable/Disable-Flag) wird nicht verändert.
- extern rc5_t rc5
- In rc5 wird der empfangene RC5-Code geliefert. Über dieses Objekt wird zudem der RC5-Empfang gesteuert. Wird ein Code mit der gewünschten Adresse ampfangen und ist rc5.flip = -1, dann wird der Code gespeichert und rc5.flip gesetzt wie es empfangen wurde.
Danach wird der Empfänger solange inaktiv, bis der Anwender wieder rc5.flip auf -1 setzt.
- rc5.code
- der empfangene RC5-Code, falls rc5.flip != -1
- rc5.addr
- die Adresse, an die gesendet wurde, falls rc5.flip != -1
- rc5.flip
- das Flip-Bit
- rc5.flip = 0
- Code empfangen, RC5-Empfang inaktiv
- rc5.flip = 1
- dito
- rc5.flip = -1
- RC5-Empfang aktiv, wartet auf nächste Übertragung
Define | default | Werte | Beschreibung |
---|---|---|---|
RC5_INT | RC5_INT0 | RC5_INT0, RC5_INT1 | Code wird generiert für INT0 resp. INT1, zB gcc-Aufruf mit -DRC5_INT=RC5_INT1 erzeugt Code für INT1 |
RC5_PRESCALE | 1024 | 64, 256, 1024 | Legt den Prescaler für Timer0 fest. Standardeinstellung auf 1024, was zu F_CPU=16000000 passt. Für kleinere CPU-Frequenzen muss evtl ein kleinerer PRESCALE gewählt werden; das geht noch nicht automatisch. |
F_CPU | Gibt die CPU-Frequenz in Hz an |
Seiteneffekte
SFRs
Der Code verwendet folgende SFRs und ändert deren Inhalt in den ISRs:
-
MCUCR
: MCU Control Reg -
GICR
: Global Interrupt Control Reg -
TCNT0
: Timer0 Counter Reg -
TIMSK
: Timer Interrupt Mask Reg
Falls eines dieser SFRs verändert wird, nachdem RC5-Empfang aktiviert wurde, muss diese Änderung atomar erfolgen!
#include <avr/io.h> #include <avr/interrupt.h> ... { sreg = SREG; cli(); TIMSK |= ... SREG = sreg; } ...
Prescaler
Timer0 verwendet den Prescaler. Ein Prescaler-Reset sollte aufgrund der langsamen Übertragung bei RC5 unkritisch sein.
Code
ANSI-C | Nein (C++ Kommentare, anonymous struct) |
Dateien | rc5.h , rc5.c
|
getestet für | ATMega8-16 @ 16MHz, Vcc = 5V |
Portierung | ATMegaXX: sollte ohne Anpassung laufen ATTiny, Classic: Anpassungen erforderlich |
Comment-Style | |
rc5.c
#include <avr/io.h> #include <avr/signal.h> #include "rc5.h" #ifndef RC5_INT #define RC5_INT RC5_INT0 #endif // RC5_INT #ifndef RC5_PRESCALE #define RC5_PRESCALE 1024 #endif // RC5_PRESCALE ////////////////////////////////////////////////////////////////////////////// rc5_t rc5; ////////////////////////////////////////////////////////////////////////////// #ifndef F_CPU #error Please define F_CPU #endif // !F_CPU // µs for a whole bit of RC5 (first & second part) #define RC5_BIT_US (64*27) #define RC5_TICKS \ ((uint8_t) ((uint32_t) (F_CPU / 1000 * RC5_BIT_US / 1000 / RC5_PRESCALE))) #define RC5_DELTA \ (RC5_TICKS / 6) typedef union { uint16_t w; uint8_t b[2]; struct { unsigned code:6; unsigned addr:5; unsigned flip:1; unsigned agc:4; } __attribute__ ((packed)); } code_t; static code_t code; static uint8_t rc5_addr; // Number of Bits received so far // Number of Interrupts occured so far; static uint8_t nbits; static uint8_t nint; ////////////////////////////////////////////////////////////////////////////// void rc5_init (uint8_t addr) { nint = 0; nbits = 0; rc5.flip = -1; rc5_addr = addr; #if (RC5_PRESCALE==1024) TCCR0 = (1 << CS02) | (1 << CS00); #elif (RC5_PRESCALE==256) TCCR0 = (1 << CS02); #elif (RC5_PRESCALE==64) TCCR0 = (1 << CS01) | (1 << CS00); #else #error This RC5_PRESCALE is not supported #endif // RC5_PRESCALE // INTx on falling edge // clear pending INTx // enable INTx interrupt #if (RC5_INT == RC5_INT0) MCUCR |= (1 << ISC01); MCUCR &= ~ (1 << ISC00); GIFR = (1 << INTF0); GICR |= (1 << INT0); #elif (RC5_INT == RC5_INT1) MCUCR |= (1 << ISC11); MCUCR &= ~ (1 << ISC10); GIFR = (1 << INTF1); GICR |= (1 << INT1); #else #error please define RC5_INT #endif // RC5_INT } ////////////////////////////////////////////////////////////////////////////// SIGNAL (SIG_OVERFLOW0) { TIMSK &= ~(1 << TOIE0); uint8_t _nbits = nbits; code_t _code = code; if (26 == _nbits) { _nbits++; _code.w <<= 1; } if (27 == _nbits && 3 == _code.agc && 0 > rc5.flip) { uint8_t _rc5_code; uint8_t _rc5_addr; // we do the bit manipulation stuff by hand, because of code size _rc5_code = _code.b[0] & 0x3f; // 0b00111111 : #0..#5 _code.w <<= 2; _rc5_addr = _code.b[1] & 0x1f; // 0b00011111 : #6..#10 if (rc5_addr & 0x80 || rc5_addr == _rc5_addr) { rc5.code = _rc5_code; rc5.addr = _rc5_addr; char flip = 0; if (_code.b[1] & 0x20) // 0b00100000 : #11 flip = 1; rc5.flip = flip; } } nint = 0; nbits = 0; // INTx on falling edge // clear pending INTx // enable INTx interrupt #if (RC5_INT == RC5_INT0) MCUCR |= (1 << ISC01); MCUCR &= ~ (1 << ISC00); GIFR = (1 << INTF0); GICR |= (1 << INT0); #elif (RC5_INT == RC5_INT1) MCUCR |= (1 << ISC11); MCUCR &= ~ (1 << ISC10); GIFR = (1 << INTF1); GICR |= (1 << INT1); #endif } ////////////////////////////////////////////////////////////////////////////// #if (RC5_INT == RC5_INT0) SIGNAL (SIG_INTERRUPT0) #elif (RC5_INT == RC5_INT1) SIGNAL (SIG_INTERRUPT1) #endif // RC5_INT { code_t _code = code; uint8_t _nint = nint; if (0 == _nint) { // INTx on both edges // clear pending INTx #if (RC5_INT == RC5_INT0) MCUCR &= ~ (1 << ISC01); MCUCR |= (1 << ISC00); GIFR = (1 << INTF0); #elif (RC5_INT == RC5_INT1) MCUCR &= ~ (1 << ISC11); MCUCR |= (1 << ISC10); GIFR = (1 << INTF1); #endif // RC5_INT TCNT0 = 0; TIFR = (1 << TOV0); TIMSK |= (1 << TOIE0); _code.w = 0; } else { uint8_t tcnt0 = TCNT0; TCNT0 = 0; // Number of bits of the just elapsed period; uint8_t n = 1; // Bits received so far uint8_t _nbits = nbits; // is TCNT0 close to RC5_TICKS or RC5_TICKS/2 ? if (tcnt0 > RC5_TICKS + RC5_DELTA) goto invalid; else if (tcnt0 < RC5_TICKS/2 - RC5_DELTA) goto invalid; else if (tcnt0 > RC5_TICKS - RC5_DELTA) n = 2; else if (tcnt0 > RC5_TICKS/2 + RC5_DELTA) goto invalid; // store the just received 1 or 2 bits do { _nbits++; if (_nbits & 1) { _code.w <<= 1; _code.b[0] |= _nint & 1; } } while (--n); if (0) { invalid: // disable INTx, run into Overflow0 #if (RC5_INT == RC5_INT0) GICR &= ~(1 << INT0); #elif (RC5_INT == RC5_INT1) GICR &= ~(1 << INT1); #endif // RC5_INT _nbits = 0; } nbits = _nbits; } code = _code; nint = 1+_nint; }
rc5.h
#ifndef _RC5_H_ #define _RC5_H_ #include <inttypes.h> #define RC5_INT0 0 #define RC5_INT1 1 #define RC5_ALL 0xff typedef struct { uint8_t code; uint8_t addr; volatile char flip; } rc5_t; extern rc5_t rc5; extern void rc5_init (uint8_t addr); #endif /* _RC5_H_ */
Beispiele
Initialisierung
#include <avr/io.h> #include <avr/interrupt.h> #include "rc5.h" ... // der ensprechende INT-Port muss INPUT sein // RC5 initialisieren, alle Adressen zulassen rc5_init (RC5_ALL); // Interrupts zulassen sei(); ...
Anwendung
#include <avr/io.h> #include <avr/interrupt.h> #include "rc5.h" ... // FIXME: eigentlich sollte es nicht nötig sein, das atomar zu machen // Code atomar machen uint8_t sreg = SREG; cli(); // Gibt's was Neues? if (-1 == rc5.flip) { // Nein, dann // atomaren Block beenden SREG = sreg; // ...und zurück (oder sonst was machen) return; } // Ja, dann code merken, und evtl. rc5.addr, falls man die nicht sowieso kennt uint8_t code = rc5.code; // und auf nächstes Zeichen warten rc5.flip = -1; // atomaren Block beenden SREG = sreg; // code (evtl. addr) auswerten ...
RC5-Code
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
Start | F | Adresse | Code | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | -C6 | F | A4 | A3 | A2 | A1 | A0 | C5 | C4 | C3 | C2 | C1 | C0 |
14·1,778 ms = 24,889 ms |
- Start
- Zum Start wird zweimal eine 1 gesendet. Dadurch erkennt ein RC5-Empfänger, daß ein Transfer beginnt, und er kann seine Verstärkung optimal auf die Signalstärke einregeln. Weil die 6 Code-Bits nur 64 Codes zulassen, wird bei neueren Fernbedienungen ein invertiertes siebtes Codebit als zweites Startbit übertragen. Für Codes 0..63 ist dies 1 und für Werte 64..127 ist es 0.
- Flip-Bit (F)
- Wechselt mit jedem Tastendruck zwischen 0 und 1. Damit lässt sich unterscheiden, ob eine Taste dauerhaft gedrückt wird oder die gleiche Taste mehrfach gedrückt wurde. Bei Dauerdruck wird das gleiche Signal immer wiederholt.
- Adresse
- Die 5 Adress-Bits erlauben die Auswahl 32 verschiedener Geräte, z.B. 0=TV.
- Code
- Das Kommando. Die 6 Kommando-Bits erlauben 64 Kommandos. Weil das für neuere Geräte zu wenig ist, wird bei neueren Fernbedienungen ein invertiertes siebtes Kommando-Bit als zweites Startbit übertragen. Für Kommandos 0..63 ist dies 1 und für 64..127 ist es 0.
WebLinks
Autor
--SprinterSB 18:17, 7. Dez 2005 (CET)