Y Abi Musteraufgaben 🚧
1 BCD-Uhr

Eine BCD-Uhr mit LCD- und Matrixanzeige soll realisiert werden.
- Zum Stellen und Anzeige der Uhrzeit wird eine über I2C angeschlossene LCD-Anzeige verwendet.
- Die Matrixanzeige ist ein über SPI angeschlossenes MAX7219-Modul.
- Zwei Taster MENU_S, zum Menu-Weiterschalten und S2, zur Erhöhung des Wertes dienen der Uhrzeiteinstellung.
- MENU_S (z.B. PC13) ist prellfrei und Low-Aktiv und löst isr_Menu() aus.
- S2 (z.B. PA1) ist (prellfrei) und High-Aktiv, benötigt PullDown-Widerstand und löst isr_S2() aus.
Vorgegebener Code / Info
#include <Wire.h> // Wire Bibliothek einbinden
#include <LiquidCrystal_PCF8574.h>
#include <SPI.h>
LiquidCrystal_PCF8574 lcd (0x27); // LCD-Adresse auf 0x27 für 16 Zeichen und 2 Zeilen ein
#define MENU_S PC13 // Menu-Umschalter entprellter Taster auf Board, Low aktiv
#define S2 PA1 // Zählt den einzustellenden Wert hoch, (pellt), High aktiv, benötigt PullDown
#define CS D10 // CS Signal auf D10
typedef enum {INTRO,STUNDEN_STELLEN,MINUTEN_STELLEN,SEKUNDEN_STELLEN,ZEIT_ANZEIGEN} zustandstyp; // Aufzaehlungstyp
zustandstyp zustand = INTRO; // Startzustand
HardwareTimer sekTimer = HardwareTimer(TIM2);
char buf[16]; // Pufferspeicher bei Verwendung von sprintf
volatile int z_sek=0,z_min=0,z_std=0; // Zeit
void ausgebenLCD_Menu(){ // das Menu zum Zeiteinstellen
}
void ausgebenLCD_Uhrzeit(){ // Uhrzeit wird in Zeile 1 ausgegeben
}
void ausgebenLED_Sekunden(){ // Ausgabe auf PORTC
}
void sendData (unsigned char msb, unsigned char lsb){ // Hilfsfunktion zur Datenuebertragung
}
void ausgebenMatrix(){ // Zeit als BCD auf Matrix ausgeben
}
void isr_Menu(){ // schaltet das Menu der Zeiteingabe weiter
}
void isr_S2(){ // Einstellen der Werte
}
void incUhrzeit(){ // erhöht z_sek und passt z_min und z_std an
z_sek++;
}
void isr_sekTimer(){ // wird jede Sekunde aufgerufen
incUhrzeit();
ausgebenLCD_Uhrzeit();
ausgebenLED_Sekunden();
ausgebenMatrix();
}
void setup() {
pinMode(PC0, OUTPUT); // ohne diese Zeile klappts nicht
GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
lcd.begin(16, 2); // initialize the lcd
lcd.clear();
lcd.setBacklight(255);
}
void loop() {
}
Hinweis: Fehler in der Formelsammlung bei formatierter Ausgabe:, verwenden Sie bei sprintf statt I2C_LCD(buf) lcd.print(buf) zur Ausgabe.
Eine formatierte Ausgabe kann bei STM32 auch mit lcd.printf(…) ohne Buffer erfolgen.
Darüber hinaus sind wir es gewohnt Variablen klein zu schreiben. Kleinschreibung bei std,min,sec führt aber zu einer Kollision mit bereits definierten Bezeichnen der Programmierumgebung!
char buf[16]; // Puffer für sprintf festlegen
sprintf(buf,"%02u : %02u : %02u ",Std,Min,Sec);
//I2C_LCD(buf); // Falsch!
lcd.print(buf); // Richtig.
// alternativ bei STM32
lcd.printf("%02u : %02u : %02u ",Std,Min,Sec);
1.1 BCD-Code der Matrix entschlüsseln
Ermitteln Sie die Uhrzeit auf dem Bild der Matrix aus Abbildung 1.
Lösung
23:59:47
1.2 UML-Zustandsdiagramm, Einstellen der Uhrzeit
Beim Programmstart wird das Intro zum Einstellen der Uhrzeit auf der LCD-Anzeige ausgegeben. Ein UP ausgebenLCD_Menu() gibt in Abhängigkeit des Zustands den jeweiligen Text aus. Das Menu wird durch MENU_S und isr_Menu() weitergeschaltet.
In den Stellen-Menupunkten soll S2 über die isr_S2() eine Erhöhung um eins der jeweiligen Zeit-Variablen bewirken. Nach dem Maximalwert wird wieder der Wert 0 eingestellt.
Nach dem Einstellen der Zeitwerte wird im Modus ZEIT_ANZEIGEN der Sekundentimer gestartet und die Uhrzeit auf der LCD-Anzeige, einigen PORT-LEDs und der Matrix ausgegeben.
Wird im Zustand ZEIT_ANZEIGEN MENU_S betätigt lässt sich die Uhrzeit erneut einstellen.
| Zustand | LCD-Ausgabe | Info |
|---|---|---|
| INTRO | Uhr stellen… weiter mit MENU_S | |
| STUNDEN_STELLEN | Std => S2: 00 weiter mit MENU_S | Mit S2 wird z_std erhöht. Nach 23 springt der Wert wieder auf 0. |
| MINUTEN_STELLEN | Min => S2: 00 weiter mit MENU_S | Mit S2 wird z_min erhöht. Nach 59 springt der Wert wieder auf 0. |
| SEKUNDEN_STELLEN | Sek => S2: 00 weiter mit MENU_S | Mit S2 wird z_sek erhöht. Nach 59 springt der Wert wieder auf 0. |
| ZEIT_ANZEIGEN | Uhrzeit 00 : 00 : 00 | Zeigt die Uhrzeit an. |
1.2.1 🖌 Ergänzen Sie das UML-Zustandsdiagramm.
1.2.2 🏋️ Erstellen Sie den Quellcode für isr_Menu().
1.2.3 🏋️ Erstellen Sie den Quellcode für ausgebenLCD_Menu().
Lösungsvorschlag Zustandsdiagramm
Lösung isr_Menu()
void isr_Menu(){ // schaltet das Menu der Zeiteingabe weiter
switch(zustand){
case INTRO:
zustand=STUNDEN_STELLEN;
break;
case STUNDEN_STELLEN:
zustand=MINUTEN_STELLEN;
break;
case MINUTEN_STELLEN:
zustand=SEKUNDEN_STELLEN;
break;
case SEKUNDEN_STELLEN:
sekTimer.resume();
zustand=ZEIT_ANZEIGEN;
break;
case ZEIT_ANZEIGEN:
sekTimer.pause();
zustand=INTRO;
}
ausgebenLCD_Menu(); // entry/ausgebenLCD_Menu()
}
Lösung ausgebenLCD_Menu()
void ausgebenLCD_Menu(){ // das Menu zum Zeiteinstellen
lcd.setCursor(0,0);
switch(zustand){
case INTRO:
lcd.print("Uhr stellen... ");
lcd.setCursor(0, 1);
lcd.print("weiter mit MENU_S ");
break;
case STUNDEN_STELLEN:
//lcd.printf("Std => S2: %02u ",z_std);
sprintf(buf,"Std => S2: %02u ",z_std); // mit sprintf
lcd.print(buf);
break;
case MINUTEN_STELLEN:
lcd.printf("Min => S2: %02u",z_min); // mit printf
break;
case SEKUNDEN_STELLEN:
lcd.printf("Sec => S2: %02u",z_sek);
break;
case ZEIT_ANZEIGEN:
lcd.print(" Uhrzeit ");
break;
}
}
1.3 Externe Interrupts für Taster einstellen
Erstellen Sie die Codesequenz für die Initialisierung der Interrupts für Taster MENU_S isr_Menu() und S2 isr_S2().
Lösung Arduino
pinMode(MENU_S,INPUT); // nicht unbedingt erforderlich da Userbutten PC13 PullUps hat
attachInterrupt (digitalPinToInterrupt (MENU_S), isr_Menu, FALLING);
pinMode(S2,INPUT_PULLDOWN); // PA1 auf Sturm-Board braucht Pulldown
attachInterrupt (digitalPinToInterrupt (S2), isr_S2, RISING);
1.4 Quarzgenauer Sekundentakt
Mit Timer TIM2 soll jede Sekunde der Interrupt isr_sekTimer() ausgelöst werden können.
Erstellen Sie die Codesequenz dafür. Begründen Sie Ihre Einstellungen.
Lösungsvorschläge Arduino
// 32MHZ/3200 -> 10kHz 1/10kHz = 100µs 10000*100µs = 1s
/*sekTimer.setPrescaleFactor(3200); // 0-65535
sekTimer.setOverflow(10000); //0-65535 => Prescaler AKTIV!
*/
sekTimer.setOverflow(1, HERTZ_FORMAT); // lass die API rechnen
sekTimer.attachInterrupt(isr_sekTimer); // Interrupt einstellen
1.5 UP incUhrzeit() zum Einstellen der Zeitwerte
Das UP incUhrzeit() erhöht die Sekunden und stellt bei Überlauf (>59) von z_sek die Werte von z_min und z_std sinnvoll für eine richtige Zeitausgabe ein.
Erstellen Sie den Code für incUhrzeit().
Lösung
void incUhrzeit(){ // erhöht z_sek und passt z_min und z_std an
z_sek++;
if(z_sek>59){
z_sek=0;
z_min++;
if(z_min>59){
z_min=0;
z_std++;
if(z_std>23){
z_std=0;
}
}
}
}
1.6 LCD-Ausgabe der Uhrzeit mit ausgebenLCD_Uhrzeit()
Die Uhrzeit wird in der zweiten Zeile der LCD-Anzeige ausgegeben im Format 00 : 00 : 00.
Erstellen Sie den Code für ausgebenLCD_Uhrzeit().
Lösungsvorschlag
void ausgebenLCD_Uhrzeit(){ // Uhrzeit wird in Zeile 1 ausgegeben
lcd.setCursor(0,1);
lcd.printf("%02u : %02u : %02u ", z_std, z_min, z_sek);
}
1.7 Ausgabe der Sekunden als BCD auf PortC mit ausgebenLED_Sekunden()
Ein UP ausgebenLED_Sekunden() gibt den Sekundenwert z_sek als BCD-Wert auf den 8 PORTC-LED aus (LEDs SturmBoard). Das obere Nibble zeigt die 10er und das untere Nibble die Einer an. Bsp: 42 -> 0b01000010.
Erstellen Sie den Code für ausgebenLED_Sekunden().
Lösung
void ausgebenLED_Sekunden(){ // Ausgabe auf PORTC
GPIOC->ODR= (z_sek/10)<<4 | z_sek%10;
}
1.8 Ausgeben der Uhrzeit auf der LED-Matrix mit MAX7219
Der Baustein wird per SPI angesteuert (siehe Abbildung 1). Der Baustein erwartet 16Bit Daten pro Übertragung. Das MSB gibt dabei die Adresse des internen 8Bit-Registers vor und das LSB den Inhalt. Diese Parameter sollen bei der Schnittstelle und beim Baustein eingestellt werden:
- MSBFIRST
- SPI_CLOCK_DIV32, soll hier mit 1MHz betrieben werden
- SPI_MODE0
- Keine BCD-Dekodierung
- Helligkeit ist 7
- 8-Zeilen
- Kein Shutdown
- Kein Display-Test
| 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 |
1.8.1 UP sendData (unsigned char msb, unsigned char lsb)
Das UP sendData (unsigned char msb, unsigned char lsb) sendet die zwei Byte über die SPI-Schnittstelle zum Baustein.
Erstellen Sie den Quellcode.
Lösung
void sendData (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!
}
1.8.2 Bausteinschnittstelle und Baustein initialisieren
Erstellen Sie die Codesequenz zur Initialisierung.
Lösung
pinMode(CS, OUTPUT); // CS-Pin initialisieren
digitalWrite(CS, HIGH); // bei SPI_MODE0 ist CS in Ruhe 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
sendData(0x09, 0x00); // Decoding: Aus: keine BCD Dekodierung fuer 7 Segment
sendData(0x0A, 0x05); // Intesity: Helligkeit auf 5 von 15 einstellen
sendData(0x0B, 0x07); // Scan Limit: Es sind 8 Zeilen mit LEDs
sendData(0x0C, 0x01); // Shutdown: Bit0 = 1: Normal operation mode, kein Ruhezustand
sendData(0x0F, 0x00); // Display Test: Bit0 = 1: Displaytest An: Alle LEDs an!
1.8.3 Ausgeben auf der MAX7219 LED-Matrix mit ausgebenMatrix()
Das UP ausgebenMatrix() gibt mittels sendData(..) die Zeit als BCD-Code aus, siehe Abbildung 1.
Erstellen Sie den Quellcode.
Lösung
void ausgebenMatrix(){
sendData(0x08,z_std/10);
sendData(0x07,z_std%10);
sendData(0x05,z_min/10);
sendData(0x04,z_min%10);
sendData(0x02,z_sek/10);
sendData(0x01,z_sek%10);
}
1. Lösungscode
Meine ganze Lösung
#include <Wire.h> // Wire Bibliothek einbinden
#include <LiquidCrystal_PCF8574.h>
#include <SPI.h>
LiquidCrystal_PCF8574 lcd (0x27); // LCD-Adresse auf 0x27 für 16 Zeichen und 2 Zeilen ein
#define MENU_S PC13 // Menu-Umschalter entprellter Taster auf Board, Low aktiv
#define S2 PA1 // Zählt den einzustellenden Wert hoch, (pellt), High aktiv, benötigt PullDown
#define CS D10 // CS Signal auf D10
typedef enum {INTRO,STUNDEN_STELLEN,MINUTEN_STELLEN,SEKUNDEN_STELLEN,ZEIT_ANZEIGEN} zustandstyp; // Aufzaehlungstyp
zustandstyp zustand = INTRO; // Startzustand
HardwareTimer sekTimer = HardwareTimer(TIM2);
char buf[16]; // Pufferspeicher bei Verwendung von sprintf
volatile int z_sek=0,z_min=0,z_std=0; // Zeit
void ausgebenLCD_Menu(){ // das Menu zum Zeiteinstellen
lcd.setCursor(0,0);
switch(zustand){
case INTRO:
lcd.print("Uhr stellen... ");
lcd.setCursor(0, 1);
lcd.print("weiter mit MENU_S ");
break;
case STUNDEN_STELLEN:
//lcd.printf("Std => S2: %02u ",z_std);
sprintf(buf,"Std => S2: %02u ",z_std); // mit sprintf
lcd.print(buf);
break;
case MINUTEN_STELLEN:
lcd.printf("Min => S2: %02u",z_min); // mit printf
break;
case SEKUNDEN_STELLEN:
lcd.printf("Sec => S2: %02u",z_sek);
break;
case ZEIT_ANZEIGEN:
lcd.print(" Uhrzeit ");
break;
}
}
void ausgebenLCD_Uhrzeit(){ // Uhrzeit wird in Zeile 1 ausgegeben
lcd.setCursor(0,1);
lcd.printf("%02u : %02u : %02u ", z_std, z_min, z_sek);
}
void ausgebenLED_Sekunden(){ // Ausgabe auf PORTC
GPIOC->ODR= (z_sek/10)<<4 | z_sek%10;
}
void sendData (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 ausgebenMatrix(){ // Zeit als BCD auf Matrix ausgeben
sendData(0x08,z_std/10);
sendData(0x07,z_std%10);
sendData(0x05,z_min/10);
sendData(0x04,z_min%10);
sendData(0x02,z_sek/10);
sendData(0x01,z_sek%10);
}
void isr_Menu(){ // schaltet das Menu der Zeiteingabe weiter
switch(zustand){
case INTRO:
zustand=STUNDEN_STELLEN;
break;
case STUNDEN_STELLEN:
zustand=MINUTEN_STELLEN;
break;
case MINUTEN_STELLEN:
zustand=SEKUNDEN_STELLEN;
break;
case SEKUNDEN_STELLEN:
sekTimer.resume();
zustand=ZEIT_ANZEIGEN;
break;
case ZEIT_ANZEIGEN:
sekTimer.pause();
zustand=INTRO;
}
ausgebenLCD_Menu();
}
void isr_S2(){ // Einstellen der Werte
switch(zustand){
case INTRO:
break;
case STUNDEN_STELLEN:
if(z_std>=23) z_std=0;
else z_std++;
break;
case MINUTEN_STELLEN:
if(z_min>=59) z_min=0;
else z_min++;
break;
case SEKUNDEN_STELLEN:
if(z_sek>=59) z_sek=0;
else z_sek++;
break;
}
ausgebenLCD_Menu();
}
void incUhrzeit(){ // erhöht z_sek und passt z_min und z_std an
z_sek++;
if(z_sek>59){
z_sek=0;
z_min++;
if(z_min>59){
z_min=0;
z_std++;
if(z_std>23){
z_std=0;
}
}
}
}
void isr_sekTimer(){ // wird jede Sekunde aufgerufen
incUhrzeit();
ausgebenLCD_Uhrzeit();
ausgebenLED_Sekunden();
ausgebenMatrix();
}
void setup() {
pinMode(PC0, OUTPUT); // ohne diese Zeile klappts nicht
GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
lcd.begin(16, 2); // initialize the lcd
lcd.clear();
lcd.setBacklight(255);
//Serial.begin(9600);
//Serial.printf("%02u : %02u : %02u\n", z_std, z_min, z_sek);
pinMode(MENU_S,INPUT); // nicht unbedingt erforderlich da Userbutten PullUps hat
attachInterrupt (digitalPinToInterrupt (MENU_S), isr_Menu, FALLING);
pinMode(S2,INPUT_PULLDOWN); // PA1 auf Sturm-Board braucht Pulldown
attachInterrupt (digitalPinToInterrupt (S2), isr_S2, RISING);
// 32MHZ/3200 -> 10kHz 1/10kHz = 100µs 10000*100µs = 1s
/*sekTimer.setPrescaleFactor(3200); // 0-65535
sekTimer.setOverflow(10000); //0-65535 => Prescaler AKTIV!
*/
sekTimer.setOverflow(1, HERTZ_FORMAT); // lass die API rechnen
sekTimer.attachInterrupt(isr_sekTimer); // Interrupt einstellen
//ausgebenLCD_Menu();
//sprintf(buf,"Hallo");
//I2C_LCD(buf); // Fehler!
pinMode(CS, OUTPUT); // CS-Pin initialisieren
digitalWrite(CS, HIGH); // bei SPI_MODE0 ist CS in Ruhe 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
sendData(0x09, 0x00); // Decoding: Aus: keine BCD Dekodierung fuer 7 Segment
sendData(0x0A, 0x05); // Intesity: Helligkeit auf 5 von 15 einstellen
sendData(0x0B, 0x07); // Scan Limit: Es sind 8 Zeilen mit LEDs
sendData(0x0C, 0x01); // Shutdown: Bit0 = 1: Normal operation mode, kein Ruhezustand
sendData(0x0F, 0x01); // Display Test: Bit0 = 1: Displaytest An: Alle LEDs an!
delay(300); // 500 ms delay
sendData(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
sendData(adr,0x00); // Zeile = 0
}
delay(200); // 500 ms delay
ausgebenLCD_Menu();
}
void loop() {
}
1.x Optionale Aufgabenideen
SuS bekommen Blockbild, s.o und sollen die Leitungen richtig anschließen.
Aufgaben zur Rechnerarchitektur?
2.0 Temperatur- und Feuchtigkeitsbeurteilung für Gewächshaus mit Bosch BME280 Sensor
Synopsis: 🔗 BME280 Datenblatt 🔗 VEML7700 High Accuracy Ambient Light Sensor With I2C Interface
🍬 Info zu den Breakout-Boards
Viele einfache Board mit 4 Anschlüssen können nur I2C und dabei ist die Adresse nicht einstellbar, trotz Lötbrücken, der SDO-Eingang ist fest GND verbunden siehe Bild rechts. Bei der Versorgungsspannung aufpassen der Chip kann 1,71 bis 3,6 V. Bei manchen Breakout-Boards ist noch ein Spannungsregler verbaut, dann darf er auch mit 5V versorgt werden.
Wenn beim Einschalten CSB mit VDD verbunden ist oder frei gelassen wird (interner PullUP-Widerstand) macht der Chip I2C.
| VCC (VDD, VIN) | Versorgungsspannung (!) |
| GND | Ground 0V |
| SCL | Serial Clock (SCK) |
| SDA | Serial Data (SPI: IN) |
| CSB | Chip Select (Modus I2C oder SPI) |
| SDO | I2C: Adresse; SPI: Serial Data Out |

Augen auf beim Breakout Board Kauf! Das rechte Board ist praktisch unbrauchbar wenn mehr als ein Sensor verwendet werden soll.
2.1 Sensoren mit µC verbinden (Hardware)
2.1.1 Zwei Sensoren mit I2C
Hobbygärtner Oliver will in seinem Gewächshaus die Temperaturschichtung und Feuchtigkeit beurteilen. Dazu verwendet er zwei BME280 Sensoren und schließt sie über I2C an den µC an. Im Datenblatt findet er ein Anschlussschema für I2C und zwei Abbildungen für das Schreiben und Lesen von Daten. Im I2C Betrieb dient der SDO-Eingang zum Einstellen des X-Bits der I2C-Adresse.
| S | Start |
| P | Stop |
| ACKS | Acknowledge by slave |
| ACKM | Acknowledge by master |
| NACKM | Not acknowledge by master |
- Wofür dienen die Widerstände R1 und R2?
- Welche I2C-Adressen lassen sich bei dem BME280 einstellen?
- Beschreiben Sie detailliert den Ablauf wie Daten in die Register des Sensors geschrieben werden. Gehen Sie dabei auf den Begriff not auto-incremented ein.
- Beschreiben Sie prinzipiell wie Daten aus den Registern des Sensor gelesen werden. Gehen Sie dabei auf den Begriff auto-incremented ein.



Lösungen
- Die Widerstände R1 und R2 sind PullUp-Widerstände. Bei I2C werden OpenDrain-Ausgänge für SCL und SDA verwendet damit haben bei sperrenden Transistoren die Leitungen den Zustand High und sind nicht undefiniert / floating.
- Bei SDO auf GND ergibt sich 0b1110110 = 0x76. Bei SDO auf VDD 0x77.
- Beim Schreiben von Daten auf den Sensor wird vom Master nach der Startbedingung zuerst die Sensoradresse mit Schreibbit gesendet. Der Sensor antwortet durch SDA nach GND ziehen mit Acknowledge. Danach sendet der Master jeweils Byte-Paare bestehend aus Registeradresse und Daten zum Slave. Der Slave Quittiert den Empfang mit ACK. Nach dem letzten Paar wird die Stoppbedingung gesendet. Besonders ist bei diesem Baustein das zu jedem Datum zuerst das Zielregister angegeben werden muss und nicht einfach intern ein Pointer hochgezählt wird (not auto-incremented).
- Beim Lesen von Daten wird zuerst die Adresse des Startregisters an den Sensor geschickt. Danach werden Datenbytes angefordert. Beginnend mit dem Startregister überträgt der Sensor die Daten zum Master. Dabei wird die Register-/Pointeradresse laufend erhöht (auto-incremented).
2.1.2 Bei drei Sensoren wird die SPI-Schnittstelle notwendig
Ein weiterer Sensor soll die Außentemperatur und Luftfeuchte messen. Mit I2C geht das nicht weil sich nur zwei BME-Sensoren darüber adressieren lassen. Deshalb werden die Sensoren nun über SPI angebunden.
Wechsel in die SPI-Schnittstelle
CBS hat einen eingebauten PullUp-Widerstand. Bei SPI werden die Sensoren durch ein CSB-Low-Signal angesprochen. Beim Einschalten der Spannung haben die Ausgänge des µC zunächst den Zustand Floating. Dabei ist der Zustand von CSB High und der Chip wird zunächst im I2C-Modus arbeiten. Sobald CSB einmal den Zustand Low bekommt, schaltet der Sensor bis zum nächsten Power-On-Reset in den SPI-Modus.
SPI-Mode 0 ’00‘ oder 3 ’11‘ ?
Da bei SPI nicht festgelegt wurde, wann die Daten übernommen werden ergeben sich 4 Betriebsmodi, siehe Tabelle (sollte so schön in nächster ForSa stehen 😜).
Der Sensor kann sich automatisch auf Mode 0 oder 3 einstellen, je nachdem welchen Zustand SCK nach einer fallenden Flanke von CSB hat.
Figure 11 zeigt das Timing-Diagramm für Mode 3.

| 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 ¯|_↑¯|_↑¯ |

2.1.2.1 Unterschied und Gemeinsamkeit zwischen Mode 0 und Mode 3
Erklären Sie den Unterschied und die Gemeinsamkeit zwischen Mode 0 und Mode 3.
Lösung
Der Unterschied ist der Zustand von SCK in Ruhe, also solange CSB High ist. Die Gemeinsamkeit ist die Übernahme der Daten bei steigender Flanke.
2.1.2.2 Sensoren parallel schalten oder Kaskadieren
Es gibt zwei Möglichkeiten die Bausteine bei SPI zu verkabeln. Erstellen Sie dafür jeweils ein Blockschaltbild für 2 Sensoren und empfehlen Sie mit Kenntnis von Firgure 11 begründet eine Variante.
Lösung
ToDo Bild
Kaskadieren funktioniert nicht, da der Sensor die über SDI empfangene Registeradresse und Schreibrichtung nicht an SDO durchreicht. Somit kann ein in der Kaskade nachgelagerter Sensor nicht sinnvoll angesprochen werden.
2.1.2.3 Datenübertragung beschreiben
Beschreiben Sie detailliert wie Daten zum Sensor übertragen und vom Sensor gelesen werden.


Lösung
Beim Schreiben auf den Sensor wird nach Auswählen des Sensors durch CSB auf Low Byte-Paare aus Registeradresse (Control byte) MSB ist dabei Low und Daten gesendet. Am Ende wird CSB wieder auf High geschaltet. Beim Lesen wird nach Auswählen des Sensors durch CSB auf Low die Startadresse mit gesetztem MSB gesendet und danach sendet der Sensor mit jedem weiteren Takt die Inhalte der Register, die Registeradressen werden dabei automatisch erhöht.
