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?