Mit einer Universal-Fernbedienung vom Wühltisch und einem Infrarot-Empfänger lässt sich leicht, preiwert und komfortabel eine Eingabeeinheit für einen µC aufbauen. IR-Empfänger-Bausteine wie TSOP1736, TSOP1836, SFH506-36, etc sind für weniger als 1€ erhältlich. Das einzige Problem ist dann, die Fernbedienung so einzustellen, daß sie RC5-Code sendet. Meist haben die Fernbedienungen nur ein Liste mit Gerätemarken anbei, in der steht, welchen Gerätecode man eingeben muss. Um die Bedienung RC5 senden zu lassen, muss man also erst etwas rumprobieren...
Hier der Software-Teil des Empfängers; bestehend aus drei C-Quellen:
- rc5.h
- Ein Header, der in den C-Modulen includet wird, wo man die RC5-Funktionalität braucht.
- rc5.c
- Einer Quelldatei, die Controller-unabhängigen C-Code enthält.
- rc5-avr.c
- Einer Quelldatei, die Controller-abhängigen C-Code enthält, hier für avr-gcc und ein paar AVRs.
Zu compileren ist nur die µC-abhängige Quelle, welche den µC-unabhängigen Teil benutzt (includet). Die Quellen sind ausführlich dokumentiert – allerdings in Englisch.
Code-Statistik: 731 Zeilen (127 Zeilen ohne Leerzeilen und Kommentare).
rc5.h
µC-unabhängiger Teil (Header)
/* * RC5 protocol implementation. * * Contributed by Georg-Johann Lay <georgjohann@web.de> * * @Author : Georg-Johann Lay * @Date : 2007-02-02 * @File : rc5.h * @Language: ISO C 99 * @Purpose : Implementation of RC5 receiver software */ /* This implementation assumes a ONE to be encoded as HIGH-LOW transition and a ZERO encoded as LOW-HIGH transition, i.e. just the other way round as specified by the RC5 contract. This inversion comes from the IR receiver's open collector output driver that inverts the signal. The RC5 signal consists of 14 bits ================================== AGC (Automatic Gain Control) These two bits allow the RC5 receiver hardware's input amplifier to adapt to the signal level. TOGGLE bit (aka FLIP bit) This bit toggles each time a new key is pressed. That allows to distinguish between holding down a key and pressing a key twice or more. ADDRESS Five address bits to select a device (0=TV, 20=CD, ...). COMMAND Six bits of very information. To support more than 64 commands, newer RC5 like protocols use a seventh bit whose complement is transmitted as second AGC bit. Thus, an RC5 frame looks like this: G ~C6 F A4 A3 A2 A1 A0 C5 C4 C3 C2 C1 C0 With C6 always 0 (i.e. second AGC always 1) if just 64 commands are supported. An RC5 bit ========= consists of two parts: The first half bit and the second half bit which are separated by the respective signal level transition. Between two bits there may or may not be a transition. A Bit lasts 1728 µs. */ #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 // Allow any RC5 device address rc5_init (RC5_ALL); // enable all interrupts sei(); // main loop while (1) { // New RC5 frame arrived? if (rc5.flip >= 0) { // Yes: // evaluate RC5 data in `rc5' or make a local copy // of it for later use ... // Tell receiver software to get more RC5 frames rc5.flip = -1; } // done RC5 } // 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 rc5.flip to -1 again to indicate you read the data and 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; // This is not used by the RC5 software. It may be used by the application. // The main purpose of this field is to make `rc5_t' to have a size // of 4 bytes (instead of 3). This is much better if the user makes a local // copy of the rc5 object because the copy then can be hold in registers // and saves the overhead of a frame. char info; } rc5_t; // Publish `rc5'. 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. */ extern void rc5_init (uint8_t addr); #define RC5_ALL 0xff // 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
µC-unabhängiger Teil. Diese Datei wird nicht compiliert sondern vom hardware-abhängigen Teil includiert. Erklärung dazu findet sich im Quellkommentar.
/* * RC5 decoder * * Contributed by Georg-Johann Lay <georgjohann@web.de> * * @Author : Georg-Johann Lay * @Date : 2007-02-02 * @File : rc5.c * @Language: ISO C 99 * @Purpose : Implementation of RC5 receiver, target independent part. * Do not compile this file. * It is just included by the target dependent part `rc5-foo.h'. */ /* To implement an RC5 receiver there are basically two approaches: -1- Look at the signal level in time gaps of a bit's or half bit's duration. This gives a straight forward and simple implementation. However, the time intervals must be followed very strictly. Otherwise, synchronization will get lost and the receiver won't work. So one needs a quite exact timer. Moreover, not all RC5 transmitters follow exactly the RC5 signal standard and introduce tolerances in bit durations that make this approach obsolete. -2- Synchronize with the signal at each signal edge and evaluate the time that elapsed since the last edge. This approch is much more powerful: It does not need sophisticated timer capabilities, allows both integrity checks of the received data and allowing tolerances in the transmitted protocol. We take way -2- */ // The rc5 data struct to communicate with the application // typedef'ed in `rc5.h' rc5_t rc5; // Some tweaks... #ifdef __GNUC__ # define inline __attribute__((always_inline)) #endif // GCC // 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(); ////////////////////////////////////////////////////////////////////////////// // An RC5 frame has 14 bits: // G1 G2[~C.6] F A4 A3 A2 A1 A0 C5 C4 C3 C2 C1 C0 // G: automatic Gain control bits // F: flip bit, aka. toggle bit // A: address // C: command, complement if G2 may serve as seventh command bit #define RC5_BITS_PER_FRAME 14 // 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 or 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 we shall listen to: // If the MSB is set all addresses are welcome uint8_t addr; // Number of edges (external interrupts) occured so far uint8_t nedge; // 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 half bit count rc5_info.hbits = 0; // .flip = -1: no RC5 data available, listen rc5.flip = -1; // Initialize the hardware rc5_timer_run(); rc5_timer_enable_irq(); rc5_xint_enable(); } ////////////////////////////////////////////////////////////////////////////// // This routine evaluates the RC5 frame and stores it in the // `rc5' struct, provided it is ok and the address matches. void rc5_timer_isr() { // The collected bits. union data16 data = rc5_info.data; // Number of bits uint8_t nbits = (uint8_t) (1+rc5_info.hbits) / 2; // An RC5 frame consists of 14 bits. if (RC5_BITS_PER_FRAME == nbits #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 // Gather all address bits in data.asByte[1] 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: code (command), address and flip. rc5.addr = rc5_addr; rc5.code = rc5_code; int8_t flip = 0; if (data.asByte[1] & 0x20) // 0b00100000 : Bit #11 flip = 1; rc5.flip = flip; } } // If we land here and `hbits' is not as expected, // we did not store anything and do just a reset. // Reset and wait for the next RC5 frame rc5_info.hbits = 0; } ////////////////////////////////////////////////////////////////////////////// // This routine collects the bits void rc5_xint_isr() { // The number of half bits. uint8_t hbits = rc5_info.hbits; // Either we are done with this frame or 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 (hbits >= 2*RC5_BITS_PER_FRAME -1) return; // Read the timer value. uint8_t timer = RC5_TIMER_REG; // Reset the timer to 0. RC5_TIMER_REG = 0; // The number of edges (interrupts) occured so far. uint8_t nedge = rc5_info.nedge; // Start of a new RC5 frame? if (0 == hbits) { // Yes: // We are only interested in falling edges if (1 == (RC5_PIN_REG >> RC5_PAD) % 2) return; // Set all bits of RC5 frame and edge count to 0 rc5_info.data.asWord = 0; nedge = 0; // Mimic the first AGC's first half bit timer = RC5_TICKS/2; } // Number of half bits of the just elapsed period // 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. // We received at least one half bit. hbits++; // Test interval's length if (timer > RC5_TICKS - RC5_DELTA) { // Two half bits: one more... hbits++; // reduce the validity check to the case of one half bit. timer -= RC5_TICKS/2; } // Test if a half bit's length is invalid: // |timer - RC5_TICKS/2| > RC5_DELTA ? if (timer < RC5_TICKS/2 - RC5_DELTA || timer > RC5_TICKS/2 + RC5_DELTA) { // Invalid: // Setting `nbits' to some invalid value will make // this ISR and the timer ISR ignore the rubbish. hbits = 0xff; // Force a soon timer overflow RC5_TIMER_REG = -RC5_TICKS; } else { // Valid: // Test if the edge occured in the middle of a bit, i.e. // if the number of half bits is odd. // If so, this edge tells us the bit's value. if (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). union data16 data = rc5_info.data; data.asWord <<= 1; // Store the bit. Edge polarity toggles; due to signal // inversion, a falling edge (even) is a ONE and // a raising edge (odd) is a ZERO. // Note that edge count starts at 0. if (nedge % 2 == 0) data.asByte[0] |= 1; // Store data rc5_info.data = data; if (hbits >= 2*RC5_BITS_PER_FRAME -1) // We have collected all bits. // Maybe there will be one more IRQ (hbit) but we do not care. // Frame is complete: force a soon timer overflow. RC5_TIMER_REG = -RC5_TICKS; } } // write back the local copies rc5_info.hbits = hbits; rc5_info.nedge = 1+nedge; }
rc5-avr.c
µC-abhängiger Teil (AT90S2313, ATtiny2313, ATmega8/16/32/48/88/168).
/* * RC5 implementation for some AVR derivatives. * * Contributed by Georg-Johann Lay <georgjohann@web.de> * * @Author : Georg-Johann Lay * @Date : 2007-02-02 * @File : rc5-avr.c * @Language: ISO C 99 * @Purpose : Implementation of RC5 receiver, target dependent part * for some AVR derivatives. */ /* 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 (AVR). 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 particular, 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. avr-gcc does not support all AVR derivatives on C level. Smaller AVRs are just supported on assembler level. To get a start for an assembler implementation, compile the module for an AVR that is "close" to the AVR you like and then look at avr-gcc's assembler output. - Peek at avr-gcc's assembler output > avr-gcc -S rc5-avr.c -fverbose-asm -Os -mmcu=... -DF_CPU=... or > avr-gcc -c rc5-avr.c -fverbose-asm -Os -mmcu=... -DF_CPU=... -save-temps - Compile with debug info and disassemble the object (elf32-avr): > avr-gcc -g -c rc5-avr.c -Os -mmcu=... -DF_CPU=... > avr-objdump -h -S -j .text rc5-avr.o Disassembling an Intel HEX file or the like is not recommended because any debug, symbol or source information will be lost. Again, the ISR implementation will be mostly hardware independent. The hardware dependent stuff accumulates in `rc5_init', so you best write that routine by hand. Concerning the ISRs, there is some potential for optimization; particularly in the ISRs' prologue and epilogue. */ // Test if we are avr-gcc #if !defined (__GNUC__) || !defined (__AVR__) # error This code is hardware dependent and intended for avr-gcc! #endif // avr-gcc #include <avr/io.h> #include <avr/interrupt.h> // On older avr-gcc we need `avr/signal.h' (nowadays in `avr/interrupt.h'). #ifndef SIGNAL # include <avr/signal.h> #endif #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... Newer AVR derivatives provide a more fine grained prescaler. 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 only 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. // You can also configure the Analog Comparator to work as a // source of pin change events. 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 written to the timer configuration register in order to start it. The values below fit the controllers below. For other AVR derivatives 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 //////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////