Y Abi Musteraufgaben 🚧

1 BCD-Uhr

Testaufbau

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.
Funktionsblöcke
Abbildung 1

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

UML-Zustandsdiagramm Vorgabe
UML-Zustandsdiagramm Vorgabe

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.

ZustandLCD-AusgabeInfo
INTROUhr stellen…
weiter mit MENU_S
STUNDEN_STELLENStd => S2: 00
weiter mit MENU_S
Mit S2 wird z_std erhöht.
Nach 23 springt der Wert wieder auf 0.
MINUTEN_STELLENMin => S2: 00
weiter mit MENU_S
Mit S2 wird z_min erhöht.
Nach 59 springt der Wert wieder auf 0.
SEKUNDEN_STELLENSek => 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.
Menu zum Einstellen der Uhrzeit

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
Zustandsdiagramm Lösung
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
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

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 (!)
GNDGround 0V
SCLSerial Clock (SCK)
SDASerial Data (SPI: IN)
CSBChip Select (Modus I2C oder SPI)
SDOI2C: Adresse; SPI: Serial Data Out
BME280 Breakout Boards
BME280 Breakout Boards

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.

SStart
PStop
ACKSAcknowledge by slave
ACKMAcknowledge by master
NACKMNot acknowledge by master
  1. Wofür dienen die Widerstände R1 und R2?
  2. Welche I2C-Adressen lassen sich bei dem BME280 einstellen?
  3. Beschreiben Sie detailliert den Ablauf wie Daten in die Register des Sensors geschrieben werden. Gehen Sie dabei auf den Begriff not auto-incremented ein.
  4. Beschreiben Sie prinzipiell wie Daten aus den Registern des Sensor gelesen werden. Gehen Sie dabei auf den Begriff auto-incremented ein.
BME280 Anschlussschema für I2C
BME280 Anschlussschema für I2C
I2C Write
I2C Write
I2C read
I2C read
Lösungen
  1. 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.
  2. Bei SDO auf GND ergibt sich 0b1110110 = 0x76. Bei SDO auf VDD 0x77.
  3. 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).
  4. 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.

BME280 Anschlussschema für SPI
BME280 Anschlussschema für SPI
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 Mode11
SPI Mode11

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.

SPI write
SPI write
SPI read
SPI read
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.

2.2 Daten auslesen und verarbeiten (Software)

2.2.1 I2C-Schnittstelle

2.2.2 SPI-Schnittstelle

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert