1.2 Aufgaben zum Einstieg

SOS ausgeben mit BoardLED D13. … – – – …

Auf der BoardLED 13 soll ein SOS-Blinken ausgegeben werden.
Hier das Zeitschema zur Ausgabe. Mit einer Variable int dit soll die Dauer des kurzen Signals einstellbar sein.
Zum Testen usw. https://morsedecoder.com
Erstellen Sie ein Programm, ein dit soll die Länge 200ms haben. Hier ein paar Anregungen:

  • Lineares Programm (Ausgaben einfach runter programmieren)?
  • Mit Unterprogrammen dit(), dah()
  • Zählschleifen verwenden
Lösungsvorschlag (klick mich wenn du nicht weiter kommst)
// https://de.wikipedia.org/wiki/Morsecode#Zeitschema_und_Veranschaulichung
#define BOARD_LED D13
char empfangByte = 0; // für empfangenes Byte
int dit = 200; // Basislänge in ms

void setup(){   // Einmalige Ausführung => Initialisierungen...
    pinMode(BOARD_LED, OUTPUT);  // Pin als Ausgang
    Serial.begin(115200); // Serielle Schnittstelle starten und Baudrate festlegen
}

void led_dit(){ // Dit ausgeben
  digitalWrite(BOARD_LED,HIGH);
  delay(dit);
  digitalWrite(BOARD_LED,LOW);
  delay(dit);   // ein dit Symbolabstand
  Serial.print(".");
}
void led_dah(){ // Dah ausgeben
  digitalWrite(BOARD_LED,HIGH);
  delay(3*dit);
  digitalWrite(BOARD_LED,LOW);
  delay(dit);   // ein dit Symbolabstand
  Serial.print("-");
}
void buchstabenAbstand(){ // Buchstabenstand ausgeben
  delay(2*dit);     // 3-1
  Serial.print(" ");
}
void wortAbstand(){ // Wortabstand ausgeben
  delay(6*dit);     // 7-1
  Serial.print(" ");
}
void morseSOS(){
  int i;
  for(i=0;i<3;i++){ // S
    led_dit();
  }
  buchstabenAbstand();
  for(i=0;i<3;i++){ // O
    led_dah();
  }
  buchstabenAbstand();
  for(i=0;i<3;i++){ // S
    led_dit();
  }
}
void loop(){;
    morseSOS();
    delay(1000);
}
// Vorgabe
#define BOARD_LED D13
int dit = 200; // Basislänge in ms

void setup(){   // Einmalige Ausführung => Initialisierungen...
    pinMode(BOARD_LED, OUTPUT);  // Pin als Ausgang
    Serial.begin(115200); // Serielle Schnittstelle starten und Baudrate festlegen
}
void morseSOS(){
  
}
void loop(){
    morseSOS();
    delay(1000);
}

String ausgeben … — …

  • Die SOS-Ausgabe soll nun mit Taste an PA1 starten und dabei ein Unterprogramm ausgebenString(s);
  • Der Morsecode soll in einem String s =”… — …”; gespeichert sein.
Lösungsvorschlag
#define BOARD_LED D13
char empfangByte = 0; // für empfangenes Byte
int dit = 200; // Basislänge in ms
String s="... --- ..."; // auszugebender Code

void setup(){   // Einmalige Ausführung => Initialisierungen...
    pinMode(BOARD_LED, OUTPUT);  // Pin als Ausgang
    pinMode(PA1,INPUT_PULLDOWN); // PA1 als Eingang
    Serial.begin(115200); // Serielle Schnittstelle starten und Baudrate festlegen
}

void led_dit(){ // Dit ausgeben
  digitalWrite(BOARD_LED,HIGH);
  delay(dit);
  digitalWrite(BOARD_LED,LOW);
  delay(dit);   // ein dit Symbolabstand
  Serial.print(".");
}
void led_dah(){ // Dah ausgeben
  digitalWrite(BOARD_LED,HIGH);
  delay(3*dit);
  digitalWrite(BOARD_LED,LOW);
  delay(dit);   // ein dit Symbolabstand
  Serial.print("-");
}
void buchstabenAbstand(){ // Buchstabenstand ausgeben
  delay(2*dit);     // 3-1
  Serial.print(" ");
}
void wortAbstand(){ // Wortabstand ausgeben
  delay(6*dit);     // 7-1
  Serial.print("  ");
}
void morseSOS(){
  int i;
  for(i=0;i<3;i++){ // S
    led_dit();
  }
  buchstabenAbstand();
  for(i=0;i<3;i++){ // O
    led_dah();
  }
  buchstabenAbstand();
  for(i=0;i<3;i++){ // S
    led_dit();
  }
}
void ausgebenString(String s){
  int i;
  for (i=0;i<s.length();i++){
    switch(s[i]){
      case '.':
        led_dit();
        break;
      case '-':
        led_dah();
        break;
      case ' ':
        buchstabenAbstand();    
    }
  }
}
void loop(){;
    //morseSOS();
    if(digitalRead(PA1)){ // wenn Taste A1 dedrückt
      ausgebenString(s);
      wortAbstand();
      ausgebenString("-- --- .-. ... .");
      Serial.println();
      delay(1000);
    }
}

Bonus: String ausgeben “SOS”

  • Das Unterprogramm ausgebenString(String s) soll nun ganze Sätze verarbeiten können.
  • Die Eingabe kann über den Seriellen Monitor erfolgen.

Noch keine Lösung…

Ampelsteuerung (mit Sturm-Board, Multifunction-Shield)

Für die Ampeln werden auf dem Multifunction-Shield die LEDs an PC0, PC2, PC4 für die Autos (Straße) und später PC6 und PC7 für die Fußgänger verwendet. Zunächst wird nur eine Autoampelsteuerung mit PC0, PC2, PC4 implementiert. ¡Quellcode muss anständig formatiert und mit sinnvollen Kommentaren versehen sein!

Es gibt 4 Auto-Ampelphasen: Grün, Gelb, Rot, Rot-Gelb. Nach Rot-Gelb wird wieder auf Grün gesprungen.

Erstellen Sie den Code für setup(), um die verwendeten Port-Pins als Ausgänge zu definieren.
Erstellen Sie den Code für loop(), um mit digitalWrite(..), delay(1000) die Ampelphasen im Sekundentakt zu wechseln.

Lösungsvorschlag
void setup() {
  pinMode(PC0,OUTPUT); // Rot
  pinMode(PC2,OUTPUT); // Gelb
  pinMode(PC4,OUTPUT); // Grün
}

void loop() {
  digitalWrite(PC4,HIGH); // Grün an
  delay(1000);
  digitalWrite(PC4,LOW);  // Grün aus
  digitalWrite(PC2,HIGH); // Gelb an
  delay(1000);
  digitalWrite(PC2,LOW);  // Gelb aus
  digitalWrite(PC0,HIGH); // Rot an
  delay(1000);
  digitalWrite(PC2,HIGH); // Gelb an
  delay(1000);
  digitalWrite(PC0,LOW);  // Rot aus
  digitalWrite(PC2,LOW);  // Gelb aus
}
LEDs Multifunction-Shield
LEDs Multifunction-Shield

Fußgängerampel

Nun bauen wir eine Fußgängerampel, die Pins PC6 und PC7 sind dafür vorgesehen. Die Fußgänger bekommen nur während der Auto-Rot-Phase Grün, sonst sehen sie Rot. Mit digitalWrite(..) würde der Code lang und unübersichtlich werden, daher setzen wir die Ausgänge mit GPIOC->ODR = Bitmuster in einem Rutsch. Für das Bitmuster ergänzen Sie diese Tabelle:

Ampel-PhasePC7 Fuß-GNPC6 Fuß-RTPC5PC4 Auto-GNPC3PC2 Auto-GEPC1PC0 Auto-RTGPIOC->ODR=
0 Grün010100000b0101 000
1 Gelb1000
2 Rot1000
3 Rot-Gelb000
Ampelphasen
Lösung
Ampel-PhasePC7 Fuß-GNPC6 Fuß-RTPC5PC4 Auto-GNPC3PC2 Auto-GEPC1PC0 Auto-RTGPIOC->ODR=
0 Grün010100000b0101 0000
1 Gelb010001000b0100 0100
2 Rot100000010b1000 0001
3 Rot-Gelb010001010b0100 0101
Ampelphasen

Die 4 Ampelphasen werden in einem Feld unsigned char ampelphase[4]= {0b0101 000,…}; gespeichert, vervollständigen Sie die Initialisierung. Für die grüne Ampelphase kann nun schlicht geschrieben werden: GPIOC->ODR= ampelphase[0];

Die Ampel zeigt normalerweise für die Autofahrer Grün (Phase 0). Ein Taster an PA1 (PullDown einschalten, Abfrage mit digitalRead(PA1)) ist für Fußgänger und startet folgenden Ablauf:

  • 6 Sec warten (sinnlos weiter rumstehen)
  • Phase 1 ausgeben
  • 2 Sec warten
  • Phase 2 ausgeben
  • 6 Sec warten
  • Phase 3 ausgeben
  • 3 Sec warten
  • zurück zu Phase 0

Ergänzen Sie den Code für setup(), das Feld und erstellen Sie den neuen Code für loop().

Lösungsvorschlag
void setup() {
  pinMode(PC0,OUTPUT); // Rot
  pinMode(PC2,OUTPUT); // Gelb
  pinMode(PC4,OUTPUT); // Grün
  pinMode(PC6,OUTPUT); // FRot
  pinMode(PC7,OUTPUT); // FGrün
  pinMode(PA1,INPUT_PULLDOWN);
}
const unsigned char ampelphase[4]={0b01010000,0b01000100,0b10000001,0b01000101};
void loop() {
  GPIOC->ODR = ampelphase[0]; // Grün
  while(!digitalRead(PA1));
  delay(6000);
  GPIOC->ODR = ampelphase[1]; // Gelb
  delay(2000);
  GPIOC->ODR = ampelphase[2]; // Rot
  delay(6000);
  GPIOC->ODR = ampelphase[3]; // RotGelb
  delay(3000);
}

Abstimmanzeige (mit Sturm-Board, Multifunction-Shield)

Die Anzahl der gedrückten Tasten PA1, PA6 und PA10 soll auf den LED LC0 (PC0), LC1 (PC1 und LC2 (PC2) als Leuchtband ausgegeben werden. Siehe Funktionstabelle.

Code-Vorgabe

void setup() {
  pinMode(PA1,INPUT_PULLDOWN); // PA1 als Eingang mit PullDown Widerstand
  pinMode(PA6,INPUT_PULLDOWN);
  pinMode(PA10,INPUT_PULLDOWN);
  pinMode(PC0,OUTPUT);
  pinMode(PC1,OUTPUT);
  pinMode(PC2,OUTPUT);
}

void loop() {
  int anzahl =0;
  if(digitalRead(PA1)) anzahl++;
  digitalWrite(PC0,HIGH); // LED LC0 einschalten
}
PA10PA6PA1LC2 (PC2)LC1 (PC1)LC0 (PC0)
000000
001001
010001
011011
100001
101011
110011
111111
Funktionstabelle nach Bitposition geordnet
Lösungsvorschlag
void setup() {
  pinMode(PA1,INPUT_PULLDOWN); // PA1 als Eingang mit PullDown Widerstand
  pinMode(PA6,INPUT_PULLDOWN);
  pinMode(PA10,INPUT_PULLDOWN);
  pinMode(PC0,OUTPUT);
  pinMode(PC1,OUTPUT);
  pinMode(PC2,OUTPUT);
}

void loop() {
  int anzahl =0;
  if(digitalRead(PA1)) anzahl++;
  if(digitalRead(PA6)) anzahl++;
  if(digitalRead(PA10)) anzahl++;
  switch (anzahl){
    case 0:
      digitalWrite(PC0,LOW);
      digitalWrite(PC1,LOW);
      digitalWrite(PC2,LOW);
      break;
    case 1:
      digitalWrite(PC0,HIGH);
      digitalWrite(PC1,LOW);
      digitalWrite(PC2,LOW);
      break;
    case 2:
      digitalWrite(PC0,HIGH);
      digitalWrite(PC1,HIGH);
      digitalWrite(PC2,LOW);
      break;
    case 3:
      digitalWrite(PC0,HIGH);
      digitalWrite(PC1,HIGH);
      digitalWrite(PC2,HIGH);
      break;      
  }
}

Zeiten mit Oszilloskop messen

Wie schnell kann ausgegeben werden? Die LED an D13 (PA5) so schnell wie möglich blinken lassen.
Das Signal mit einem Digital-Oszilloskop messen. (Bin hier im Paradies, hab ein Analog Discovery Studio)

Analog_Discovery_Studio
Analog_Discovery_Studio
Hameg_HM507
Hameg_HM507
Siglent_SDS1152CML
Siglent_SDS1152CML

In der Schule haben wir Hameg und Siglent Oszis.
Und neuerdings auch Analog Discovery Studios 😍.

Mit digitalWrite() Pin-Zustand ändern

#define Board_LED D13 //PA5

void setup(){   // Einmalige Ausführung => Initialisierungen...
    pinMode(Board_LED, OUTPUT);  // Pin als Ausgang
}
void loop(){
    digitalWrite(Board_LED,HIGH);
    digitalWrite(Board_LED,LOW);
}
Messung mit einfachen Strippen
Messung mit einfachen Strippen
Messung mit Tastkopf
Messung mit Tastkopf

Ein richtiger Oszi-Tastkopf ergibt sauberere Signale als bei Verwendung einfacher Strippen, wenn die Frequenz höher wird.
Durch die digitale Messwerterfassung lassen sich Werte für die Dauer des positiven-, negativen Pegels und der Frequenz des Signals sofort anzeigen. Zwischen High und Low vergehen 1,4µs, zwischen Low und High 2,2µs warum?
Die loop() braucht für irgendwelche Arbeit etwas Zeit, neuer Versuch mit while(1):

#define BOARD_LED D13

void setup(){   // Einmalige Ausführung => Initialisierungen...
    pinMode(BOARD_LED, OUTPUT);  // Pin als Ausgang
}
void loop(){
  while(1){
    digitalWrite(BOARD_LED,HIGH);
    digitalWrite(BOARD_LED,LOW);
  }  
}
Messung mit While statt Loop
Messung mit While statt Loop

Yeah, wir werden schneller! Aber da geht noch was: Direkt auf das PortA zugreifen und das Bit 5 im ODR massieren:

Mit GPIOA->ODR die Hardware direkter ansprechen

Die Arduino-Funktion digitalWrite(..) ermöglicht einzelne Port-Pins einfach zu verändern. Das kostet allerdings Zeit durch interne Umrechnungen von Arduino-Bezeichnung des Pins zu passender Adresse und Bits des Ziel-Controllers. Wie Sie aus TG11 Assembler schon wissen, wird im ODR (Output Data Register) des Ports die Ausgabe-Bitwerte für die Port-Pins gespeichert. Wenn PA5 als Ausgang 1 sein soll, muss im ODR an Bit5 eine 1 stehen. Wenn ich nur PA5 auf 1 setzen will sollte ich die anderen Bits in Ruhe lassen. Mit GPIOA->ODR |= (1<<5) lade ich das ODR von PortA, mit |= (1<<5) setze ich an Bit5 eine 1 und schreibe den Wert ins ODR zurück. (1<<5) ist eine 1 5 mal nach links schieben also
(1<<5) ergibt 0b100000. Zum Rücksetzen von PA5 lade ich ODR und sorge mit einem bitweisen und mit einer 0 an Position 5 dafür dass PA0 wieder 0 wird.

void setup(){   // Einmalige Ausführung => Initialisierungen...
    pinMode(PA5, OUTPUT);  // PA5 (D13) als Ausgang
}
void loop(){
  while(1){
    GPIOA->ODR |= (1<<5);  // PA5 <-1
    GPIOA->ODR &= ~(1<<5); // PA5 <-0
  }  
}
Direkter Port-Zugriff
Direkter Port-Zugriff

Wow, über sechs mal schneller, die Flanken werden flacher, das Messen kommt an Grenzen?
Geht da noch was? Noch schneller “Blinken”?
Statt den Port erst ein zu lesen und dann wieder neu zu setzen mit |= und &= gibt es noch ein BSRR-Register (Bit-Set-Reset-Register). Dabei werden die Bits im Ausgaberegister mit einem Maschinenbefehl direkt verändert:

Mit GPIOA->BSRR noch schneller werden

Statt ODR zu laden, zu verändern und wieder zu speichern gibt es ein Spezialregister BSRR (Bit Set Reset Register). Wenn dort in Position 0..15 eine 1 reingeschrieben wird ist im ODR an dieser Position eine 1 gesetzt, die Ausgabe an dem PortPin wird 1. Um ein Bit in der Position n zurück zu setzen, Reset, muss an Stelle 16+n eine 1 geschrieben werden. Unten das Beispiel für PA5 (D13):

void setup(){   // Einmalige Ausführung => Initialisierungen...
    pinMode(PA5, OUTPUT);  // PA5 (D13) als Ausgang
}
void loop(){
  while(1){
    GPIOA->BSRR = (1<<5);    // PA5 <-1 mit BSRR können Bits gesetzt werden
    GPIOA->BSRR = (1<<16+5); // PA5 <-0 die oberen 16Bit sind fürs Rücksetzen
  }  
}
BSRR verwenden
BSRR verwenden

Irre, beinnahe drei mal schneller, das Oszi kommt noch gut hinterher bei 6,4 MHz!
Wir sind bei 30ns Befehlszeit zwischen an und aus, der Rück-Sprung in der While-Schleife braucht halt etwas…
Fazit: 6,4 MHz vs. 278 kHz: 23 mal schnellere Signale wenn hardwarenah programmiert wird.
Diskutieren Sie die Vor- und Nachteile bei der Programmentwicklung hardwarenah zu arbeiten versus die Arduino-Methoden zu verwenden.

Ausführungszeiten von Unterprogrammen und später Interrupt Service Routinen (ISR) mit Oszi messen können

Wie genau ist delay(500)?

#define ZEIT 500

void setup(){   // Einmalige Ausführung => Initialisierungen...
    pinMode(PA5, OUTPUT);  // PA5 (D13) als Ausgang
}
void loop(){
  while(1){
    GPIOA->BSRR = (1<<5);    // PA5 <-1
    delay(ZEIT);
    GPIOA->BSRR = (1<<16+5); // PA5 <-0
    delay(ZEIT);
  }  
}
delay(500)
delay(500)
delay(1)
delay(1)

Okay, recht genau. Zum Spass wollen wir uns mal ein eigenes Delay basteln:

#define ZEIT 1000

void setup(){   // Einmalige Ausführung => Initialisierungen...
    pinMode(PA5, OUTPUT);  // PA5 (D13) als Ausgang
}
//int a=0; // bei globaler Definition scheint kein volatile nötig
void myDelay(int n){ // mein Delay
  volatile int a=0;  // volatile bedeutet veränderlich
  for(int i=0;i<n;i++){
    a=!a; // in der Schleife muss es was zu tun geben
  }
}
void loop(){
  while(1){
    GPIOA->BSRR = (1<<5);    // PA5 <-1
    myDelay(ZEIT);
    GPIOA->BSRR = (1<<16+5); // PA5 <-0
    myDelay(ZEIT);
  }  
}
myDelay
myDelay

War das eine Geburt, der Compiler optimiert sinnlose Befehlssequenzen weg, z.B. einfach Hochzählen ohne dabei etwas zu verändern. Die Variable a zu invertieren sollte eine Aufgabe sein. Allerdings muss sie dazu entweder global, d.h. ausserhalb von myDelay() definiert werden oder mit volatile als veränderlich gekennzeichnet sein. Probieren Sie es aus!
Bonus: Verbessern Sie myDelay() so, dass die Zeit stimmt.

Lösungsvorschlag
#define ZEIT 1000

void setup(){   // Einmalige Ausführung => Initialisierungen...
    pinMode(PA5, OUTPUT);  PA5 (D13) als Ausgang
}

void myDelay(int n){
  volatile int a=0;
  for(int i=0;i<n*2905;i++){
    a=!a;
  }
}
void loop(){
  while(1){
    GPIOA->BSRR = (1<<5);    // PA5 <-1
    myDelay(ZEIT);
    GPIOA->BSRR = (1<<16+5); // PA5 <-0
    myDelay(ZEIT);
  }  
}

Brummen und Prellen messen

Das Oszi ist noch warm, noch ein paar Messungen damit machen. Den Tastkopf mit A1 verbinden, dort ist die Taste an PA1 angeschlossen. Dieses Programm ausführen und mit dem Finger die Leitung A1 berühren.

void setup(){   // Einmalige Ausführung => Initialisierungen...
    pinMode(PA5, OUTPUT);  PA5 (D13) als Ausgang
    pinMode(PA1,INPUT);
}
void loop(){
  digitalWrite(Board_LED,digitalRead(PA1));
}
50 Hz Brummen macht der Finger
50 Hz Brummen macht der Finger

Wir wirken wie eine Antenne für die 50Hz Netzspannung, also mit dem “Finger-Test” lässt sich rausfinden ob ein Eingang “hochohmig” also kein Pullup- oder Pulldown-Widerstand eingeschaltet ist. Jetzt INPUT_PULLDOWN einstellen und mal schauen ob der Taster an PA1 prellt:

Tasterprellen entdeckt
Tasterprellen entdeckt

Musste das Oszi auf Single-Shot umstellen und ein wenig rumprobieren bis mir diese Aufnahme gelungen ist.
150µs prellt es hier.

Tastenprellen beim Loslassen
Tastenprellen beim Loslassen


Richtig übel kann das Prellen beim Loslassen der Taste werden, wie lange ist es im Bild oben?
Zum Abschluss noch den blauen User-Button an PC13 auf dem Nucleo-Board analysieren: Aha mit PullUp-Widerstand verbunden, ist High, Fingertest erzeugt kein Brummen. Scheint sogar elektrisch entprellt zu sein.
Idee für Hardcore-Bonusaufgabe: Software, die Prellen aufzeichnet, und auf dem seriellen Plotter ausgibt..

Abstimmanzeige 2.0

Können Sie die Ausgabe der LED jetzt eleganter lösen?

Lösungsvorschlag
void setup() {
  pinMode(PA1,INPUT_PULLDOWN);
  pinMode(PA6,INPUT_PULLDOWN);
  pinMode(PA10,INPUT_PULLDOWN);
  pinMode(PC0,OUTPUT);
  pinMode(PC1,OUTPUT);
  pinMode(PC2,OUTPUT);
}

void loop() {
  int anzahl =0;
  if(digitalRead(PA1)) anzahl++;
  if(digitalRead(PA6)) anzahl++;
  if(digitalRead(PA10)) anzahl++;
  switch (anzahl){
    case 0: GPIOC->ODR =0; break;
    case 1: GPIOC->ODR =1; break;
    case 2: GPIOC->ODR =3; break;
    case 3: GPIOC->ODR =7; break;
  }
}
Raffinierter Lösungsvorschlag
void setup() {
  pinMode(PA1,INPUT_PULLDOWN);
  pinMode(PA6,INPUT_PULLDOWN);
  pinMode(PA10,INPUT_PULLDOWN);
  pinMode(PC0,OUTPUT);
  pinMode(PC1,OUTPUT);
  pinMode(PC2,OUTPUT);
}

void loop() {
  int aus =0;
  int ein = GPIOA->IDR & 0b10001000010;
  while(ein >0){              // solange einsen
    if(ein&1) aus=(aus<<1)+1; // zähle die 1
    ein >>= 1;                // schiebe 1 nach rechts
  }
  GPIOC->ODR = aus;
}

Lüftersteuerung mit LED-Leuchtbandanzeige

IO
IO

Ein Lüfter soll mit den Tasten PA1 (Ein/Aus) PA6 (Lüfter langsamer) und PA10 (Lüfter schneller) gesteuert werden. Es gibt 5 Lüfterstufen 0..4. Zunächst wird nur die LED-Anzeige (Leuchtband von LED LC0..LC3 an PC0..PC3) der Lüfterstufen entwickelt.
Die letzte Lüfterstufe soll beim Ausschalten gemerkt werden. Bei ausgeschaltetem Lüfter leuchtet keine LED und die Taster PA6 und PA10 sind ohne Funktion.

StufeLC3 (PC3)LC2 (PC2)LC1 (PC1)LC0 (PC0)
0
1O
2OO
3OOO
4OOOO
Anzeige der Lüfterstufen

Um sich den Code (8 Zeilen mit pinMode(..) ) zum Festlegen von PC0..PC7 als Ausgang für die LEDs zu sparen werden die Ausgänge über das Mode-Register gesetzt:
GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
Ob und wie ein Port-Pin als Ausgang verwendet wird steht im MODER drin. Kann ja auch ohne Arduino-Zeug festgelegt werden.. Leider muss wenigstens ein pinMode(PC0, OUTPUT) im Quellcode rein sonst ging es nicht.. Wieso 0x5555 und nicht 0xFF könnte noch gefragt werden. Ja, so ein Pin kann Eingang oder Ausgang mit entweder OpenDrain oder PushPull sein. Also den Pin nach unten ziehen OpenDrain (OpenCollector) oder nix machen oder nach unten ziehen Pull -> 0 oder nach oben Push -> 1 liefern. Somit ergibt sich pro PortPin mehr als zwei Möglichkeiten Eingang, OpenDrain, PushPull. Folglich braucht es 2 Bit im ModeR pro Pin.
Dieser Test-Quellcode ist schon gegeben:

void setup(){   // Einmalige Ausführung => Initialisierungen...
    Serial.begin(115200);  // Serielle Schnittstelle zum debuggen
    pinMode(PC0, OUTPUT);  // ohne diese Zeile klappts nicht
    GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
    GPIOC->ODR = 0xff;     // PC0..PC7 einschalten
    delay(500);
    GPIOC->ODR = 0;        // PC0..PC7 ausschalten
    pinMode(PA1,INPUT_PULLDOWN);  // Ein-Aus-Schalten
    pinMode(PA6,INPUT_PULLDOWN);  // minusLuefter
    pinMode(PA10,INPUT_PULLDOWN); // plusLuefter
}

void plusLuefter(){
  Serial.println("plusLuefter");
  if(GPIOC->ODR<0b1000){ // wenn noch nicht Stufe 4
    GPIOC->ODR = (GPIOC->ODR<<1)+1; // schiebe nach links und setze PC0
  }
}

void loop(){
  if(digitalRead(PA10)){ // wenn Taste PA10 gedrückt
    plusLuefter();
    delay(2); // Entprellen
    while(digitalRead(PA10)); // warten bis Taste losgelassen
    delay(2); // Entprellen
  }
}

Entwickeln Sie das vollständige Programm.
Bonus: Eine Status-LED ob der Lüfter an ist wäre wünschenswert. LED5 an PC5 soll dafür genutzt werden.

Lösungsvorschlag
void setup(){   // Einmalige Ausführung => Initialisierungen...
    Serial.begin(115200);  // Serielle Schnittstelle zum debuggen
    pinMode(PC0, OUTPUT);  // ohne diese Zeile klappts nicht
    GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
    GPIOC->ODR = 0xff;   // PC0..PC7 einschalten
    delay(500);
    GPIOC->ODR = 0;     // PC0..PC7 ausschalten
    pinMode(PA1,INPUT_PULLDOWN);  // Ein-Aus-Schalten
    pinMode(PA6,INPUT_PULLDOWN);  // minusLuefter
    pinMode(PA10,INPUT_PULLDOWN); // plusLuefter
}
int stufe=0; // merken der Lüfterstufe
int an=0;    // ist der Lüfter an 
void plusLuefter(){
  Serial.println("plusLuefter");
  if(stufe<0b1000){ // wenn noch nicht Stufe 4
    stufe = (stufe<<1)+1;
  }
  GPIOC->ODR = stufe | an<<5;
}
void minusLuefter(){
  Serial.println("minusLuefter");
  if(stufe>0){ // wenn noch nicht Stufe 0
    stufe = stufe>>1;
  }
  GPIOC->ODR = stufe | an<<5;
}
void loop(){
  if(digitalRead(PA1)){ // wenn Taste PA1 gedrückt an/aus
    if(an){ // wenn Lüfter an
      an=0;
      GPIOC->ODR=0;
      Serial.println("Luefter aus");
    }
    else{
      an=1;
      GPIOC->ODR=stufe | an<<5;
      Serial.println("Luefter an");
    }
    delay(2); // Entprellen
    while(digitalRead(PA1)); // warten bis Taste losgelassen
    delay(2); // Entprellen
  }
  else if(an){
    if(digitalRead(PA10)){ // wenn Taste PA10 gedrückt
      plusLuefter();
      delay(2); // Entprellen
      while(digitalRead(PA10)); // warten bis Taste losgelassen
      delay(2); // Entprellen
    }
    if(digitalRead(PA6)){ // wenn Taste PA6 gedrückt
      minusLuefter();
      delay(2); // Entprellen
      while(digitalRead(PA6)); // warten bis Taste losgelassen
      delay(2); // Entprellen
    }
  }
}

Bessere Tastenabfrage mit buttonCheck()

Das Abfragen und Entprellen mehrerer Tasten müsste doch eleganter als oben funktionieren. Durch das Warten bis eine Taste losgelassen wird ist das Hauptprogramm blockiert. Meine Lösung dafür ist ein Unterprogramm buttonCheck() das fragt die Tasten auf einmal ab und schreibt Änderungen (Flankendedektion) in zwei globale Variablen buttonEnter -bei Drücken von Tasten und buttonExit -bei Loslassen von Tasten. Das Unterprogramm benötigt dafür letztlich maximal die Zeit fürs Entprellen, das Hauptprogramm wird nicht lange blockiert. Im Hauptprogramm müssen nur noch die Bits der Tasten von buttonEnter bzw. buttonExit überprüft werden.
Hier der Beispielcode:

void setup(){   // Einmalige Ausführung => Initialisierungen...
    Serial.begin(115200);  // Serielle Schnittstelle zum debuggen
    pinMode(PC0, OUTPUT);  // ohne diese Zeile klappts nicht
    GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
    pinMode(PA1,INPUT_PULLDOWN);  // Ein-Aus-Schalten
    pinMode(PA6,INPUT_PULLDOWN);  // minusLuefter
    pinMode(PA10,INPUT_PULLDOWN); // plusLuefter
    pinMode(PC8, OUTPUT);         // Pin als Ausgang
}

#define BUTTONREADER GPIOA->IDR & 0b10001000010 // nur die relevanten Tastenbits
int buttonOld = 0;              // alter Tasten-Zustand 
int buttonEnter,buttonExit;     // gedrueckte und losgelassene Tasten 

void buttonCheck(){             // Tastaturabfrage mit Flankendedektion  
  int buttonTest,tmp; 
  buttonEnter = 0, buttonExit = 0; 
  buttonTest = BUTTONREADER;       // Einlesen
  if (buttonOld != buttonTest){    // hat sich was getan 
    delay(5);                      // 5ms Prellen abwarten 
    tmp = BUTTONREADER;            // noch mal Einlesen
    if (tmp == buttonTest){        // ist es stabil? 
      buttonEnter = (~buttonOld) & buttonTest; // steigende Flanke !alt und neu 
      buttonExit = buttonOld & (~buttonTest);  // fallende Flanke alt und !neu 
      buttonOld = buttonTest; 
    } 
  } 
} 
int an=0;    //  Lüfter aus
void loop(){
  GPIOC->BSRR = (1<<8);    // PC8 <-1
  buttonCheck();  // alle Tasten abfragen
  GPIOC->BSRR = (1<<16+8); // PC8 <-0
  if(buttonEnter&(1<<1)){ // wenn Taste PA1 gedrückt an/aus
    if(an){ // wenn Lüfter an
      an=0;
      GPIOC->ODR=0;
      Serial.println("Luefter aus");
    }
    else{
      an=1;
      GPIOC->ODR= 1<<5;  // Betriebs-LED an
      Serial.println("Luefter an");
    }
  }
}

Messen Sie die Ausführungsdauer von buttonCheck() an PC8 ohne Tastendruck. Die Zeit bei einem Tasten-Ereignis zu messen ist schwieriger, da im Single-Shot Modus des Oszis auf ein Tastenereignis getriggert werden muss.

Entprellen mit buttonCheck
Entprellen mit buttonCheck

Wieder so ein übles Nachprellen einer Taste (C1 gelb), buttonCheck() hält brav inne und wartet das Prellen ab (C2 blau). Scharfen Beobachtern fällt auf, dass die Wartezeit kleiner als das programmierte 5ms Delay ist. Wie funktioniert eigentlich das Arduino-Delay? Eine simple Warteschleife ist es jedenfalls nicht, sonst wäre die Zeit eher länger…
Hat was mit Timern zu tun und das lernen Sie bald kennen..
Bauen Sie buttonCheck() in ihre Lösung ein.

Lösungsvorschlag
void setup(){   // Einmalige Ausführung => Initialisierungen...
    Serial.begin(115200);  // Serielle Schnittstelle zum debuggen
    pinMode(PC0, OUTPUT);  // ohne diese Zeile klappts nicht
    GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
    pinMode(PA1,INPUT_PULLDOWN);  // Ein-Aus-Schalten
    pinMode(PA6,INPUT_PULLDOWN);  // minusLuefter
    pinMode(PA10,INPUT_PULLDOWN); // plusLuefter
}

#define BUTTONREADER GPIOA->IDR & 0b10001000010 // nur die relevanten Tastenbits
int buttonOld = 0;              // alter Tasten-Zustand 
int buttonEnter,buttonExit;     // gedrueckte und losgelassene Tasten 

void buttonCheck(){             // Tastaturabfrage mit Flankendedektion  
  int buttonTest,tmp; 
  buttonEnter = 0, buttonExit = 0; 
  buttonTest = BUTTONREADER;       // Einlesen
  if (buttonOld != buttonTest){    // hat sich was getan 
    delay(5);                      // Prellen abwarten 
    tmp = BUTTONREADER;            // noch mal Einlesen
    if (tmp == buttonTest){        // ist es stabil? 
      buttonEnter = (~buttonOld) & buttonTest; // steigende Flanke !alt und neu 
      buttonExit = buttonOld & (~buttonTest);  // fallende Flanke alt und !neu 
      buttonOld = buttonTest; 
    } 
  } 
} 
int stufe=0; // merken der Lüfterstufe
int an=0;    // ist der Lüfter an 
void plusLuefter(){
  Serial.println("plusLuefter");
  if(stufe<0b1000){ // wenn noch nicht Stufe 4
    stufe = (stufe<<1)+1;
  }
  GPIOC->ODR = stufe | an<<5;
}
void minusLuefter(){
  Serial.println("minusLuefter");
  if(stufe>0){ // wenn noch nicht Stufe 0
    stufe = stufe>>1;
  }
  GPIOC->ODR = stufe | an<<5;
}
void loop(){
  buttonCheck();  // alle Tasten abfragen
  if(buttonEnter&(1<<1)){ // wenn Taste PA1 gedrückt an/aus
    if(an){ // wenn Lüfter an
      an=0;
      GPIOC->ODR=0;
      Serial.println("Luefter aus");
    }
    else{
      an=1;
      GPIOC->ODR=stufe | an<<5;
      Serial.println("Luefter an");
    }
  }
  else if(an){
    if(buttonEnter&(1<<10)){ // wenn Taste PA10 gedrückt
      plusLuefter();
    }
    if(buttonEnter&(1<<6)){ // wenn Taste PA6 gedrückt
      minusLuefter();
    }
  }
}

Leistungssteuerung mit PWM durch analogWrite()

Achtung! Erst ruhig alles durchlesen, Links verfolgen und verstehen, dann anfangen. 🤠
Die LED an PC7 soll jetzt entsprechend der Lüfterleistungsstufe verschieden hell leuchten.
Dazu wird PulsWeitenModulation (PWM) verwendet. Lesen: Einfach [arduino] Umfangreich [wikipedia]
Entsprechend der Lüfterstufe wird ein Helligkeitswert durch analogWrite(…) ausgegeben:
stufe->hell: 0->0; 1->10; 2->40; 3->100; 4->255.
Verwenden Sie analogWrite(PC7,hell), wobei int hell einen Wert von 0..255 annehmen kann.
Notwendige Umbauarbeiten: Die Variable stufe beinhaltet nun nicht mehr das Ausgabemuster sondern die Werte 0..4. Ein Unterprogramm ausgebenStufe() lässt die entsprechenden LED PC0..PC3 leuchten und setzt den Wert von stufe in den Helligkeitswert hell um und gibt ihn mit analogWrite(..) aus.

Messen Sie den Signalverlauf an PC7 bei verschiedenen Helligkeitsstufen, das Signal kann mit dem Tastkopf hier abgegriffen werden [Portpins]. Mit welcher Frequenz wird das Signal ausgegeben?
Bonus: Die Auflösung könnte gesteigert werden mit analogWriteResolution().

Lösungsvorschlag
void setup(){   // Einmalige Ausführung => Initialisierungen...
    Serial.begin(115200);  // Serielle Schnittstelle zum debuggen
    pinMode(PC0, OUTPUT);  // ohne diese Zeile klappts nicht
    GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
    pinMode(PA1,INPUT_PULLDOWN);  // Ein-Aus-Schalten
    pinMode(PA6,INPUT_PULLDOWN);  // minusLuefter
    pinMode(PA10,INPUT_PULLDOWN); // plusLuefter
    //analogWriteResolution (16); //0-65535 PWM duty cycle
    //analogWriteFrequency(2000); // Set PMW period to 2000 Hz instead of 1000
    pinMode(PA5,OUTPUT);
}

#define BUTTONREADER GPIOA->IDR & 0b10001000010 // nur die relevanten Tastenbits
int buttonOld = 0;              // alter Tasten-Zustand 
int buttonEnter,buttonExit;     // gedrueckte und losgelassene Tasten 

void buttonCheck(){             // Tastaturabfrage mit Flankendedektion  
  int buttonTest,tmp; 
  buttonEnter = 0, buttonExit = 0; 
  buttonTest = BUTTONREADER;       // Einlesen
  if (buttonOld != buttonTest){    // hat sich was getan 
    delay(5);                      // Prellen abwarten 
    tmp = BUTTONREADER;            // noch mal Einlesen
    if (tmp == buttonTest){        // ist es stabil? 
      buttonEnter = (~buttonOld) & buttonTest; // steigende Flanke !alt und neu 
      buttonExit = buttonOld & (~buttonTest);  // fallende Flanke alt und !neu 
      buttonOld = buttonTest; 
    } 
  } 
} 
int stufe=0; // merken der Lüfterstufe
int an=0;    // ist der Lüfter an 
void plusLuefter(){
  Serial.println("plusLuefter");
  if(stufe<4){ // wenn noch nicht Stufe 4
    stufe++;
  }
  ausgebenStufe();
}
void minusLuefter(){
  Serial.println("minusLuefter");
  if(stufe>0){ // wenn noch nicht Stufe 0
    stufe--;
  }
  ausgebenStufe();
}
void ausgebenStufe(){
  GPIOC->ODR =0;
  for(int i=0;i<stufe;i++){
    GPIOC->ODR = (GPIOC->ODR<<1)+1;
  }
  GPIOC->ODR |= an<<5; // PC5 als Status LED
  int hell;
  switch (stufe){
    case 0: hell=0;break;
    case 1: hell=10;break;
    case 2: hell=40;break;
    case 3: hell=100;break;
    case 4: hell=255;break;
  }
  Serial.printf("Stufe: %d Helligkeit: %d\n",stufe,hell);
  analogWrite(PC7,hell);
}

void loop(){
  buttonCheck();  // alle Tasten abfragen
  if(buttonEnter&(1<<1)){ // wenn Taste PA1 gedrückt an/aus
    if(an){ // wenn Lüfter an
      an=0;
      GPIOC->ODR=0;
      Serial.println("Luefter aus");
    }
    else{
      an=1;
      ausgebenStufe();
      Serial.println("Luefter an");
    }
  }
  else if(an){
    if(buttonEnter&(1<<10)){ // wenn Taste PA10 gedrückt
      plusLuefter();
    }
    if(buttonEnter&(1<<6)){ // wenn Taste PA6 gedrückt
      minusLuefter();
    }
  }
}

ToDo: Motor mit Propeller über L293D anschließen.

Aufgaben: Analogwert von Poti an PA0 einlesen und ausgeben

Erstellen Sie ein Programm, dass den Wert von Poti mit analogRead(PA0) einliest und als Leuchtband auf LED0..6 ausgibt, LED7 soll entsprechend des Wertes mit analogWrite(..) verschieden hell leuchten. Die normale Auflösung von analogRead() ist 10 Bit, d.h. der Wertebereich ist 0..1023.
Bonus: Mit analogReadResolution(12) kann die Auflösung auf 12Bit hochgesetzt werden.

Lösungsvorschlag
void setup(){   // Einmalige Ausführung => Initialisierungen...
    Serial.begin(115200);  // Serielle Schnittstelle zum debuggen
    pinMode(PC0, OUTPUT);  // ohne diese Zeile klappts nicht
    GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
    pinMode(PA1,INPUT_PULLDOWN);  // Ein-Aus-Schalten
    pinMode(PA6,INPUT_PULLDOWN);  // minusLuefter
    pinMode(PA10,INPUT_PULLDOWN); // plusLuefter
    //analogWriteResolution (16); //0-65535 PWM duty cycle
    //analogWriteFrequency(2000); // Set PMW period to 2000 Hz instead of 1000
    //analogReadResolution(12);
    pinMode(PA5,OUTPUT);
}

void ausgebenLeuchtband(int n){
  GPIOC->ODR =0;
  for(int i=0;i<n;i++){
    GPIOC->ODR = (GPIOC->ODR<<1)+1;
  }
}

void loop(){
  int adWert = analogRead(PA0);
  ausgebenLeuchtband(adWert/(1023/7));
  int hellLED7 = adWert>>2;
  analogWrite(PC7,hellLED7);
  Serial.printf("AD-: %4d Helligkeit: %3d\n",adWert,hellLED7);
  delay(300);
}

Erstellen Sie ein Programm, dass den Wert von Poti an PA0 einliest und zuerst einstellig und dann zweistellig auf der 7-Segmentanzeige ausgibt.
Tipp: Verwenden Sie ein Umwandungs-Array int bcd_7seg[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};

Lösungsvorschlag einstellig
void setup(){   // Einmalige Ausführung => Initialisierungen...
    pinMode(PC0, OUTPUT);  // ohne diese Zeile klappts nicht
    GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
    pinMode(PC11,OUTPUT); // Einer
    pinMode(PC12,OUTPUT); // Zehner
}

int bcd_7seg[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; // Umrechnung

void ausgebenSiebenSegment(int n){
  GPIOC->ODR = bcd_7seg[n] | (1<<11); // Einer einschalten
}

void loop(){
  int adWert = analogRead(PA0);
  ausgebenSiebenSegment(adWert/(1023/9));
  delay(100);
}
Lösungsvorschlag zweistellig
void setup(){   // Einmalige Ausführung => Initialisierungen...
    pinMode(PC0, OUTPUT);  // ohne diese Zeile klappts nicht
    GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
    pinMode(PC11,OUTPUT); // Einer
    pinMode(PC12,OUTPUT); // Zehner
}

int bcd_7seg[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; // Umrechnung
int adWert=0;
void loop(){
  adWert = (analogRead(PA0)+adWert*9)/10; // Werte glätten
  int aus=adWert/10;
  //aus = aus>99?99:aus; // Wert begrenzen
  if(aus>99) aus=99;
  GPIOC->ODR = bcd_7seg[aus%10] | (1<<11); // Einer ausgeben
  delay(10);
  GPIOC->ODR = bcd_7seg[aus/10] | (1<<12); // Zehner ausgeben
  delay(10);
}

Bonus: Temperaturwert von NTC-Widerstand an PA4 einlesen

In der Boardbeschreibung wird ein NTC-Temperaturabhängiger Widerstand [Pollin] auf Seite 5 erwähnt...

void setup(){   // Einmalige Ausführung => Initialisierungen...
    pinMode(PC0, OUTPUT);  // ohne diese Zeile klappts nicht
    GPIOC->MODER = 0x5555; // PC0..PC7 als Ausgang
    pinMode(PC11,OUTPUT); // Einer
    pinMode(PC12,OUTPUT); // Zehner
    analogReadResolution(12); // damit Temperatur genauer wird 12Bit
}

int bcd_7seg[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; // Umwandlung
float Wert = 0;
void loop(){
  float R2_25 = 1500; // Widerstandswert NTC bei 25°C
  float R2_theta;
  float R1 = 1500;    // Widerstandswert R1
  float dt;
  float t;
  float alpha = -0.045;
  Wert = (analogRead(PA4)+Wert*9)/10; // Messungen beruhigen, mitteln
  R2_theta = R1*Wert/(4095-Wert);
  dt = (R2_theta/R2_25-1)/alpha; // näherungsweise
  t = 25+dt;
  int aus = t; // in int umwandeln
  GPIOC->ODR = bcd_7seg[aus%10] | (1<<11); // Einer ausgeben
  delay(10);
  GPIOC->ODR = bcd_7seg[aus/10] | (1<<12); // Zehner ausgeben
  delay(10);
}

Schreibe einen Kommentar

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