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 = 0 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. Aufgaben: Mit MAX7219 7-Segment Modul Zeitzähler bauen

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.

Ziffer76543210HexWert
DPABCDEFG
0011111100x7E
7Segmentreihenfolge
7Segmente MAX7219
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); // falls der BCD-Dekoder verwendet wird
    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,0);   // Blank ausgeben ohne BCD-Dekoder
      //send_data(adr,0xF); // Blank ausgeben mit BCD-Dekoder
    }
    else{
      send_data(adr,tab7Seg[ausgabe%10]); // ohne Verwendung des BCD-Dekoders mit eigener Tabelle
      //send_data(adr,stelle[adr-1]);     // mit BCD-Dekoder
      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
Verwenden Sie diesmal den BCD-Dekoder des Bausteins, 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;
    }
  }
}

🚧 Aufgabe: Countdownzähler bis ABI-Prüfung

Eine 7-Segmentanzeige zeigt die verbleibende Zeit in Tagen 0.9999 4-stellig mit unterdrückten führenden Nullen bis zur zur Prüfung an z.B: ABI 60d siehe Bild. Wegen der Buchstaben kann der BCD-Dekoder des Bausteins nicht verwendet werden, Sie benötigen eine Umrechnungstabelle für die Ziffern 0..9 tab7Seg[] und die Kenntnis der Hexcodes für A,B,C,d,!.

Ziffer76543210HexWert
DPABCDEFG
A011101110x77
B
I
d
!
HEX-Werte für Zeichen ermitteln

Die Restzeit in Tagen kann mit Datumseingabe im Format ttmmjj zweier Tage (ABI-Datum und heute) über Serielle Schnittstelle oder stellenweise mittels Tasten PB0,PB3,PB4 eingegeben werden (die PA-Tasten kollidieren mit der SPI-Schnittstelle). Um die Tagedifferenz zu ermitteln wird das Datum auf das Julianische Datum🔗 umgerechnet.

ABI Zähler
ABI Zähler ABI 60d
7Segmente MAX7219
long julianTag(int tag, int monat, int jahr){ // Umrechnen auf Tag
  jahr+=8000;
  if(monat<3) { jahr--; monat+=12; }
  return (jahr*365L) +(jahr/4) -(jahr/100) +(jahr/400) -1200820 +(monat*153+3)/5-92 +tag-1;
}
UART Timingdiagramm
UART Timingdiagramm

Teilaufgabenideen

  1. Serielle Schnittstelle erklären und Timingdiagramm analysieren.
  2. ISR einstellen und Array mit Resttagen erniedrigen
  3. Zustandsdiagramm in Code umsetzen
  4. Helligkeit der Anzeige einstellen
Lösungsvorschlag
#include <SPI.h>
#define CS  D10 // CS Signal auf D10
static HardwareTimer mytimer = HardwareTimer(TIM2);  // Timerinstanz sowie Timeruaswahl

const unsigned char tab7Seg[] = {0x7E, 0x30, 0x6D, 0x79, 0x33, 0x5B, 0x5F, 0x70, 0x7F, 0x7B}; // Umwandlung
unsigned char tage[4]={0,0,0,0}; // Tageanzahl mit 4 Stellen 0..9999 Einer zuerst
const unsigned char maxDatum[3]={31,12,99}; // Maximalwerte bei Datumseingabe via UART
int stelle; // Stelle zum Einstellen 0..3
int helligkeit=5;
int tagessekunden=0; // max: 60*60*24 = 86400
long tagedifferenz=0;
enum zustandstyp {ANZEIGEN,STELLEN}; // definiere Aufzählungstyp
enum zustandstyp zustand = ANZEIGEN; // definiere und initialisiere Variable

#define BUTTONREADER GPIOB->IDR & 0b00000011001 // nur die relevanten Tastenbits
int buttonOld = 0;              // alter Tasten-Zustand 
int buttonEnter,buttonExit;     // gedrueckte und losgelassene Tasten 

void buttonCheck(){             // Tastaturabfrage mit Flankendedektion  
  int buttonTest,tmp; 
  buttonEnter = 0, buttonExit = 0; 
  buttonTest = BUTTONREADER;       // Einlesen
  if (buttonOld != buttonTest){    // hat sich was getan 
    delay(10);                      // 5ms Prellen abwarten 
    tmp = BUTTONREADER;            // noch mal Einlesen
    if (tmp == buttonTest){        // ist es stabil? 
      buttonEnter = (~buttonOld) & buttonTest; // steigende Flanke !alt und neu 
      buttonExit = buttonOld & (~buttonTest);  // fallende Flanke alt und !neu 
      buttonOld = buttonTest; 
    } 
  } 
}
long julianDay(int tag, int monat, int jahr){ // Rückgabewert dieser Funktion ist "long"
  jahr+=8000;
  if(monat<3) { jahr--; monat+=12; }
  return (jahr*365L) +(jahr/4) -(jahr/100) +(jahr/400) -1200820 +(monat*153+3)/5-92 +tag-1;
}

void isr_Sec(){ // Sekunden verarbeiten
  tagessekunden++;
  if(tagessekunden>=86400-1){ // ein Tag ist rum, Resttage erniedrigen 86400-1
    tagessekunden=0;
    int i=0;
    while(i<4){
      if(tage[i]>0){ // abziehen möglich
        tage[i]--;
        break;
      }
      tage[i]=9;
      i++;
    }
    if (tage[0]==0 && tage[1]==0 && tage[2]==0 && tage[3]==0){
      mytimer.pause();
      for(i=0;i<6;i++){
        send_data(i,0xB0); // ! ausgeben
      }
      return;
    }
  }  
  ausgebenZeit();  // Zeit ausgeben
}
void ausgebenZeit(){  // Zeit auf Anzeige ausgeben
  static int n=0;
  n++;
  unsigned char blank=1;
  for(int adr=5; adr>=2; adr--){  // alle Stellen ausgeben
    if(zustand==STELLEN && n%8 && adr-2!=stelle){ // dunkler machen der anderen Stellen
      send_data(adr,0x00); // Blank ausgeben
    }
    else if(blank && tage[adr-2]==0){
      if(zustand==STELLEN) send_data(adr,tab7Seg[0]); // 0 ausgeben
      else send_data(adr,0x00); // Blank ausgeben
    }
    else{
      send_data(adr,tab7Seg[tage[adr-2]]);
      blank=0;
    }
  }
  if(tagessekunden&1)send_data(1,0); // 'd' blinken lassen..
  else send_data(1,0x3D); // d
}
void stelleUhrMitString(String s){ // Zeit mittels String einstellen
  unsigned char datum[3]={0,0,0}; // Tag Monat Jahr
  if(s.length()!=6){
    Serial.printf("Laenge stimmt nicht: %d ",s.length()); 
    Serial.println(s); // printf ist bei %s gerade kaputt..
    return;
  }
  int i,c,t; // temporäre Variablen
  for(i=0;i<6;i++){ // String durchlaufen
    c=s[i];
    if (c>='0' && c<='9'){  // ist es eine Ziffer?
      t= (c-'0')*10;        // Ziffer in Wert umwandlen
    }
    else goto fehler;
    i++;
    c=s[i];
    if (c>='0' && c<='9'){
      t+= c-'0';
    }
    else goto fehler;
    if(t>maxDatum[i/2]) goto fehler;
    datum[i/2]=t;
  }
  t=julianDay(datum[0],datum[1],datum[2]+2000);
  Serial.printf("JulianTag: %d \n",t);
  if(tagedifferenz==0) tagedifferenz=t;
  else if(tagedifferenz>t)tagedifferenz-=t;
  else tagedifferenz=t-tagedifferenz;
  Serial.printf("tagedifferenz: %d \n",tagedifferenz);
  t=tagedifferenz;
  for(i=0;i<4;i++){ // Stellen einstellen
    tage[i]=t%10;
    t/=10;
  }
  return;
  fehler:
    Serial.printf("Ziffer stimmt nicht an Stelle: %d ",i);
    Serial.println(s); // printf ist bei %s gerade kaputt..
}
void incStelle(){ // Stelle erhöhen
  if(tage[stelle]<9){
    tage[stelle]++;
  }
  else tage[stelle]=0;
}
void decStelle(){ // Stelle erniedrigen
  if(tage[stelle]==0){
    tage[stelle]=9;
  }
  else tage[stelle]--;
}
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);
  pinMode(PB0,INPUT_PULLDOWN);  // Zeit einstellen
  pinMode(PB3,INPUT_PULLDOWN);  // rauf
  pinMode(PB4,INPUT_PULLDOWN);  // runter
  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(300);
  send_data(0x0F,0x00);  // Display Test: Bit0 = 0: Displaytest Aus: Anzeigen was in Register steht
  send_data(8,0x77); // A
  send_data(7,0x7F); // B
  send_data(6,0x06); // I
  send_data(1,0x3D); // d
  ausgebenZeit();
  mytimer.setOverflow(1000);         // 1000 ms
  mytimer.setPrescaleFactor(32000);  // 1 kHz
  mytimer.attachInterrupt(isr_Sec);  // Sekunde ist rum
  Serial.begin(9600); // Serielle Schnittstelle mit 9600 Baud starten
}
#define T_EINSTELLEN buttonEnter&(1<<0) 
#define T_HOCH buttonEnter&(1<<3)
#define T_RUNTER buttonEnter&(1<<4)
void loop(){
  String s;
  buttonCheck();
  switch (zustand){
    case ANZEIGEN:
      if(Serial.available()){ // wenn Zeichen gesendet wurden
        s = Serial.readStringUntil('\n'); // lese Daten in String ein
        stelleUhrMitString(s);
        tagessekunden=0;
        mytimer.resume();
      }
      if(T_EINSTELLEN){ // wenn Taste PB0 gedrückt
        zustand=STELLEN;
        stelle=0;
        mytimer.pause(); // Timer stoppen
      }
      if(T_HOCH){ // wenn Taste PB3 gedrückt
        if(helligkeit<15) helligkeit++;
        send_data(0x0A, helligkeit);
      }
      if(T_RUNTER){ // wenn Taste PB4 gedrückt
        if(helligkeit>0) helligkeit--;
        send_data(0x0A, helligkeit);
      }
      break;
    case STELLEN:
      ausgebenZeit();
      if(T_EINSTELLEN){ // wenn Taste PB0 gedrückt
        if(stelle==3){
          zustand=ANZEIGEN;
          tagessekunden=0;
          mytimer.resume(); // Timer starten
        }
        else{
          stelle++;
        }
      }
      if(T_HOCH){ // wenn Taste PB3 gedrückt
        incStelle();
      }
      if(T_RUNTER){ // wenn Taste PB4 gedrückt
        decStelle();
      }
  }
  delay(2);
}

8. 🚧 RFID-Modul MFRC522

Synopsis: [funduino.de/nr-18-rfid-kit] [Arduino RFID Library for MFRC522] [www.nxp.com/docs/en/data-sheet/MFRC522.pdf] [Security Access using MFRC522 RFID Reader with Arduino]

Die günstig zu erwerbenden RFID-Module werden über die SPI-Schnittstelle angeschlossen, obgleich der Chip auch I2C und RS232 könnte.

RFID mit STM32 und Logic Analyzer
RFID mit STM32 und Logic Analyzer
Verkabelung im Detail

Folgender Code soll die UID („Unique Identification Number“) einer Karte aus lesen und ausgeben. Dazu muss die Library MFRC522 Installiert werden siehe z.B. funduino.de/nr-18-rfid-kit . Leider wird dabei eine Fehlermeldung wegen Pointer-Vergleich angegeben, die Library muss für STM32 modifiziert werden:
Statt > 0 sollte ein != 0 in den Zeilen der Fehlermeldung stehen:

// in MFRC522Extended.cpp Zeile 824 und 847
if (backData && (backLen != 0)) { // statt backLen > 0
#include "MFRC522.h" // RFID-Bibiothek hinzufügen

#define SDA 10 // SS bzw. CS
#define RST 9  // Reset bzw. Sleep bei Log. 0

MFRC522 mfrc522(SDA, RST); // RFID-Empfänger benennen, Pins zuordnen

void setup() {
  Serial.begin(9600);
  SPI.begin();
  mfrc522.PCD_Init(); // Initialisierung des RFID-Empfängers
}

void loop() {
  String WertHEX;
  if (!mfrc522.PICC_IsNewCardPresent()) { // Wenn keine Karte in Reichweite ist ..
    // .. wird die Abfrage wiederholt.
    return;
  }
  if (!mfrc522.PICC_ReadCardSerial()) { // Wenn kein RFID-Sender ausgewählt wurde ..
    // .. wird die Abfrage wiederholt.
    return;
  }
  Serial.println("Karte entdeckt!");
  for (byte i = 0; i < mfrc522.uid.size; i++) { // für alle Werte
    WertHEX = WertHEX + String(mfrc522.uid.uidByte[i], HEX) + " ";
  }
  Serial.println("Hexwerte: " + WertHEX);
  delay(1000);
}

Mit dem Logic Analyzer wurde die ständige Abfrage ob ein RFID-Tag vorhanden ist aufgenommen:

SPI-Signale bei RFID-Modul-Abfrage
SPI-Signale bei RFID-Modul-Abfrage

Aufgaben

  1. Erklären Sie am Beispiel des Timing-Diagramms die Funktionsweise der SPI Datenübertragung.
  2. Mit welchem SPI-Mode werden die Daten übertragen, Begründung.
  3. Wird MSB- oder LSB-First übertragen, Begründung.
  4. Mit welcher Frequenz wird übertragen, begründete Abschätzung reicht.

9. 🤐 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?