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?

Schreibe einen Kommentar

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