Für meine CPU habe ich eine Anzeige entworfen, die auf einer 11*5 LED Matrix beruht. Jede Ziffer wird in einem 3*5 Feld ausgegeben. Der Attiny fragt regelmäßig ab, welche Information, z.B. Register oder Steuerbits, an den Datenpins anliegen und im Anschluß die Daten selber. So kann ich mit den wenigen Eingängen die der Attiny 84 hat, trotzdem bis zu 16 Datenarten abfragen.
Zur Zeit werden davon jedoch nur 5 Zustände genutzt:
Die Ergebnisse der Rechenoperationen der ALU:
Zum Vergleich hier die Anzeige in der ALU und die LED Matrix.
Die LED in der Anzeigematrix haben die gleichen Farben bekommen, wie klassische Darstellung. Natürlich ist die Matrix flexibler einzusetzen, da der Attiny ja umprogrammiert werden kann. Ich plane in meiner CPU mehrere dieser Anzeigen zu verwenden.
Da ich weiterhin die Arduinoumgebung zum Flashen des Attiny verwende, jedoch einen externen Editor zum Coden, nutze ich auch gerne mal ein paar Funktionen aus der Arduinowelt. Warum soll ich das Rad oft neu erfinden?
In der Arduino IDE gibt es die Funktion shiftOut(..), die jedoch sehr langsam ist und für meine kleine Attinyumgebung zuviel Overhead erzeugt. Diese habe ich durch die kleinere Funktion shiftOut_neu(..) ersetzt:
void shiftOut_neu(byte val){ for (byte i = 0; i < 8; i++) { //digitalWrite(dataPin, !!(val & (1 << i))); if ( ( !!(val & (1 << i)) ) == HIGH ) PORTA |= (1 << dataPin); else PORTA &= ~(1 << dataPin); //digitalWrite(clockPin, HIGH); PORTA |= (1 << clockPin); //digitalWrite(clockPin, LOW); PORTA &= ~(1 << clockPin); } }
Die Ports des Attiny spreche ich direkt an, habe aber die Arduinobefehle als Kommentar stehen lassen, so kann man leichter nachvollziehen, was gemacht werden soll.
Zwei Shiftregister brauchen zusammen 16 Bit, deshalb habe ich ein kleine Sendefunktion senden(..) genommen, die 2×8 Bit überträgt:
void senden(byte wert1, byte wert2) { // Damit beim übertragen die LED nicht flattern //digitalWrite(latchPin, LOW); PORTA &= ~(1 << latchPin); // Jetzt die Werte an den 595 senden shiftOut_neu(wert2); shiftOut_neu(wert1); //Und die LED wieder anschalten //digitalWrite(latchPin, HIGH); PORTA |= (1 << latchPin); }
Der „Bildschirm“ besteht ja aus 11 Spalten und 5 Reihen. Um jeden der einzelen „Pixel“ individuell an- und ausschalten zu können habe ich einen Bildschirmspeicher, der den Zustand jeder LED einzelnd speichert. „true“ bedeutet an- und „false“ ausgeschaltet.
bool Bildschirm[5][11] = { // Jede Stelle steht für eine LED {false, false, false, false, false, false, false, false, false, false, false}, {false, false, false, false, false, false, false, false, false, false, false}, {false, false, false, false, false, false, false, false, false, false, false}, {false, false, false, false, false, false, false, false, false, false, false}, {false, false, false, false, false, false, false, false, false, false, false} };
Die Funktion ledLeuchten() überträgt nacheinander alle Zeilen an die Shiftregister. Es wird immer nur eine Zeile angeschaltet, die restlichen 4 Zeilen sind wärenddessen ausgeschaltet.
void ledLeuchten() { word ausgabe; word reihe = 32768; for (byte iR = 0; iR < 5; iR++) { ausgabe = 0; for (byte n = 0; n < 11; n++) { (Bildschirm[iR][n] == true) ? ausgabe |= (1 << n) : 0; } ausgabe = ausgabe + (reihe >> iR); senden(byte(ausgabe >> 8), byte(ausgabe)); } }
Zum Abschluß noch das ganze Programm, so direkt in der Arduino IDE kompilierbar.
/* Display für die CPU Mit einem ATTINY84 und zwei Shiftregistern 74595 */ //Pin für den ST_CP des 74HC595 const int latchPin = 3; //Pin für den SH_CP des 74HC595 const int clockPin = 2; //Pin für den DS des 74HC595 const int dataPin = 1; //Eingangspins für die Daten const int einPin3 = 8; // PB2 Stelle 3 x... const int einPin2 = 0; // PA0 Stelle 2 .x.. const int einPin1 = 7; // PA7 Stelle 1 ..x. const int einPin0 = 10; // PB0 Stelle 0 ...x //Wertepins für die Datenart const int wertPin3 = 9; // PB1 Bit 2 x.. const int wertPin2 = 4; // PA4 Bit 1 .x. const int wertPin1 = 6; // PA6 Bit 0 ..x bool Bildschirm[5][11] = { // Jede Stelle steht für eine LED {false, false, false, false, false, false, false, false, false, false, false}, {false, false, false, false, false, false, false, false, false, false, false}, {false, false, false, false, false, false, false, false, false, false, false}, {false, false, false, false, false, false, false, false, false, false, false}, {false, false, false, false, false, false, false, false, false, false, false} }; const byte Ziffer[16][5] = { // Die 5*3 Matrix für die Anzeigbaren Zeichen { 7 , 5 , 5 , 5 , 7 }, // 0 { 4 , 6 , 5 , 4 , 4 }, // 1 { 7 , 4 , 7 , 1 , 7 }, // 2 { 7 , 4 , 6 , 4 , 7 }, // 3 { 1 , 5 , 7 , 4 , 4 }, // 4 { 7 , 1 , 7 , 4 , 7 }, // 5 { 7 , 1 , 7 , 5 , 7 }, // 6 { 7 , 4 , 4 , 4 , 4 }, // 7 { 7 , 5 , 7 , 5 , 7 }, // 8 { 7 , 5 , 7 , 4 , 7 }, // 9 { 7 , 5 , 7 , 5 , 5 }, // A { 3 , 5 , 3 , 5 , 3 }, // B { 7 , 1 , 1 , 1 , 7 }, // C { 3 , 5 , 5 , 5 , 3 }, // D { 7 , 1 , 3 , 1 , 7 }, // E { 7 , 1 , 3 , 1 , 1 } // F }; void shiftOut_neu(byte val){ for (byte i = 0; i < 8; i++) { //digitalWrite(dataPin, !!(val & (1 << i))); if ( ( !!(val & (1 << i)) ) == HIGH ) PORTA |= (1 << dataPin); else PORTA &= ~(1 << dataPin); //digitalWrite(clockPin, HIGH); PORTA |= (1 << clockPin); //digitalWrite(clockPin, LOW); PORTA &= ~(1 << clockPin); } } void senden(byte wert1, byte wert2) { // Damit beim übertragen die LED nicht flattern //digitalWrite(latchPin, LOW); PORTA &= ~(1 << latchPin); // Jetzt die Werte an den 595 senden shiftOut_neu(wert2); shiftOut_neu(wert1); //Und die LED wieder anschalten //digitalWrite(latchPin, HIGH); PORTA |= (1 << latchPin); } byte wertEinlesen() { // PB2 Stelle 3 x... // PA0 Stelle 2 .x.. // PA7 Stelle 1 ..x. // PB0 Stelle 0 ...x byte ergebnis = 0; //ergebnis = digitalRead(einPin3) * 8 + digitalRead(einPin2) * 4 + digitalRead(einPin1) * 2 + digitalRead(einPin0); ergebnis = ( ( ( PINB & (1 << PB2)) >> PB2 ) * 8); ergebnis += ( ( ( PINA & (1 << PA0)) >> PA0 ) * 4); ergebnis += ( ( ( PINA & (1 << PA7)) >> PA7 ) * 2); ergebnis += ( ( ( PINB & (1 << PB0)) >> PB0 ) ); return ergebnis; } byte stelleEinlesen() { /* PB1 Bit 2 x.. PA4 Bit 1 .x. PA6 Bit 0 ..x 0-0-0 Controller (blaue LED) 0-0-1 Nibble A (grüne LED) 0-1-0 Nibble B (gelbe LED) 0-1-1 Ergebnis (rote LED) 1-0-0 ><=Co (farbige LED) Rest noch ungenutzt */ byte ergebnis= 0; ergebnis = ( ( ( PINB & (1 << PB1)) >> PB1 ) * 4); ergebnis += ( ( ( PINA & (1 << PA4)) >> PA4 ) * 2); ergebnis += ( ( ( PINA & (1 << PA6)) >> PA6 ) ); return ergebnis; } void zifferSetzen(byte stelle, byte wert) { stelle = stelle * 3 - 3; // Stelle 1: rechts, Stelle 2: Mitte, Stelle 3: links for (byte zeile = 0; zeile < 5; zeile++) { for (byte n = 0; n < 3; n++) { if ( ((Ziffer[wert][zeile] & ( 4 >> n)) ) > 0 ) Bildschirm[zeile][stelle + n] = true; else Bildschirm[zeile][stelle + n] = false; } } } void controllerSetzen(byte zahl) { for (byte i = 0; i < 4; i++) { if ( ((( zahl & (1<<i) )) >> i ) == 1 ) Bildschirm[3-i][9] = true; else Bildschirm[3-i][9] = false; } } void statusSetzen(byte zahl) { for (byte i = 0; i < 4; i++) { if ( ((( zahl & (1<<i) )) >> i ) == 1 ) Bildschirm[4-i][10] = true; else Bildschirm[4-i][10] = false; } } void ledLeuchten() { word ausgabe; word reihe = 32768; for (byte iR = 0; iR < 5; iR++) { ausgabe = 0; for (byte n = 0; n < 11; n++) { (Bildschirm[iR][n] == true) ? ausgabe |= (1 << n) : 0; } ausgabe = ausgabe + (reihe >> iR); senden(byte(ausgabe >> 8), byte(ausgabe)); } } void setup() { //PINS setzen: OUTPUT für das Shiftregister pinMode(latchPin, OUTPUT); pinMode(clockPin, OUTPUT); pinMode(dataPin, OUTPUT); //INPUT_PULLUP für Daten. Pullup, damit jederzeit ein definierter Zustand herrscht. pinMode(einPin3, INPUT_PULLUP); pinMode(einPin2, INPUT_PULLUP); pinMode(einPin1, INPUT_PULLUP); pinMode(einPin0, INPUT_PULLUP); pinMode(wertPin3, INPUT_PULLUP); pinMode(wertPin2, INPUT_PULLUP); pinMode(wertPin1, INPUT_PULLUP); // Zu Beginn alle LED ausschalten ledLeuchten(); } void loop() { byte stelle = stelleEinlesen(); byte zahl = wertEinlesen(); switch(stelle) { case 0: controllerSetzen(zahl); break; case 1: zifferSetzen(3, zahl); break; case 2: zifferSetzen(2, zahl); break; case 3: zifferSetzen(1, zahl); break; case 4: statusSetzen(zahl); break; default: break; // sonst: Nichts tun } ledLeuchten(); }
Kompiliert für einen Attiny84, der den internen Oszillator mit 8MHz nutzt, ergibt sich bei mir dieses Resultat:
Der Sketch verwendet 1234 Bytes (15%) des Programmspeicherplatzes. Das Maximum sind 8192 Bytes. Globale Variablen verwenden 144 Bytes (28%) des dynamischen Speichers, 368 Bytes für lokale Variablen verbleiben. Das Maximum sind 512 Bytes.
Die einzelnen Spalten und Reihen sind jeweils an einen Ausgang eines Shiftregisters angeschlossen.