3.8 🍀 SPI Schnittstelle

1. Funktionsprinzip der SPI-Schnittstelle

SPI bedeutet Serial Peripheral Interface und dient hauptsächlich zur seriellen Datenübertragung von einem µC zu einer Peripherie und von der Peripherie zum µC. Die Datenübertragung wird durch einen Master/Controller gesteuert. Er gibt den Takt der Übertragung über eine Leitung SCK (Serial Clock) und wählt über eine Leitung CS/SS (Chip Select/Slave Select) die gewünschte Peripherie aus. Der µC sendet seine Daten bitweise über eine Leitung MOSI (Master Out Slave In) und erhält ggfs. Daten der Peripherie über eine Leitung MISO (Master In Slave Out). Im Prinzip werden einfach Bits durch Schieberegister im Takt von SCK geschoben. !CS steuert den Beginn der Übertragung (low) und wann die Daten aus den Schieberegistern übernommen werden sollen (low↑high). Hier eine Demo für eine 8Bit Übertragung (MSB-First, SPI-Modus 0). Der Master sendet 0xC3 und empfängt 0x47. Das Bild zeigt den Zustand nach der Übertragung.

SPI einfach
SPI einfach
  • SCK bis zu 10MHz schnell
  • 8 oder 16 Bit Übertragungen
  • Bidirektionale Übertragung möglich
  • Sehr einfaches Protokoll
SPI Demo 8Bit
SPI Demo 8Bit

Video zum Verständnis

Anschluss mehrerer Bausteine

Wenn mehrere Peripherie-Bausteine an den Bus angeschlossen werden sollen gibt es zwei Prinzipien, auch die Kombination wäre möglich:

SPI Sternverkabelung
SPI Sternverkabelung

Bei der Sternverkabelung wählt der µC über mehrere CS-Leitungen den gewünschten Kommunikationspartner aus. Nur dieser darf dann auch die MISO-Leitung belegen.

SPI Kaskade
SPI Kaskade

Bei der Kaskadierung werden die Daten nacheinander durch alle Empfänger geschoben.

Die Benennung der Signale ist nicht genormt

Eine Vielfalt von Bezeichnungen für gleiche Leitungen und neue Bezeichnungen mit Varianten wegen “Political Correctness

Master/Slave (alte Bezeichnungen)Controller/Peripheral (neuere Bezeichnungen)BedeutungAus Sicht der Peripherie
Master In Slave Out (MISO)Controller In, Peripheral Out (CIPO) [POCI]Datenübertragung vom µC zur PeripherieSerial Data Out (SDO) [DOUT]
Master Out Slave In (MOSI)Controller Out Peripheral In (COPI) [PICO]Datenübertragung von Peripherie zu µCSerial Data In (SDI) [DIN]
Slave Select pin (SS)Chip Select Pin (CS)Die Peripherie als Empfänger auswählen
(low aktiv)
Serial Clock (SCK) [SCLK]Serial Clock (SCK) [SCLK] {CLK}Takt für die Datenübertragung
Alte, neue und weitere Bezeichnungen der Leitungen

Die Reihenfolge der Bit-Übertragung kann eingestellt werden

Reihenfolge der Bits bei der Datenübertragung: MSB- oder LSB-First? In der Regel wird das höchstwertige Bit, das MSB zuerst übertragen:

SPI.setBitOrder(MSBFIRST); // Hoechstwertiges Bit zuerst

SPI-Modus: Bei welchem Zustand von SCK werden die Daten übernommen?

Es wurde nicht festgelegt, welches der Ruhezustand von SCK ist und bei welchem SCK-Zustand die Daten übernommen werden sollen, daher entwickelten sich 4 Varianten, die wir nun berücksichtigen dürfen. In der Regel wird SPI-Modus 0 angewendet:

SPI (SCK) ModusCPOL (Clock Polarity)CPHA (Clock Phase)Bedeutung
00 Clock Idle Low0 Übernahme bei 1. FlankeSCK in Ruhe 0 Übernahme bei steigender Flanke _↑¯|_↑¯|_
10 Clock Idle Low1 Übernahme bei 2. FlankeSCK in Ruhe 0 Übernahme bei fallender Flanke _|¯↓_|¯↓_
21 Clock Idle High0 Übernahme bei 1. FlankeSCK in Ruhe 1 Übernahme bei fallender Flanke ¯↓_|¯↓_|¯
31 Clock Idle High1 Übernahme bei 2. FlankeSCK in Ruhe 1 Übernahme bei steigender Flanke ¯|_↑¯|_↑¯
SPI Modi
SPI.setDataMode(SPI_MODE0); // SCK in Ruhe Low, Uebernahme bei steig. Flanke

2. Einen 74HC595 als PortExpander verwenden

Hier im Beispiel sollen 2 LED über den SPI-Bus mittels eines einfachen 74HC595 angesteuert werden, es werden 8Bit übertragen. Bit0 lässt die rote- und Bit1 die grüne LED leuchten.

  • !OE wird mit GND verbunden, damit die LED leuchten.
  • RCLK ist mit CS verbunden, bei steigender Flanke werden die Werte im Schieberegister übernommen.
  • !SRCLR ist mit VCC verbunden, es ist kein Reset des Schieberegisters notwendig.
  • SRCLK ist mit SCK verbunden, damit bei steigender Flanke die Werte in das Schieberegister übernommen werden.
  • SER ist mit MOSI verbunden, die Daten werden in das Schieberegister rein geschoben.

Beispiel für Anwendung: Bei einem 8*8*8 LED-Cube werden typischerweise 8 Ebenen mit jeweils 64 LED im Zeitmultiplexverfahren angesteuert. Für die LED werden 64 Ausgänge benötigt, mit 8 74HC595 und SPI ist das ein Kinderspiel. Einfach die Chips kaskadieren und die 8 Byte für die jeweilige Ebene mit SPI verschicken.

74HC595 Innenschaltung
74HC595 Innenschaltung
Fritzing Aufbau
Fritzing Aufbau
Messaufbau
Messaufbau

Testcode

#include <SPI.h>
#define CS  D10 // CS Signal auf D10
#define UserButton  PC13 // Zum Umschalten der LED

void send_data (unsigned char lsb){ // Hilfsfunktion zur Datenuebertragung
  digitalWrite(CS, LOW); // Uebertragung beginnt 
  SPI.transfer(lsb); // 8 Bit Transfer LSB
  digitalWrite(CS, HIGH); // Uebertragung fertig, uebernehme Daten!
}
void setup(){
  pinMode(UserButton, INPUT); // Zum Umschalten des Musters
  pinMode(CS, OUTPUT);
  digitalWrite(CS, HIGH);
  SPI.begin();  // SPI-Schnittstelle einrichten
  SPI.setBitOrder(MSBFIRST);            // Hoechstwertiges Bit zuerst
  SPI.setClockDivider(SPI_CLOCK_DIV32); // SCK mit 1 MHz, koennte auch 10 MHz
  SPI.setDataMode(SPI_MODE0);           // SCK in Ruhe Low, Uebername bei steigender Flanke
  delay(10); // kurz warten bis verdaut..
}

void loop(){
  if(digitalRead(UserButton)) send_data(1); // Rote LED leuchten lassen
  else send_data(2); // Gruene LED leuchten lassen
  //send_data(digitalRead(UserButton)?1 :2); // Sende 1 oder 2 je nach UserButton-Zustand
  delay(1000);
}

Mit Logic-Analyzer von Analog Discovery Studio zuschauen

SPI-Signal Rot: Bit0 = 1
SPI-Signal Rot: Bit0 = 1
SPI-Signal Grün: Bit1 = 1
SPI-Signal Grün: Bit1 = 1

3. Übertragung zu MAX7219 mit 8×8 Matrix-LED-Anzeige

Der MAX7219 ist für die Ansteuerung von 8 7-Segment-Anzeigen oder einer 8×8 LED Matrix gedacht. Die LEDs werden im Zeitmultiplex-Verfahren angesteuert. Dazu muss im laufenden Betrieb nur der Inhalt der Anzeigen/Zeilen bei Änderung übertragen werden.

ToDo: Bild mit Zeilen/Spalten gemäß Doku des Shields:
Zeilen gehen von oben nach unten. Spalten von rechts nach links. Rechts oben ist (0,0) links unten ist (7,7).

Synopsis: [Fritzing-Part 🔗] [wolles-elektronikkiste.de/led-matrix-display-ansteuern 🔗]

Probeschaltung SPI-Schnittstelle
Probeschaltung SPI-Schnittstelle
Testaufbau mit Logic Analyzer und Punktmatrix
Testaufbau mit Logic Analyzer und Punktmatrix

Testcode

#include <SPI.h>
#define CS  D10 // CS Signal auf D10
#define UserButton  PC13 // Zum Umschalten des Musters
const unsigned char tabX[]= {0x81,0x42,0x24,0x18,0x18,0x24,0x42,0x81};  // Muster fuer X
const unsigned char tabO[]= {0x00,0x18,0x24,0x42,0x42,0x24,0x18,0x00};  // Muster fuer o

void send_data (unsigned char msb, unsigned char lsb){ // Hilfsfunktion zur Datenuebertragung
  digitalWrite(CS, LOW); // Uebertragung beginnt
  SPI.transfer(msb); // 8 Bit Transfer MSB      
  SPI.transfer(lsb); // 8 Bit Transfer LSB
  //SPI.transfer16(msb<<8 | lsb); // 16 Bit Transfer
  digitalWrite(CS, HIGH); // Uebertragung fertig, uebernehme Daten!
}
void setup(){
  pinMode(UserButton, INPUT); // Zum Umschalten des Musters
  pinMode(CS, OUTPUT);
  digitalWrite(CS, HIGH);
  SPI.begin();  // SPI-Schnittstelle einrichten fuer MAX7219
  SPI.setBitOrder(MSBFIRST);            // Hoechstwertiges Bit zuerst
  SPI.setClockDivider(SPI_CLOCK_DIV32); // SCK mit 1 MHz, koennte auch 10 MHz
  SPI.setDataMode(SPI_MODE0);           // SCK in Ruhe Low, Uebername bei steigender Flanke
  delay(10); // kurz warten bis verdaut..
  // MAX7219 Initialisierung des Chips
  send_data(0x09, 0x00); // Decoding:     Aus: keine BCD Dekodierung fuer 7 Segment
  send_data(0x0A, 0x05); // Intesity:     Helligkeit auf 5 von 15 einstellen
  send_data(0x0B, 0x07); // Scan Limit:   Es sind 8 Zeilen mit LEDs
  send_data(0x0C, 0x01); // Shutdown:     Bit0 = 1: Normal operation mode, kein Ruhezustand
  send_data(0x0F, 0x01); // Display Test: Bit0 = 1: Displaytest An: Alle LEDs an!
  delay(500); // 500 ms delay
  send_data(0x0F,0x00);  // Display Test: Bit0 = 0: Displaytest Aus: Anzeigen was in Register steht
  for(int adr=1; adr<=8; adr++){ // alle LEDs ausschalten, sonst wird zufaelliges Muster ausgegeben
    send_data(adr,0x00);         // Zeile = 0
  }
  delay(500); // 500 ms delay
}

void loop(){
  for(int adr=1; adr<9; adr++){  // alle Zeilen ausgeben
    send_data(adr,digitalRead(UserButton)?tabX[adr-1] :tabO[adr-1] ); //MSB = digit 1-8 Spalte  LSB Segment zeile high aktiv
  }
  delay(1000);
}

Mit Logic-Analyzer bei der Datenübertragung zuschauen

Mit dem Logic-Analyzer von Az-Delivery und der Software Logic2 von saleae dem Geschehen zuschauen, diese Einstellungen dafür machen:

Trigger einstellen
1. Trigger einstellen
SPI-Analyzer hinzufügen
2. SPI-Analyzer hinzufügen
SPI-Analyzer hinzufügen
3. SPI-Analyzer hinzufügen
SPI-Kanäle einstellen
4. SPI-Kanäle einstellen
Übertragung eines Bytes
Übertragung eines Bytes
Übertragung mehrerer Bytes
Übertragung mehrerer Bytes

Die Bits werden einfach durch die Schieberegister mit dem SCK-Takt durchgeschoben. Bei steigender Flanke von !CS werden sie Werte von den Bausteinen übernommen und ausgewertet. Die Ausgabe DOUT des Moduls wurde mit Kanal 3 des Logic-Analyzers gemessen, es ist gut zu sehen, dass einfach der Inhalt des 16Bit Schieberegisters weiter geschoben wird. Ein nachfolgendes Modul würde ihn erhalten.

Den MAX7219 prinzipiell verstehen

Im MAX7219 gibt es 14 8-Bit Register mit dem der Chip eingestellt und die Ausgabe in die Matrix gesteuert werden kann. Es werden 16-Bit Daten zum Chip übertragen, das MSByte gibt die Register-Adresse vor das LSByte den Inhalt. Hier ein Schnellüberblick. (Mehr Info? RTFM! Datenblatt )

NameD15-D12D11D10D9D8Adresse HexBeschreibung
No-OPX00000x00Tue nichts, wird zum Daten durchschieben zum nächsten Modul verwendet
Digit 0X00010x01Inhalt Digit0, Zeile 0
Digit 1X00100x02Inhalt Digit1, Zeile 1
Digit 2X00110x03Inhalt Digit2, Zeile 2
Digit 3X01000x04Inhalt Digit3, Zeile 3
Digit 4X01010x05Inhalt Digit4, Zeile 4
Digit 5X01100x06Inhalt Digit5, Zeile 5
Digit 6X01110x07Inhalt Digit6, Zeile 6
Digit 7X10000x08Inhalt Digit7, Zeile 7
Decode
Mode
X10010x09Bei 0 wird nichts dekodiert,
sonst kann eine BCD-Dekodierung ausgewählt werden
IntensityX10100x0ADas untere Nibble gibt die Helligkeit vor (0..15)
Scan LimitX10110x0BWie viele Stellen/Zeilen angeschlossen sind (0..7)
ShutdownX11000x0CBit0 = 1 Normal, bei Bit0 = 1 gilt Shutdown (aus)
Display TestX11110x0FBit0 = 0 Normal, bei Bit0 = 1 gilt Test, alle LED an
Überblick MAX7219 Register

4. Aufgabe: Einen Würfel bauen

Entwickeln Sie ein Programm für einen elektronischen Würfel mit Matrixanzeige.
Der User-Button dient dabei als Würfeln-Taste. Der Zufall entsteht durch die millis()-Funktion, die die Millisekunden seit dem Reset zählt.
Ein Zustandsdiagramm, eine Tabelle für die Würfelbildausgabe und ein wenig Quellcode sollen den Weg weisen.

Würfelbild für 3 Augen
Würfelbild für 3 Augen
wuerfel[]Zeile 0Zeile 1Zeile 2Zeile 3Zeile 4Zeile 5Zeile 6Zeile 7
0
1
20xC00xC00x000x180x180x000x030x03
3
4
5
Array für Würfelausgaben entwickeln
Tabelle Lösung
wuerfel[]Zeile 0Zeile 1Zeile 2Zeile 3Zeile 4Zeile 5Zeile 6Zeile 7
00x000x000x000x180x180x000x000x00
10xC00xC00x000x000x000x000x030x03
20xC00xC00x000x180x180x000x030x03
30xC30xC30x000x000x000x000xC30xC3
40xC30xC30x000x180x180x000xC30xC3
50xC30xC30x000xC30xC30x000xC30xC3
Array für Würfelausgaben entwickeln

Codevorgabe

#include <SPI.h>
#define CS  D10 // CS Signal auf D10
#define USER_BUTTON  PC13 // Zum Umschalten des Musters

typedef enum {ANZEIGEN,WUERFELN} zustandstyp;  // Aufzaehlungstyp
zustandstyp zustand = ANZEIGEN; // Startzustand
unsigned char zahl=0;
const unsigned char wuerfel[6][8]= {{}, // Muster fuer Wuerfel
                                    {},
                                    {0xC0,0xC0,0x00,0x18,0x18,0x00,0x03,0x03},
                                    {},
                                    {},
                                    {}};  

void send_data (unsigned char msb, unsigned char lsb){ // Hilfsfunktion zur Datenuebertragung
  digitalWrite(CS, LOW); // Uebertragung beginnt
  SPI.transfer(msb); // 8 Bit Transfer MSB      
  SPI.transfer(lsb); // 8 Bit Transfer LSB
  //SPI.transfer16(msb<<8 | lsb); // 16 Bit Transfer
  digitalWrite(CS, HIGH); // Uebertragung fertig, uebernehme Daten!
}
void setup(){
  pinMode(USER_BUTTON, INPUT); // Zum Umschalten des Musters
  pinMode(CS, OUTPUT);
  digitalWrite(CS, HIGH);
  SPI.begin();  // SPI-Schnittstelle einrichten fuer MAX7219
  SPI.setBitOrder(MSBFIRST);            // Hoechstwertiges Bit zuerst
  SPI.setClockDivider(SPI_CLOCK_DIV32); // SCK mit 1 MHz, koennte auch 10 MHz
  SPI.setDataMode(SPI_MODE0);           // SCK in Ruhe Low, Uebername bei steigender Flanke
  delay(10); // kurz warten bis verdaut..
  // MAX7219 Initialisierung des Chips
  send_data(0x09, 0x00); // Decoding:     Aus: keine BCD Dekodierung fuer 7 Segment
  send_data(0x0A, 0x03); // Intesity:     Helligkeit auf 5 von 15 einstellen
  send_data(0x0B, 0x07); // Scan Limit:   Es sind 8 Zeilen mit LEDs
  send_data(0x0C, 0x01); // Shutdown:     Bit0 = 1: Normal operation mode, kein Ruhezustand
  send_data(0x0F, 0x01); // Display Test: Bit0 = 1: Displaytest An: Alle LEDs an!
  delay(500); // 500 ms delay
  send_data(0x0F,0x00);  // Display Test: Bit0 = 0: Displaytest Aus: Anzeigen was in Register steht
  for(int adr=1; adr<=8; adr++){ // alle LEDs ausschalten, sonst wird zufaelliges Muster ausgegeben
    send_data(adr,0x00);         // Zeile = 0
  }
  delay(500); // 500 ms delay
  anzeigen();
}

void anzeigen(){
  
}

void loop(){
  switch(zustand){
    
  }
}

Aufgabe: Beim Würfeln heller leuchten

Erweitern Sie das Programm entsprechend des Zustandsdiagramms, damit beim Würfeln (solange USER_BUTTON) die Anzeige etwas heller leuchtet.

Lösungsvorschlag
#include <SPI.h>
#define CS  D10 // CS Signal auf D10
#define USER_BUTTON  PC13 // Zum Umschalten des Musters

typedef enum {ANZEIGEN,WUERFELN} zustandstyp;  // Aufzaehlungstyp
zustandstyp zustand = ANZEIGEN; // Startzustand
unsigned char zahl=0;
const unsigned char wuerfel[6][8]= {{0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00}, // Muster fuer Wuerfel
                                    {0xC0,0xC0,0x00,0x00,0x00,0x00,0x03,0x03},
                                    {0xC0,0xC0,0x00,0x18,0x18,0x00,0x03,0x03},
                                    {0xC3,0xC3,0x00,0x00,0x00,0x00,0xC3,0xC3},
                                    {0xC3,0xC3,0x00,0x18,0x18,0x00,0xC3,0xC3},
                                    {0xC3,0xC3,0x00,0xC3,0xC3,0x00,0xC3,0xC3}};  

void send_data (unsigned char msb, unsigned char lsb){ // Hilfsfunktion zur Datenuebertragung
  digitalWrite(CS, LOW); // Uebertragung beginnt
  SPI.transfer(msb); // 8 Bit Transfer MSB      
  SPI.transfer(lsb); // 8 Bit Transfer LSB
  //SPI.transfer16(msb<<8 | lsb); // 16 Bit Transfer
  digitalWrite(CS, HIGH); // Uebertragung fertig, uebernehme Daten!
}
void setup(){
  pinMode(USER_BUTTON, INPUT); // Zum Umschalten des Musters
  pinMode(CS, OUTPUT);
  digitalWrite(CS, HIGH);
  SPI.begin();  // SPI-Schnittstelle einrichten fuer MAX7219
  SPI.setBitOrder(MSBFIRST);            // Hoechstwertiges Bit zuerst
  SPI.setClockDivider(SPI_CLOCK_DIV32); // SCK mit 1 MHz, koennte auch 10 MHz
  SPI.setDataMode(SPI_MODE0);           // SCK in Ruhe Low, Uebername bei steigender Flanke
  delay(10); // kurz warten bis verdaut..
  // MAX7219 Initialisierung des Chips
  send_data(0x09, 0x00); // Decoding:     Aus: keine BCD Dekodierung fuer 7 Segment
  send_data(0x0A, 0x03); // Intesity:     Helligkeit auf 5 von 15 einstellen
  send_data(0x0B, 0x07); // Scan Limit:   Es sind 8 Zeilen mit LEDs
  send_data(0x0C, 0x01); // Shutdown:     Bit0 = 1: Normal operation mode, kein Ruhezustand
  send_data(0x0F, 0x01); // Display Test: Bit0 = 1: Displaytest An: Alle LEDs an!
  delay(500); // 500 ms delay
  send_data(0x0F,0x00);  // Display Test: Bit0 = 0: Displaytest Aus: Anzeigen was in Register steht
  for(int adr=1; adr<=8; adr++){ // alle LEDs ausschalten, sonst wird zufaelliges Muster ausgegeben
    send_data(adr,0x00);         // Zeile = 0
  }
  delay(500); // 500 ms delay
  anzeigen();
}

void anzeigen(){
  for(int adr=1; adr<9; adr++){  // alle Zeilen ausgeben
    send_data(adr,wuerfel[zahl][adr-1]); //MSB = digit 1-8 Spalte  LSB Segment zeile high aktiv
  }
}

void loop(){
  switch(zustand){
    case ANZEIGEN:
      if(!digitalRead(USER_BUTTON)){
        zustand = WUERFELN;
        delay(20);
        send_data(0x0A, 0x0F);
      } 
      break;
    case WUERFELN:
      if(digitalRead(USER_BUTTON)){
        zustand = ANZEIGEN;
        delay(20);
        send_data(0x0A, 0x02);
      } 
      zahl=millis()%6;
      anzeigen();
  }
}

Bonus: 4 Würfel auf einer Anzeige

Es sollen nun 4 Würfel angezeigt werden.
Entwickeln Sie eine Lösung…

ToDo: Lösung erstellen

4 Würfel auf einer Anzeige
4 Würfel auf einer Anzeige

5. Matrix-Animationen ausgeben: TG IT 12

Bitmuster erstellen z.B. mit [tools.mezmedia.de/LEDCube-Pattern-Tool-master/index.html] Quelle: [github.com/stocka/LEDCube-Pattern-Tool]

Animationscode

#include <SPI.h>
#define CS  D10
struct imElt { // Datenstruktur fuer Daten und Dauer der Anzeige
  unsigned char z[8];
  unsigned long int imageDuration;
}
const imageTab[] = { // TG IT 12
 { 0b00000000, 0b11100110, 0b01001000, 0b01001000, 0b01001010, 0b01001001, 0b01000110, 0b00000000, 10000 }, 
 { 0b00000000, 0b01011100, 0b01001000, 0b01001000, 0b01001000, 0b01001000, 0b01001000, 0b00000000, 10000 }, 
 { 0b00000000, 0b00100100, 0b01101010, 0b00100010, 0b00100100, 0b00101000, 0b00101110, 0b00000000, 10000 }, 
 { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
};

void send_data (unsigned char msb, unsigned char lsb){ // Hilfsfunktion
  digitalWrite(CS, LOW); // Uebertragung beginnt
  SPI.transfer(msb); // 8 Bit        
  SPI.transfer(lsb);
  //SPI.transfer16(msb<<8 | lsb); // 16 Bit
  digitalWrite(CS, HIGH); // Uebertragung fertig
}
void setup(){
  pinMode(CS, OUTPUT);
  digitalWrite(CS, HIGH);
  SPI.begin();  // SPI-Schnittstelle einrichten fuer MAX7219
  SPI.setBitOrder(MSBFIRST);            // Hoechstwertiges Bit zuerst
  SPI.setClockDivider(SPI_CLOCK_DIV32); // SCK mit 1 MHz, koennte auch 10 MHz
  SPI.setDataMode(SPI_MODE0);           // SCK in Ruhe Low, Uebername bei steigender Flanke
  delay(10); // kurz warten bis verdaut..
  // MAX7219 Initialisierung des Chips
  send_data(0x09, 0x00); // Decoding:     Aus: keine BCD Dekodierung fuer 7 Segment
  send_data(0x0A, 0x05); // Intesity:     Helligkeit auf 5 von 15 einstellen
  send_data(0x0B, 0x07); // Scan Limit:   Es sind 8 Zeilen mit LEDs
  send_data(0x0C, 0x01); // Shutdown:     Bit0 = 1: Normal operation mode, kein Ruhezustand
  send_data(0x0F, 0x01); // Display Test: Bit0 = 1: Displaytest An: Alle LEDs an!
  delay(500); // 500 ms delay
  send_data(0x0F,0x00);  // Display Test: Bit0 = 0: Displaytest Aus: Anzeigen was in Register steht
  for(int adr=1; adr<=8; adr++){ // alle LEDs ausschalten, sonst wird zufaelliges Muster ausgegeben
    send_data(adr,0x00);         // Zeile = 0
  }
  delay(500); // 500 ms delay
}

void displayImage(int index){ // Daten zur Anzeige uebertragen
  unsigned long int duration = imageTab[index].imageDuration;
  unsigned char adr;
  for(int adr=1; adr<9; adr++){
	  send_data(adr,imageTab[index].z[adr-1]);
  }
  delay(duration/10);
}

void loop(){
  int i = 0;
    do {
      displayImage(i);
      i++;
    } while (imageTab[i].imageDuration != 0); // Bis Animation Ende
}

Weiteres Tool zum Erstellen

Allerdings können damit keine Zeiten eingestellt werden: [xantorohara.github.io/led-matrix-editor/]

ToDo: Code zur Auswertung erstellen…

6. Zwei Anzeigen kaskadieren

Einfach die Ausgänge der ersten Anzeige mit den Eingängen der zweiten Anzeige verbinden. Zunächst ist zu sehen, dass die zweite Anzeige eigentlich das gleiche wie die erste Anzeige ausgibt, es wird ja schlicht mit jedem neuen Transfer der Inhalt des letzten Transfers weiter geschoben. Will man etwas unterschiedliches anzeigen müssen die Daten passend bei der Übernahme durch CS an der jeweiligen Anzeige anliegen, hier ein Testcode:

#include <SPI.h>
#define CS  D10 // CS Signal auf D10
#define UserButton  PC13 // Zum Umschalten des Musters
const unsigned char tabX[]= {0x81,0x42,0x24,0x18,0x18,0x24,0x42,0x81};  // Muster fuer X
const unsigned char tabO[]= {0x00,0x18,0x24,0x42,0x42,0x24,0x18,0x00};  // Muster fuer o

void send_data (unsigned char msb, unsigned char lsb){ // Hilfsfunktion zur Datenuebertragung
  digitalWrite(CS, LOW); // Uebertragung beginnt
  SPI.transfer(msb); // 8 Bit Transfer MSB      
  SPI.transfer(lsb); // 8 Bit Transfer LSB
  //SPI.transfer16(msb<<8 | lsb); // 16 Bit Transfer
  digitalWrite(CS, HIGH); // Uebertragung fertig, uebernehme Daten!
}
void send_data_2 (unsigned char msb, unsigned char lsb,unsigned char lsb2){ // Hilfsfunktion zur Datenuebertragung
  digitalWrite(CS, LOW); // Uebertragung beginnt
  SPI.transfer(msb); // 8 Bit Transfer MSB      
  SPI.transfer(lsb); // 8 Bit Transfer LSB
  SPI.transfer(msb); // 8 Bit Transfer MSB      
  SPI.transfer(lsb2); // 8 Bit Transfer LSB
  //SPI.transfer16(msb<<8 | lsb); // 16 Bit Transfer
  digitalWrite(CS, HIGH); // Uebertragung fertig, uebernehme Daten!
}
void setup(){
  pinMode(UserButton, INPUT); // Zum Umschalten des Musters
  pinMode(CS, OUTPUT);
  digitalWrite(CS, HIGH);
  SPI.begin();  // SPI-Schnittstelle einrichten fuer MAX7219
  SPI.setBitOrder(MSBFIRST);            // Hoechstwertiges Bit zuerst
  SPI.setClockDivider(SPI_CLOCK_DIV32); // SCK mit 1 MHz, koennte auch 10 MHz
  SPI.setDataMode(SPI_MODE0);           // SCK in Ruhe Low, Uebername bei steigender Flanke
  delay(10); // kurz warten bis verdaut..
  // MAX7219 Initialisierung des Chips
  send_data(0x09, 0x00); // Decoding:     Aus: keine BCD Dekodierung fuer 7 Segment
  send_data(0x0A, 0x05); // Intesity:     Helligkeit auf 5 von 15 einstellen
  send_data(0x0B, 0x07); // Scan Limit:   Es sind 8 Zeilen mit LEDs
  send_data(0x0C, 0x01); // Shutdown:     Bit0 = 1: Normal operation mode, kein Ruhezustand
  send_data(0x0F, 0x01); // Display Test: Bit0 = 1: Displaytest An: Alle LEDs an!
  send_data(0,0); // noch einen weiter schieben, damit Anzeige 2 auch initialisiert ist
  delay(500); // 500 ms delay
  send_data(0x0F,0x00);  // Display Test: Bit0 = 0: Displaytest Aus: Anzeigen was in Register steht
  for(int adr=1; adr<=8; adr++){ // alle LEDs ausschalten, sonst wird zufaelliges Muster ausgegeben
    send_data(adr,0x00);         // Zeile = 0
  }
  delay(500); // 500 ms delay
}

void loop(){
  for(int adr=1; adr<9; adr++){  // alle Zeilen ausgeben
    send_data_2(adr,!digitalRead(UserButton)?tabX[adr-1] :tabO[adr-1],digitalRead(UserButton)?tabX[adr-1] :tabO[adr-1]);
  delay(1000);
}

7. Aufgabe: 7-Segment Modul anschließen und damit einen Millisekundenzähler betreiben

Schließen Sie statt der Punktmatrixanzeige das Modul mit den 8 7-Segmentanzeigen an. Um nun Ziffern anzeigen zu können kann entweder in den BCD-Modus umgeschaltet werden oder mit einem Array const unsigned char tab7Seg[] = {..} die Ziffer umgewandelt werden. Die Reihenfolge der Segmente ist in der Tabelle zu sehen.
Erstellen Sie den Hex-Code für das Array und den Programm-Code für eine Millisekundenanzeige.

76543210
DPABCDEFG
7Segmentreihenfolge
Lösungsvorschlag
#include <SPI.h>
#define CS  D10 // CS Signal auf D10

const unsigned char tab7Seg[] = {0x7E, 0x30, 0x6D, 0x79, 0x33, 0x5B, 0x5F, 0x70, 0x7F, 0x7B}; // Umwandlung

void send_data (unsigned char msb, unsigned char lsb){ // Hilfsfunktion zur Datenuebertragung
  digitalWrite(CS, LOW); // Uebertragung beginnt
  SPI.transfer(msb); // 8 Bit Transfer MSB      
  SPI.transfer(lsb); // 8 Bit Transfer LSB
  //SPI.transfer16(msb<<8 | lsb); // 16 Bit Transfer
  digitalWrite(CS, HIGH); // Uebertragung fertig, uebernehme Daten!
}
void setup(){
  pinMode(CS, OUTPUT);
  digitalWrite(CS, HIGH);
  SPI.begin();  // SPI-Schnittstelle einrichten fuer MAX7219
  SPI.setBitOrder(MSBFIRST);            // Hoechstwertiges Bit zuerst
  SPI.setClockDivider(SPI_CLOCK_DIV32); // SCK mit 1 MHz, koennte auch 10 MHz
  SPI.setDataMode(SPI_MODE0);           // SCK in Ruhe Low, Uebername bei steigender Flanke
  delay(10); // kurz warten bis verdaut..
  // MAX7219 Initialisierung des Chips
  //send_data(0x09, 0x00); // Decoding:     Aus: keine BCD Dekodierung fuer 7 Segment
  send_data(0x09, 0xFF); // Decoding:     An: BCD Dekodierung fuer 7 Segment
  send_data(0x0A, 0x05); // Intesity:     Helligkeit auf 5 von 15 einstellen
  send_data(0x0B, 0x07); // Scan Limit:   Es sind 8 Zeilen mit LEDs
  send_data(0x0C, 0x01); // Shutdown:     Bit0 = 1: Normal operation mode, kein Ruhezustand
  send_data(0x0F, 0x01); // Display Test: Bit0 = 1: Displaytest An: Alle LEDs an!
  delay(500); // 500 ms delay
  send_data(0x0F,0x00);  // Display Test: Bit0 = 0: Displaytest Aus: Anzeigen was in Register steht
  for(int adr=1; adr<=8; adr++){ // alle LEDs ausschalten, sonst wird zufaelliges Muster ausgegeben
    send_data(adr,0x00);         // Zeile = 0
  }
  delay(500); // 500 ms delay
}

void loop(){
  unsigned long ausgabe=millis(); // 87654321; 
  for(int adr=1; adr<9; adr++){  // alle Stellen ausgeben
    //send_data(adr,tab7Seg[ausgabe%10]); // ohne Verwendung des Dekoders mit eigener Tabelle
    send_data(adr,ausgabe%10);
    ausgabe/=10;
  }
}

Aufgabe: Führende Nullen unterdrücken

Die führenden Nullen sollen nun unterdrückt werden, ausserdem soll ein Dezimalpunkt bei Sekunde eingefügt werden.
Tipp: Verwenden Sie ein Array unsigned char stelle[8] in dem Sie zunächst die Ausgabe einspeichern und bei der Ausgabe bei den führenden Nullen entweder 0xF bei Verwendung des BCD-Modus oder tab7Seg[8] mit Erweiterung der Tabelle um einen Element 0x00 ausgeben. Mit | 0x80 lässt sich der Dezimalpunkt einschalten.
Erstellen Sie den Code.

Lösungsvorschlag
void loop(){
  unsigned long ausgabe=millis();
  unsigned char stelle[8];
  for(int i=0;i<8;i++){
    stelle[i]=ausgabe%10;
    if (i==3) stelle[i]|=0x80; // Dezimalpunkt einbauen
    ausgabe/=10;
  }
  unsigned char blank=1;
  for(int adr=8; adr>=1; adr--){  // alle Stellen ausgeben
    if(blank && stelle[adr-1]==0){
      send_data(adr,0xF); // Blank ausgeben
    }
    else{
      send_data(adr,stelle[adr-1]);
      blank=0;
    }
  }
}

Aufgabe: Anzeige von millis() in Stunden.Minuten.Sekunden.Hundertstel

Die Millisekunden sollen nun im Format Stunden.Minuten.Sekunden.Hundertstel angezeigt werden: 2.59.59.99
Ausserdem sollen dabei führende Nullen unterdrückt werden.

Stunden.Minuten.Sekunden.Hundertstel
Stunden.Minuten.Sekunden.Hundertstel
Code-Tipp anzeigen
stelle[3]=ausgabe%6;
ausgabe/=6;
stelle[4]=ausgabe%10 | 0x80; // Minuten mit Dezimalpunkt
ausgabe/=10;
Lösungsvorschlag
void loop(){
  unsigned long ausgabe=millis();
  unsigned char stelle[8]={0,0,0,0,0,0,0,0};
  ausgabe/=10; // in hundertstel umwandeln
  stelle[0]=ausgabe%10; // hundertstel
  ausgabe/=10;
  stelle[1]=ausgabe%10; // zehntel
  ausgabe/=10;
  stelle[2]=ausgabe%10 | 0x80; // Sekunden mit Dezimalpunkt
  ausgabe/=10;
  stelle[3]=ausgabe%6;
  ausgabe/=6;
  stelle[4]=ausgabe%10 | 0x80; // Minuten mit Dezimalpunkt
  ausgabe/=10;
  stelle[5]=ausgabe%6;
  ausgabe/=6;
  stelle[6]=ausgabe%10 | 0x80; // Stunden mit Dezimalpunkt
  ausgabe/=10;
  stelle[7]=ausgabe%10;
  unsigned char blank=1;
  for(int adr=8; adr>=1; adr--){  // alle Stellen ausgeben
    if(blank && (stelle[adr-1]&~0x80)==0){
      send_data(adr,0xF); // Blank ausgeben
    }
    else{
      send_data(adr,stelle[adr-1]);
      blank=0;
    }
  }
}

Aufgabenidee: Countdownzähler für ABI-Prüfung

Eine 7-Segmentanzeige zeigt die verbleibende Zeit bis zur Prüfung an z.B: 365d 11h

8. 🤐 LED&KEY Module mit TM1638

In den Abi-Übungen taucht ein für mich neues Shield auf: [amzon] Doku fand ich das, leider kein schönes Datenblatt dabei:

Dachte erst es wäre mit SPI einfach, aber besser nutzt man wohl die Library. Hab ein paar Dinger bestellt, ob ich sie gut finden werde?