Aus RN-Wissen.de
Wechseln zu: Navigation, Suche
Laderegler Test Tueftler Seite

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.

Bitte beachte auch die Hinweise zu Inkompatibilitäten von avr-gcc!

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 ~ 0x170 (370 Bytes)
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.

Anschluss TSOP17xx an AVR, R1 ca 10kΩ
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

Funktionsweise

Diese RC5-Software braucht eine Zeitbasis, für welche Timer0 verwendet wird. Timer0 wird deshalb verwendet, weil er bei vielen AVRs der "schwächste" Timer ist, also der Timer, der am wenigsten Funktionalität bietet. So fehlt ihm bei vielen AVR-Modellen ein OutputCompare-Funktion, und er kann keine PWM erzeugen. Seine Funktionen reichen für einen RC5-Empfänger aber aus.

Die am externen Interrupt-Port (der beim Compilieren durch das Makro RC5_INT ausgewählt werden kann) gemessenen Zeiten zwischen eintreffenden Flanken, d.h. Wechseln zwischen LOW und HIGH, werden ausgewertet und in Bits umgewandelt. Falls die Zeiten nicht dem RC5-Standard entsprechen, werden die Daten verworfen. Wurde ein kompletter RC5-Frame (also Adresse, Kommand, Flip-Bit, etc) empfangen und stimmt die übermittelte Adresse mit der Adresse überein, die bei der Initialisierung des Empfängers mit rc5_init angegeben wurde, dann werden die empfangenen Daten in der globalen Struktur rc5 zur weiteren Auswertung gespeichert.

Von Timer0 wird der Overflow-Interrupt genutzt, um ungültigen Code zu erkennen und den Empfänger bei ungültigem Code wieder in einen definierten Zustand zu versetzten. Der externe Interrupt wertet die Zeiten aus, speichert die empfangenen Bits und baut so den Inhalt der rc5-Struktur auf.

Der Anwender braucht sich nicht um diese Interna zu kümmern. Nach Aufruf der Initialisierung läuft der RC5-Empfang "nebenher".

In der rc5-Struktur können die empfangenen Daten abgeholt werden, und über ihre .flip-Komponente wird der Empfang gesteuert und mitgeteilt, ob neue Daten angekommen sind.

Interface

rc5.h
#define RC5_INT0 0
#define RC5_INT1 1

#define RC5_ALL 0xff

typedef struct
{
        uint8_t code;
        uint8_t addr;
        volatile signed 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 empfangen 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)
Compiler avr-gcc 3.4.1. Siehe auch Inkompatibilitäten von avr-gcc
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/interrupt.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;
                        signed 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;
        
        uint8_t tcnt0 = TCNT0;
        TCNT0 = 0;
        
        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 */
        
                TIFR = (1 << TOV0);
                TIMSK |= (1 << TOIE0);
                _code.w = 0;
        }
        else
        {
                /* 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 signed 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"

   ...
   /* Gibt's was Neues? */
   if (-1 == rc5.flip)
   {
      /* Nein, dann mach irgendwas (oder nix) */
   }
   else
   {
      /* Ja, dann rc5.code merken und evtl. rc5.addr */
      /* falls man die braucht und nicht sowieso schon kennt */
      uint8_t code = rc5.code;
      uint8_t addr = rc5.addr;
      /* und auf naechstes Zeichen warten */
      rc5.flip = -1;
      
      /* code (evtl. addr) auswerten */
   }
   ...

Baustelle

Baustelle.gif An diesem Artikel arbeitet gerade Mitglied SprinterSB.

Am besten momentan noch keine gravierenden Ergänzungen / Änderungen vornehmen.

Dieser Hinweis verschwindet wenn der Autor soweit ist. Sollte dieser Hinweis länger als drei Tage auf einer Seite sein, bitte beim Autor SprinterSB per PM / Mail oder Forum nachfragen ob er vergessen wurde.

rc5.h

/* 
 * RC5 protocol implementation.
 * 
 * Contributed by Georg-Johann Lay <georgjohann@web.de>
 */

/* 
  This implementation assumes a ONE to be encoded as LOW-HIGH and a ZERO
  encoded as HIGH-LOW, i.e. just the other way round as specified by the
  RC5 contract. However, most IR receivers have an open collector output
  driver that inverts the signal and everything is fine.
 */

#ifndef _RC5_H_
#define _RC5_H_

#include <inttypes.h>

/* 
  A typical snippet of C code will look like this:
  (Sample code for AVR and avr-gcc. Compile rc5-avr.c
   and link against the generated object.
   Compile:
      avr-gcc -Os -c rc5-avr.c -mmcu=... -DF_CPU=...)
  

  #include <avr/io.h>
  #include <avr/interrupt.h>

  #include "rc5.h"

  int main()
  {
      // Initialize Hardware
      rc5_init (RC5_ALL);

      // enable all interrupts
      sei();

      // main loop
      while (1)
      {
          if (rc5.flip >= 0)
          {
              // evaluate RC5 data or make a local copy of it
              ...

              // get more RC5 frames
              rc5.flip = -1;
          }
      } // main loop

      // We never come here, thus no return needed.
      // It would be dead, anyway...
  }
 */


/* 
  The structure that represents an RC5 frame.
  If rc5.flip = -1, then the receiver will listen for a new RC5 frame
  an pass it in `rc5' upon reception, provided the address matches.
  After reception of a frame, rc5.flip will be 0 or 1.
  After you got the RC5 data, just set .flip to -1 again to indicate
  you read the data the next RC5 frame may be received.
 */  
typedef struct
{
    // The RC5 code (command) 
    uint8_t code;

    // The device address: 0=TV ... 
    uint8_t addr;

    // The flip bit 
    volatile char flip;
} rc5_t;

extern rc5_t rc5;

/* 
  Initialize the RC5 receiver software.
  Pass the device address to listen to in `addr'.
  If the MSB of `addr' is set, then all device addresses are accepted.
  You may use RC5_ALL as address in that case.
 */

#define RC5_ALL 0xff

extern void rc5_init (uint8_t addr);

// There is an extension of the RC5 protocol that features a seventh 
// command bit and thereby extends the number of commands from 64 to 128. 
// This bit's complement is shipped as the second AGC bit. To activate the 
// recognition of such RC5 frames (i.e. frames with command = 64...127) 
// just define the following macro or add `-DRC5_RC6' to the compiler's 
// command line arguments. 

// #define RC5_RC6 

#endif /* _RC5_H_ */

rc5.c

/* 
 * RC5 decoder
 * 
 * Contributed by Georg-Johann Lay <georgjohann@web.de>
 *
 * Do not compile this file. It is just included by some other source.
 */

// The rc5 data struct to communicate with the application 
rc5_t rc5;

// Some tweaks... 
#ifdef __GNUC__
#   define INLINE __attribute__((always_inline))
#else
#   define INLINE inline
#endif // GCC 

#if defined (__GNUC__) && defined (__AVR__) && defined (__OPTIMIZE__)
#   define RELOAD(reg,var) \
        __asm__ __volatile (" " : "=" reg (var) : "0" (var))
#else
#   define RELOAD(...)
#endif // avr-gcc && optimize 

// Target dependent stuff that is needed in rc5_init() 
// The implementation can be found in the target dependent source file. 
static INLINE void rc5_timer_run();
static INLINE void rc5_timer_enable_irq();
static INLINE void rc5_xint_enable();

// (Mostly) target independent RC5 algorithms, implemented in this file 
static INLINE void rc5_timer_isr();
static INLINE void rc5_xint_isr();

// //////////////////////////////////////////////////////////////////////////// 

// Time in µs for a whole bit of RC5 (first & second half bit) 
#define RC5_BIT_US   (64*27)

// Number of timer ticks for the duration of one RC5 bit. 
// This can be used in a C source or by the preprocessor for 
// a range check, i.e. a valid F_CPU/RC5_PRESCALE ratio. 
#define RC5_TICKS_VAL \
    F_CPU / 1000 * RC5_BIT_US / 1000 / RC5_PRESCALE
    
#define RC5_TICKS \
    ((uint8_t) ((uint32_t) (RC5_TICKS_VAL)))
    
#define RC5_DELTA \
    ((RC5_TICKS_VAL) / 6)

// Test RC5_TICKS_VAL for a reasonable value 
#if (RC5_TICKS_VAL > 200) || (RC5_TICKS_VAL < 12)
#error This is a bad combination of F_CPU and RC5_PRESCALE!
#endif

// This union allows to access 16 bits as word and as two bytes. 
// This approach is (probably) more efficient than shifting. 
union data16
{
    uint16_t asWord;
    uint8_t  asByte[2];
};

// Local information that we need just in this file 
struct 
{
    // The very RC5 data buffer 
    union data16 data;
    // The address that we listen to 
    uint8_t      addr;
    // Number of external interrupts occured so far 
    uint8_t      nint;
    // Number of half bits bits received so far 
    uint8_t      hbits;
} rc5_info;


// //////////////////////////////////////////////////////////////////////////// 
    
void rc5_init (uint8_t addr)
{
    // Save RC5 address that we listen to 
    rc5_info.addr = addr;

    // Reset internal infos 
    rc5_info.nint  = 0;
    rc5_info.hbits = 0;

    // .flip = -1: no RC5 data availible, listen 
    rc5.flip = -1;

    // Initialize the hardware 
    rc5_timer_run();
    rc5_timer_enable_irq();
    rc5_xint_enable();
}

// //////////////////////////////////////////////////////////////////////////// 

void rc5_timer_isr()
{
    // Make local copies 
    uint8_t hbits     = rc5_info.hbits;
    union data16 data = rc5_info.data;
    
    if (26 == hbits)
    {
        // If the last bit transmitted is a ZERO, then the last 
        // half bit has the same signal level as idle, so there will 
        // be no edge and thereby no XINT after the last half bit 
        // of a ZERO. 
        // So we must add the last ZERO half bit by hand. 
        data.asWord <<= 1;
        hbits++;
    }

    // An RC5 frame consists of 14 bits. As our numbering of half bits 
    // starts at 0, the half bits are enumerated from 0 to 27. 
    if (27 == hbits
#ifdef RC5_RC6        
        // check for the first AGC bit to be 1 
        && data.asByte[1] >= 0x20
#else
        // check for the two AGC bits to be 1 
        && data.asByte[1] >= 0x30
#endif // RC6 
        // Did the application allow reception of new RC5 data? 
        && rc5.flip < 0)
    {
        uint8_t rc5_code;
        uint8_t rc5_addr;
        
        // We do the bit manipulation stuff by hand because of code size. 
        rc5_code = data.asByte[0] & 0x3f; // 0b00111111 : Bits #0..#5 
#ifdef RC5_RC6        
        // Second AGC is abused as ~code.6 
        if (0 == (data.asByte[1] & 0x10))
            rc5_code |= (1 << 6);
#endif // RC6 
        
        data.asWord <<= 2;
        rc5_addr = data.asByte[1] & 0x1f; // 0b00011111 : Bits #6..#10 

        // Are we addressee? 
        if (rc5_info.addr == rc5_addr
            || rc5_info.addr >= 128)
        {
            // Yes. Store the RC5 frame. 
            rc5.code = rc5_code;
            rc5.addr = rc5_addr;
            
            int8_t flip = 0;
            if (data.asByte[1] & 0x20) // 0b00100000 : Bit #11 
                flip = 1;
            rc5.flip = flip;
        }
    }

    // Reset and wait for the next RC5 frame 
    rc5_info.nint  = 0;
    rc5_info.hbits = 0;
}

// //////////////////////////////////////////////////////////////////////////// 

void rc5_xint_isr()
{
    // Read the timer value 
    uint8_t timer = RC5_TIMER_REG;

    // Making local copies is recommended 
    uint8_t nint  = rc5_info.nint;
    union data16 data;

    // This is no RC5. Maybe some other protocol, maybe a transient 
    // error or the like. Just run into the timer overflow and clean up 
    // the mess there. Disabling XINT is not the best idea if we have 
    // code size in mind. Errors will not occur very often, anyway. 
    if (nint >= 128)
        return;

    // Reset the timer. 
    RC5_TIMER_REG = 0;

    // Start of RC5 frame? 
    if (0 == nint)
    {
        // No half bits received yet. 
        // We are only interested in falling edges 
        if (1 == (RC5_PIN_REG >> RC5_PAD) % 2)
            return;

        // Set all bits of RC5 frame to 0 
        data.asWord = 0;
    }
    else
    {
        // Accumulate the just received half bit(s) 

        // Get what we already have 
        data = rc5_info.data;
        
        // Number of half bits of the just elapsed period 
        // which is 1 or 2 or invalid. 
        // As RC5 toggles in the middle of each bit 
        // one interval of constant signal level lasts 
        // RC5_TICKS or RC5_TICKS/2. In the first case, 
        // a bit's second part has the same level as the 
        // subsequent bit's first part. 
        uint8_t n = 1;

        // Test interval's length 
        if (timer > RC5_TICKS - RC5_DELTA)
        {
            // Two half bits: reduce the 
            // validity check to the case of one half bit. 
            n = 2;
            timer -= RC5_TICKS/2;
        }

        // Test if a half bit's length is valid 
        if (timer < RC5_TICKS/2 - RC5_DELTA
            || timer > RC5_TICKS/2 + RC5_DELTA)
        {
            // Invalid: 
            // Set nint to remind an invalid signal (nint >= 128) 
            nint = 128;
        }
        else
        {
            // Valid: 
            // Store the just received 1 or 2 half bits 
            
            do
            {
                // Increase the number of half bits that we have. 
                // Most IR receivers have an open collector output 
                // drive that inverts the signal. Plain RC5 encodes 
                // a ONE as "10" and a ZERO as "01". With the inversion, 
                // we have to look at the second half of a bit 
                // to see its value. Note that the counting of half 
                // bits starts at 0. 
                if (++rc5_info.hbits % 2 == 1)
                {
                    // We shift data left by one and then store the 
                    // received bit in the LSB. This is best because 
                    // RC5 is big endian (MSB first). 
                    data.asWord <<= 1;

                    // To undestand what RELOAD does, just imagine it did 
                    // nothing ;-) It's just a tweak. 
                    // It knocks out some optimizer pass, so we are deep 
                    // in gcc's internals. 
                    // You don't really want to understand what's going on, 
                    // do you? Alternatively, you can run gcc with 
                    // `-fno-loop-optimize' turned on for this module 
                    // and throw away the RELOAD stuff. 
                    // Or you just don't care for quenching out the 
                    // last bits of performance. 
                    RELOAD ("r", nint);

                    // Store the bit 
                    if (nint & 1)
                        data.asByte[0] |= 1;
                }
            } 
            while (--n);
        }       
    }

    // write back the local copies 
    rc5_info.data = data;
    rc5_info.nint = 1+nint;
}

rc5-avr.c

/* 
 * RC5 implementation for some AVR derivatives.
 * 
 * Contributed by Georg-Johann Lay <georgjohann@web.de>
 * 
 * This is a C module that implements an RC5 receiver for some AVRs.
 */  

/* 
  For the best results, use optimization '-Os' (optimize for size).

  On one hand, we want the implementation to be as efficient as
  possible because we run on a hardware with quite limited resources.
  On the other hand, we want the code to be as portable as can be.
  Therefore, we follow an approach that looks a bit odd at first sight:
  We include a C source.
  (You may rename it to `foo.h' and feel more familiar.)
  
  This has two reasons: First, we keep the target independent part of
  the implementation separated from the target dependent part.
  Second, including the C code (in contrast to linking against an
  object or a library) supplies the compiler with a great amount
  of additional information. In particullar, it is supplied with the
  very source code. That information, in turn, allows optimization
  strategies like inlining, better register allocation, etc.

  For this reason, the target dependent part and the target independent
  part are intertwined and the abstraction from the hardware is just on
  the source level, not on the object level. (This was our goal with
  respect to optimization!). However, this code may serve as a starting
  point if you want to implement a hardware abstraction by means of
  objects, i.e. supply it in a library.

  On AVR, the code eats up at about 320 bytes of your valuable flash and
  needs at about 20 bytes of SRAM total (static and dynamic).
  The incraese of interrupt respond times can be analysed statically,
  but I did not investigate in that.
 */

#include <avr/io.h>
#include <avr/interrupt.h>

#include "rc5.h"

// The timer's counter register, counting up. As Timer0 comes along 
// with the least resources, we use that timer. It is an 8 bit timer 
// that overflows at 255 to 0 and generates a timer overflow IRQ each 
// time it does so. 
// If you use a 16 bit timer, make sure it has the same behaviour, e.g. 
// by using its "clear timer on compare match" functionality. 
#define RC5_TIMER_REG TCNT0

// The hardware port the IR receiver is connected to: INT0 
// You may also use INT1 or use PCINT and filter for events that come 
// from the IR receiver. You may also configure the analog comparator 
// to work as external events' interrupt generator. 
// !!! CHANGING THIS PORT REQUIRES ADJUSTING BOTH THE EXTERNAL 
// !!! INTERRUPT'S SIGNAL AND THE RC5_XINT_ENABLE() FUNCTION. 
#define RC5_PIN_REG   PIND
#define RC5_PAD       PORTD2

// We need the frequency the controller is running at. 
// "F_CPU" is a commonly used identifier for it. 
#ifndef F_CPU
#   error Please define F_CPU by adding a source line like #define F_CPU ...
#   error or by specifying the -D compiler option like '-DF_CPU=...'
#endif // F_CPU 

/* 
  Set `RC5_PRESCALE' to some reasonable default. It is the prescaler
  to use with the RC5 timer. As you can imagine, I did not test it
  for every possible F_CPU, so I hope for the best...

  Allow `RC5_PRESCALE' to be defined on the command line with `-DRC5_...'
 */
#ifndef RC5_PRESCALE
#   if F_CPU >= 12000000
#       define RC5_PRESCALE 1024
#   elif F_CPU >= 4000000
#       define RC5_PRESCALE 256
#   else
#       define RC5_PRESCALE 64
#  endif // switch F_CPU 
#endif // RC5_PRESCALE 

// The implementation of target independent RC5 stuff 
#include "rc5.c"

// //////////////////////////////////////////////////////////////////////////// 
// The interrupt service routines (ISRs) 
// //////////////////////////////////////////////////////////////////////////// 

// I implemented the two ISRs as SIGNAL, i.e. they cannot be interrupted 
// because the I-flag is unset by the AVR hardware during their execution. 
//  
// If you want to use the code in an application that has to fit harder 
// real time requirements, i.e. shorter interrupt respond times for 
// other IRQs, then you can implement the ISRs as INTERRUPTs or doing 
// things like yield(). 
//  
// However, there are some caveats. One of them is that you must care 
// for and handle frequent transient errors because otherwise 
// `rc5_xint_isr' will begin to recurse. One approach could be to 
// disable the external interrupt during execution of that routine. 
// An other approath could be to lock the routine... 

// The timer overflow interrupt service routine 
SIGNAL (SIG_OVERFLOW0)
{
    rc5_timer_isr();
}

// The external interrupt's ISR 
// This might also be a PCINT, a pin change interrupt on newer AVRs. 
// If you use multiple pin change interrupts, then you have to add 
// a test wether or not the pin change event comes from the IR receiver's 
// pin. 
SIGNAL (SIG_INTERRUPT0)
{
    rc5_xint_isr();
}

/* 
  Here we go. What we have to implement:
 
  void rc5_timer_run()         Run (start) the overflow timer
  void rc5_timer_enable_irq()  Enable timer overflow IRQs
  void rc5_xint_enable()       Enable edge triggered external interrupt IRQs
 */

/* 
  RC5_TIMER_RUN is the value that must be writter to the
  timer configuration register in order to start it.
  
  The values below fit the controllers below.
  
  For other AVR derivatives like ATtiny this may look completely different.
  If so, take care for it in your own derivative's section below.
 */  
#if (RC5_PRESCALE==1024)
#   define      RC5_TIMER_RUN ((1 << CS02) | (1 << CS00))
#elif   (RC5_PRESCALE==256)
#   define      RC5_TIMER_RUN (1 << CS02)
#elif   (RC5_PRESCALE==64)
#   define      RC5_TIMER_RUN ((1 << CS01) | (1 << CS00))
#else
#   error This RC5_PRESCALE is not supported
#endif // RC5_PRESCALE 

// ////////////////////////////////////////////////////////////////////////// 
// ////////////////////////////////////////////////////////////////////////// 
#if defined (__AVR_ATmega8__) || defined (__AVR_AT90S2313__) \
    || defined (__AVR_ATmega32__) || defined (__AVR_ATmega16__) 
// ////////////////////////////////////////////////////////////////////////// 
// ////////////////////////////////////////////////////////////////////////// 

void rc5_timer_run()
{
    TCCR0 = RC5_TIMER_RUN;
}

void rc5_timer_enable_irq()
{
    TIMSK |= (1 << TOIE0);
}

void rc5_xint_enable()
{
    MCUCR = (MCUCR | (1 << ISC00)) & ~ (1 << ISC01);
    
#if defined (__AVR_AT90S2313__) 
    GIMSK |= (1 << INT0);
#else   
    GICR |= (1 << INT0);
#endif // AT90S2313 

// MCUCR = (MCUCR | (1 << ISC10)) & ~ (1 << ISC11); 
// GIMSK |= (1 << INT1); // S2313 
// GICR |= (1 << INT1);  // M8 
}

// ////////////////////////////////////////////////////////////////////////// 
// ////////////////////////////////////////////////////////////////////////// 
#elif defined (__AVR_ATtiny2313__)
// ////////////////////////////////////////////////////////////////////////// 
// ////////////////////////////////////////////////////////////////////////// 

void rc5_timer_run()
{
    TCCR0B = RC5_TIMER_RUN;
}

void rc5_timer_enable_irq()
{
    TIMSK |= (1 << TOIE0);
}

void rc5_xint_enable()
{
    // INTx on both edges 
    MCUCR = (MCUCR | (1 << ISC00)) & ~ (1 << ISC01);
    GIMSK |= (1 << INT0);

// MCUCR = (MCUCR | (1 << ISC10)) & ~ (1 << ISC11); 
// GIMSK |= (1 << INT1); 
}

// ////////////////////////////////////////////////////////////////////////// 
// ////////////////////////////////////////////////////////////////////////// 
#elif defined (__AVR_ATmega48__) || defined (__AVR_ATmega88__) \
    || defined (__AVR_ATmega168__)
// ////////////////////////////////////////////////////////////////////////// 
// ////////////////////////////////////////////////////////////////////////// 

void rc5_timer_run()
{
    TCCR0B = RC5_TIMER_RUN;
}

void rc5_timer_enable_irq()
{
    TIMSK0 |= (1 << TOIE0);
}

void rc5_xint_enable()
{
    // INTx on both edges 
    EICRA = (EICRA | (1 << ISC00)) & ~ (1 << ISC01);
    EIMSK |= (1 << INT0);

// EICRA = (EICRA | (1 << ISC10)) & ~ (1 << ISC11); 
// EIMSK |= (1 << INT1); 
}

// ////////////////////////////////////////////////////////////////////////// 
// ////////////////////////////////////////////////////////////////////////// 
#else
#error This AVR derivative is (not yet) supported.
#error Feel free to add the required code.
#endif // AVR derivatives 
// ////////////////////////////////////////////////////////////////////////// 
// ////////////////////////////////////////////////////////////////////////// 

Siehe auch

WebLinks

Autor

--SprinterSB 18:17, 7. Dez 2005 (CET)


LiFePO4 Speicher Test