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


// Konfigurationsparameter

#define DATA      PB0
#define CLOCK     PB2
#define DIGI1     PB1
#define DIGI2     PB5
#define ADC1      PB4
#define ADC2      PB3
#define ADC1REF   REF_VCC  // oder REF_INT
#define ADC2REF   REF_VCC  // oder REF_INT
#define F_CPU     128000
#define F_DATA    100


// Hilfsmakros

#define REG(regno)  __asm__(#regno)
#define NAKED_IN_SECTION(sect) __attribute__((naked, section(sect)))
#define GOTO_FUNC(f) goto *(void *)(f)
#define ADCCHAN (uint8_t[6]){0xff, 0xff, 1, 3, 2, 0}
#define ADC1MUX (ADC1REF<<6 | ADCCHAN[ADC1])
#define ADC2MUX (ADC2REF<<6 | ADCCHAN[ADC2])


// Symbole für Referenzspannungsquellen

enum {REF_VCC, REF_INT};


// Unions für die Konvertierung zwischen 16-/24-Bit-Werten und einzelnen Bytes

typedef union {
  uint16_t all;
  uint8_t byte[2];
} conv16;

typedef union {
  __uint24 all;
  uint8_t byte[3];
} conv24;


// GLobale Registervariablen

register uint8_t zero   REG( 1);  // GCC erwartet in R1 immer eine 0
register conv16  adcsum REG( 2);  // Summe von jeweils 8 ADC-Werten
register uint8_t chksum REG( 4);  // Prüfsumme
register conv24  output REG(14);  // 24-Bit-Ausgabe
register uint8_t cycle  REG(17);  // Zähler der 5ms-Zyklen


// Interrupthandler

void tim0_compa(void) NAKED_IN_SECTION(".init3");
void tim0_compa(void) {

  // cycle läuft periodisch von 0 bis 63

  if(cycle < 16) {
    // Zyklus 0 bis 15:
    // - Je 8 Analogwerte von ADC2 und ADC1 einlesen und Mittelwerte bilden
    // - Digitalwerte einlesen
    // - 24-Bit-Ausgabe in Variable output aufbauen

    adcsum.all += ADC;
    if(cycle % 8 == 7) {    // Zyklen 7 und 15
      // Mittelwert der ADC-Werte eintragen, dabei den zuletzt eingetragenen
      // Mittelwert den Bits 0-7 und 16-17 von output nach 8-15 und 18-19
      // verschieben

      adcsum.all /= 8;
      output.byte[2] = (output.byte[2]<<2  | adcsum.byte[1]) & 0x0f;
      output.byte[1] = output.byte[0];
      output.byte[0] = adcsum.byte[0];

      // Signale der digitalen Eingänge in die Bits 20-21 (Bits 4-5 des
      // höchstwertigen Bytes) eintragen

      if(PINB & 1<<DIGI1)
        output.byte[2] |= 1<<4;
      if(PINB & 1<<DIGI2)
        output.byte[2] |= 1<<5;

      // Variablen für Prüf- ADC-summe zurücksetzen

      chksum = 0;
      adcsum.all = 0;

      // Multiplexer und Spannungsreferenz durch Toggeln der entsprechenden
      // Bits auf jeweils anderen Kanal umschalten

      ADMUX ^= ADC1MUX ^ ADC2MUX;
    }
  }
  else {
    // Zyklus 16 bis 63: 
    // - Daten- und Taktsignal generieren
    // - 2-Bit-Prüfsumme bilden

    if(cycle % 2 == 0) {
      // in jedem geradzahligen Zyklus ...

      if(cycle == 60)
        // in Zyklus 60 die inzwischen vollständige Prüfsumme output eintragen,
        // so dass die beiden Bits in den Zyklen 60/61 und 62/63 gesendet werden.

        output.byte[0] = chksum;

      // ... das jeweils niederwertigste Bit von output als Datenbit ausgeben
      // und output weitershiften

      uint8_t bit = output.byte[0] & 0x01;
      PORTB = 1<<DIGI1 | 1<<DIGI2 | bit<<DATA;
      output.all >>= 1;

      // dabei zählt chksum die // 1-Bits

      chksum += bit;
    }

    // in jedem Zyklus den Takt toggeln.

    PINB |= 1<<CLOCK;
  }

  // cycle modulo 64 weiterzählen

  cycle = (cycle + 1) % 64;
  reti();
}


// Etwas hackish, aber der Flash-Speicher soll ja von Adresse 0 an dicht mit
// Programmcode belegt werden:
//
// Das Hauptprogramm besteht aus drei Teilen, die so gestaltet sind, dass sie
// nacheinander ausgeführt werden und der Interrupthandler zwischen main2 und
// main3 an die Adresse 0x000c zu liegen kommt

void main3(void);


void main1(void) NAKED_IN_SECTION(".init1");
void main1(void) {
  zero = 0;
  adcsum.all = 0;

  // Digitale Eingangspuffer der benutzen ADC-Pins deaktivieren

  DIDR0  = 1<<ADC1 | 1<<ADC2;
}


void main2(void) NAKED_IN_SECTION(".init1");
void main2(void) {
  GOTO_FUNC(main3);
}


void main3(void) {
  PORTB  = 1<<DIGI1 | 1<<DIGI2;       // Pull-Ups
  DDRB   = 1<<DATA | 1<<CLOCK;        // Ausgänge

  // ADC im Free-Running-Modus

  ADMUX  = ADC2MUX;
  ADCSRA = 1<<ADATE | 1<<ADSC | 1<<ADEN;

  // Timer im CTC-Modus erzeugt Interupts mit dem doppelten Datentakt F_DATA

  OCR0A  = (uint8_t)((double)F_CPU / 64 / (2 * F_DATA) + 0.5)-1;
  TCCR0A = 1<<WGM01;
  TCCR0B = 1<<CS00 | 1<<CS01;
  TIMSK0 = 1<<OCIE0A;
  sei();

  for(;;);
}
