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?