/* 
Beispielprogramm zur Ansteuerung einer 3-stelligen 7-Segm. LED-Anzeige
über Schieberegister (4094). 
Für dieses Beispielprogramm wird ein ATtiny45 verwendet.

Funktionen:
void init_led(void)             aktiviert die Port-Ausgänge

direkte Ausgabe:
void zeige_segmente(uint8_t z)  schreibt in die Schieberegister
void zeige_ziffer(uint8_t z)    direkte Ausgabe der Ziffer
void zeige_p_ziffer(uint8_t z)  direkte Ausgabe der Ziffer mit '.' rechts unten

indirekte Ausgabe:
void led_ausgabe(void)          Ausgabe des ausgabe_register[]
void zeige_int(int16_t z)       rechtsbündige Ausgabe einer Zahl im Bereich -99 -> 999

http://www.mino-elektronik.de

Alle Angaben ohne Gewaehr !
2013-12-16
*/

#define LED_STELLEN 3

#include <avr/io.h>
#include <string.h>

#define F_CLOCK  8000000            // interner RC-Oszillator    
#define BIT(x)  (1<<x)

// Segmente für HEF4094
#define SEGA    0x20
#define SEGB    0x10
#define SEGC    0x02
#define SEGD    0x04
#define SEGE    0x08
#define SEGF    0x40
#define SEGG    0x80
#define SEGP    0x01


#define ZAHL8   0xff-SEGP           // ALLE SEGMENTE AUSSER '.'
#define ZAHL0   ZAHL8-SEGG
#define ZAHL1   SEGB+SEGC
#define ZAHL6   ZAHL8-SEGB


// Ausgabe an PORTB: PB0 - PB2
#define AUSGABE_PORT    PORTB       // ggf. an die eigenen Pins anpassen
#define AUSGABE_DDR     DDRB
#define LED_DATA        BIT(2)
#define LED_CLOCK       BIT(1)
#define LED_STROBE      BIT(0)


uint8_t ausgabe_register[LED_STELLEN+1];    // fuer max. Stellen + Luft


uint8_t zeichensatz[] = {
    ZAHL8-SEGG,             // 0
    SEGB+SEGC,              // 1
    ZAHL8-SEGF-SEGC,        // 2
    ZAHL8-SEGE-SEGF,        // 3
    ZAHL8-SEGA-SEGD-SEGE,   // 4
    ZAHL8-SEGB-SEGE,        // 5
    ZAHL8-SEGB,             // 6
    SEGA+SEGB+SEGC,         // 7
    ZAHL8,                  // 8
    ZAHL8-SEGE,             // 9
    ZAHL8-SEGD,             // 'A'
    ZAHL8-SEGA-SEGB,        // 'b'
    ZAHL8-SEGB-SEGC-SEGG,   // 'C'
    ZAHL8-SEGA-SEGF,        // 'd'
    ZAHL8-SEGB-SEGC,        // 'E'
    ZAHL8-SEGB-SEGC-SEGD    // 'F'
};


void zeige_segmente(uint8_t z)              // Ausgabe in Schieberegister
{
uint8_t n;
  AUSGABE_PORT &= ~LED_CLOCK;               // auf passiven Pegel bringen
  for(n=0; n<8; n++) {
    if(z & 0x80) AUSGABE_PORT |= LED_DATA;  // Bit7 zuerst ausgeben
    else AUSGABE_PORT &= ~LED_DATA;
    z += z;                                 // nächtes bit
    AUSGABE_PORT |= LED_CLOCK;              // und CLK dazu 
    AUSGABE_PORT |= LED_CLOCK;              // Impuls lang genug machen
    AUSGABE_PORT |= LED_CLOCK;              // 
    AUSGABE_PORT |= LED_CLOCK;              // 
    AUSGABE_PORT &= ~LED_CLOCK;
  }
}

void zeige_ziffer(uint8_t z)                // direkte Ausgabe: Ziffer ohne Punkt
{
  zeige_segmente(zeichensatz[z]);
}

void zeige_p_ziffer(uint8_t z)              // direkte Ausgabe: Ziffer mit '.' rechts unten
{
  zeige_segmente(zeichensatz[z] | SEGP);
}

void led_strobe(void)                       // Segmente in die Ausgangsregister schreiben
{
    AUSGABE_PORT |= LED_STROBE;             // auf '1' setzen
    AUSGABE_DDR  |= LED_STROBE;             // als AUsgang konfigurieren
    AUSGABE_PORT &= ~LED_STROBE;            // und wieder auf '0'
}

void led_ausgabe(void)                      // Ausgabe des ausgabe_register[]
{
uint8_t n;
  for(n=0; n < LED_STELLEN; n++) {
    zeige_segmente(ausgabe_register[n]);    // in die Schieberegister
  }
  led_strobe();                             // und dort in die Ausgangsregister
}

void loesche_ausgabe_register(void)         // löscht nur das ausgabe_register[]
{
uint8_t n;
  for(n=0; n < LED_STELLEN; n++)
    ausgabe_register[n] = 0;                // alle Segemente löschen
}

void loesche_anzeige(void)                  // Anzeige direkt löschen
{
  loesche_ausgabe_register();               // ausgabe_register[] löschen
  led_ausgabe();                            // und Anzeige auffrischen
}


void init_led(void)
{
uint8_t n;
  AUSGABE_PORT &= ~(LED_DATA | LED_CLOCK);  // auf '0'
  AUSGABE_DDR  |= (LED_DATA | LED_CLOCK);   // und als Ausgänge
  for(n=0; n < LED_STELLEN; n++) {
      zeige_segmente(SEGG);                 // "---" anzeigen
  }
  led_strobe();
}


void int2a(int16_t l, char *s)              // Wandelroutine bin -> String
{
uint16_t tab[] = {10000, 1000, 100, 10, 1, 0};
uint16_t temp;
int8_t i=0,j;
  if(l) {                                   // wenn l != 0
    if(l < 0) {
      l = -l;
      *s++ = '-';                           // '-' Vorzeichen einfügen
    }
    while(tab[i] > l) i++;                  // Vornullen unterdruecken
    do {
      temp = tab[i];
      j = '0';
      if(temp) {
        for(;j <= '9'; j++) {
            l-=temp;
            if(l < 0) {
                l += temp;
                break;
            }
        }
        *s++ = j;
        i++;
      }
    } while(temp);
  } else *s++ = '0';                        // wenn l == 0
  *s = 0;                                   // String abschließen
}

void zeige_led_str(char *s)                 // Ziffernfolge ausgeben
{
uint8_t n, i;
  n = 0;
  i = strlen(s);
  while(i < LED_STELLEN) {                  // rechtsbündig anzeigen
    ausgabe_register[n++] = 0;
    i++;
  }
  i = 0;
  while(s[i]) {
    if(s[i] == '-') ausgabe_register[n] = SEGG;
    else ausgabe_register[n] = zeichensatz[s[i] & 0x0f]; // Zeichen nur 0-15 verhanden
    n++;
    i++;
  }
  led_ausgabe();
}
    
void zeige_int(int32_t z)
{
char s[10];                                 // kann noch verkleinert werden
    int2a(z++, s);                          // zahl wandeln
    zeige_led_str(s);                       // und anzeigen
}


// Beispiel; fortlaufend von -99 -> 999 zählen
int16_t main(void)
{
volatile int16_t zahl = -99L;               // Startwert
volatile int16_t n;
  CLKPR = 0x80; CLKPR = 0x0;                // CKDIV auf 0; voller CPU-Takt 
     
  init_led();                               // "---" anzeigen
  while(1) {                                
    zeige_int(zahl++);                      // anzeigen
    if(zahl > 999l) zahl = -99l;            // über ganzen Bereich zählen
    for(n=0; n<30000;n++);                  // warten
  }
  return(0);
}

