8x8-LED-Matrix-Schaltung vom Breadboard auf ein Prototype Shield migrieren

Nachdem ich in meinen letzten Raspberry Pi-Projekten die Schaltung auf zwei 830er Breadboards (eines war zu klein für die 8x8-Matrix) eine Schaltung aufgebaut habe, um mit zwei 74HC595 Schieberegistern über nur drei Leitungen eine 8x8-LED-Matrix anzusteuern und den Code dafür dann auch noch von Python in C mit WiringPi-API portiert habe, wobei die WiringPi-API-Befehle sehr dem Arduino-C ähneln liegt es doch nahe, das Projekt auf den Arduino zu portieren und dabei gleich ein bisschen platzsparender anzusetzen.

Liebling! Ich habe die Schaltung geschrumpft!

Hier das Raspberry-Projekt, für das ich aufgrund der großen 8x8-LED-Matrix gleich zwei Breadboards brauchte, was eine Größe von 18 x 10 cm (180 cm2) ausmachte:



im Vergleich von nur 7 x 5.5 cm (38.5 cm2), also rund einem Fünftel der Fläche. Das Protoype Shield wird einfach auf den Arduino Uno aufgesteckt und führt alle wichtigen Leitungen durch. Für unter 2 Euro bekommt man es aus Fernost mitsamt eines Mini-Breadboards, dass zwischen die Buchsenleisten auf der Bestückungsseite passt und auf dem man kleine Schaltungen zusammenstecken kann.

Hat man alles gesteckt und getestet kann man sich dann daran machen, die Bauteile auf dem Shield fest zu verlöten. Der Raum reicht dafür für kleinere und kleine und vielleicht auch mittlere Schaltungen, wobei mit der Aufbau der Lötpunkte sehr gefällt und viel Spielraum lässt.

Rechts die Schaltung von der Bestückungsseite. Links die beiden IC-Sockel sind für die HC595er und rechts die beiden halben IC-Sockel mit je 8 Pins sind für das 8x8-LED-Matrix-Modul. Unter der Matrix verschwinden dann die 8 Vorwiderstände mit je 100 Ω.


Auf der Rückseite geht es schon ein bisschen verwirrender zu.

Die 8 Ausgangspins des 1. 595er wollen über Vorwiderstände an die Row-Pins der Matrix und die 8 Ausgangspins des 2. 595er an die Col-Pins der Matrix angeschlossen werden. Plus die Datenleitungen zu den 595ern.

Zu viel Kabelgewirr darf man hier auch nicht auftürmen, sonst ist beim Stecken auf den Arduino Uno der Atmel oder die USB-Buchse im Weg. Das heißt, soweit man die Version mit gesockeltem ATmega und großer USB-Buchse hat. Bei den neueren Unos mit SMD-ATmega und Micro-USB-Buchse sollte man mehr Freiheit haben.




Fertig bestückt und auf den Arduino gesetzt sieht die Platine dann schön aufgeräumt aus.

Das Schöne an dem Prototype Shield ist, dass sie nicht viel kosten, und man sie abziehen und zur Seite packen kann, während man den Arduino für andere Projekte braucht. Braucht man das alte Projekt wieder, wird die Platine wieder rausgekramt, aufgesteckt und der Code neu hochgeladen und man ist gleich wieder startklar.

Wobei einem eins klar ein muss: so schnell wie das Stecken auf dem Breadboard ist die Schaltung nicht aufgebaut. Das Löten dauert doch ein bisschen länger, besonders, wenn man auch noch die Kabel zuschneiden, abisolieren und verzinnen muss. Auch die Fehlersuche dauert länger, weil es auf der kleinen Platine wesentlich gedrängter und unübersichtlicher zugeht.

Dafür rupft man nicht so schnell mal ein Kabel raus und so eine Platine lässt sich einfacherer und sicherer verstauen als ein Breadboard. Und solange man die teureren Bauteile wie ICs sockelt, kann man diese auch wiederverwenden, sollte man die Schaltung doch mal nicht mehr benötigen.

Das Ergebnis ist eine raumsparende Schaltung für den Arduino, die das gleiche tut wie auf demRaspberry Pi:




Der Source-Code

Der Code dafür konnte dafür fast ohne Anpassungen aus dem C-Code für den Raspi übernommen werden:
//////////////////////////////////////////////////////// // (C) 2018 by Oliver Kuhlemann // // Bei Verwendung freue ich mich über Namensnennung, // // Quellenangabe und Verlinkung // // Quelle: http://cool-web.de/arduino/ // //////////////////////////////////////////////////////// #include <stdio.h> #include <stdint.h> #include <unistd.h> #include <time.h> #include <string.h> //#define HIGH 1 //#define LOW 0 #define False 0 #define True 1 // Es gelten die BCM-Pinnummern! #define PinData 2 #define PinClockStore 3 #define PinClockShift 4 #define ClockPulseLen 1 // in Microsekunden void setup() { // put your setup code here, to run once: pinMode(PinData, OUTPUT); pinMode(PinClockStore, OUTPUT); pinMode(PinClockShift, OUTPUT); Serial.begin(9600); } void storeTick() { // einen Puls auf die ClockStore-Leitung schicken digitalWrite(PinClockStore, HIGH); delayMicroseconds (ClockPulseLen); digitalWrite(PinClockStore, LOW); delayMicroseconds (ClockPulseLen); } void shiftTick() { // einen Puls auf die ClockShift-Leitung schicken digitalWrite(PinClockShift, HIGH); delayMicroseconds (ClockPulseLen); digitalWrite(PinClockShift, LOW); delayMicroseconds (ClockPulseLen); } void clearDots() { // alle Punkte löschen for (int i=0;i<16;i++) { digitalWrite(PinData, LOW); shiftTick(); // nächstes Bit } storeTick(); // alle Bits fertig. Speichern } void lightCol(uint8_t col,uint8_t colByte) { // Vorwiderstände hängen an den Row-Ausgängen, sonst keine gleichmäßige Helligkeit // zuerst das colByte invertiert for (uint8_t b=0; b<8; b++) { uint8_t w = (b == (7-col)); w^=1; // invertieren, weil wird müssen auf Low und damit zu GND durchschalten digitalWrite(PinData,w); // w = 0 | 1 shiftTick(); // nächstes Bit } // dann das rowByte einschieben for (uint8_t b=0; b<8; b++){ uint8_t w = (colByte & 1); digitalWrite(PinData,w); // w = 0 | 1 shiftTick(); // nächstes Bit colByte = colByte >> 1; } storeTick(); // alle Bits fertig. Speichern } void lightDot(uint8_t x, uint8_t y) { lightCol(x,1<<(7-y)); } uint8_t countSetBits(uint8_t byte) { uint8_t cnt=0; for (uint8_t i=0; i<8; i++) { if (byte & 1) cnt++; byte = byte >> 1; } return cnt; } void lightMatrix(uint8_t *rowArray, uint16_t msecs) { uint8_t colArray[8] = {0, 0, 0, 0, 0, 0, 0, 0}; for (uint8_t col=0; col<8; col++) { for (uint8_t row=0; row<8; row++) { colArray[col] += (rowArray[7-row] & 128)/128 * (1<<row); //printf ("col %d, row %d, bit=%d, macht %d\n",col,row,(rowArray[row] & 128)/128,(rowArray[row] & 128)/128 * (1<<row)); rowArray[7-row] = rowArray[7-row]<<1; } // for (int i=0;i<8;i++) printf("col %d: %d\n",col, colArray[col]); } for (uint16_t i=0; i<msecs/6.6; i++) { for (uint8_t col=0; col<8; col++) { lightCol(col, colArray[col]); delayMicroseconds(500); //clearDots(); //delayMicroseconds(10); } } clearDots(); } void scrollShow (uint8_t colArray[8], uint16_t verzoegerung) { for (uint16_t i=0; i < verzoegerung/6.6; i++) { // und anzeigen for (uint8_t col=0; col<8; col++) { lightCol(col, colArray[col]); delayMicroseconds(500); } clearDots(); } } void scroll (char *text[8], uint16_t cols, uint16_t verzoegerung) { // cols=Anzahl von Spalten gesamt uint8_t colArray[8] = {0, 0, 0, 0, 0, 0, 0, 0}; //ersten Screen reinschieben for (uint8_t c=0; c<7; c++) { for (uint8_t i=0; i<7;i++) { colArray[i]=colArray[i+1]; } uint8_t colByte=0; for (uint8_t row=0; row<8; row++) { if (text[row][c] == '#') colByte += 1<<(7-row); } colArray[7]=colByte; scrollShow(colArray,verzoegerung); } // gesamten Text durchscrollen for (uint16_t col=0; col < cols-8; col++) { for (uint16_t c=col; c<col+8; c++) { // 8 ZeilenBytes holen uint8_t colByte=0; for (uint8_t row=0; row<8; row++) { if (text[row][c] == '#') colByte += 1<<(7-row); } colArray[c-col] = colByte; } scrollShow(colArray,verzoegerung); } // letzten Screen auch noch rausschieben for (uint8_t c=0; c<8; c++) { for (uint8_t i=0; i<7;i++) { colArray[i]=colArray[i+1]; } colArray[7]=0; scrollShow(colArray,verzoegerung); } } void loop(void) { clearDots(); // die Dots der Reihe nach durchschalten for (int i=0; i<64; i++) { uint8_t x=i%8; uint8_t y=i/8; lightDot(x,y); delay (25); } clearDots(); delay (500); // kleine Warte-Animation /* 0 1 2 3 4 5 6 7 . . # # # # . . 0 . # . . . . # . 8 # . . . . . . # 16 # . . . . . . # 24 # . . . . . . # 32 # . . . . . . # 40 . # . . . . # . 48 . . # # # # . . 56 */ uint8_t dots[20] = {2,3,4,5, 14, 23, 31, 39, 47, 54, 61,60,59,58, 49, 40, 32, 24, 16, 9}; for (uint8_t w=0; w < 10; w++) { for (int i=0; i<20; i++) { uint8_t x=dots[i]%8; uint8_t y=dots[i]/8; lightDot(x,y); delay (10); } } clearDots(); delay (500); // ein 8x8-Matrix-Bild anzeigen uint8_t pic[8] = { 0b01100110, 0b10011001, 0b10000001, 0b10000001, 0b10000001, 0b01000010, 0b00100100, 0b00011000 }; lightMatrix (pic,2000); clearDots(); delay (500); // einen Scroll-Text durchlaufen lassen char *scrollText[8] = { "###...##.##....####....####....####...#..............#...#...#####..####.......####....#####", ".#...#..#..#..#....#..#....#..#....#..#..............#...#...#......#...#......#...#...#....", ".#...#.....#..#.......#....#..#....#..#..............#...#...#......#...#......#....#..#....", ".#...#.....#..#.......#....#..#....#..#......#####...#...#...####...####.......#....#..####.", ".#...#.....#..#.......#....#..#....#..#..............#...#...#......#...#......#....#..#....", ".#....#...#...#.......#....#..#....#..#..............#.#.#...#......#...#......#....#..#....", ".#.....#.#....#....#..#....#..#....#..#..............##.##...#......#...#..##..#...#...#....", "###.....#......####....####....####...#####..........#...#...#####..####...##..####....#####" }; scroll (scrollText,92,100); delay (500); clearDots(); }