3.8 🍀 SPI Schnittstelle
- Funktionsprinzip der SPI-Schnittstelle
- Einen 74HC595 als PortExpander verwenden
- Übertragung zu MAX7219 mit 8×8 Matrix-LED-Anzeige
- Aufgabe: Einen Würfel bauen
- Matrix-Animationen ausgeben: TG IT 12
- Zwei Anzeigen kaskadieren
- 7-Segment Modul anschließen und damit einen Millisekundenzähler betreiben
- 🤐 LED&KEY Module mit TM1638
Synopsis: [az-delivery.de/products/saleae-logic-analyzer]
[mikrocontroller.net/articles/Serial_Peripheral_Interface]
[mikrocontroller.net/articles/SPI_Daisychain]
[docs.arduino.cc/learn/communication/spi]
[analog.com/media/en/technical-documentation/data-sheets/MAX7219-MAX7221.pdf]
[learn.sparkfun.com/tutorials/serial-peripheral-interface-spi/all]
[ti.com/lit/ds/symlink/sn74hc595.pdf]
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.
- SCK bis zu 10MHz schnell
- 8 oder 16 Bit Übertragungen
- Bidirektionale Übertragung möglich
- Sehr einfaches Protokoll
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:
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.
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) | Bedeutung | Aus Sicht der Peripherie |
---|---|---|---|
Master In Slave Out (MISO) | Controller In, Peripheral Out (CIPO) [POCI] | Datenübertragung vom µC zur Peripherie | Serial Data Out (SDO) [DOUT] |
Master Out Slave In (MOSI) | Controller Out Peripheral In (COPI) [PICO] | Datenübertragung von Peripherie zu µC | Serial 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 |
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) Modus | CPOL (Clock Polarity) | CPHA (Clock Phase) | Bedeutung |
---|---|---|---|
0 | 0 Clock Idle Low | 0 Übernahme bei 1. Flanke | SCK in Ruhe 0 Übernahme bei steigender Flanke _↑¯|_↑¯|_ |
1 | 0 Clock Idle Low | 1 Übernahme bei 2. Flanke | SCK in Ruhe 0 Übernahme bei fallender Flanke _|¯↓_|¯↓_ |
2 | 1 Clock Idle High | 0 Übernahme bei 1. Flanke | SCK in Ruhe 1 Übernahme bei fallender Flanke ¯↓_|¯↓_|¯ |
3 | 1 Clock Idle High | 1 Übernahme bei 2. Flanke | SCK in Ruhe 1 Übernahme bei steigender Flanke ¯|_↑¯|_↑¯ |
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.
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
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 🔗]
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:
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 )
Name | D15-D12 | D11 | D10 | D9 | D8 | Adresse Hex | Beschreibung |
---|---|---|---|---|---|---|---|
No-OP | X | 0 | 0 | 0 | 0 | 0x00 | Tue nichts, wird zum Daten durchschieben zum nächsten Modul verwendet |
Digit 0 | X | 0 | 0 | 0 | 1 | 0x01 | Inhalt Digit0, Zeile 0 |
Digit 1 | X | 0 | 0 | 1 | 0 | 0x02 | Inhalt Digit1, Zeile 1 |
Digit 2 | X | 0 | 0 | 1 | 1 | 0x03 | Inhalt Digit2, Zeile 2 |
Digit 3 | X | 0 | 1 | 0 | 0 | 0x04 | Inhalt Digit3, Zeile 3 |
Digit 4 | X | 0 | 1 | 0 | 1 | 0x05 | Inhalt Digit4, Zeile 4 |
Digit 5 | X | 0 | 1 | 1 | 0 | 0x06 | Inhalt Digit5, Zeile 5 |
Digit 6 | X | 0 | 1 | 1 | 1 | 0x07 | Inhalt Digit6, Zeile 6 |
Digit 7 | X | 1 | 0 | 0 | 0 | 0x08 | Inhalt Digit7, Zeile 7 |
Decode Mode | X | 1 | 0 | 0 | 1 | 0x09 | Bei 0 wird nichts dekodiert, sonst kann eine BCD-Dekodierung ausgewählt werden |
Intensity | X | 1 | 0 | 1 | 0 | 0x0A | Das untere Nibble gibt die Helligkeit vor (0..15) |
Scan Limit | X | 1 | 0 | 1 | 1 | 0x0B | Wie viele Stellen/Zeilen angeschlossen sind (0..7) |
Shutdown | X | 1 | 1 | 0 | 0 | 0x0C | Bit0 = 1 Normal, bei Bit0 = 1 gilt Shutdown (aus) |
Display Test | X | 1 | 1 | 1 | 1 | 0x0F | Bit0 = 0 Normal, bei Bit0 = 1 gilt Test, alle LED an |
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.
wuerfel[] | Zeile 0 | Zeile 1 | Zeile 2 | Zeile 3 | Zeile 4 | Zeile 5 | Zeile 6 | Zeile 7 |
---|---|---|---|---|---|---|---|---|
0 | ||||||||
1 | ||||||||
2 | 0xC0 | 0xC0 | 0x00 | 0x18 | 0x18 | 0x00 | 0x03 | 0x03 |
3 | ||||||||
4 | ||||||||
5 |
Tabelle Lösung
wuerfel[] | Zeile 0 | Zeile 1 | Zeile 2 | Zeile 3 | Zeile 4 | Zeile 5 | Zeile 6 | Zeile 7 |
---|---|---|---|---|---|---|---|---|
0 | 0x00 | 0x00 | 0x00 | 0x18 | 0x18 | 0x00 | 0x00 | 0x00 |
1 | 0xC0 | 0xC0 | 0x00 | 0x00 | 0x00 | 0x00 | 0x03 | 0x03 |
2 | 0xC0 | 0xC0 | 0x00 | 0x18 | 0x18 | 0x00 | 0x03 | 0x03 |
3 | 0xC3 | 0xC3 | 0x00 | 0x00 | 0x00 | 0x00 | 0xC3 | 0xC3 |
4 | 0xC3 | 0xC3 | 0x00 | 0x18 | 0x18 | 0x00 | 0xC3 | 0xC3 |
5 | 0xC3 | 0xC3 | 0x00 | 0xC3 | 0xC3 | 0x00 | 0xC3 | 0xC3 |
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
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.
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|
DP | A | B | C | D | E | F | G |
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.
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:
- https://www.handsontec.com/dataspecs/display/TM1638.pdf
- 😄 https://g-heinrichs.de/attiny/module/TM1638.pdf
- https://jetpackacademy.com/wp-content/uploads/2018/06/TM1638_cheat_sheet_download.pdf
- http://softgeniedoc.dk/contents/projects/TM1638/TM1638en.pdf
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?