/*
Konverter für PPM-Signal aus einem Multiplex-Empfängerin ein Spektrum DSM2 Sende-Signal

Copyright (C)2012  Stephan Busch

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

Betrieb des Konverters mal nicht direkt am Sender, sondern Anschluß an Multiplex 
M-Link-Empfänger (5 Kanäle, leicht auf 6 erweiterbar)
Betrieb mit 2s-Lipo, Unterspannungsüberwachung (Schutz Lipo)
Fehlerüberwachung, Anzeige mit Status-LEDs

wichtige Komponenten:
- Multiplex Empfänger, z.B. RX-5 light
- Controller Attiny2313
- Sendemodul X10EMTX MLP4DSM

Controller
- liest 5 PPM-Signale aus dem Empfänger
- konvertiert in DSM2-Daten
- sendet DSM2-Pakete
- mit Bind-Modus

Funktion:
- normal: Strom ein und los gehts
  Heartbeat blinkt langsam
  PPM-LED flackert hektisch
  Fehler-LED am Anfang an, wenn aus betriebsbereit
  blinkende Fehler-LED und Piezo signalisiert aufgetretene Fehler, Anzahl Blinker/Piepser = Fehlernummer (s.u.)
  aufgelaufene Fehler können durch Druck auf den Taster gelöscht/Quittiert werden
  
- bind: (Empfänger im Modell in Bind-Modus), Taster drücken, dann Strom ein
  Heartbeat blinkt schnell
  PPM-LED flackert hektisch
  nach ca. 10s normaler Betrieb (s.o.)

Fehlermeldungen (LED3 blinkt und Piezo piepst), können binär kombiniert sein
 1: Akkuspannung zu niedrig
 2: PPM-Synchronisation fehlerhaft (in Sync-Pause nicht alle Signale low)
 4: Overflow PPM-Timer (Pause zwischen PPM-Blöcken zu lang)
 8: ungültige Signaldauer eines PPM-Kanals
16: PPM-Signal unvollständig (rising/falling edges nicht paarig)
32: serielle Übertragung läuft noch (neues PPM-Signal kommt zu schnell)

Nicht zu dicht auf die Erde stellen: reduziert Reichweite stark


fuses: einstellen auf 8 Mhz internen Takt
berechnet mit http://www.engbedded.com/fusecalc
avrdude -P /dev/ttyS0 -c stk500 -p t2313 -U lfuse:w:0xe4:m -U hfuse:w:0xdf:m -U efuse:w:0xff:m

Programmieren per ISP des Controllers und Anschluß des Empfängers an PCINT2...6 stören sich
gegenseitig. Also:
zum Programmieren => Empfänger von Spannung trennen
Während Programmlauf => ISP-Kabel trennen

WICHTIG! 3.3V Spannungsversorgung (zwingend für Funkmodul X10EMTX)
z.B. bei Trestaufbau am STK500 einstellen:
Parameter des STK500 einstellen: im Terminal mode
avrdude -P /dev/ttyS0 -c stk500 -p t2313 -t
dann:
parms       // Anzeige aktueller Zustand
help        // help
vtarg 3.3   // Spannung auf 3.3V

Stromverbrauch ca. 50mA bei Verwendung von 20mA-LEDs

Pin-Belegung ATTiny 2313:
PORTA0   nc (Reset)
PORTA1   nc (Quarz)
PORTA2   nc (Quarz)
PORTB0   nc (AIN0), verwendet interne Spannungsreferenz
PORTB1   (AIN1) Eingang Spannungsteiler von Akku
PORTB2   Eingang Empfänger Kanal 1
PORTB3   Eingang Empfänger Kanal 2
PORTB4   Eingang Empfänger Kanal 3
PORTB5   Eingang Empfänger Kanal 4
PORTB6   Eingang Empfänger Kanal 5
PORTB7   nc (Reserve: Eingang Empfänger Kanal 6)
PORTD0   Eingang: Bind-Taster
PORTD1   Ausgang seriell: X10EMTX
PORTD2   Ausgang: LED0: Heartbeat
PORTD3   Ausgang: LED1: PPM-Erfassen/Senden
PORTD4   Ausgang: LED2: Fehler-Anzeige
PORTD5   Ausgang: Piezo Alarmgeber
PORTD6   nc (Reserve)

Signale:
Multiplex PPM:    962 ... 1521 ... 2080 ms  (bei RX-5 light jeweils zwei Kanäle gleichzeitig)
Spektrum digital:  85 ...  511 ...  938
Gas und Höhe:   gleiche Richtung
Seite und Quer: invertiert
Gas muß ggf. ein wenig Richtung Minimum getrimmt werden.

DSM2-Paket:
2 Byte Header:    0x0100                                       // normal senden
                  0x8000                                       // Bind-Modus (ca. 1s senden, dann normal)
2 Byte Kanal 1    %000CCCPP %PPPPPPPP                          // C: Kanalnr-1 P: Positions-Wert
2 Byte Kanal 2    ...
...
2 Byte Kanal 6                                                 // Kanal 6 nicht unterstützt

Struktur:
- Pin Change Interrupt beobachtet PPM-Signale vom Empfänger
  Pulszeiten mit Timer1 messen
  Erfassen beenden, wenn alle Kanäle eingelesen wurden
  DSM2-Paket zusammenstellen
  Paketversand anstoßen
- Seriell senden im Interrupt
- main() nur Initialisierung, Ansteuern LEDs und Piezo und Abfrage Taster

*/

// ist alles auf 8MHz ingerichtet
#ifndef F_CPU
#warning "F_CPU not defined"
#endif

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdint.h>

#define NODEBUGTX

// DSM2 Daten buffer und Verwaltung
static volatile uint16_t DSM2_buffer[] =                       // Datenpuffer zum Senden
{0x0100, 0x00B9, 0x05F2, 0x05F2, 0x05F2, 0x1354, 0x14AA};      // Vorbelegung
#define DSM2BYTES (sizeof(DSM2_buffer))                        // Größe DSM2-Paket in Bytes (ist 14)

static volatile uint16_t DSM2_bind[] =                         // bind-Paket
{0x8000, 0x00AA, 0x05FF, 0x09FF, 0x0DFF, 0x13D4, 0x14AA};      // mit fest belegten Werten

static uint8_t *DSM2_send = (uint8_t*)DSM2_buffer;             // Pointer für Senderoutine: buffer oder bind
static uint8_t DSM2_tx_index = 0;                              // Index für Senderoutine


// DSM2 Modus
#define DSM2_NORMAL 0                                          // normale Übertragung
#define DSM2_BIND 1                                            // Bind-Modus
static volatile uint8_t DSM2_mode = DSM2_NORMAL;               // Sendemodus

#define DSM2_DIST_MS 22                                        // ungefährer Abstand der Sendungen in ms
#define DSM2_BINDTIME_MS 10000                                 // ms Bind-Dauer
#define DSM2_BIND_NUMBER ((DSM2_BINDTIME_MS/DSM2_DIST_MS))     // Anzahl Bind-Pakete
static uint16_t DSM2_bind_count = 0;                           // Zähler für Senderoutine


// Fehler-Flag
static volatile uint8_t ErrorState = 0;                        // Fehlerflag
#define ERR_POWER 0x01                                         // Akkuspannung zu niedig
#define ERR_PPM_SYNC 0x02                                      // Fehler: PPM-Sync fehlerhaft
#define ERR_PPM_OVERFLOW 0x04                                  // Fehler: overflow PPM-Timer
#define ERR_PPM_TIME 0x08                                      // Fehler: ungültige PPM-Dauer
#define ERR_PPM_MISSING 0x10                                   // Fehler: PPM-Signal unvollständig
#define ERR_TXINCOMPLETE 0x20                                  // Fehler: Übertragung läuft noch

// Blink-Zähler (nur ungefähre Zeiten, da interrupts aktiv)
#define LOOP_DELAY_MS 100                                      // Wartezeit im main-loop
#define HBSLOWMS 1000                                          // Heartbeat langsam in ms
#define HBFASTMS 200                                           // Blinkdauer schnell in ms
#define HBSLOW (HBSLOWMS/LOOP_DELAY_MS)                        // Anzahl Schleifen
#define HBFAST (HBFASTMS/LOOP_DELAY_MS)                        // Anzahl Schleifen
static volatile uint8_t hbblink = 0;                           // Heartbeat Blink-Zähler
#define ERRBLINKMS 200                                         // Fehlerblinker
#define ERRBLINK (ERRBLINKMS/LOOP_DELAY_MS)                    // Anzahl Schleifen
static volatile uint8_t errblink = 0;                          // Fehler Zeischschleifen Blink-Zähler
static volatile uint8_t errcount = 0;                          // Fehler Anzahl Blinker-Zähler


// LEDs
#define LED_PORT PORTD                                         // Anschluß
#define LED_DDR DDRD
#define LED0 0x04                                              // Heart-Beat
#define LED0_ON (~(LED0))
#define LED0_OFF (LED0)
#define LED1 0x08                                              // PPM-Aktivität
#define LED1_ON (~(LED1))
#define LED1_OFF (LED1)
#define LED2 0x10                                              // Fehler
#define LED2_ON (~(LED2))
#define LED2_OFF (LED2)

// Taster
#define SW_PIN PIND
#define SW_PORT PORTD
#define SW_DDR DDRD
#define SWBIND 0x01

// Piezo-Alarm
#define PIEZO_PORT PORTD
#define PIEZO_DDR DDRD
#define PIEZO 0x20
#define PIEZO_ON (~(PIEZO))
#define PIEZO_OFF (PIEZO)

// PPM-Signale
#define PPM_PORT PINB
#define PPM_MASK 0x7C                                          // Maske für PPM-Signal
static volatile uint8_t PPM_lstate = 0;                        // letzter Status der Eingänge
static volatile uint8_t PPM_astate;                            // für aktuellen ppm-Status
static volatile uint8_t PPM_cstate;                            // für Statusänderung

static volatile uint16_t PPM_atime;                            // aktueller Timer
static volatile uint16_t PPM_ltime = 0;                        // letzte Zeit

static volatile uint16_t time_c1on;                            // Schaltzeiten merken
static volatile uint16_t time_c1off;
static volatile uint16_t time_c2on;
static volatile uint16_t time_c2off;
static volatile uint16_t time_c3on;
static volatile uint16_t time_c3off;
static volatile uint16_t time_c4on;
static volatile uint16_t time_c4off;
static volatile uint16_t time_c5on;
static volatile uint16_t time_c5off;
static volatile uint8_t input_state_on;                        // Merker für eingelesene on-Flanken
static volatile uint8_t input_state_off;                       // Merker für eingelesene off-Flanken

#define PPM_PAUSE_MS 10                                        // Mindestdauer für PPM-Pause in ms
#define PPM_PAUSE ((PPM_PAUSE_MS * F_CPU / 1000 / 8))          // Mindestdauer in Timer-Ticks
#define PPM2DSMGH(ppm) ((ppm - 851) * 45 / 59)                 // Umrechnung PPM-Werte in DSM2-Werte Gas/Höhe
#define PPM2DSMSQ(ppm) (1023 - (ppm - 851) * 45 / 59)          // Umrechnung PPM-Werte in DSM2-Werte Seite/Quer



//
// USART senden gesteuert durch Interrupt
//
ISR(USART_TX_vect)
{
   // Bytes Tiny2313 speichert 2 Byte nicht in der für das Senden richtigen Reihenfolge
   // Sendeindices der einzelnen Bytes des DSM2-Paketes
   static uint8_t i2i[] = { 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12 };

#ifdef DEBUGTX
// als hex ausgeben
   static char bin2hex[] = "0123456789ABCDEF";
   uint8_t c;

   if ( DSM2_tx_index < 2*DSM2BYTES ) {                        // Zeichen aus DSM2-Telegramm?
      c = DSM2_send[i2i[DSM2_tx_index/2]];
      if ( (DSM2_tx_index & 0x01) == 0 ) {
         UDR = bin2hex[c >> 4];
      } else {
         UDR = bin2hex[c & 0x0f];
      }
      DSM2_tx_index++;
   } else if (DSM2_tx_index == 2*DSM2BYTES ) {                 // NL
      UDR = '\n';
      DSM2_tx_index++;
   } else if (DSM2_tx_index == 2*DSM2BYTES+1 ) {               // CR
      UDR = '\r';
      DSM2_tx_index++;
   }
   if ( DSM2_tx_index == 2*DSM2BYTES+2 ) {                     // war letztes Zeichen
      UCSRB &= ~( 1 << TXCIE );                                // interrupt abschalten
      DSM2_tx_index = 0;                                       // index auf Anfang setzen
      LED_PORT |= LED1_OFF;                                    // LED1 off (on bei Start PPM-Erfassung)
   }

#else  // notdef DEBUGTX

   if ( DSM2_tx_index < DSM2BYTES ) {                          // gültiger Index?
      UDR = DSM2_send[i2i[DSM2_tx_index++]];                   // Wert senden
   }
   if ( DSM2_tx_index == DSM2BYTES ) {                         // war letztes Zeichen
      UCSRB &= ~( 1 << TXCIE );                                // interrupt abschalten
      DSM2_tx_index = 0;                                       // index auf Anfang setzen
      LED_PORT |= LED1_OFF;                                    // LED1 off (on bei Start PPM-Erfassung)
   }

#endif

}


//
// Einlesen PPM-Signale durch PCI (Pin Change Interrupt)
// Verarbeiten und Anstoß Senden der Daten
//
ISR(PCINT_vect)
{
   uint16_t ppm_diff;

   // zeitkritisch
   PPM_atime = TCNT1;                                          // timer auslesen
   PPM_astate = PINB & PPM_MASK;                               // Status der Eingänge

   // auf lange Pause synchronisieren
   if ( (PPM_atime - PPM_ltime) > PPM_PAUSE ) {                // Pause gefunden
      TCNT1 = 8;                                               // Timer zurücksetzen (nicht 0, isct schon Zeit vergangen)
      PPM_atime = 0;                                           // 0 als verwendeten Zeitpunkt merken
      input_state_on = 0;                                      // Kanäle müssen neu erfaßt werden
      input_state_off = 0;
      if ( PPM_lstate != 0 ) {                                 // letzter Status muß Ruhe gewesen sein
         ErrorState |= ERR_PPM_SYNC;                           // Fehler eintragen
      }
      LED_PORT &= LED1_ON;                                     // Start der PPM-Erfassung (off nach Senden)
   }

   PPM_cstate = PPM_lstate ^ PPM_astate;                       // Statusänderung ermitteln

   if ( (PPM_cstate & 0x04) != 0 ) {                           // Kanal 1 geändert
      if ( (PPM_astate & 0x04) != 0 ) {                        // Signal an
         time_c1on = PPM_atime;
         input_state_off |= 0x04;                              // erfaßt merken
      } else {                                                 // Signal aus
         time_c1off = PPM_atime;
         input_state_on |= 0x04;                               // erfaßt merken
      }
   }

   if ( (PPM_cstate & 0x08) != 0 ) {                           // Kanal 2 geändert
      if ( (PPM_astate & 0x08) != 0 ) {                        // Signal an
         time_c2on = PPM_atime;
         input_state_off |= 0x08;                              // erfaßt merken
      } else {                                                 // Signal aus
         time_c2off = PPM_atime;
         input_state_on |= 0x08;                               // erfaßt merken
      }
   }

   if ( (PPM_cstate & 0x10) != 0 ) {                           // Kanal 3 geändert
      if ( (PPM_astate & 0x10) != 0 ) {                        // Signal an
         time_c3on = PPM_atime;
         input_state_off |= 0x10;                              // erfaßt merken
      } else {                                                 // Signal aus
         time_c3off = PPM_atime;
         input_state_on |= 0x10;                               // erfaßt merken
      }
   }

   if ( (PPM_cstate & 0x20) != 0 ) {                           // Kanal 4 geändert
      if ( (PPM_astate & 0x20) != 0 ) {                        // Signal an
         time_c4on = PPM_atime;
         input_state_off |= 0x20;                              // erfaßt merken
      } else {                                                 // Signal aus
         time_c4off = PPM_atime;
         input_state_on |= 0x20;                               // erfaßt merken
      }
   }

   if ( (PPM_cstate & 0x40) != 0 ) {                           // Kanal 5 geändert
      if ( (PPM_astate & 0x40) != 0 ) {                        // Signal an
         time_c5on = PPM_atime;
         input_state_off |= 0x40;                              // erfaßt merken
      } else {                                                 // Signal aus
         time_c5off = PPM_atime;
         input_state_on |= 0x40;                               // erfaßt merken
      }
   }

   // wenn alles gut gegangen ist, ist jetzt genügend Zeit, um die erfaßten Daten zu verarbeiten
   if ( input_state_on == PPM_MASK ) {                                  //off-Flanke für alle Kanäle erfaßt?

      if ( input_state_off == PPM_MASK ) {                              // on-Flanken für alle Kanäle erfaßt?

         // im normalen Modus: DSM2-Daten berechnen
         if ( DSM2_mode == DSM2_NORMAL ) {                              // normaler Modus

            ppm_diff = time_c4off - time_c4on;                          // ppm-Zeit Gas
            if ( ppm_diff > 850 && ppm_diff < 2200 ) {                  // Signal gültig?
               DSM2_buffer[1] = PPM2DSMGH( ppm_diff );                  // DSM2 Gas setzen
            } else {
               ErrorState |= ERR_PPM_TIME;                              // Fehler merken
            }

            ppm_diff = time_c1off - time_c1on;                          // ppm-Zeit Quer
            if ( ppm_diff > 850 && ppm_diff < 2200 ) {                  // Signal gültig?
               DSM2_buffer[2] = PPM2DSMSQ( ppm_diff ) | (1 << 10);      // DSM2 Quer setzen
            } else {
               ErrorState |= ERR_PPM_TIME;                              // Fehler merken
            }

            ppm_diff = time_c2off - time_c2on;                          // ppm-Zeit Höhe
            if ( ppm_diff > 850 && ppm_diff < 2200 ) {                  // Signal gültig?
               DSM2_buffer[3] = PPM2DSMGH( ppm_diff ) | (2 << 10);      // DSM2 Höhe setzen
            } else {
               ErrorState |= ERR_PPM_TIME;                              // Fehler merken
            }

            ppm_diff = time_c3off - time_c3on;                          // ppm-Zeit Seite
            if ( ppm_diff > 850 && ppm_diff < 2200 ) {                  // Signal gültig?
               DSM2_buffer[4] = PPM2DSMSQ( ppm_diff ) | (3 << 10);      // DSM2 Seite setzen
            } else {
               ErrorState |= ERR_PPM_TIME;                              // Fehler merken
            }

            ppm_diff = time_c5off - time_c5on;                          // ppm-Zeit Kanal 5
            if ( ppm_diff > 850 && ppm_diff < 2200 ) {                  // Signal gültig?
               DSM2_buffer[5] = PPM2DSMGH( ppm_diff ) | (4 << 10);      // DSM2 Gear setzen
            } else {
               ErrorState |= ERR_PPM_TIME;                              // Fehler merken
            }

         // bin im Bind-Modus
         } else {                                                       // Bind-Modus
            if ( DSM2_bind_count > DSM2_BIND_NUMBER ) {                 // genügend Bind-Pakete gesendet?
               DSM2_mode = DSM2_NORMAL;
               DSM2_send = (uint8_t *)DSM2_buffer;                      // ab jetzt normalen Sendepuffer verwenden
            }
            DSM2_bind_count++;                                          // Bind-Pakete zählen
         }

         // Sendung starten
         if ( DSM2_tx_index == 0 ) {                                    // ist letzte Sendung abgeschlossen?
            UCSRB |= ( 1 << TXCIE );                                    // Paket versenden (Sendeinterrupt starten)
         } else {                                                       // Zeit-Problem aufgetreten
            ErrorState |= ERR_TXINCOMPLETE;                             // Fehler eintragen;
         }

      } else {                                                          // on- und off-Flanken nicht paarig erfaßt
         ErrorState |= ERR_PPM_MISSING;                                 // Fehler eintragen
      }

      input_state_on = 0;                                               // Kanäle müssen neu erfaßt werden
      input_state_off = 0;
      TCNT1 = 0;                                                        // Timer zurücksetzen

   }

   PPM_lstate = PPM_astate;                                             // verwendeten Status der PPM-Eingänge merken
   PPM_ltime = PPM_atime;                                               // verwendeten Zeitpunkt merken
}

//
// Timer 1 overflow => das sollte nicht passieren! PPM-Signal defekt
//
ISR(TIMER1_OVF_vect)
{
   input_state_on = 0;                                                  // erfaßte PPM-Daten ungültig machen
   input_state_off = 0;
   ErrorState |= ERR_PPM_OVERFLOW;                                      // Fehler eintragen
}


//
// Ansteuern LEDs und Piezo, Akkuüberwachung
//
int main(void)
{

   // USART einstellen
#ifdef DEBUGTX
   UBRRH = 0;                                                           // ca. 115200 Baud
   UBRRL = 8;
   UCSRA |= (1 << U2X);                                                 // double transmission speed (geringerer Fehler)
#else
   UBRRH = 0;                                                           // 125000 Baud setzen
   UBRRL = 3;
// MLP4DSM mit Quarz 4,194 MHz sendet mit 131094 Baud
// Falls Kommunikationsprobleme auftauchen, ggf. internen Oszillator schneller betreiben
//    OSCCAL += 2;                                                      // internen Oscillator etwas schneller betreiben
#endif
   UCSRB = (1 << TXEN);                                                 // UART TX einschalten
   UCSRC = (1 << UCSZ1)|(1 << UCSZ0);                                   // Asynchron 8N1

   // Timer1 für Puls-Messung
//   TCCR1A = 0x00;                                                       // normal
   TCCR1B = (1 << CS11);                                                // prescaler 8
   TIMSK |= (1 << TOIE1);                                               // interrupt bei timer overflow ein (Fehlererkennung)

   // PORT-Pins für PPM-Signale konfigurieren
   GIMSK |= (1 << PCIE);                                                // Interrupt bei Pin-Change
   PCMSK |= PPM_MASK;                                                   // gewünschte Pins aktivieren
//   PORTB |= PPM_MASK;                                                   // Pullup (notwendig?)

   // LEDs: Status-Ausgabe
   LED_DDR  |= (LED0 | LED1 | LED2);                                    // Ausgabe
   LED_PORT |= (LED0 | LED1 | LED2);                                    // alle auf 1 => LEDs aus

   // Hochlauf an Fehler-LED visualisieren
   LED_PORT &= LED2_ON;                                                 // Fehler-LED an

   // Taster: Bind-mode
//   SW_DDR   &= ~(SWBIND);                                               // Bind-Taster ist Eingang
   SW_PORT |= SWBIND;                                                   // interner Pullup für Taster

   // Ausgang für Alarm aktivieren
   PIEZO_DDR  |= PIEZO;                                                 // Ausgabe
   PIEZO_PORT |= PIEZO;                                                 // Ton aus

   // Bind gewünscht?
   if ( (SW_PIN & SWBIND) == 0 ) {                                      // Bind-Taster gedrückt?
      DSM2_send = (uint8_t *)DSM2_bind;                                 // auf Bind-Daten umschalten
      DSM2_mode = DSM2_BIND;                                            // erstmal Bind
   }

   // Analog-Komparator: Überwachung Akku
   ACSR |= (1 << ACBG);                                                 // Verwendung interner Referenzspanung
   DIDR |= 0x03;                                                        // DIO auf Analog-Pins aus

   // warum ist das nötig? sonst startet der Sende-Interrupt nicht
   UDR = '\n';                                                          // ein Byte seriell senden
   _delay_ms( 1 );

   // jetzt gehts los: System starten
   sei();                                                               // interrupts ein

   // warten: MPX-Empfänger bootet, interne Referenzspannung stabilisiert, ...
   _delay_ms(2000);                                                     // kurz warten
   ErrorState = 0;                                                      // Fehler erst ab jetzt

   // Fehler-LED aus: betriebsbereit
   LED_PORT |= LED2_OFF;                                                // LED aus


   // ...
   while(1)
   {

      // Akkuspannung überwachen, ginge auch per Interrupt
      if ( (ACSR & (1 << ACO)) != 0 ) {                                 // Spannung zu niedrig
         ErrorState |= ERR_POWER;                                       // Fehler setzen
      }

      // Fehlerstatus zurücksetzen, wenn Bind-Taster im Betrieb gedrückt
      if ( (SW_PIN & SWBIND) == 0 ) {                                   // Bind-Taster gedrückt?
         ErrorState = 0;                                                // wieder ok
         LED_PORT |= LED2_OFF;                                          // Fehler-LED aus
         PIEZO_PORT |= PIEZO_OFF;                                       // Ton aus
      }

      // Betriebs-LED-Blinken
      hbblink++;                                                        // Anzahl Interrupts für Blinker
      if ( ( DSM2_mode == DSM2_NORMAL && hbblink > HBSLOW ) ||          // normaler Modus: langsam
           ( DSM2_mode == DSM2_BIND   && hbblink > HBFAST ) ) {         // Bind-Modus: schnell
         if ( (LED_PORT & LED0) == (LED0_ON & LED0) ) {                 // LED0 umschalten
            LED_PORT |= LED0_OFF;
         } else {
            LED_PORT &= LED0_ON;
         }
         hbblink = 0;                                                   // Zeit neu starten
      }

      // Fehler LED und Piezo ansteuern
      if ( ErrorState != 0 ) {                                          // Fehler aufgetreten?
         errblink++;
         if ( errblink > ERRBLINK ) {                                   // wechsel mal wieder
            errcount++;                                                 // Fehlernummer mal blinken
            if ( (errcount & 0x01) && errcount < 2 * ErrorState ) {
               LED_PORT &= LED2_ON;
               PIEZO_PORT &= PIEZO_ON;
            } else {
               LED_PORT |= LED2_OFF;
               PIEZO_PORT |= PIEZO_OFF;
            }
            if ( errcount > 2 * ErrorState + 6 ) {                      // danach kleine Pause
               errcount = 0;
            }
            errblink = 0;                                               // Zeit neu starten
         }
      }

      _delay_ms(LOOP_DELAY_MS);                                         // warten
   }

   return 0;                                                            // never!
}

