Fischertechnik
AVR
Raspberry Pi
Elektronik
Netzwerk
Sonstiges


















Impressum

Laufschrift aus 8x8-LED-Modulen


Angeregt durch den Artikel 64pixels How To habe ich eine Platine entwickelt, die genau unter ein 8x8-Display (32mm x 32mm) passt und zusätzlich einen ISP-6-Wannenstecker und zwei parallel geschaltete dreipolige Stecker mit dem letzten freien Port-Pin des ATtiny2313 und den Stromanschlüssen aufnimmt.
Mit dem freien Portanschluss ist eine Kaskadierung mit weiteren Modulen möglich. Dabei haben alle Module die selbe Software und nur die Konfiguration entscheidet über die Rolle "Master" oder "Slave an Position x".
Platinenlayout ATtiny2313 mit 8x8-LED-Display und ISP
Die Software unterstützt zur Zeit 4 Displays nebeneinander, also 32x8 Bildpunkte. Der Bildschirmspeicher belegt 32 der 128 Byte RAM. Eine Erweiterung auf 6-8 Displays erscheint realistisch möglich.
Die Änderung der Konfiguration ist nach dem Einschalten der Stromversorgung möglich. Die Sequenz zur Konfiguration ist so gestaltet, dass normales Einschalten der kaskadierten Module nicht zu einer Konfigurationsänderung der Module führt. Allerdings muss ich hier den Code nocheinmal verbessern, da eine leere Batterie schon mal zur Rekonfiguration geführt hat.
Die Anzahl der Module ist durch das RAM der einzelnen ATtiny2313 beschränkt und sollte sich verdoppeln lassen, wenn der ATtiny4313 eingesetzt wird. Dann könnten auch weitere Zeichen, Symbole oder Schriftarten implementiert werden oder sehr lange Texte. Aktuell sind die 2kByte Flash randvoll und ich habe schon überlegt, ob der Master nicht durch einen zusätzlichen ATtiny85 realisiert werden sollte und sich die Probleme mit dem Flash damit erledigen. Damit hätte ich aber unterschiedlichen Code auf Master und Slaves zu pflegen und durch fehlenden Handlungsdruck habe ich bisher auf eine solche Erweiterung verzichtet.
Ein Master mit freien Portleitungen hätte allerdings den Charme einer leichten externen Steuerung der Lauflichtinhalte z.B. per USB...

Eine Optimierung des ersten Codes habe ich noch in den Flash gequetscht nachdem mich die stark unterschiedliche Helligkeit von waagerechten und senkrechten Linien zu sehr gestört hat. Im ersten Code wurden die acht Zeilen des Displays nacheinander angezeigt und die Helligket der LED hing davon ab, wieviele LED innerhalb einer Zeile gleichzeitig leuchten sollten. Horizontale Linien waren entsprechend dunkel und eine einzelne vertikale Linie sehr hell. Jetzt wechselt das Multiplexing nach jedem Bild: Nach einer zeilenweisen Darstellung folgt eine spaltenweise. Hierdurch erscheinen Linien in beide Richtungen gleichhell. Der prinzipielle Nachteil geringer und vom Bildinhalt abhängiger Helligkeit ist der sehr billigen Ansteuerung geschuldet und lässt sich auch mit der Optimierung nicht heilen.
Wer mit dem existierenden Code zufrieden ist, kann auf ISP und Platine verzichten und mit der "tinkerlog.com-Bauweise" Module unter 3 Euro bauen. Meine einfarbig roten 8x8-LED haben pro Stück ca. einen Euro gekostet und die 2313 lagen auch nur unwesentlich darüber. Etwas Draht und Lötzinn dazu, das war es dann.
So ganz teuer ist die Platinenversion aber auch nicht, da 15 Nutzen auf eine normale Europlatine passen. Ich habe diese bei Platinenbelichter.de herstellen lassen und kann dieses durchaus empfehlen.
Der ATtiny2313 bietet durch das "Corner-Pinning" die nette Möglichkeit Sockel mit integriertem 100nF-Kondensator einzusetzen; falls also bei langen Zuleitungen Störungen oder Instabilitäten entstehen, einfach einen solchen Sockel nutzen; er kann auch nachträglich zwischen Chip und Billigsockel gesteckt werden. Alternativ liegt an allen drei Steckern die Versorgungsspannung und ein Kondensator kann dort leicht ergänzt werden.
Das Display wird auf der Lötseite montiert; bei einer einseitigen Platine ohne Durchkontaktierung ist das Löten deshalb etwas fummelig und die Anschlüsse des Displays sollten so lang wie möglich bleiben. Leichter ist es sicher, wie auf den Fotos vom Prototypen zu sehen, auch hier Sockel zu verwenden.
Platine mit Display 8x8 von schräg vorne Platine mit Display 8x8 von schräg hinten
Die Bilder zeigen noch den Prototypen mit einem anderen Platinenlayout. Das oben gezeigte Layout hat zwei dreipolige Stecker zur Vereinfachung der Kaskadierung.
Die Schaltung lief bisher gut mit zwei NiCd-Zellen; heute habe ich sie erstmalig an einem Billig-LiPo 1S15C 420mAh betrieben. Ob das dauerhaft geht, weiß ich noch nicht.

Quelltext

/* -----------------------------------------------------------------------
 * Title:    8x8 LED dot matrix slave-code
 * Author:   Christoph Niessen
 * Date:     29.08.2010
 * Hardware: ATtiny2313V
 *
 * Fuses siehe Seite 161 ...
 * 
 * Fuse-Bits:               1=nicht         110=1,8 V   1=Reset-Pin geht
 * Fuse High  11011101     DebugWire|......|BOD BOD BOD| RSTDISBL
 * Fuse Low   11100010     Clockdevider|CKOUT|SUT SUT| CKSEL
 *                            1=nicht   1=nicht  10=   0010=
 *                              teilen  ausgeben slow  internal 4MHz
 *
 * Slave liest von PD6 Befehle um den Bildschirmspeicher zu beeinflussen
 * Bildschirmspeicher ist COUNT Displays gross und umfasst deshalb
 * COUNT * 8 Bytes
 * Jeder Slave kennt den gesamten Bildschirmspeicher, zeigt aber nur
 * den eigenen 8-Spalten/8-Reihen-Anteil an.
 * Im ersten Ansatz hat der Bildschirmspeicher die Hoehe eines Displays.
 * Der erste Befehl ist "verschiebe Bildschirminhalt um eine Spalte und
 *                       fuege eine Spalte rechts hinzu".
 * Befehle haben TLV-Format wobei im Laengenfeld T und L nicht mitzaehlen.
 * eine dunkle Spalte anhaengen ist dann z.B.
 * 0x00, 0x01, 0x00
 * spaeter koennen dann evtl auch gleich mehrere angehaengt werden:
 * 0x00, 0x04, 0xaa, 0x55, 0xaa, 0x55  fuegt "an-aus-Raster" ueber 4 Spalten an
 * Sinnvollerweise ist der Bildspeicher fuer solche Operationen so
 * organisiert, dass jede Spalte in einem Speicherwort (BYTE) steht.
 *
 * Bituebertragung:
 * ================
 * 
 * Ruhepegel ist "1"
 *
 * Feste Uebertragungsrate von 1 Bit in 2 Millisekunden
 *
 * Uebertragung einer "0" durch 667 uSekunden "0" und 1333 uSekunden "1"
 * Uebertragung eines Bytes LSB first
 * 
 */
/*
 *      ATtiny2313/4313/8313
 * R8 16 - PD0    PB7 - 1 C4
 * R7 15 - PD1    PB6 - 2 C2
 * C7 14 - PA1    PB5 - 3 R2
 * R1 13 - PA0    PB4 - 4 R3
 * C5 12 - PD2    PB3 - 5 C1
 * R6 11 - PD3    PB2 - 6 R5
 * R4 10 - PD4    PB1 - 7 C3
 * C8  9 - PD5    PB0 - 8 C6
 *                                         C
 * CPM12088 BA                             |
 *     B3 B6 B1 B7 D2 B0 A1 D5       +-----+
 *  A0  o  o  o  o  o  o  o  o       |     |
 *  B5  o  o  o  o  o  o  o  o      _+_    |
 *  B4  o  o  o  o  o  o  o  o      \ /    |
 *  D4  o  o  o  o  o  o  o  o     __V__   |
 *  B2  o  o  o  o  o  o  o  o       |     |
 *  D3  o  o  o  o  o  o  o  o  R ---+-----+---
 *  D1  o  o  o  o  o  o  o  o             |
 *  D0  o  o  o  o  o  o  o  o
 *
 */

#define   ACT_COL_1 PORTB |=  (1<< PB3)
#define   ACT_COL_2 PORTB |=  (1<< PB6)
#define   ACT_COL_3 PORTB |=  (1<< PB1)
#define   ACT_COL_4 PORTB |=  (1<< PB7)
#define   ACT_COL_5 PORTD |=  (1<< PD2)
#define   ACT_COL_6 PORTB |=  (1<< PB0)
#define   ACT_COL_7 PORTA |=  (1<< PA1)
#define   ACT_COL_8 PORTD |=  (1<< PD5)
#define INACT_COL_1 PORTB &= ~(1<< PB3)
#define INACT_COL_2 PORTB &= ~(1<< PB6)
#define INACT_COL_3 PORTB &= ~(1<< PB1)
#define INACT_COL_4 PORTB &= ~(1<< PB7)
#define INACT_COL_5 PORTD &= ~(1<< PD2)
#define INACT_COL_6 PORTB &= ~(1<< PB0)
#define INACT_COL_7 PORTA &= ~(1<< PA1)
#define INACT_COL_8 PORTD &= ~(1<< PD5)
#define   ACT_ROW_1 PORTA &= ~(1<< PA0)
#define   ACT_ROW_2 PORTB &= ~(1<< PB5)
#define   ACT_ROW_3 PORTB &= ~(1<< PB4)
#define   ACT_ROW_4 PORTD &= ~(1<< PD4)
#define   ACT_ROW_5 PORTB &= ~(1<< PB2)
#define   ACT_ROW_6 PORTD &= ~(1<< PD3)
#define   ACT_ROW_7 PORTD &= ~(1<< PD1)
#define   ACT_ROW_8 PORTD &= ~(1<< PD0)
#define INACT_ROW_1 PORTA |=  (1<< PA0)
#define INACT_ROW_2 PORTB |=  (1<< PB5)
#define INACT_ROW_3 PORTB |=  (1<< PB4)
#define INACT_ROW_4 PORTD |=  (1<< PD4)
#define INACT_ROW_5 PORTB |=  (1<< PB2)
#define INACT_ROW_6 PORTD |=  (1<< PD3)
#define INACT_ROW_7 PORTD |=  (1<< PD1)
#define INACT_ROW_8 PORTD |=  (1<< PD0)
/* ------------------------------------------------------------------------- */
#define COUNT	4			// maximal 4 Displays
enum cmd { ADD_RIGHT 
         , CLEAR_ALL
     };
/* ------------------------------------------------------------------------- */
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
/* ------------------------------------------------------------------------- */
#define CHAR_OFFSET 0x20
const uint8_t font[] PROGMEM = {
 0x00,  0x00,  0x00,  0x00,  0x00, // 20 ' '
 0x00,  0x00,  0x5F,  0x00,  0x00, // 21 '!'
 0x00,  0x03,  0x00,  0x03,  0x00, // 22 '"'
 0x14,  0x7F,  0x14,  0x7F,  0x14, // 23 '#'
 0x24,  0x2A,  0x7F,  0x2A,  0x12, // 24 '$'
 0x43,  0x33,  0x08,  0x66,  0x61, // 25 '%'
 0x36,  0x49,  0x56,  0x20,  0x50, // 26 '&'
 0x00,  0x00,  0x03,  0x00,  0x00, // 27 '''
 0x00,  0x3E,  0x41,  0x00,  0x00, // 28 '('
 0x00,  0x00,  0x41,  0x3E,  0x00, // 29 ')'
 0x2A,  0x1C,  0x7F,  0x1C,  0x2A, // 2A '*'
 0x08,  0x08,  0x3E,  0x08,  0x08, // 2B '+'
 0x00,  0x80,  0x40,  0x20,  0x00, // 2C ','
 0x08,  0x08,  0x08,  0x08,  0x08, // 2D '-'
 0x00,  0x00,  0x40,  0x00,  0x00, // 2E '.'
 0x40,  0x20,  0x1C,  0x02,  0x01, // 2F '/'
 0x3E,  0x61,  0x5D,  0x43,  0x3E, // 30 '0'
 0x02,  0x41,  0x7F,  0x40,  0x00, // 31 '1'
 0x62,  0x51,  0x49,  0x49,  0x46, // 32 '2'
 0x22,  0x49,  0x49,  0x49,  0x36, // 33 '3'
 0x18,  0x14,  0x12,  0x79,  0x10, // 34 '4'
 0x2F,  0x49,  0x49,  0x49,  0x31, // 35 '5'
 0x3E,  0x49,  0x49,  0x49,  0x32, // 36 '6'
 0x00,  0x01,  0x71,  0x0D,  0x03, // 37 '7'
 0x36,  0x49,  0x49,  0x49,  0x36, // 38 '8'
 0x26,  0x49,  0x49,  0x49,  0x3E, // 39 '9'
 0x00,  0x00,  0x22,  0x00,  0x00, // 3A ':'
 0x00,  0x00,  0x40,  0x24,  0x00, // 3B ';'
 0x08,  0x14,  0x22,  0x41,  0x00, // 3C '<'
 0x14,  0x14,  0x14,  0x14,  0x14, // 3D '='
 0x00,  0x41,  0x22,  0x14,  0x08, // 3E '>'
 0x02,  0x01,  0x51,  0x09,  0x06, // 3F '?'
 0x3E,  0x41,  0x5D,  0x55,  0x1E, // 40 '@'
 0x7C,  0x0A,  0x09,  0x0A,  0x7C, // 41 'A'
 0x7F,  0x49,  0x49,  0x49,  0x36, // 42 'B'
 0x3E,  0x41,  0x41,  0x41,  0x22, // 43 'C'
 0x7F,  0x41,  0x41,  0x41,  0x3E, // 44 'D'
 0x7F,  0x49,  0x49,  0x49,  0x41, // 45 'E'
 0x7F,  0x09,  0x09,  0x01,  0x01, // 46 'F'
 0x3E,  0x41,  0x49,  0x49,  0x3A, // 47 'G'
 0x7F,  0x08,  0x08,  0x08,  0x7F, // 48 'H'
 0x00,  0x41,  0x7F,  0x41,  0x00, // 49 'I'
 0x21,  0x41,  0x41,  0x41,  0x3F, // 4A 'J'
 0x7F,  0x08,  0x14,  0x22,  0x41, // 4B 'K'
 0x7F,  0x40,  0x40,  0x40,  0x40, // 4C 'L'
 0x7F,  0x02,  0x04,  0x02,  0x7F, // 4D 'M'
 0x7F,  0x02,  0x1C,  0x20,  0x7F, // 4E 'N'
 0x3E,  0x41,  0x41,  0x41,  0x3E, // 4F 'O'
 0x7F,  0x09,  0x09,  0x09,  0x06, // 50 'P'
 0x3E,  0x41,  0x51,  0x21,  0x5E, // 51 'Q'
 0x7F,  0x09,  0x19,  0x29,  0x46, // 52 'R'
 0x26,  0x49,  0x49,  0x49,  0x32, // 53 'S'
 0x01,  0x01,  0x7F,  0x01,  0x01, // 54 'T'
 0x3F,  0x40,  0x40,  0x40,  0x3F, // 55 'U'
 0x1F,  0x20,  0x40,  0x20,  0x1F, // 56 'V'
 0x7F,  0x20,  0x10,  0x20,  0x7F, // 57 'W'
 0x41,  0x22,  0x1C,  0x22,  0x41, // 58 'X'
 0x01,  0x02,  0x7C,  0x02,  0x01, // 59 'Y'
 0x61,  0x51,  0x49,  0x45,  0x43, // 5A 'Z'
 0x00,  0x7F,  0x41,  0x41,  0x00, // 5B '['
 0x03,  0x04,  0x08,  0x10,  0x60, // 5C '\'
 0x00,  0x41,  0x41,  0x7F,  0x00, // 5D ']'
 0x04,  0x02,  0x01,  0x02,  0x04, // 5E '^'
 0x40,  0x40,  0x40,  0x40,  0x40, // 5F '_'
 0x00,  0x01,  0x02,  0x04,  0x00, // 60 '`'
 0x38,  0x44,  0x44,  0x28,  0x7C, // 61 'a'
 0x7F,  0x28,  0x44,  0x44,  0x38, // 62 'b'
 0x38,  0x44,  0x44,  0x44,  0x28, // 63 'c'
 0x38,  0x44,  0x44,  0x28,  0x7F, // 64 'd'
 0x38,  0x44,  0x54,  0x54,  0x18, // 65 'e'
 0x08,  0x7C,  0x0A,  0x02,  0x04, // 66 'f'
 0x18,  0x54,  0x54,  0x54,  0x3C, // 67 'g'
 0x7F,  0x08,  0x04,  0x04,  0x78, // 68 'h'
 0x00,  0x48,  0x7A,  0x40,  0x00, // 69 'i'
 0x00,  0x20,  0x44,  0x44,  0x3D, // 6A 'j'
 0x7E,  0x10,  0x28,  0x44,  0x00, // 6B 'k'
 0x00,  0x00,  0x42,  0x7E,  0x40, // 6C 'l'
 0x78,  0x04,  0x78,  0x04,  0x78, // 6D 'm'
 0x7C,  0x08,  0x04,  0x04,  0x78, // 6E 'n'
 0x38,  0x44,  0x44,  0x44,  0x38, // 6F 'o'
 0x7C,  0x18,  0x24,  0x24,  0x18, // 70 'p'
 0x18,  0x24,  0x24,  0x18,  0x7C, // 71 'q'
 0x04,  0x7C,  0x08,  0x04,  0x08, // 72 'r'
 0x48,  0x54,  0x54,  0x54,  0x24, // 73 's'
 0x04,  0x3E,  0x44,  0x20,  0x00, // 74 't'
 0x3C,  0x40,  0x40,  0x20,  0x7C, // 75 'u'
 0x1C,  0x20,  0x40,  0x20,  0x1C, // 76 'v'
 0x3C,  0x40,  0x20,  0x40,  0x3C, // 77 'w'
 0x44,  0x28,  0x10,  0x28,  0x44, // 78 'x'
 0x04,  0x08,  0x70,  0x08,  0x04, // 79 'y'
 0x44,  0x64,  0x54,  0x4C,  0x44, // 7A 'z'
 0x38,  0x45,  0x44,  0x29,  0x7C, // 7B a-Umlaut
 0x38,  0x45,  0x44,  0x45,  0x38, // 7C o-Umlaut
 0x3C,  0x41,  0x40,  0x21,  0x7C  // 7D u-Umlaut
};
/* ------------------------------------------------------------------------- */
const prog_char LAUFSCHRIFT[] PROGMEM = "Lauflicht C. Niessen... ";
/* ------------------------------------------------------------------------- */
uint8_t slave_ee EEMEM = 0;		// stores the slave number in eeprom
static uint8_t screen_mem[8*COUNT];	// screen memory
static uint8_t active_col;		// active col
static volatile uint16_t counter = 0;	// used for delay function
static uint8_t slave = 0;		// actual slave number from eeprom
static uint8_t *slave_mem = screen_mem;	// screen memory for this slave
static uint8_t Sync  = (0==0);		// Empfangskanal synchronisiert ?

static uint8_t get_command[10];
static uint8_t get_lastbyte=0,
               get_bitcount=0,
               *get_ptr=get_command;
// prototypes
void delay_ms(uint16_t delay);
void show_char();
void clear_screen(void);
/* ------------------------------------------------------------------------- */
/*
 * ISR TIMER0_OVF_vect
 * Handles overflow interrupts of timer 0.
 *
 * 4MHz
 * ----
 * Prescaler 8 ==> 1953.1 Hz
 * Complete display = 244 Hz
 *
 * Stand 2010-09-19  125 +/- 4 Zyklen also knapp 32 uSekunden
 */
volatile uint8_t row_or_col=0;

ISR(TIMER0_OVF_vect) {	

  uint8_t col;
  counter++;

  PORTA = (1 << PA0);
  PORTB = (1 << PB5) | (1 << PB4) | (1 << PB2) | (0 << PB0) ;
  INACT_COL_5;
  INACT_COL_8;
  INACT_ROW_4;
  INACT_ROW_6;
  INACT_ROW_7;
  INACT_ROW_8;

  // next col
  active_col = (active_col+1) % 8;
  if(active_col==0)row_or_col=~row_or_col;
  if(row_or_col==0){
  col = slave_mem[active_col];

  if ((col & 0x01) != 0   ) ACT_ROW_1;
  if ((col & 0x02) != 0   ) ACT_ROW_2;
  if ((col & 0x04) != 0   ) ACT_ROW_3;
  if ((col & 0x08) != 0   ) ACT_ROW_4;
  if ((col & 0x10) != 0   ) ACT_ROW_5;
  if ((col & 0x20) != 0   ) ACT_ROW_6;
  if ((col & 0x40) != 0   ) ACT_ROW_7;
  if ((col & 0x80) != 0   ) ACT_ROW_8;
  // activate col
  asm volatile ("mov r30,%0":: "d" (active_col) : "r30" );
  asm volatile ("add r30,r30":::"r30");
  asm volatile ("ldi r31,lo8(0)":::"r31");
  asm volatile ("subi r30,lo8(-(gs(SprngLst1)))":::"r30");
  asm volatile ("sbci r31,hi8(-(gs(SprngLst1)))":::"r31");
  asm volatile ("ijmp\n"
                "SprngLst1:\n"
               );  
  ACT_COL_1;asm volatile ("rjmp FERTIG1");
  ACT_COL_2;asm volatile ("rjmp FERTIG1");
  ACT_COL_3;asm volatile ("rjmp FERTIG1");
  ACT_COL_4;asm volatile ("rjmp FERTIG1");
  ACT_COL_5;asm volatile ("rjmp FERTIG1");
  ACT_COL_6;asm volatile ("rjmp FERTIG1");
  ACT_COL_7;asm volatile ("rjmp FERTIG1");
  ACT_COL_8;asm volatile ("FERTIG1:");
  } else {
  col = (1<<active_col);
  asm volatile ("mov r25,%0":: "d" (col) : "r25" );
  asm volatile ("lds r26,slave_mem":::"r26");
  asm volatile ("lds r27,slave_mem+1":::"r27");
  asm volatile ("ld r24,X+"     "\n\t"
                "and r24,r25"   "\n\t"
                "breq .+2":::"r24", "r26", "r27"); ACT_COL_1;
  asm volatile ("ld r24,X+"     "\n\t"
                "and r24,r25"   "\n\t"
                "breq .+2":::"r24", "r26", "r27"); ACT_COL_2;
  asm volatile ("ld r24,X+"     "\n\t"
                "and r24,r25"   "\n\t"
                "breq .+2":::"r24", "r26", "r27"); ACT_COL_3;
  asm volatile ("ld r24,X+"     "\n\t"
                "and r24,r25"   "\n\t"
                "breq .+2":::"r24", "r26", "r27"); ACT_COL_4;
  asm volatile ("ld r24,X+"     "\n\t"
                "and r24,r25"   "\n\t"
                "breq .+2":::"r24", "r26", "r27"); ACT_COL_5;
  asm volatile ("ld r24,X+"     "\n\t"
                "and r24,r25"   "\n\t"
                "breq .+2":::"r24", "r26", "r27"); ACT_COL_6;
  asm volatile ("ld r24,X+"     "\n\t"
                "and r24,r25"   "\n\t"
                "breq .+2":::"r24", "r26", "r27"); ACT_COL_7;
  asm volatile ("ld r24,X+"     "\n\t"
                "and r24,r25"   "\n\t"
                "breq .+2":::"r24", "r26", "r27"); ACT_COL_8;

  // activate col
  asm volatile ("mov r30,%0":: "d" (active_col) : "r30" );
  asm volatile ("add r30,r30":::"r30");
  asm volatile ("ldi r31,lo8(0)":::"r31");
  asm volatile ("subi r30,lo8(-(gs(SprngLst)))":::"r30");
  asm volatile ("sbci r31,hi8(-(gs(SprngLst)))":::"r31");
  asm volatile ("ijmp\n"
                "SprngLst:\n"
               );  
  ACT_ROW_1;asm volatile ("rjmp FERTIG");
  ACT_ROW_2;asm volatile ("rjmp FERTIG");
  ACT_ROW_3;asm volatile ("rjmp FERTIG");
  ACT_ROW_4;asm volatile ("rjmp FERTIG");
  ACT_ROW_5;asm volatile ("rjmp FERTIG");
  ACT_ROW_6;asm volatile ("rjmp FERTIG");
  ACT_ROW_7;asm volatile ("rjmp FERTIG");
  ACT_ROW_8;asm volatile ("FERTIG:");
  };
}
/* ------------------------------------------------------------------------- */
/*
 * delay_ms nutzt die regelmaessigen Unterbrechungen mit ca. 2 KHz
 * also ca. 0.5 Millisekunden pro Tick
 * Maximal ca. 30 Sekunden
 */
void delay_ms(uint16_t delay) {
  uint16_t t = delay * 2;
  counter = 0;
  while (counter < t) {}
}
/* ------------------------------------------------------------------------- */
void add_right(uint8_t x){
  uint8_t i;
  for(i=0;i<8*COUNT-1;i++)
    screen_mem[i]=screen_mem[i+1];
  screen_mem[COUNT*8-1]=x;
}
/* ------------------------------------------------------------------------- */
void clear_all(){
  uint8_t i;
  for(i=0;i<8*COUNT;i++)
    screen_mem[i]=0;
}
/* ------------------------------------------------------------------------- */
/* Schleifendurchlauf hat 6 Zyklen, also 1,5 uSekunden

 Achtung: Overhead durch ISR ist nicht beruecksichtigt

 1cc:   00 00    1      nop
 1ce:   01 96    2      adiw    r24, 0x01       ; 1
 1d0:   84 17    1      cp      r24, r20
 1d2:   95 07    1      cpc     r25, r21
 1d4:   d8 f3    1      brcs    .-10            ; 0x1cc <send_one_bit+0x14>
*/
#define TOTAL	(1333-2)              // Korrektur fuer Overhead etc.
#define TOTAL33	(TOTAL/3)
#define TOTAL66	(2*TOTAL33)
/* ------------------------------------------------------------------------- */
void send_one_bit(uint8_t b){
  uint16_t breite,i;
  breite=(b==0)?TOTAL33:TOTAL66;
  PORTD &= ~(1<< PD6);
  for(i=0;i<breite;i++)asm volatile ("nop");
  breite=TOTAL-breite;
  PORTD |=  (1<< PD6);
  for(i=0;i<breite;i++)asm volatile ("nop");
}
/* ------------------------------------------------------------------------- */
void send_one_byte(uint8_t b){
  uint8_t i,j;
  for(i=0;i<8;i++){
    j=b&0x01;
    send_one_bit(j);
    b>>=1;
  };
}
/* ------------------------------------------------------------------------- */
void send_one_row_right(uint8_t b){
  send_one_byte(ADD_RIGHT);
  send_one_byte(1);
  send_one_byte(b);
}
/* ------------------------------------------------------------------------- */
/*
1 if condition is false (no skip)
2 if condition is true (skip is executed) and the instruction skipped is 1 word
3 if condition is true (skip is executed) and the instruction skipped is 2 words

 15e:   01 96   2       adiw    r24, 0x01       ; 1
 160:   86 9b   1(,2,3) sbis    0x10, 6 ; 16
 162:   fd cf   2       rjmp    .-6             ; 0x15e <get_one_bit+0xa>

 Schleife benoetigt pro Durchlauf 5 Zyklen, also ca. 1,25 uSekunden
 in 666 uSekunden werden ca. 533 Durchlaeufe geschafft, in 1,33 Millesekunden
 etwa 1066; beides Werte, die in 16 Bit passen.
 800 ist genau die Mitte.

 Der Timer-Interrupt schlaegt alle 500 uSekunden zu und benoetigt ca. 90 Zyklen
 fuer eine 0 (666 uSekunden) kommt er ein oder zwei Mal; entsprechend ist
 die erwartete Breite hier 18 oder 36 kleiner, also 505 bzw 497.
 Fuer eine 1 (1333 uSekunden) kommt er zwei oder drei Mal; entsprechend ist
 die erwartete Breite hier 36 oder 54 kleiner, also 1030 oder 1012

*/
uint8_t get_one_bit(){
  uint16_t breite=0;
  // warte bis fallende Flanke ...
  while (  (PIND & (1 << PD6))){
    if(1500<breite++){
      get_lastbyte=0;
      get_bitcount=0;
      get_ptr=get_command;
    }
  }
  
  // ...  und zaehle bis zur steigenden.
  breite=0;
  while ((!(PIND & (1 << PD6))) || breite <20) breite++;
  return( (breite<800)?0:0x80 );
}
/* ------------------------------------------------------------------------- */
uint8_t get_one_byte(){
  uint8_t b,i,rc;
  rc=0;
  for(i=0;i<8;i++){
    b=get_one_bit();
    rc>>=1; // LSB first
    rc|=b;
  }
  return(rc);
}
/* ------------------------------------------------------------------------- */
uint8_t get_cmd(){
  uint8_t i,l;
  get_ptr=get_command;
  *get_ptr++=get_one_byte();
  *get_ptr++=l=get_one_byte();
  if(l<=sizeof(get_command)-2)
    for(i=0;i<l;i++)*get_ptr++=get_one_byte();
  return(get_ptr-get_command);
}
/* ------------------------------------------------------------------------- */
void main(void) {

  uint8_t i = 0;

  // timer 0 setup, prescaler 8
  TCCR0B |= (1 << CS01);
 
  // enable timer 0 interrupt
  TIMSK |= (1 << TOIE0);	

  // define outputs
  DDRA = 0x03;  
  DDRB = 0xFF;
  DDRD = 0x3F;
  
  // inaktiviere Zeilen(1) and Spalten(0) aber Pull-UP fuer den Eingang D6
  PORTA = (1 << PA0);
  PORTB = (1 << PB5) | (1 << PB4) | (1 << PB2) | (0 << PB0) ;
  PORTD = (1 << PD4) | (1 << PD3) | (1 << PD1) | (1 << PD0) | (1 << PD6);

  sei();
  slave_mem=screen_mem;
  if(PIND & (1<<PD6)){
    slave = eeprom_read_byte(&slave_ee);
    if(slave>=COUNT){
      slave=0;
      eeprom_write_byte(&slave_ee, slave);  
    };
  } else {
    slave=0;
    while( !(PIND & (1<<PD6)) ){
      uint8_t x,k;
      slave=((slave+1)%COUNT);
      x=(slave+'0'-' ');x+=(x<<2);
      for(k=0;k<5;k++)
      slave_mem[k]=pgm_read_byte(&font[x+k]);
      delay_ms(2000);
    };
    eeprom_write_byte(&slave_ee, slave);  
  };
  if(slave==0){
    slave_mem[0]=0x7f;  //  *******
    slave_mem[1]=0x02;  //       * 
    slave_mem[2]=0x04;  //      *  
    slave_mem[3]=0x02;  //       * 
    slave_mem[4]=0x7f;  //  *******
    delay_ms(1000); // Master wartet laenger als der Slave !!!
  } else {
    int k,x;
    x=(slave+'0'-' ');x+=(x<<2);
    for(k=0;k<5;k++)
      slave_mem[k]=pgm_read_byte(&font[x+k]);
  }
  delay_ms(1500);
  slave_mem=screen_mem+8*slave;
  switch(slave){
    case 0: // Master
      DDRD |= (1<< PD6);
      i=0;
      while(1==1){
        uint8_t j,k,c;
        uint16_t h1,h2;
        c=pgm_read_byte(&LAUFSCHRIFT[0]);
        for(j=1;c!=0;j++){
          h1=c-' ';
          h1+=h1<<2; // *5
          for(k=0;k<5;k++){
            h2=pgm_read_byte(&font[h1++]);
            send_one_row_right(h2);add_right(h2);
            delay_ms(50);
          };
          send_one_row_right(0);add_right(0);
          delay_ms(50);
          c=pgm_read_byte(&LAUFSCHRIFT[j]);
        }
        delay_ms(50);
      }
      break;
    default: // Slave
      while (1) {
        if(get_cmd()>=2)
          switch(get_command[0]){
            case ADD_RIGHT:
              add_right(get_command[2]);
              break;
            case CLEAR_ALL:
              clear_all();
              break;
      }
  }
  };
}
/* ------------------------------------------------------------------------- */