Dieser Code empfängt und dekodiert NEC-IR-Signale ohne externe Bibliothek.
Er dient dazu, das Prinzip der Pulslängen-Modulation zu veranschaulichen und das Verständnis der Infrarot-Signalübertragung zu vertiefen. |
Messprinzip
Die Zeiterfassung erfolgt mit micros()
, das die Dauer von LOW- und HIGH-Pulsen misst (Pulslängen-Modulation).
Das Signal wird erfasst, indem measurePulse(state)
die Zeit für jeden Zustand speichert.
Ein Timeout (60 ms) verhindert endlose Messungen.
Zur Identifikation eines NEC-Signals:
Quellcode (engl. Sourcecode)
Listing 1: infraredreceiver.ino
/**********************************************************************
IR-Decoder-Beispiel: NEC-Protokoll mit Pulstabelle
- Erfasst Pulse (LOW/HIGH) mit micros().
- Zeigt eine formatierte Pulstabelle mit Bit-Spalte.
- Dekodiert NEC-Signale auf zwei Arten:
1) decodeRawNEC (MSB-first)
2) decodeBitReverseNEC (LSB-first)
**********************************************************************/
#define IR_PIN 7 // Pin für den IR-Empfänger
#define MAX_PULSES 70 // Max. Anzahl erfassbarer Pulse-Paare
#define TIMEOUT_US 60000UL // 60 ms Sicherheitszeit
#define HIGH_PAUSE 10000UL // 10 ms: Abbruchkriterium (Ende des IR-Signals)
bool USE_REGISTER = true; // true = direkten Registerzugriff!
unsigned long pulseArray[MAX_PULSES][2];
int pulseIndex = 0; // Anzahl erfasster Pulse-Paare
bool capturing = false; // Flag: Erfassung läuft?
void setup() {
Serial.begin(115200);
pinMode(IR_PIN, INPUT);
Serial.println("IR-Decoder gestartet.");
}
void loop() {
// Messung starten?
if (!capturing && isIrSignalDetected()) {
capturing = true;
pulseIndex = 0;
capturePulses(); // Pulse erfassen
printPulses(); // Tabelle ausgeben
decodeRawNEC(); // Dekodierung (MSB-first)
decodeBitReverseNEC(); // Dekodierung (LSB-first)
capturing = false;
}
}
// ---------------------------------------------------------------------
// Erfasst abwechselnd LOW- und HIGH-Dauer, bis eine lange HIGH-Pause
// (HIGH_PAUSE) oder das Array voll ist.
// ---------------------------------------------------------------------
void capturePulses() {
while (pulseIndex < MAX_PULSES) {
unsigned long lowDuration = measurePulse(LOW);
unsigned long highDuration = measurePulse(HIGH);
pulseArray[pulseIndex][0] = lowDuration;
pulseArray[pulseIndex][1] = highDuration;
pulseIndex++;
if (highDuration > HIGH_PAUSE) {
break; // Ende des Signals
}
}
}
bool isIrSignalDetected() {
if (USE_REGISTER)
return !(PIND & (1 << IR_PIN));
return digitalRead(IR_PIN) == LOW;
}
// ---------------------------------------------------------------------
// Misst, wie lange der Pin im angegebenen Zustand (LOW/HIGH) bleibt,
// bricht nach TIMEOUT_US ab (z.B. 60 ms).
// ---------------------------------------------------------------------
unsigned long measurePulse(bool state) {
bool checkState = (state == LOW);
unsigned long startTime = micros();
while (isIrSignalDetected() == checkState) {
if (micros() - startTime > TIMEOUT_US) {
break;
}
}
return micros() - startTime;
}
// ---------------------------------------------------------------------
// Gibt die aufgenommenen Pulse als Tabelle im Serial Monitor aus.
// Spalten: | Label | LOW(µs) | HIGH(µs) | Bit |
// Startbit = Zeile 0, Bits 1..32, danach Stopbit?
// Zur Bit-Bestimmung wird ein simpler Schwellwert (HIGH > 1000 µs) genutzt.
// ---------------------------------------------------------------------
void printPulses() {
Serial.println("Infrarot Signal empfangen:");
Serial.println("");
Serial.println("| Label | LOW(µs) | HIGH(µs) | Bit |");
Serial.println("|------------|---------:|---------:|-----|");
for (int i = 0; i < pulseIndex; i++) {
String label;
if (i == 0) {
label = "Startbit";
} else if (i <= 32) {
label = "Bit " + String(i);
} else {
label = "Stopbit";
}
char bitVal = '-';
if (i >= 1 && i <= 32) {
if (pulseArray[i][1] > 1000) {
bitVal = '1';
} else {
bitVal = '0';
}
}
char lineBuffer[80];
sprintf(
lineBuffer,
"| %-10s | %8lu | %8lu | %c |",
label.c_str(),
pulseArray[i][0],
pulseArray[i][1],
bitVal
);
Serial.println(lineBuffer);
}
Serial.println();
}
// ---------------------------------------------------------------------
// Dekodiert das Signal MSB-first
// (klassischer Ansatz: data << 1, dann Bit rein).
// ---------------------------------------------------------------------
void decodeRawNEC() {
if (pulseIndex < 34) {
Serial.println("[decodeRawNEC] Zu wenige Pulse für NEC (mind. 34)!");
return;
}
unsigned long startLow = pulseArray[0][0];
unsigned long startHigh = pulseArray[0][1];
if (startLow < 8000 || startLow > 10000 ||
startHigh < 4000 || startHigh > 5500) {
Serial.println("[decodeRawNEC] Kein gültiges Startbit!");
return;
}
uint32_t data = 0;
for (int i = 1; i <= 32; i++) {
unsigned long highDur = pulseArray[i][1];
if (highDur > 1000) {
data = (data << 1) | 1; // Bit 1
} else {
data = (data << 1); // Bit 0
}
}
Serial.println("[decodeRawNEC] Ergebnis (MSB-first):");
printHex32(data);
printNECBytes(data);
Serial.println("");
}
// ---------------------------------------------------------------------
// Dekodiert das Signal LSB-first
// (bitweises Umkehren durch data >> 1 | (bit << 31)).
// ---------------------------------------------------------------------
void decodeBitReverseNEC() {
if (pulseIndex < 34) {
Serial.println("[decodeBitReverseNEC] Zu wenige Pulse für NEC (mind. 34)!");
return;
}
unsigned long startLow = pulseArray[0][0];
unsigned long startHigh = pulseArray[0][1];
if (startLow < 8000 || startLow > 10000 ||
startHigh < 4000 || startHigh > 5500) {
Serial.println("[decodeBitReverseNEC] Kein gültiges Startbit!");
return;
}
uint32_t data = 0;
for (int i = 1; i <= 32; i++) {
unsigned long highDur = pulseArray[i][1];
int bitVal = (highDur > 1000) ? 1 : 0;
data = (data >> 1) | ((uint32_t)bitVal << 31);
}
Serial.println("[decodeBitReverseNEC] Ergebnis (LSB-first):");
printHex32(data);
printNECBytes(data);
Serial.println("");
}
// ---------------------------------------------------------------------
// Gibt den 32-Bit-Wert stets als achtstellige HEX-Zahl aus (mit 0ern).
// ---------------------------------------------------------------------
void printHex32(uint32_t val) {
char buf[9];
for (int i = 7; i >= 0; i--) {
uint8_t nibble = val & 0xF;
val >>= 4;
buf[i] = (nibble < 10)
? (char)('0' + nibble)
: (char)('A' + (nibble - 10));
}
buf[8] = '\0';
Serial.print("32-Bit Hex: 0x");
Serial.println(buf);
}
// ---------------------------------------------------------------------
// Zerlegt die 32-Bit-Daten in 4 Bytes (Addr, ~Addr, Cmd, ~Cmd)
// und gibt sie aus. Prüfung, ob ~Addr und ~Cmd jeweils Inversionen sind.
// ---------------------------------------------------------------------
void printNECBytes(uint32_t data) {
uint8_t addr = (data >> 24) & 0xFF;
uint8_t addrInv = (data >> 16) & 0xFF;
uint8_t cmd = (data >> 8) & 0xFF;
uint8_t cmdInv = data & 0xFF;
Serial.print("Addr = 0x");
Serial.print(addr, HEX);
Serial.print(", ~Addr = 0x");
Serial.print(addrInv, HEX);
Serial.print(", Cmd = 0x");
Serial.print(cmd, HEX);
Serial.print(", ~Cmd = 0x");
Serial.println(cmdInv, HEX);
bool verify = ((uint8_t)(addr ^ addrInv) == 0xFF) && ((uint8_t)(cmd ^ cmdInv) == 0xFF);
if (!verify) {
Serial.println("→ WARNING: Keine typische Inversion!\n");
}
}
Erklärungen zum Quellcode
loop - Hauptprogrammschleife
Sobald ein IR-Signal erkannt wird (isIrSignalDetected()
), startet die Verarbeitung:
capturePulses()
: Erfasst die LOW-/HIGH-Pulse und speichert sie.
printPulses()
: Gibt die erfasste Pulstabelle im Serial Monitor aus.
decodeRawNEC()
: Dekodiert das Signal im MSB-first-Format.
decodeBitReverseNEC()
: Dekodiert das Signal im LSB-first-Format.
Danach wird capturing
zurückgesetzt, um neue Signale zu empfangen.
pulseArray - zweidimensionales Array
pulseArray
ist ein zweidimensionales Array pulseArray[MAX_PULSES][2]
, das die gemessenen Dauerwerte eines empfangenen IR-Signals speichert.
Jeder Eintrag i
entspricht einem gemessenen Puls-Paar (LOW/HIGH), beginnend mit dem Startbit und gefolgt von den Datenbits des IR-Signals.
decodeRawNEC() – MSB-first Dekodierung
Prüft das Startbit (ca. 9000 µs LOW + 4500 µs HIGH).
Liest die folgenden 32 Datenbits:
Bit 1, wenn HIGH-Dauer > 1000 µs.
Bit 0, wenn HIGH-Dauer ≤ 1000 µs.
Speichert das Bit von links nach rechts in data
:
Gibt das 32-Bit-Ergebnis als HEX-Wert und die Einzelbytes (Adresse, Kommando) aus.
decodeBitReverseNEC() – LSB-first Dekodierung
Identisch zu decodeRawNEC()
, aber die Bits werden umgekehrt gespeichert:
Das Ergebnis entspricht einer spiegelbildlichen Darstellung der Binärdaten.
Beispiel: Makeblock Fernbedienung
Der Code infraredreceiver.ino
erzeugt die folgende Ausgabe, wenn ‚A‘ mit der Makeblock-Fernbedienung gesendet wird. Diese wird von einem Arduino Nano über die serielle Schnittstelle ausgegeben.
Infrarot Signal empfangen:
| Label | LOW(µs) | HIGH(µs) | Bit |
|------------|---------:|---------:|-----|
| Startbit | 9116 | 4428 | - |
| Bit 1 | 624 | 524 | 0 |
| Bit 2 | 596 | 524 | 0 |
| Bit 3 | 596 | 520 | 0 |
| Bit 4 | 596 | 524 | 0 |
| Bit 5 | 572 | 548 | 0 |
| Bit 6 | 596 | 520 | 0 |
| Bit 7 | 600 | 516 | 0 |
| Bit 8 | 600 | 524 | 0 |
| Bit 9 | 592 | 1624 | 1 |
| Bit 10 | 624 | 1620 | 1 |
| Bit 11 | 624 | 1624 | 1 |
| Bit 12 | 624 | 1620 | 1 |
| Bit 13 | 624 | 1624 | 1 |
| Bit 14 | 624 | 1620 | 1 |
| Bit 15 | 624 | 1624 | 1 |
| Bit 16 | 620 | 1620 | 1 |
| Bit 17 | 624 | 1624 | 1 |
| Bit 18 | 628 | 520 | 0 |
| Bit 19 | 596 | 1620 | 1 |
| Bit 20 | 624 | 524 | 0 |
| Bit 21 | 596 | 520 | 0 |
| Bit 22 | 592 | 524 | 0 |
| Bit 23 | 596 | 1616 | 1 |
| Bit 24 | 628 | 524 | 0 |
| Bit 25 | 596 | 520 | 0 |
| Bit 26 | 596 | 1624 | 1 |
| Bit 27 | 624 | 520 | 0 |
| Bit 28 | 596 | 1624 | 1 |
| Bit 29 | 620 | 1624 | 1 |
| Bit 30 | 624 | 1616 | 1 |
| Bit 31 | 620 | 524 | 0 |
| Bit 32 | 596 | 1620 | 1 |
| Stopbit | 624 | 60008 | - |
[decodeRawNEC] Ergebnis (MSB-first):
32-Bit Hex: 0x00FFA25D
Addr = 0x0, ~Addr = 0xFF, Cmd = 0xA2, ~Cmd = 0x5D
[decodeBitReverseNEC] Ergebnis (LSB-first):
32-Bit Hex: 0xBA45FF00
Addr = 0xBA, ~Addr = 0x45, Cmd = 0xFF, ~Cmd = 0x0
Analyse
Die Verwendung der micros()
-Methode liefert präzisere und konsistentere Werte als beispielsweise eine auf delay()-basierten Messung, was zu einer zuverlässigeren Erfassung der IR-Signale führt.
Abschnitt | Erwartet (NEC) µs | Gemessen µs | Abweichung | Bewertung |
Startbit LOW | 9000 | 9116 | +1.3% | ✅ Sehr gut |
Startbit HIGH | 4500 | 4428 | -1.6% | ✅ Sehr gut |
Bit 0 LOW | 560 | 624 | +11.4% | ⚠ Leichte Abweichung |
Bit 0 HIGH | 560 | 524 | -6.4% | ✅ Gut |
Bit 1 LOW | 560 | 592-628 | +5-12% | ⚠ Leichte Abweichung |
Bit 1 HIGH | 1690 | 1616-1624 | -4.4% | ✅ Sehr gut |
Stopbit LOW | 560 | 624 | +11.4% | ⚠ Leichte Abweichung |
Stopbit HIGH | keine Angabe | 60008 | N/A | 🚩 Typische HIGH-Pause (Signalende) |
Das Startbit liegt sehr nahe an den Sollwerten (~9000 µs LOW und ~4500 µs HIGH) mit einer minimalen Abweichung von +1.3 % (LOW) und -1.6 % (HIGH).
Die Datenbits weichen typischerweise um 5–12 % ab, wobei die LOW-Phasen tendenziell etwas länger und die HIGH-Phasen für „0“ leicht kürzer als erwartet sind. Diese Abweichungen liegen im akzeptablen Bereich für IR-Signale.
Beim Stopbit wird ein kurzer LOW-Puls (~624 µs) erfasst, der leicht über dem erwarteten Wert von 560 µs liegt (+11.4 % Abweichung). Danach folgt eine lange HIGH-Phase (~60008 µs), was das typische Signalende des NEC-Protokolls anzeigt.
→ Fazit: Das Signal wird präzise erkannt und dekodiert, die Messergebnisse liegen im akzeptablen Toleranzbereich. 🚀
MSB (Most Significant Bit) ist das höchstwertige Bit einer Zahl, während LSB (Least Significant Bit) das niederwertigste Bit ist.
Beim Speichern oder Übertragen von Daten gibt es zwei mögliche Reihenfolgen:
Je nach Protokoll kann eine der beiden Darstellungen verwendet werden, weshalb eine Umwandlung zwischen MSB-first und LSB-first manchmal erforderlich ist.
Was macht decodeBitReverseNEC() genau?
Die Funktion decodeBitReverseNEC()
dekodiert das NEC-Signal LSB-first, also das niederwertigste Bit zuerst. Das bedeutet:
Beispiel
Original (MSB-first) Empfang
Angenommen, das empfangene Signal besteht aus 32 Bits:
MSB → 00000000 11111111 10100010 01011101 ← LSB
Hexadezimal als MSB-first:
0x00FFA25D
Bitweise Spiegelung für LSB-first
Wenn wir LSB-first dekodieren, wird nicht nur die Reihenfolge der Bytes umgedreht, sondern auch die Bits in jedem Byte selbst:
MSB-first Byte | LSB-first umgekehrt |
00000000 (0x00) ↓ | 00000000 (0x00) |
11111111 (0xFF) | 11111111 (0xFF) |
10100010 (0xA2) | 01000101 (0x45) |
01011101 (0x5D) | 10111010 (0xBA) ↑ |
Das ergibt Hexadezimal als LSB-first:
0xBA45FF00
Warum werden nicht nur die Bytes, sondern auch die Bits umgedreht?
In decodeBitReverseNEC()
passiert folgendes:
uint32_t data = 0;
for (int i = 1; i <= 32; i++) {
unsigned long highDur = pulseArray[i][1];
int bitVal = (highDur > 1000) ? 1 : 0;
data = (data >> 1) | ((uint32_t)bitVal << 31);
}
Erklärung:
bitVal
wird als 0 oder 1 gesetzt, basierend auf der HIGH-Zeit.
A: Das neue Bit wird an die höchste Position (« 31
) geschrieben.
B: Der Rest der bereits gespeicherten Bits wird um eine Position nach rechts verschoben (» 1
).
Beide Ausdrücke (A und B) werden oder
verknüpft.
Das bedeutet:
Ergebnis: