Mein Dekoder für RC-5 in C im Interruptbetrieb
Hallo Alle,
es wird hier eine Routine beschrieben zum Dekodieren des RC-5-Codes. Gundlage ist eine, von einem vorhandenen Timer verfügbare Zeitbasis. Der Timer kann ansonsten für andere, konstante Zeitaufgaben benutzt werden. Ein solcher Timer ist in vielen Programmen bereits vorhanden.
Hintergrund:
Mein bisher vorhandener RC-5-Dekoder läuft als Hauptprogramm - ich muss nach dem Aufruf warten, bis eine Taste gedrückt wird. Damit kann ich z.B. verschieden Tasks bzw. Modi in meinem MiniD0 oder WALL R starten/schalten. Ich wollte immer schon eine ähnliche Routine im Interruptbetrieb. Ich kenne den Code im RNWissen von SprinterSB - wollte aber mal versuchen, einen schlankeren Code zu schreiben, die knappe Variante von PDannegger aus mikrocontroller-nett ist mir zu undurchsichtig. Ausserdem interessierte mich die tatsächliche Codegestaltung (die einzelnen Bits) und die problemlose Ausgabe dieser Dinge auf ein übliches Terminal. Na ja, ansonsten: es gibt besser und schöner geschriebene C-Passagen.
Der folgende Code beschreibt diese Routine(n), der "nackte" Code der Dekoder-IRS hat 23 Zeilen. Die Routine stellte das Befehlsbyte zur Verfügung, derzeit wird es in der Timer-IRS "gelöscht" - dort könnte es in eine FIFO o.ä. übertragen werden. Das ist hier nicht geschehen.
Vielleicht nutzt jemandem das Ganze.
Gegebenheit:
Ein vorhandener Timer-Interrupt der als Bordzeit eingeführt wurde zur Zeitnahme verschiedener Sensoren und derzeit verschiedene Zeitabläufe steuert. Interruptabstand 50 µs, eine Zeitscheibe (zwischen zwei Interrupts) nenne ich tupsi =: Time Unit Per Sensor Interrupt.
Der Sensor ist ein SFH5110, der in ähnlicher Schaltung wie beim asuro verwendet wird. Damit wird das Signal der Infrarotfernsteuerung invertiert auf den Controllerpin geschaltet.
Der Code wurde für die RNControl mit mega1284/20 MHz geschrieben, ein Anpassung sollte leicht/wird möglich sein.
Aufgabenstellung:
RC-5-Dekoder im Interrupt
Interruptquelle ist ein externer Interrupt, hier extINT2 am mega1284. Interruptauslöser ist ein SFH5110 in der Schaltung à la asuro.
Erweiterte Aufgabe:
Im Testbetrieb werden die Codebits und der Befehlscode dezimal über UART ausgegeben.
Code:
Das erste Codefenster zeigt die "nackte" Interruptroutine
Das zweite Codefenster zeigt >>auszugsweise<< - aber hoffentlich komplett ALLE erforderlichen Definitionen und Deklarationen (keine Funktionsprototypen) und Codesequenzen.
Anmerkungen:
Die Zeit für 1 Codebit beträgt beim RC-5 1,778ms, in meinen tupsi à 50 µs sind das etwa 33 .. 38 Zeitscheiben, genauer: 35,56. Wegen der Toleranzen habe ich das entsprechende Zeitfenster - heisst bei mir RCbit_zt, auf 26 bis 44 tupsi gesetzt. Entsprechende Anpassungen sind erforderlich, wenn beim Verwenden der Routinen eine andere Zeitbasis zur Verfügung steht. Wenn innerhalb dieses zulässigen Zeitfensters eine Interrupt erfolgt, wird er ausgewertet und die Messzeit für das nächste Bit wieder auf Null gesetzt. Durch dieses Vorgehen "justiert" sich die Routine in weiten Grenzen auf die Fernsteuerung.
Im Code wird über USART das Bitmuster des Codes ausgegeben. Es wird anschließend NUR das Befehlsbyte weiter verarbeitet (meine Billigst-IR-RC hat garkein Adressbyte) und auch das Togglebit wird nicht ausgewertet. Das Befehlsbyte wird ebenfalls auf USART ausgegeben - damit ist es einfach möglich, den Tastencode vorhandener Fernsteuerungen zu analysieren. Schließlich wird die Gesamtdauer eines Signalzyklus einer Fernsteuerung im Rahmen der Zeitbasis angegeben, damit die mögliche Reaktionszeit des Codes und Abweichungen der Fernsteuerung von der Standardfrequenz abgeschätzt werden können.
Für Risiken und Nebenwirkungen wird nicht gehaftet. Eine kommerzielle Nutzung ist ausschließlich nur nach Rücksprache und meiner Zustimmung erlaubt.
Codefenster eins
Code:
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Start RC5-Decoding: Level (RC5prt & (1<<RC5pin)) ist low
// ===== RC-5-DECOding-Byte (RCDECO) ist Null
// RCvorsc >= 99 -- nur dann ist "vorher" länger high
// Es folgen 23 Zeilen relevanter Code zum Dekodieren
if ((!(RC5prt & (1<<RC5pin))) && (!RCDECO) && (RCvorsc > 99)) //
{ //
RCges_zt = 18; // ?? Gesamtzeit in tupsi f EINEN kplt CodeSATZ
RCBptr = 13; // RC-5-Code: Bitpointer (0..13) f RC-5-Code-Word
RCDECO |= ((uint16_t)1<<13 ); //
sendUSART ("\t1"); //
RCBptr = 12; // ... und pointer auf nächstes Bit
RCbit_zt = 0; // Zeit für 1 Codebit 1,778ms, in tupsi 33 .. 38
} //
if (RCBptr >= 0) //
{ //
if ((RCbit_zt>26) && (RCbit_zt < 44)) // <<## gute Funktion, so bleibts
{ // Ist ein gültiges Bit erkannt worden ?
if (!(RC5prt & (1<<RC5pin))) // High oder low level ?
{ //
RCDECO |= ((uint16_t)1<<RCBptr ); // Bei LOW schreib "1" weg
sendUSART ("1"); //
} // Ende if (!(RC5prt & (1<<RC5pin))) : High oder low
if (RC5prt & (1<<RC5pin)) sendUSART ("0");
RCBptr -- ; // ... und pointer auf nächstes Bit
RCbit_zt = 0; // Zeit für 1 Codebit 1,778ms, in tupsi 33 .. 38
} // Ende if ((RCbit_zt>26) && (RCbit_zt < 48))
} // Ende if (RCBptr >= 0)
Codefenster zwei
Code:
#define LCg 6 // gnLED2 auf PC6 - onboard RNControl
volatile int16_t Izeit_1; // Wertbereich int16: 32.767. uint16: 65.535
volatile int16_t Izthrznt; // Der zeitliche Horizont, z.B. 20000 für 2 sec
volatile int16_t Isecundn; // Sekunden Programmlaufzeit, 32.767 sec sind
// 546,117 Minuten bzw. 9 Std
volatile int16_t icntdwn; // Countdownzähler (max 32767 = 9 Std)
// ============================================================================== =
// === RC-5_Daten, Daten fürs RC-5 Decoding
// ============================================================================== =
#define RC5prt PINB // Eingangsport für RC5-Decoding
#define RC5pin PB2 // Pointer auf gewählten Sensorpinn für RC-5
volatile int16_t RCCODE [12]; // 12 Words für komplette Codes + Flags + Reserve
volatile uint8_t RCCptr; // Pointer auf den aktuellen Code
volatile int16_t RCDECO; // Word für EINEN komplett dekodierten Code
volatile int8_t RCBptr; // RC-5-Code: Bitpointer für RC-5-Code-Word
// beim Dekodieren. Beginn: 14 für Startbit 1
volatile uint8_t RC5roff; // RC5-read-off aus- (=1) / ein- (=0) -schalten
// roff = 0 <=> Lesen ist ein, 1 <=> Lesen ist aus
// - - - - - - - - - - - - - - -
// Zeiten
volatile int16_t RCbit_zt; // Zeit für EIN Codebit 1,778ms, in tupsi 35,56
// <=> mit Toleranz 33 .. 38
volatile int16_t RCges_zt; // ?? Gesamtzeit in tupsi für einen kplt. CodeSATZ
volatile uint16_t RCvorsc; // Vorstartcounter - TupsiZeit vor dem ersten Bit
volatile uint16_t RCzeit1; // RC-5-zeit. Wird im main genullt und im timer
// freilaufend+unkontrolliert hochgezählt wird bis zum Überlauf
// ============================================================================== =
// ============================================================================== =
// ============================================================================== =
// === Initialisierung fuer Timer2 mega168, m328, m1284 und Ähnliche
void TC2TMR_init(void) // Init Tmr/Cntr 2, 8-Bit auf 20 kHz = 50 µs
{ //
TCCR2A |= (1<<WGM21); // Timer im CTC-Mode, Top=OCR2A doc S 157
TCCR2B |= (1<<CS21); // Prescaler 1/8 / Clock <- CPU doc S 158
OCR2A = 124; // Preset 124 für 50µs bei 20Mhz
TIMSK2 |= (1<<OCIE2A); // Tmr/Cntr2 CompareA interrupt enabled
Izeit_1 = 0; // Laufzeit nullen - unabhängig von Isecndn !!
} // Ende void TC2TMR_init(void)
// ============================================================================== =
// ============================================================================== =
// === Nicht unterbrechbare ISR für timer2 => zählt hoch im Takt 20 kHz = 50 µs
ISR(TIMER2_COMPA_vect) // Vektor 7
{ //
if (Izeit_1 <= Izthrznt) //Interrupt-Timer = 1 ... 20 000 ... (1 sec blink)
{ //
Izeit_1 ++; // Izeit_1 bleibt bis 32000 in der int16-Grenze
} //
else // in if (Izeit_1 <= Izthrznt)
{ // ... Eine Sekunde ist voll =>
Izeit_1 = 1; // ansonsten: Rückstellen auf Eins
icntdwn = icntdwn + 1; // Countdownzähler hoch(!!)zählen
PORTC ^= (1<<LCg); // gnLED auf Pin PC6 toggeln <=> Heartbeat
Isecundn ++; // Sekundenzähler hochtackern, max 9 Std
} // Ende if (Izeit_1 < Izthrznt)
// - - Ende der eigentlichen Timer2-ISR
// - - - - - - - - - - - - - - -
// - - Jetzt für RC-5-Analyse Zeiten etc.
if (RC5prt & (1<<RC5pin)) // WENN RC5-pinn high; Encoder empfängt nichts
{ // => zähle Vorstartcounter hoch
// Vorstartcounter in IRS(INT2_vect) nullen
RCvorsc ++; // RC-5-Vor-Sequenz-Counter bis 100 hochzählen
if (RCvorsc > 100) RCvorsc = 100; // Begrenze RCvors
} // Ende if (RC5prt & (1<<RC5pin))
// - - - - - - - - - - - - - - -
RCbit_zt ++; // Zeit für 1 Codebit 1,778ms, in tupsi 33 .. 38
RCzeit1 ++; // Tupsicounter uint16_t für RC-5-Decoding
RCges_zt ++;
if (RCges_zt >= 490 && RCDECO) // Endemarkierung RC-5-Deco 27Nov2012-15:22
{ //
RCDECO = 0; //
} //
if ( RCzeit1 > 2000) // "Reset" Dekodierung bei einer zehntel Sekunde
{ //
RC5roff = 1; //
RCzeit1 = 0; //
} //
// - - - - - - - - - - - - - - -
return; //
} // Ende ISR(TIMER2_COMPA_vect)
// ============================================================================== =
// ============================================================================== =
// === Initialisierung fuer EXT_INT2 auf Pin PB2 bei m1284 für
// Vector 4 {1-35}, Progr.addr. $0006 INT2 External Interrupt Request 2
// ============================================================================== =
void XTI_2_init( void ) // Init. INT 2 auf any edge für RC-5
{ // d.h. EICRA ISC20 = 1 doc S67
// - - - - - - - - - - - - - - - -
EICRA |= (1<<ISC20); // Interrupt auf any edge
EIMSK |= (1<<INT2); // und erlaube INT2 in EIMSK
} // Ende void XTI_2_init( void )
// ============================================================================== =
// ============================================================================== =
// === ISR für EXT_INT2 auf Pin PB2 zum Dekodieren von RC-5
ISR (INT2_vect) //
{ //
int16_t RCbb;
char wortadc[12]; // Übersetzungsfeld für Werteausgabe
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Start RC5-Decoding: Level (RC5prt & (1<<RC5pin)) ist low
// ===== RC-5-DECOding-Byte (RCDECO) ist Null
// RCvorsc >= 99 -- nur dann ist "vorher" länger high
if ((!(RC5prt & (1<<RC5pin))) && (!RCDECO) && (RCvorsc > 99)) //
{ //
RCges_zt = 18; // ?? Gesamtzeit in tupsi f EINEN kplt CodeSATZ
// hier ein offset, weil ja 1/2 Bit vorbei ist
RCBptr = 13; // RC-5-Code: Bitpointer (0..13) f RC-5-Code-Word
// fängt auf Position 14! an mit >>13<< (0..13) für Startbit 1!
// Hauptaufgabe: Schreibe Bit ins Target
RCDECO |= ((uint16_t)1<<13 ); //
sendUSART ("\t1");
RCBptr = 12; // ... und pointer auf nächstes Bit
RCbit_zt = 0; // Zeit für 1 Codebit 1,778ms, in tupsi 33 .. 38
} //
// - - - - - - - - - - - - - - - -
if (RCBptr >= 0) //
{ //
if ((RCbit_zt>26) && (RCbit_zt < 44)) // <<## gute Funktion, so bleibts
{ // Ist ein gültiges Bit erkannt worden ?
if (!(RC5prt & (1<<RC5pin))) // High oder low level ?
{ //
RCDECO |= ((uint16_t)1<<RCBptr ); // Bei LOW schreib "1" weg
sendUSART ("1"); //
} // Ende if (!(RC5prt & (1<<RC5pin))) : High oder low
if (RC5prt & (1<<RC5pin)) sendUSART ("0");
RCBptr -- ; // ... und pointer auf nächstes Bit
RCbit_zt = 0; // Zeit für 1 Codebit 1,778ms, in tupsi 33 .. 38
} // Ende if ((RCbit_zt>26) && (RCbit_zt < 48))
} // Ende if (RCBptr >= 0)
// - - - - - - - - - - - - - - - -
// TESTWEISE Ausgabe Codewort und Zeitbedarf
if ((RCBptr < 0) && (RCges_zt < 1000))
{ //
RCBptr = 0; // doppelte Ausgabe verhindern
sendUSART(" => dez: "); // Ausgabezeile eröffnen
// lo = word & 0x7f; 0x7F wegen 7 Bit Befehlsbyte
RCbb = RCDECO & 0x7F;
itoa(RCbb, wortadc, 10); // aktueller Wert
sendUSART(wortadc); // ... ausgeben.
sendUSART(" , RCges_zt = "); // Neue Zeile
itoa (RCges_zt, wortadc, 10); // aktueller Wert
sendUSART(wortadc); // ... ausgeben.
sendUSART("\r\n"); // Neue Zeile
} // Ende if (RCBptr < 0)
// - - - - - - - - - - - - - - - -
} // Ende ISR (INT2_vect)
// ============================================================================== =
So wie im Codefenster unten sieht ein Testlauf aus mit Ausgabe am Terminal von br@y. Die Ausgabe nach "... Aktion" wird vom vorgestellten Code geliefert : Bitmuster der gesamten Codesequenz, dezimale Entsprechung des sechsbittigen Befehlsbytes, Zeitdauer der Dekodierung vom Beginn des Codes bis zum Ende. Man kann auch schön die Funktion des Toggelbits (Bit 3) erkennen.
Zum Zeitbedarf, hier in 50µs-tupsi: 482 tupsi zu 50 µs sind 24,2 ms; da das erste Halbbit fehlt - es kann ja erst auf die erste Flanke des Datentelegramms getriggert werden, die ist aber in der Mitte vom Startbit 1 - komme ich ziemlich genau auf die fast 25 ms der gesamten Telegrammlänge.
Code:
C501 R5M_x15 m1284p/20MHz 27Nov2012 15:28
I2C >>400kHz [t05], I2C mit Taste [gelb], dann [OK] zu MoCo328
Motoren rauf+runter mit Taste [P100]
I2C-Slave ist Adresse : 132 dez = 0x84 ,
Gute Funktion mit extINT2 für RC-5
Aktuell - Zur-Verfügung-Stellung des RC-5-Code
Aktiv : Taste [MIX], [P100], [9] und [gelb]
Bitte um Aktion _
11000000010100 => dez: 20 , RCges_zt = 483
11100000011001 => dez: 25 , RCges_zt = 482
11000000100011 => dez: 35 , RCges_zt = 482
11100000011011 => dez: 27 , RCges_zt = 482
11000000001010 => dez: 10 , RCges_zt = 484
11100000000110 => dez: 6 , RCges_zt = 483
Rückmeldungen sind erwünscht.
Optimierung der RC-5-IR-Fernsteuerung für Roboter etc.
Ein paar Schräubchen gedreht an der IR-Fernsteuerung (Schlechtwetterbeschäftigung).
Seit meinen ersten Robotern – Dottie über WALL R und archie – erfolgt deren Steuerung mit ner alten TV-Fernbedienung von einem längst entsorgten Fernseher. Dessen IR-Code ist Philips RC-5, siehe z.B. Unterlagen von San Bergmans hier – mit den Feinheiten zum Manchestercode auf 36 kHz-Basis.
Code:
/* ============================================================================= =
============================================================================= =
Beispiele für RC-5-Signalgang/Manchester-Codierung
| | | | Bitdauer 2x889 µs (2*17,78 tupsi) => 1,778 ms/35,56 tupsi
+---+ | | +---+ Für den Wert des Bits ist Übergang in Bitmitte massgebend
|HHH| | | |HHH|
|HHH+---+ +---+HHH| ==> Übergang von 1 nach Null <=> Bitwert 0
|Logic 0| |Logic 1| ==> Übergang von 0 nach 1 <=> Bitwert 1
Beispiel (Graphik zeigt neun Bits) :
| | | | | | | | | |
| +-+-+ | +-+-+ | +-+ +-+-+ +-+ +-+ | 1 Bit 1,778 ms / 35,56 tupsi < s.u.
| | | | | | | | | | | | | | | | | | | 14 Bit 24,892 ms / 497,84 tupsi
+-+ | +-+-+ | +-+-+ +-+ | +-+ +-+ +-+ Gap 114 ms / 2800 tupsi
Bit- | | | | | | | | | |
Wert 1 0 1 0 1 1 0 0 0
// - - - - - - - - - - - - - - -
Der Rest der RC-Variablen (die folgenden) ist im Prinzip unnötiger Schotter
und wurde am 08 Nov 2013 entfernt (bis Rev. ~x30 enthalten)
>>>> Messung (26.6.2020) mit DISCOVERY_2 am Steckbrett mit zwei parallel <<
>>>> geschalteten SFH5110 zeigt eine Varianz der Pulse zwischen <<
>>>> ca. 850 µs und 980 µs >> entsprechend 17 (34) und 19,6 (39) tupsi <<
tupsi sind 50µs-Zeitscheiben einer Interruptroutine für heartbeat etc.
============================================================================= =
=============================================================================*/
Die Bedienung ist ab archie angepasst an die übliche TV-Fernbedienung: es gibt einerseits Ein-Tasten-Befehle (mute, START/STOP, vor(auf), zurück(ab), links, rechts, swap uvm). Implementiert ist aber auch die übliche 3-Ziffern-Kombination, die ich RC5-3 nenne. RC5-3 läuft bei archie wie beim Fernseher – wird eine Ziffer gedrückt, wartet der (Befehls-) Empfänger ne Weile auf eine zweite, danach ne Weile auf ne dritte Ziffer. Wird statt einer Ziffer eine andere Taste gedrückt, wird die laufende 3-Ziffern-Eingabe abgebrochen. Ist der 3ziffrige Befehl eingetippt, wird er sofort angesteuert/eingeleitet. Dauert die Pause nach einer Zifferneingabe zu lange, wird die laufende Eingabe abgebrochen. Als optische Eingabekontrolle dient eine grobpixelige LED-anzeige (12x10-Matrix – ne ehemalige PingPongPlatine), die auch in der untersten LED-Reihe einen kleinen „Strich“ als Busymarke beim RC5-3 zeigt.
Zusätzlich ist bei archie natürlich zu wünschen, dass Befehle aus beliebigen Richtungen (rundum) empfangen werden. Es ist daher auf einen zuverlässigen Rundum-Empfang der Steuercodes zu achten. Dies erfolgt durch mehrere IR-Empfängerplatinchen. Verwendet werden die ICs SFH5110 oder nach dessen Abkündigung TL1838.
Weiter soll der Decoder das Doppeln von Tasten verhindern.
Grobskizze des Decoders:
- IR-Decoding vom IR-Empfänger auf einem PCINT
- Start Timer sobald ein Signal (LOW) erkannt wird nach einer Pause von mind. 4,95 ms.
..Durch diese Pause ist sichergestellt, dass kein Zeichen "kurz vorher" akzeptiert wird.
- Decodieren der 14 Bits des Manchestercodes
- Wird danach ein Zifferncode erkannt –
..weiter zu RC5-3 Abschnitt, sonst Befehlsaufruf je nach Codewert.
Probleme machte mir bisher z.B. das Doppeln – wenn man zu lange drückt oder die Fernsteuertasten „doppeln“. Der Code wurde dahin überarbeitet. Dazu wurden einige längst beabsichtigte Messungen mit zwei verschiedenen IR-Empfängertypen durchgeführt. Der Signalausgang dieser Empfänger wurde am Eingangsport des Controllers zusammengeschlossen.
Die Messungen (ANALOG-DIGILENT2) zeigen Signalgänge:
1. Zwei TL1838 an EINEM Controllerport – Abgriff am Porteingang; 1 ms/DIV.
2. Zwei TL1838 an EINEM Controllerport – Abgriff am Porteingang; 100 µs/DIV.
3. High-Low-Übergang von TL1838-bl und SFH5110-ge; 1 µs/Div. Der modernere
..…TL1838 (blaue Linie) schaltet viel sauberer.
4. IR-Sendediode (in der Fernbedienung) und TL1838. Der IR-Empfänger schaltet
…..nach ca. 8 36-kHz-Pulsen auf low. 100 µs/DIV.
5. IR-Sendediode (in der Fernbedienung) und SFH5110. Der IR-Empfänger schaltet
..…nach ca. 6 36-kHz-Pulsen auf low. 100 µs/DIV.
Schaltung des IR-Empfängers :
. . . . . Vcc . . . . > . . 100R . . - . . > . .SFH5110/Vcc . . . sinngemäß mit TL1838
. . . . . GND . . . . > . . SFH5110/GND . . . . . . . . . . . . . . sinngemäß mit TL1838
. . . . . SigOut . . .> . .SFJ5110/SigOut < 22k>Vcc-in . . .. sinngemäß mit TL1838
=>=>=> Diese Schaltung mit dem kleinen Vorschaltwiderstand und dem 22k-PullUp bietet eine sehr sichere Funktion.
Kondensatoren (z.B. lt. Datenblatt) stören ! ! !
Vielleicht interessiert´s.