K (→Interface) |
(→rc5.c) |
||
| Zeile 182: | Zeile 182: | ||
{ | { | ||
uint16_t w; | uint16_t w; | ||
| − | uint8_t b[2]; | + | uint8_t b[2]; |
| − | + | ||
| − | + | ||
| − | + | ||
| − | + | ||
| − | + | ||
| − | + | ||
| − | + | ||
| − | + | ||
| − | + | ||
} code_t; | } code_t; | ||
| Zeile 207: | Zeile 198: | ||
rc5_init (uint8_t addr) | rc5_init (uint8_t addr) | ||
{ | { | ||
| − | nint = 0; | + | nint = 0; |
nbits = 0; | nbits = 0; | ||
rc5.flip = -1; | rc5.flip = -1; | ||
| Zeile 227: | Zeile 218: | ||
// enable INTx interrupt | // enable INTx interrupt | ||
#if (RC5_INT == RC5_INT0) | #if (RC5_INT == RC5_INT0) | ||
| − | MCUCR | + | MCUCR = (MCUCR | (1 << ISC01)) & ~ (1 << ISC00); |
| − | + | ||
GIFR = (1 << INTF0); | GIFR = (1 << INTF0); | ||
GICR |= (1 << INT0); | GICR |= (1 << INT0); | ||
#elif (RC5_INT == RC5_INT1) | #elif (RC5_INT == RC5_INT1) | ||
| − | MCUCR | + | MCUCR = (MCUCR | (1 << ISC11)) & ~ (1 << ISC10); |
| − | + | ||
GIFR = (1 << INTF1); | GIFR = (1 << INTF1); | ||
GICR |= (1 << INT1); | GICR |= (1 << INT1); | ||
| Zeile 257: | Zeile 246: | ||
if (27 == _nbits | if (27 == _nbits | ||
| − | && | + | && _code.b[1] >= 0x30 /* AGC == 3 */ |
&& 0 > rc5.flip) | && 0 > rc5.flip) | ||
{ | { | ||
| Zeile 286: | Zeile 275: | ||
// enable INTx interrupt | // enable INTx interrupt | ||
#if (RC5_INT == RC5_INT0) | #if (RC5_INT == RC5_INT0) | ||
| − | MCUCR | + | MCUCR = (MCUCR | (1 << ISC01)) & ~ (1 << ISC00); |
| − | + | ||
GIFR = (1 << INTF0); | GIFR = (1 << INTF0); | ||
GICR |= (1 << INT0); | GICR |= (1 << INT0); | ||
#elif (RC5_INT == RC5_INT1) | #elif (RC5_INT == RC5_INT1) | ||
| − | MCUCR | + | MCUCR = (MCUCR | (1 << ISC11)) & ~ (1 << ISC10); |
| − | + | ||
GIFR = (1 << INTF1); | GIFR = (1 << INTF1); | ||
GICR |= (1 << INT1); | GICR |= (1 << INT1); | ||
| Zeile 312: | Zeile 299: | ||
{ | { | ||
// INTx on both edges | // INTx on both edges | ||
| − | |||
#if (RC5_INT == RC5_INT0) | #if (RC5_INT == RC5_INT0) | ||
| − | MCUCR | + | MCUCR = (MCUCR | (1 << ISC00)) & ~ (1 << ISC01); |
| − | + | ||
| − | + | ||
#elif (RC5_INT == RC5_INT1) | #elif (RC5_INT == RC5_INT1) | ||
| − | MCUCR | + | MCUCR = (MCUCR | (1 << ISC10)) & ~ (1 << ISC11); |
| − | + | ||
| − | + | ||
#endif // RC5_INT | #endif // RC5_INT | ||
Version vom 23. März 2006, 12:34 Uhr
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
Das ist ein minimaler Anschlussplan für einen TSOP17xx. Um einen IR-Empfänder zu entstören und in verrauschter Umgebung einzusetzen, sei auf das jeweilige Datenblatt verwiesen.
|
IR-Empfänger vom Typ TSOP17xx an AVR
R1 ca 10 kΩ |
Für RC5 empfiehlt sich die 36kHz-Variante, also z.B. ein TSOP1736.
Weitere IR-Empfänger-ICs sind SFH 506-xx
Interface
- rc5.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);
- 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 der STruktur 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 | Über dieses Define wird eingestellt, an welchem Port auf RC5-Signale gelauscht wird. 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 Prescaler 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];
} 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 = (MCUCR | (1 << ISC01)) & ~ (1 << ISC00);
GIFR = (1 << INTF0);
GICR |= (1 << INT0);
#elif (RC5_INT == RC5_INT1)
MCUCR = (MCUCR | (1 << ISC11)) & ~ (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
&& _code.b[1] >= 0x30 /* AGC == 3 */
&& 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 = (MCUCR | (1 << ISC01)) & ~ (1 << ISC00);
GIFR = (1 << INTF0);
GICR |= (1 << INT0);
#elif (RC5_INT == RC5_INT1)
MCUCR = (MCUCR | (1 << ISC11)) & ~ (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
#if (RC5_INT == RC5_INT0)
MCUCR = (MCUCR | (1 << ISC00)) & ~ (1 << ISC01);
#elif (RC5_INT == RC5_INT1)
MCUCR = (MCUCR | (1 << ISC10)) & ~ (1 << ISC11);
#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
...
WebLinks
Autor
--SprinterSB 18:17, 7. Dez 2005 (CET)
