kramann.info
© Guido Kramann

Login: Passwort:










kramann.info
© Guido Kramann

Login: Passwort:




DAY BY DAY zu MIK -- Grundlagen der Mikrocontrollertechnik

(EN google-translate)

(PL google-translate)

Übersicht

  • Die vorliegende Seite stellt den Einstiegspunkt für diese Lehrveranstaltung dar und verzeichnet chronologisch die behandelten Inhalte.
  • Aber ein großer Teil der Inhalte findet sich nicht direkt hier, sondern die Seite hier verlinkt auf andere Bereiche von kramann.info.
  • Die Prüfung findet Semester begleitend in elektronischer Form statt (E-Test).

Die Lehrveranstaltung Grundlagen der Mikrocontrollertechnik richtet sich an Studierende der Ingenieurwissenschaften im vierten Semester. Es geht um die Programmierung von Mikrocontrollern, insbesondere um die Programmierung der internen Peripherie eines Mikrocontrollers. Unter dem Begriff "interne Peripherie" werden hier digitale Ein- und Ausgänge, Bussysteme, Timer, Analog- zu Digitalwandler und PWM-Geber zusammengefaßt. Im Wesentlichen soll die Konfiguration dessen, was in der Vorlesung mit interner Peripherie erlernt werden, sowie die Entwicklung von Software, die dann Gebrauch von dieser internen Peripherie macht. Die Programmierung erfolgt in C und C++. Für den leichten Einstieg werden zu Beginn die Arduino-IDE und die dort verfügbaren Befehle verwendet. Im weiteren Verlauf der Lehrveranstaltung soll aber auch die direkte Konfiguration von Registern erlernt werden und in diesem Zusammenhang der Gebrauch von Datenblättern zu Mikrocontrollern. In der Lehrveranstaltung und den zugehörigen Übungen kommt der Arduino Micro zum Einsatz. Jedoch wird in allgemeineren Betrachtungen auch auf andere Mikrocontroller verwiesen.

Chronologisches Verzeichnis der im Verlauf des Semesters behandelten Themen


#1 Mi 16.03.2022

Einführend sollen die nachfolgenden Fragen behandelt werden:

Teil 1: Klären einiger grundlegender Fragen zu Mikrocontrollern
Motivation / Diskussion ... Grey Walter's tortoises 1949
  1. Was ist ein Mikrocontroller?
  2. Wozu werden Mikrocontroller verwendet?
  3. Was ist ein engebettetes System?
  4. Wer stellt Mikrocontroller her?
  5. Wie unterscheidet sich ein Mikrocontroller von einem PC?
  6. Wie zeichnet sich ein Mikrocontroller-Programm aus?
  7. Was ist ein Cross-Compiler?
  8. Was ist Maschinen nahes Programmieren?
  9. Worin liegt der Sinn darin, höhere Programmiersprachen wie C oder C++ zu verwenden?
1.1 Mikrocontroller auf Wikipedia
1.2 Foto Dual In Line DIL Bauweise eines ATmega32 von Atmel.
  • 2. Durch die Verwendung von Mikrocontrollern werden vormals rein mechanische und elektromechanische Geräte, beispielsweise solche aus dem Haushaltsbereich wie Herd, Kühlschrank usw., zu sehr viel preiswerter herzustellenden anteilig mehr elektronischen Geräten.
3. Eingebettete Systeme auf Wikipedia.
4. Marktanteile der Hersteller von Mikrocontrollern
5.1 Alan Turings Grundkonzept
5.2 Die unterschiedliche Architektur beim PC und beim Mikrocontroller
6. Wesentliches Merkmal eines Mikrocontroller-Programms: Die Loop-Funktion und die Manipulation von Registern.
  • 7. Mikrocontroller besitzen in der Regel keine Benutzerschnittstelle wie ein PC. Das Entwickeln und Kompilieren der Programme für einen Mikrocontroller erfolgt darum auf einem PC mit einer Entwicklungsumgebung und einem Cross-Compiler.
8. Exkurs: Programmentwurf in Assembler.

9. Höhere Programmiersprachen kommen zum Einsatz um die Wartbarkeit und Transparenz von Programmen zu verbessern.

Teil 2: Vorbereitungen auf die erste Übung
  1. Booten und Verwenden der (Linux-) Xubuntu-Distribution auf den PC-Pool-Rechnern
  2. Verwendung der Arduino-IDE
  3. Konfiguration der Arduino-IDE für einen über USB verbundenen Arduino Micro - Mikrocontroller
  4. Schreiben eines Programms
  5. Kompilieren eines Programms
  6. Übertragen eines Programms auf den Mikrocontroller
  7. Ansteuern digitaler Ein- und Ausgänge über Arduino-Befehle
  8. Vorbesprechung der ersten Übungsaufgaben
96_Arduino -- Überblick zum Arduino Micro
96_Arduino/01_Lauflicht -- Hardware (Steckboard mit Fritzing dargestellt) und Software (C / Arduino-Befehle) für ein "Lauflicht".
Im Verlauf des Unterrichts entstandene Quelltexte:
void setup() 
{
    pinMode(1,OUTPUT);
}

void loop() 
{
    digitalWrite(1,HIGH);
    delay(1000);
    digitalWrite(1,LOW);
    delay(1000);
}

Code 0-1: Blinkende LED

void setup() 
{
    pinMode(1,OUTPUT);
    pinMode(2,INPUT);
    digitalWrite(2,HIGH); //Pullup aktivieren, intern auf +5V setzen
}
int x;
void loop() 
{
    x = digitalRead(2);
    if(x<=0)
    {
        digitalWrite(1,HIGH);
        delay(1000);
        digitalWrite(1,LOW);
        delay(1000);
    }    
}

Code 0-2: Blinkende LED mit Taster

void setup() 
{
    pinMode(1,OUTPUT);
    pinMode(2,INPUT);
    digitalWrite(2,HIGH); //Pullup aktivieren, intern auf +5V setzen
}
int x;
void loop() 
{
    x = digitalRead(2);
    if(x<=0)
    {
        //digitalWrite(1,HIGH);
        //PORTD = 8;
        PORTD = 0b00001000;
        delay(1000);
        //digitalWrite(1,LOW);
        PORTD = 0;
        delay(1000);
    }    
}

Code 0-3: Blinkende LED unter Verwendung von Register-Befehlen


#2 Mi 23.03.2022 ÜBUNG


#3 Mi 30.03.2022 VORLESUNG

Quiz
  • Worin unterscheiden sich Mikrocontroller-basierte Boards von PCs?
  • Was wird in dieser LV als "interne Peripherie" bezeichnet?
  • Welche Programmier-technische Bedeutung haben Register beim Mikrocontroller?
  • Wie groß ist ein Register?
  • Haben Sie Bitmasken und Shiftoperatoren in der Informatikvorlesung behandelt? Was ist Ihnen erinnerlich?

In dieser Lehrveranstaltung soll die Registerkonfiguration beim ersten Beispiel für interne Peripherie gelernt werden, nämlich den digitalen Ein- Und Ausgängen.

  • Zugreifbar von Außen ist bei dem ArduinoMicro lediglich der Port B.
  • Jedem digitalen Ein- und Ausgang X sind drei Port-Register zugeordnet: PORTX, PINX, DDRX.
  • Beim Port B sind das somit: PORTB, PINB und DDRB.
  • DDRB legt die Richtung jedes der 8 Bits fest, also ob es als Ein- oder Ausgang verwendet wird.
  • PINB dient zur Abfrage, ob am Eingang etwas anliegt.
  • PORTB wird dazu verwendet, Ausgangszustände zu setzen und Pullup-Widerstände zu aktivieren.

Wie der Port B beim ArduinoMicro abgerufen werden kann, ist hier dargestellt:

96_Arduino/22_Universal/02_LED_Leiste -- siehe Bild 0-1: Anschlußplan der LED-Leiste.

Das ganze IO-Registerthema wird für den ATmega32 ausführlich hier behandelt:

40_Mikrocontroller/03_DigitalIO

Wie einzelne Bits in 8-Bit-Registern manipuliert werden können, ist in diesem Unterkapitel auch dort dargestellt:

40_Mikrocontroller/03_DigitalIO/03_Bitmasken_Eingang

Auf dieser Grundlage werden die folgenden Themen behandelt:

  • Darstellung der IO-Register im Datenblatt
  • Elektrische Eigenschaften der digitalen Ein- und Ausgänge
  • Pullup-Widerstände
  • Zuständige Konfigurationsregister
  • Arbeiten mit Bitmasken und Bitshift-Operatoren
  • Einige "Quizaufgaben" zum Erlernen des Umgangs mit Bitmasken und Bitshift-Operatoren
  • Praktische Anwendung des Bitshift-Operators: Aufbau eines Lauflichts.
  • Behandlung von Arrays als Vorbereitung auf die Übung.
Einführung Bitoperationen.

Bild 0-1: Einführung Bitoperationen.

Umsetzung:
// LED bei PB3
// Taster mit Pullup bei PB5

void setup() 
{
   DDRB = 0b00001000; //PB3 ist dann Ausgang, alle sonst Eingang
   //auch möglich und gleich bedeutend:
   //DDRB = 8;
   PORTB = 0b00100000; //Pullup für Bit Nr. 5 setzen   
}

void loop() 
{
   if( (PINB & 0b00100000) == 0 )//Prüfen Bit Nr. 5
   {
       PORTB = PORTB | 0b00001000; //Setzen Bit Nr. 3
   }
   else
   {
       PORTB = PORTB & 0b11110111; //Löschen Bit Nr. 3  
       //gleichbedeutend mit:
       //PORTB = PORTB & ~0b00001000; //~ == invers
   }
}

Code 0-4: Variante 1

Schaltplan.

Bild 0-2: Schaltplan.

   1<<5 == 32
   2<<4 == 32
   2>>2 == 0
   2>>0 == 2
   2>>1 == 1

   << LINKS SHIFT
   >> RECHTS SHIFT
  
   <<N  N mal Linksshift == *2^N
   >>N  N mal Rechtsshift == /2^N

   X<<N == X*2^N
   X>>N == X/2^N

   0b00000001<<5 == 0b00100000
   0b00000010<<4 == 0b00100000
   ...

   3<<2 == 12
   <=>
   0b00000011 << 2 == 0b00001100    

Code 0-5: Übersicht Shiftoperationen.

// LED bei PB3
// Taster mit Pullup bei PB5

// PB0..PB7 sind vordefinierte Konstanten,
// die die Bits von Port B bezeichnen und folgende Belegung haben:
// PB0 == 0
// PB1 == 1
// PB2 == 2
// PB3 == 3
// PB4 == 4
// PB5 == 5
// PB6 == 6
// PB7 == 7


void setup() 
{
   //DDRB = 0b00001000; //PB3 ist dann Ausgang, alle sonst Eingang
   //DDRB = (1<<3);
   DDRB = (1<<PB3);
   //auch möglich und gleich bedeutend:
   //DDRB = 8;
   //PORTB = 0b00100000; //Pullup für Bit Nr. 5 setzen   
   //PORTB = (1<<5); //Pullup für Bit Nr. 5 setzen   
   PORTB = (1<<PB5); //Pullup für Bit Nr. 5 setzen   
}

void loop() 
{
   //if( (PINB & 0b00100000) == 0 )//Prüfen Bit Nr. 5
   //if( (PINB & (1<<5)) == 0 )//Prüfen Bit Nr. 5
   if( (PINB & (1<<PB5)) == 0 )//Prüfen Bit Nr. 5
   {
       //PORTB = PORTB | (1<<3); //Setzen Bit Nr. 3
       PORTB = PORTB | (1<<PB3); //Setzen Bit Nr. 3
   }
   else
   {
       //PORTB = PORTB & (~(1<<3)); //Löschen Bit Nr. 3  
       PORTB = PORTB & (~(1<<PB3)); //Löschen Bit Nr. 3  
       //gleichbedeutend mit:
       //PORTB = PORTB & ~0b00001000; //~ == invers
   }
}

Code 0-6: Variante 2

// LED bei PB3
// Taster mit Pullup bei PB5

// PB0..PB7 sind vordefinierte Konstanten,
// die die Bits von Port B bezeichnen und folgende Belegung haben:
// PB0 == 0
// PB1 == 1
// PB2 == 2
// PB3 == 3
// PB4 == 4
// PB5 == 5
// PB6 == 6
// PB7 == 7


void setup() 
{
   //DDRB = 0b00001000; //PB3 ist dann Ausgang, alle sonst Eingang
   //DDRB = (1<<3);
   DDRB = (1<<DDB3);
   //auch möglich und gleich bedeutend:
   //DDRB = 8;
   //PORTB = 0b00100000; //Pullup für Bit Nr. 5 setzen   
   //PORTB = (1<<5); //Pullup für Bit Nr. 5 setzen   
   PORTB = (1<<PORTB5); //Pullup für Bit Nr. 5 setzen   
}

void loop() 
{
   //if( (PINB & 0b00100000) == 0 )//Prüfen Bit Nr. 5
   //if( (PINB & (1<<5)) == 0 )//Prüfen Bit Nr. 5
   if( (PINB & (1<<PINB5)) == 0 )//Prüfen Bit Nr. 5
   {
       //PORTB = PORTB | (1<<3); //Setzen Bit Nr. 3
       PORTB = PORTB | (1<<PORTB3); //Setzen Bit Nr. 3
   }
   else
   {
       //PORTB = PORTB & (~(1<<3)); //Löschen Bit Nr. 3  
       PORTB = PORTB & (~(1<<PORTB3)); //Löschen Bit Nr. 3  
       //gleichbedeutend mit:
       //PORTB = PORTB & ~0b00001000; //~ == invers
   }
}

Code 0-7: Varainte3: Bezeichnung der Shiftwerte gemäß Datenblatt.


#4 Mi 06.04.2022 ÜBUNG

96_Arduino -- siehe Bild 0-2: Pinzuordnung zwischen Chip und Board.

Übung 1: Bauen Sie auf Ihrem Board die Schaltung hier unter Verwendung einzelner LEDs auf:


96_Arduino/22_Universal/02_LED_Leiste -- siehe Bild 0-1: Anschlußplan der LED-Leiste.

Übung 2: Programmieren Sie ein Lauflicht mit Hilfe der Programmiersprachelemente, die die Arduino-Bibliothek bietet.


Hinweise zu Übung 2:

  • Beim Lauflicht soll eine LED nach der anderen aufleuchten und der Vorgang dann wieder von vorne beginnen.
  • Verwenden sie die Funktion delay(...), um Pausen zwischen den Leuchtzuständen hinzubekommen.
void setup() 
{
    pinMode(17,OUTPUT); //PB0
    pinMode(15,OUTPUT); //PB1
    pinMode(16,OUTPUT); //PB2
    pinMode(14,OUTPUT); //PB3
    
    pinMode(8,OUTPUT); //PB4
    pinMode(9,OUTPUT); //PB5
    pinMode(10,OUTPUT);//PB6
    pinMode(11,OUTPUT);//PB7
}

void loop() 
{
    ...
}

Code 0-8: Teillösung zum Lauflicht.


Übung 3: Übertragen Sie die Register-basierte Lauflicht-Lösung aus der Vorlesung auf Ihr Board.



Übung 4: Realisieren Sie ein Lauflicht, das in zwei Richtungen laufen kann. Die Richtung soll über einen Schalter wählbar sein.



Übung 5: Wie kann ein programmierbares Lauflicht realisiert werden, also eines, bei dem Ablaufmuster auf einfache Weise vorgegeben werden können?



#5 Mi 13.04.2022 VORLESUNG

Themen

  1. Lauflicht mit Auswahl
  2. Quiz
  3. Robo-Sumo
  4. PWM Prinzip
  5. PWM mit Arduino-Befehlen
  6. PWM über Register-Konfigurationen

1. Lauflicht mit Auswahl

//Rotieren realisieren
unsigned int arr[] = {0b1111111100000000,
                      0b1000000010000000,
                      0b1010101010101010};
unsigned int auswahl = 0;
void setup() 
{
    DDRB = 0b11111111;
    Serial.begin(9600);
}
void loop() 
{
    if(Serial.available())
    {
         auswahl = arr[Serial.read()%3];
    }
    auswahl = ((auswahl << 1) | (auswahl >> 15)) ;     
    PORTB = auswahl;
    delay(200);
}

Code 0-9: Lauflicht mit Auswahl.

2. Quiz

  • Wie groß ist ein Register?
  • Welche Register-Typen gibt es im Zusammenhang mit digitalen Ein- und Ausgängen?
  • Was ist dezimal 1<<3
  • Was ist dezimal 1>>3
  • Was ist dezimal 3<<1
  • Was ist dezimal 0<<1
  • Was ist dezimal 1<<0
  • Was ist dezimal 1<
  • Was ist dezimal (1<
  • Was ist dezimal 0b00001111 & 0b11110000
  • Was ist dezimal 0b00001111 | 0b11110000
  • Was ist dezimal 0b00001111 | ~0b11110000

3. Robo-Sumo

https://www.youtube.com/watch?v=lUpUQf16qzQ
83_AV/05_SUMO/10_Umsetzung -- Zustandsübergangs-Modell

4. PWM Prinzip

40_Mikrocontroller/04_PWM

5. PWM mit Arduino-Befehlen

//Dimmen einer LED mittels "analogWrite"
void setup() 
{
  
}
int x=0;
int y=0;
void loop() 
{
    if(x<256)
       y=x;
    else
       y=511-x;   
    analogWrite(9,y);
    x++;
    x%=512;
    delay(5);
}

Code 0-10: Dimmen einer LED mittels "analogWrite"

https://www.arduino.cc/reference/en/libraries/servo/
https://docs.arduino.cc/learn/electronics/servo-motors

6. PWM über Register-Konfigurationen

#define WMIN 1000
#define WMITTE 1500
#define WMAX 2000
#define SCHRITTE 1000

//Mode 8
//Phasen- und Frequenz-korrekt
//WGM1   3 2 1 0
//       1 0 0 0
//ICR1=..... TOP
//fpwm = fclk/(2*N*TOP)
//Vorteilung
//N=8
//80Hz = 16000000/(2*8*TOP)
//TOP = 16000000/(2*8*80Hz)=12500

//dt==1000ms*(1/80Hz)/12500 == 0,001ms (1 Schritt == 0,001ms)
//=>
//1ms == 1000 Schritte
//1,5ms == 1500 Schritte
//2ms == 2000 Schritte
void setup() 
{
       TCCR1A = (1<<COM1A1) | (0<<COM1A0) | (1<<COM1B1) | (0<<COM1B0) | (0<<COM1C1) | (0<<COM1C0) | (0<<WGM11) | (0<<WGM10);
       TCCR1B = (0<<ICNC1) | (0<<ICES1) | (1<<WGM13) | (0<<WGM12) | (0<<CS12) | (1<<CS11) | (0<<CS10); //Vort. 256, s.S. 125
       
       ICR1=12500;
       
       DDRB |= (1<<PB5); //OCR1A
       DDRB |= (1<<PB6); //OCR1B
       OCR1A = WMITTE; //PWM-Breite auf Mitte setzen.  
       OCR1B = WMITTE; //PWM-Breite auf Mitte setzen.  
}

void loop() 
{
       OCR1A = WMAX; //PWM-Breite auf Null setzen.  
       OCR1B = WMAX; //PWM-Breite auf Null setzen.  
       
       delay(3000);
    
       OCR1A = WMITTE; //PWM-Breite auf Null setzen.  
       OCR1B = WMITTE; //PWM-Breite auf Null setzen.  
       
       delay(3000);
    
       OCR1A = WMIN; //PWM-Breite auf Null setzen.  
       OCR1B = WMIN; //PWM-Breite auf Null setzen.  
       
       delay(3000);
    
}

Code 0-11: Modellbau-Servos auf OC1A und OC1B mittels Timer 1 mit hoher Genauigkeit ansteuern.

Im Unterricht entstanden:
#define WMIN 1000
#define WMITTE 1500
#define WMAX 2000
#define SCHRITTE 1000

//Mode 8
//Phasen- und Frequenz-korrekt
//WGM1   3 2 1 0
//       1 0 0 0
//ICR1=..... TOP
//fpwm = fclk/(2*N*TOP)
//Vorteilung
//N=8
//80Hz = 16000000/(2*8*TOP)
//TOP = 16000000/(2*8*80Hz)=12500

//dt==1000ms*(1/80Hz)/12500 == 0,001ms (1 Schritt == 0,001ms)
//=>
//1ms == 1000 Schritte
//1,5ms == 1500 Schritte
//2ms == 2000 Schritte

int zaehler = 0;
int wert = 0;

void setup() 
{
       TCCR1A = (1<<COM1A1) | (0<<COM1A0) | (1<<COM1B1) | (0<<COM1B0) | (0<<COM1C1) | (0<<COM1C0) | (0<<WGM11) | (0<<WGM10);
       TCCR1B = (0<<ICNC1) | (0<<ICES1) | (1<<WGM13) | (0<<WGM12) | (0<<CS12) | (1<<CS11) | (0<<CS10); //Vort. 256, s.S. 125
       
       ICR1=12500;
       
       DDRB |= (1<<PB5); //OCR1A
       DDRB |= (1<<PB6); //OCR1B
       OCR1A = WMITTE; //PWM-Breite auf Mitte setzen.  
       OCR1B = WMITTE; //PWM-Breite auf Mitte setzen.  
}

void loop() 
{
       wert = zaehler;
       if(zaehler>=1000)
          wert = 2000-zaehler;
    
       OCR1A = WMIN + wert; //PWM-Breite auf Null setzen.  
       OCR1B = WMIN + wert; //PWM-Breite auf Null setzen.  
       
       
       zaehler++;
       zaehler%=2000;
       
       delay(10);
}

Code 0-12: SWEEP OHNE FOR-SCHLEIFE.


#6 Mi 20.04.2022 ÜBUNG

Aufgabe 1-3:
  • Bauen Sie die Schaltungen auf und testen folgende Beispiele aus der letzten Vorlesung:
  1. Dimmen einer LED mittels "analogWrite"
  2. SWEEP (Servo-Beispiel auf Arduino-Seite)
  3. Modellbau-Servos auf OC1A und OC1B mittels Timer 1 mit hoher Genauigkeit ansteuern.
Aufgabe 4:

Realisieren Sie das Programm zum Dimmen einer LED jetzt mit Hilfe von Registerbefehlen. Orientieren Sie sich dabei an dem Servo-Beispiel unter Verwendung von Timer1.

Aufgabe 5:

Versuchen Sie für "SWEEP OHNE FOR-SCHLEIFE" eine Konfiguration des PWM-Gebers zu finden, die zu einer noch feineren Auflösung führt als der hier mögliche Wert von 1000 bis 2000.


#7 Mi 27.04.2022

Themen:

  1. Vertiefung zu Timern
  2. Objektorientierte Programmierung

Thema 1: Vertiefung zu Timern -- Beispiele und Übungen zur Verwendung von Timern

Anhand des Timers 1 soll der Umgang mit dem Datenblatt und die Verwendung der verschiedenen Timer-Modi erübt werden.

Verwendung des Timer1 als bloßen Zähler (CTC-Mode)

Quellen für das Datenblatt zum ATmega32u4, z.B.:

https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7766-8-bit-AVR-ATmega16U4-32U4_Datasheet.pdf
http://www.kramann.info/Atmel-7766-8-bit-AVR-ATmega16U4-32U4_Datasheet.pdf
Beispiel 1: 1 Hz Blinken über CTC-Mode des Zählers 1. Zählstand wird ständig geprüft.
  • Mode: 12, CTC-Mode, Datenblatt zu Atmega32u4, S.133
  • TOP einstellbar mit ICR1
void setup() 
{
       ICR1 = 62500-1; // Siehe Datenblatt Seite 123
       TCNT1=0; //Zählregister von Timer 1
       // WGM13...WGM10 = 1100 => Mode 12 == CTC, mit ICR1 als TOP, siehe Datenblatt zu Atmega32u4, Seite 133.
       // CS12..CS10 = 100 => Vorteilung 256 => Zählen läuft mit 62500Hz, Seite 134.
       // COM1A1,COM1A0 = 00, COM1B1,COM1B0 = 00 => "normal Port-Operation" (Keine Signale herausschicken), Seite 132.
       TCCR1A = (0<<COM1A1) | (0<<COM1A0) | (0<<COM1B1) | (0<<COM1B0) | (0<<COM1C1) | (0<<COM1C0) | (0<<WGM11) | (0<<WGM10);
       TCCR1B = (0<<ICNC1) | (0<<ICES1) | (1<<WGM13) | (1<<WGM12) | (1<<CS12) | (0<<CS11) | (0<<CS10); 

       // PB7 == DIO 11, vergl. kramann.info LED-Leiste
       DDRB |= (1<<PB7);
       PORTB &= ~(1<<PB7); //LED aus
}

// 1Hz Blinken
void loop() 
{
    if(TCNT1<31250) //Bei der unteren Hälfte des Zählvorgangs aus.
        PORTB &= ~(1<<PB7); //LED aus
    else    
        PORTB |= (1<<PB7); //LED an
}

Code 0-13: 1 Hz Blinken über CTC-Mode des Zählers 1. Zählstand wird ständig geprüft.

Beispiel 2: 1 Hz Blinken über CTC-Mode mit Ausgangssignal.

siehe auch:

96_Arduino
void setup() 
{
       ICR1 = 62500/2-1; // Siehe Datenblatt Seite 123
       TCNT1=0; //Zählregister von Timer 1
       // WGM13...WGM10 = 1100 => Mode 12 == CTC, mit ICR1 als TOP, siehe Datenblatt zu Atmega32u4, Seite 133.
       // CS12..CS10 = 100 => Vorteilung 256 => Zählen läuft mit 62500Hz, Seite 134.
       // COM1A1,COM1A0 = 01, COM1B1,COM1B0 = 01 => "Toggle OCnA/OCnB/OCnC on compare match", Seite 131.
       // Frequenz, siehe: S.123.
       TCCR1A = (0<<COM1A1) | (1<<COM1A0) | (0<<COM1B1) | (1<<COM1B0) | (0<<COM1C1) | (1<<COM1C0) | (0<<WGM11) | (0<<WGM10);
       TCCR1B = (0<<ICNC1) | (0<<ICES1) | (1<<WGM13) | (1<<WGM12) | (1<<CS12) | (0<<CS11) | (0<<CS10); 

       // OCR1A ist auf Digital Pin 9 == PB5, vergl. kramann.info, Arduino.
       // OCR1B ist auf Digital Pin 10 == PB6, vergl. kramann.info, Arduino.
       DDRB |= (1<<PB5) | (1<<PB6);
       //pinMode(9,OUTPUT);  
       //pinMode(10,OUTPUT);  
}

void loop() 
{
}

Code 0-14: 1Hz über fest konfiguriertes Toggeln auf Pin9 und 10, kein Code in loop() nötig!

Beispiel 3: Feinere Auflösung mit Servo

Prinzipielle Methode zur Erzeugung eines PWM-Signals:

40_Mikrocontroller/04_PWM/03_Generierung
#define WMIN 2000
#define WMITTE 3000
#define WMAX 4000
#define SCHRITTE 2000

//Mode 14
//Fast-PWM
//WGM1   3 2 1 0
//       1 1 1 0
//ICR1=..... TOP
//fpwm = fclk/(N*TOP)
//Vorteilung
//N=8
//80Hz = 16000000/(8*TOP)
//TOP = 16000000/(8*80Hz)=25000

//dt==1000ms*(1/80Hz)/25000 == 0,0005ms (1 Schritt == 0,0005ms)
//=>
//1ms == 2000 Schritte
//1,5ms == 3000 Schritte
//2ms == 4000 Schritte

int zaehler = 0;
int wert = 0;

void setup() 
{
       TCCR1A = (1<<COM1A1) | (0<<COM1A0) | (1<<COM1B1) | (0<<COM1B0) | (0<<COM1C1) | (0<<COM1C0) | (1<<WGM11) | (0<<WGM10);
       TCCR1B = (0<<ICNC1) | (0<<ICES1) | (1<<WGM13) | (1<<WGM12) | (0<<CS12) | (1<<CS11) | (0<<CS10); //Vort. 256, s.S. 125
       
       ICR1=25000;
       
       DDRB |= (1<<PB5); //OCR1A
       DDRB |= (1<<PB6); //OCR1B
       OCR1A = WMITTE; //PWM-Breite auf Mitte setzen.  
       OCR1B = WMITTE; //PWM-Breite auf Mitte setzen.  
}

void loop() 
{
       wert = zaehler;
       if(zaehler>=SCHRITTE)
          wert = WMAX-zaehler;
    
       OCR1A = WMIN + wert; //PWM-Breite auf Null setzen.  
       OCR1B = WMIN + wert; //PWM-Breite auf Null setzen.  
       
       
       zaehler++;
       zaehler%=WMAX;
       
       delay(1); //Bei gleichem Delay bewegt sich hier der Servo halb so schnell, wie bei Mode 8.
}

Code 0-15: Doppelt so feine Auflösung mittels Fast-PWM. Vergl. Datenblatt.

Thema 2: Objekt-Orientierte Programmierung

Objektorientierte Variante des obigen Programms:

#include "Servo.h"

int zaehler = 0;
int wert = 0;

Servo servo;

void setup() 
{
     servo.start();
}

void loop() 
{
       wert = zaehler;
       if(zaehler>=SCHRITTE)
          wert = WMAX-zaehler;
    
       servo.setA(wert);       
       servo.setB(wert);       
       
       zaehler++;
       zaehler%=WMAX;
       
       delay(1);
}

Code 0-16: Hauptprogramm.

#define WMIN 2000
#define WMITTE 3000
#define WMAX 4000
#define SCHRITTE 2000

//Mode 14
//Fast-PWM
//WGM1   3 2 1 0
//       1 1 1 0
//ICR1=..... TOP
//fpwm = fclk/(N*TOP)
//Vorteilung
//N=8
//80Hz = 16000000/(8*TOP)
//TOP = 16000000/(8*80Hz)=25000

//dt==1000ms*(1/80Hz)/25000 == 0,0005ms (1 Schritt == 0,0005ms)
//=>
//1ms == 2000 Schritte
//1,5ms == 3000 Schritte
//2ms == 4000 Schritte

class Servo
{
  
    public:
        Servo()
        {
        
        }

        void start()
        {
           TCCR1A = (1<<COM1A1) | (0<<COM1A0) | (1<<COM1B1) | (0<<COM1B0) | (0<<COM1C1) | (0<<COM1C0) | (1<<WGM11) | (0<<WGM10);
           TCCR1B = (0<<ICNC1) | (0<<ICES1) | (1<<WGM13) | (1<<WGM12) | (0<<CS12) | (1<<CS11) | (0<<CS10); //Vort. 256, s.S. 125
       
           ICR1=25000;
       
           DDRB |= (1<<PB5); //OCR1A
           DDRB |= (1<<PB6); //OCR1B
           OCR1A = WMITTE; //PWM-Breite auf Mitte setzen.  
           OCR1B = WMITTE; //PWM-Breite auf Mitte setzen.          
        }

        void setA(int wert)
        {
            OCR1A = WMIN + wert; //PWM-Breite auf Null setzen.  
        }
        void setB(int wert)
        {
            OCR1B = WMIN + wert; //PWM-Breite auf Null setzen.            
        }
        
};

Code 0-17: Servo.h

Timer1_004_ServoOOP.zip -- Projekt als .zip-File.

Hinweise zu OOP und Arduino:

96_Arduino/24_OOP
40_Mikrocontroller/08_OOP
30_Informatik3/01_Vom_struct_zur_Klasse/06_Objektabstraktion
Saalübungen
  • CTC verwenden, um eine Digitaluhr zu realisieren. Ausgabe behelfsweise über die serielle Schnittstelle.
  • CTC verwenden, um in loop() eine Servo-Ansteuerung zu realiseren.
  • Ergänzen der Klasse Servo durch Methoden, denen man den Winkel (float) übergeben kann.
  • OOP für Lauflicht.
void setup() 
{
     DDRB = 255;
}
int x=0;
void loop() 
{
    PORTB = (1<<x);
    x++;
    x%=8;
    delay(100);
}

Code 0-18: Prozeduraler Entwurf Lauflicht.

OOP Entwurf
#include "Lauflicht.h"

Lauflicht licht;

void setup() 
{
     licht.start();
}
void loop() 
{
    licht.step();
    delay(100);
}

Code 0-19: OOP Lauflicht Hauptprogramm

class Lauflicht
{
    public:
       int x=0;
    
       Lauflicht()
       {
       
       }

       void start()
       {
          DDRB = 255;
       }

       //Bewegt das Lauflicht um eine Stelle weiter.
       void step()
       {
          PORTB = (1<<x);
          x++;
          x%=8;        
       }
};

Code 0-20: OOP Lauflicht.h (Klassendefinition)

LEDleisteOOP002.zip -- .zip-File des Projektes.
OPP Vektor als Beispiel
#include<math.h>
#include "Vektor.h"

Vektor v(3.0,4.0);
Vektor u(1.0,1.0);


void setup() 
{
    Serial.begin(9600);
}

void loop() 
{
    Serial.println(v.bestimmeLaenge(), DEC);
    Serial.println("
");
    Serial.println(u.bestimmeLaenge(), DEC);
    Serial.println("
");
    delay(2000);
}

Code 0-21: Hauptprogramm.

class Vektor
{
   public:
     float x,y;  
     Vektor(float xin, float yin)
     {
        x=xin;
        y=yin;
     }

     float bestimmeLaenge()
     {
         return sqrt(x*x + y*y);
     }
};

Code 0-22: Klasse Vektor.h

VektorOOP.zip -- .zip-File des Projekts.

#8 Mi 04.05.2022

Übung

#9 Mi 11.05.2022

Thema: Vertiefung zu Objektorientierter Programmierung

Objektorientierte Programmierung (OOP) kann bei verständiger Anwendung die Qualität eines Software-Projektes gegenüber einem prozeduralen Ansatz entscheidend verbessern.

Wie wollen das an einem praktischen Beispiel heute sehen. Insbesondere wird sich hier zeigen, dass OOP dazu dienen kann...

  1. einmal Erarbeitetes zu kapseln und davor zu bewahren später korrumpiert, respektive "vermasselt" zu werden,
  2. leicht handhabbare Programmierschnittstellen für höhere Entwicklungsaufgaben bereitzustellen und so die Komplexität niedrigerer Schichten zu verbergen, um "den Kopf" für die Erfüllung der auf einer höheren Ebene liegenden Anforderungen frei zu bekommen,
  3. Module zu erstellen, die sich leicht in neuen späteren Software-Projekten wiederverwenden lassen.

Beispiel: Elektronisches Würfelspiel

  • Sieben LEDs sollen einen Würfel symbolisieren und die Augenzahl darstellen.
  • Ein einzelner Start/Stop-Knopf soll den Durchgang durch die verschiedenen Augenzahlen auslösen.
  • Dieser erfolgt so schnell, dass es durch das Auge gerade nicht mehr mitverfolgbar ist.
Entwurf zum elektronischen Würfelspiel mit Zuordnung der Bits von PortB.

Bild 0-3: Entwurf zum elektronischen Würfelspiel mit Zuordnung der Bits von PortB.

Die Umsetzung erfolgt in zwei Stufen:

  • Eine Klasse "Wuerfel" soll die Funktionalitäten zur Ansteuerung der LEDs und zum Reagieren auf den Taster bereitstellen.
  • Die Klasse "Wuerfel" soll wiederum ein Objekt der Klasse "PortB" benutzen, die alles auf einfache Weise bereitstellt, was zum Arbeiten mit PortB nötig ist, vergleiche Bild oben mit der Zuordnung der Elemente des Würfels zu PortB.

Dabei werden die Objektmethoden in PORTB an die bekannten der Arduino-Umgebung angelehnt. Als Überblick ist hier das UML-Klassen-Diagramme der Klasse Wuerfel dargestellt. Die Klasse PortB und das zugehörige UML-Klassendiagramm werden im Unterricht entwickelt.

UML-Klassendiagramm der Klasse

Bild 0-4: UML-Klassendiagramm der Klasse "Wuerfel".

Im Unterricht: Entwicklung und Umsetzung von Soft- und Hardware.

#define EINGANG 0
#define AUSGANG 1
#define EIN 1
#define AUS 0


/*
 *  Ein Objekt dieser Klasse dient zur komfortablen Manipulation der Bits von Port B.
 * 
 * 
 */
class PortB
{
    public:
       /*
        *    Wir kehren die durch die Pullups eingeführte umgekehrte Logik wieder um,
        *    so, dass 1 zurückgegeben wird, wenn intern logisch LOW vorliegt,
        *    also außen eine Taste gedrückt wurde, die auf Masse / GND geht.
        */
       int digitalRead(int bitnr)
       {
            if( (PINB & (1<<bitnr)) == 0  )
            {
                return 1; // Taste gedrückt, geht auf Masse, erzeugt logisches LOW, aber wir behandeln es als positiven Fall.
            }
            else
            {
                return 0;
            }
       }
       void digitalWrite(int bitnr, int hilo)
       {
             if( hilo == 1 )
             {
                 PORTB |= (1<<bitnr);  
             } 
             else
             {
                 PORTB &= ~(1<<bitnr);  
             }
       }
       void pinMode(int bitnr, int einaus)
       {
             if( einaus == 1 )
             {
                 DDRB |= (1<<bitnr);
                 digitalWrite(bitnr,0); // initial Pin auf LOW setzen, wenn Ausgang.
             } 
             else
             {
                 DDRB &= ~(1<<bitnr);    
                 digitalWrite(bitnr,1);  
                 //this->digitalWrite(bitnr,1);  
                 //  entspricht: PORTB |= (1<<bitnr);          
             }
       }

    private:

    
};
 

Code 0-23: Klasse PortB

#include "PortB.h"

PortB portb;

void setup() 
{
  portb.pinMode(7,EINGANG);  // PB7 ist Eingang für einen Taster
  portb.pinMode(6,AUSGANG);  // PB6 ist Ausgang für eine LED
}

void loop() 
{
   if(portb.digitalRead(7) == 1) //Taste gedrückt, auf GROUND
   {
       portb.digitalWrite(6,EIN);
   }
   else
   {
       portb.digitalWrite(6,AUS);    
   }
}

Code 0-24: Hauptprogramm, die testweise PortB benutzt, um einfach per Taster mit PB7 eine LED auf PB6 zu steuern.

WuerfelOOP001_PortB_Klasse.zip -- Sketch insgesamt zu Entwicklung von PortB und Test.
WuerfelOOP002_Wuerfel_Klasse.zip -- fertigers Programm mit Klasse Wuerfel
class Wuerfel
{
    private:
        PortB portb;
        //int zustand = 0;  ... wir vereinfachen es etwas: solange Taste gedrückt, ändert sich Augenzahl
        int index = 0;
        void zeigeNaechsteAugenzahl()
        {
            switch(index)
            {
                case 0:  // Augenzahl == 1
                    portb.digitalWrite(0,EIN);
                    portb.digitalWrite(1,AUS);
                    portb.digitalWrite(2,AUS);
                    portb.digitalWrite(3,AUS);
                    portb.digitalWrite(4,AUS);
                    portb.digitalWrite(5,AUS);
                    portb.digitalWrite(6,AUS);
                break;
                case 1:  // Augenzahl == 2
                    portb.digitalWrite(0,AUS);  //PB0
                    portb.digitalWrite(1,AUS);  //PB1
                    portb.digitalWrite(2,AUS);  //PB2
                    portb.digitalWrite(3,EIN);  //PB3
                    portb.digitalWrite(4,AUS);  //PB4
                    portb.digitalWrite(5,AUS);  //PB5
                    portb.digitalWrite(6,EIN);  //PB6
                break;
                case 2:  // Augenzahl == 3
                    portb.digitalWrite(0,EIN);  //PB0
                    portb.digitalWrite(1,EIN);  //PB1
                    portb.digitalWrite(2,AUS);  //PB2
                    portb.digitalWrite(3,AUS);  //PB3
                    portb.digitalWrite(4,EIN);  //PB4
                    portb.digitalWrite(5,AUS);  //PB5
                    portb.digitalWrite(6,AUS);  //PB6
                break;
                case 3:  // Augenzahl == 4
                    portb.digitalWrite(0,AUS);  //PB0
                    portb.digitalWrite(1,EIN);  //PB1
                    portb.digitalWrite(2,AUS);  //PB2
                    portb.digitalWrite(3,EIN);  //PB3
                    portb.digitalWrite(4,EIN);  //PB4
                    portb.digitalWrite(5,AUS);  //PB5
                    portb.digitalWrite(6,EIN);  //PB6
                break;
                case 4:  // Augenzahl == 5
                    portb.digitalWrite(0,EIN);  //PB0
                    portb.digitalWrite(1,EIN);  //PB1
                    portb.digitalWrite(2,AUS);  //PB2
                    portb.digitalWrite(3,EIN);  //PB3
                    portb.digitalWrite(4,EIN);  //PB4
                    portb.digitalWrite(5,AUS);  //PB5
                    portb.digitalWrite(6,EIN);  //PB6
                break;
                default:  //case 5:   // Augenzahl == 6
                    portb.digitalWrite(0,AUS);
                    portb.digitalWrite(1,EIN);
                    portb.digitalWrite(2,EIN);
                    portb.digitalWrite(3,EIN);
                    portb.digitalWrite(4,EIN);
                    portb.digitalWrite(5,EIN);
                    portb.digitalWrite(6,EIN);
                break;
            }

            index++;

            //if(index>=6) index=0;
            //index = index % 6; // liefert ganzzahligen Rest der entsprechendenden Division, hier durch 6
            index%=6; // liefert 0..5
        }
    public:
        void start()
        {
            portb.pinMode(0,AUSGANG);
            portb.pinMode(1,AUSGANG);
            portb.pinMode(2,AUSGANG);
            portb.pinMode(3,AUSGANG);
            portb.pinMode(4,AUSGANG);
            portb.pinMode(5,AUSGANG);
            portb.pinMode(6,AUSGANG);
            portb.pinMode(7,EINGANG);            
        }

        void pruefeAufTastendruck()
        {
            if(  portb.digitalRead(7) == 1  )
                zeigeNaechsteAugenzahl();
        }
};


Code 0-25: Klasse Wuerfel

#include "PortB.h"
#include "Wuerfel.h"

Wuerfel wuerfel;

void setup() 
{
    wuerfel.start();
}

void loop() 
{
    wuerfel.pruefeAufTastendruck();
    delay(100); // gerade so wählen, dass Änderung der Augenzahl mit dem Auge nicht mehr verfolgbar ist. Eventuell kleiner machen.
}

Code 0-26: verändertes Hauptprogramm (Klasse PortB bleibt wie sie war.)

Fragen im Nachgang: Sind die eingangs gemachten Behauptungen über Sinn und Wert der objektorinetierten Umsetzung anhand des Beispiels nachvollziehbar? -- Hier sind sie nochmals: OOP kann dazu dienen...

  1. einmal Erarbeitetes zu kapseln und davor zu bewahren später korrumpiert, respektive "vermasselt" zu werden,
  2. leicht handhabbare Programmierschnittstellen für höhere Entwicklungsaufgaben bereitzustellen und so die Komplexität niedrigerer Schichten zu verbergen, um "den Kopf" für die Erfüllung der auf einer höheren Ebene liegenden Anforderungen frei zu bekommen,
  3. Module zu erstellen, die sich leicht in neuen späteren Software-Projekten wiederverwenden lassen.
Probeaufbau zum Würfelspiel

Bild 0-5: Probeaufbau zum Würfelspiel

https://youtu.be/EJl8lhz4tOU -- Test des Programms.

#10 Mi 18.05.2022

ÜBUNG


#11 Mi 25.05.2022

Behandlung von Bussystemen

Themen:

  1. Überblick zu Bussystemen
  2. Einführung zu UART (serielle Schnittstelle) mit RS232-Protokoll
  3. Verwendung der seriellen Schnittstelle mittels der Arduino-Library
  4. Verwendung der seriellen Schnittstelle mittels Registerkonfiguration
  5. Objektorientierte Kapselung der seriellen Schnittstelle
  6. Auslesen eines MPU6050 über die I2C-Schnittstelle mittels Library

1. Überblick zu Bussystemen

40_Mikrocontroller/06_UART/01_Bussysteme

2. Einführung zu UART (serielle Schnittstelle) mit RS232-Protokoll

40_Mikrocontroller/06_UART/02_UART
40_Mikrocontroller/06_UART/03_RS232

3. Verwendung der seriellen Schnittstelle mittels der Arduino-Library

Typischerweise wird ein externes Gerät seriell an die Hauptplatine angebunden. Dies soll hier prinzipiell demonstriert werden, indem zwei Arduino seriell über Kreuz miteinander verbunden werden:

             RXD____  ____TXD
Arduino 1           \/        Arduino 2
                    /\
             TXD----  ----TXD

             GND----------GND


Code 0-27: Verbindung zweier Arduino über Kreuz.


Warum ist die GND-Verbindung notwendig?


Aufbau mit zwei Arduino-Micro. Vom ersten wird der serielle Monitor am PC geöffnet.

Bild 0-6: Aufbau mit zwei Arduino-Micro. Vom ersten wird der serielle Monitor am PC geöffnet.

Zwei Arduino Micro werden nun von der Arduino IDE erkannt.

Bild 0-7: Zwei Arduino Micro werden nun von der Arduino IDE erkannt.

Aufbau der Testschaltung.

Bild 0-8: Aufbau der Testschaltung.

Kommunikation auf dem seriellen Monitor.

Bild 0-9: Kommunikation auf dem seriellen Monitor.

void setup() 
{
    Serial.begin(9600);
    Serial1.begin(9600);
}

void loop() 
{
    if(Serial.available())
    {
        char c = Serial.read();
        Serial1.write(c);
    }
    if(Serial1.available())
    {
        char antwort = Serial1.read();
        Serial.print("Antwort: ");
        Serial.write(antwort);
        Serial.println();
    }
}

Code 0-28: Sertest1 für Arduino ttyACM0, wo später der serielle Monitor geöffnet wird.

void setup() 
{
    Serial1.begin(9600);
}

void loop() 
{
    if(Serial1.available())
    {
        char c = Serial1.read();
        Serial1.write(c+1);
    }
}

Code 0-29: Sertest2 für Arduino ttyACM1

4. Verwendung der seriellen Schnittstelle mittels Registerkonfiguration

Sertest2 soll nun mittels Registerbefehlen funktionieren und als Sertest2reg gespeichert werden:


Laut Pinbelegung des Chips sind die herausgeführten seriellen Pins RXD1 und TXD1


Chip ATmega32u4.

Bild 0-10: Chip ATmega32u4.

Notwendige Registerkonfigurationen, vergl. Datenblatt ab Seite 111.

#include<avr/io.h>
#define TAKTFREQUENZ 16000000
#define BAUDRATE 9600

void setup() 
{
    //Serial1.begin(9600);
    
    //Merken des in UBRR zu speichernden Wertes.
    //Examples of Baud Rate Setting, Seite 206
    //Seite 191 Datenblatt
    //Seite 213 Datenblatt
//    unsigned int baudregister = (TAKTFREQUENZ/(16*BAUDRATE))-1; 
    unsigned int baudregister = (1000000/BAUDRATE)-1; 

    //setzen der Baudrate
    //UBRRH1 = (unsigned char) (baudregister>>8); //Setzen des HIGH-Bytes des Baudraten-Registers
    //UBRRL1 = (unsigned char)  baudregister;     //Setzen des LOW -Bytes des Baudraten-Registers
    UBRR1 = baudregister;
    //Einschalten des Senders und des Empfängers
    //Seite 190 Datenblatt
    UCSR1B = (1<<TXEN1) | (1<<RXEN1) | (0<<UCSZ12);  

    //Setzen des Nachrichtenformats: 8 Datenbits, 1 Stopbits
    //  USBS1 == 0 == 1 stop bit
    //  UCSZ12 UCSZ11 UCSZ10 == 0 1 1 == 8 Datenbit
    //Seite 212 Datenblatt
    UCSR1C = (1<<UCSZ11) |  (1<<UCSZ10) |  (0<<USBS1);
}

void loop() 
{
    while( !(UCSR1A & (1<<RXC1)) ) DDRB|=0;  //Warten bis der Uebertragungspuffer ein Zeichen empfangen hat
  
    int c = UDR1;
    //Serial1.write(c+1);
    while( !(UCSR1A & (1<<UDRE1)) ) DDRB|=0; //Warten bis der Uebertragungspuffer leer ist
    UDR1 = c+1;
}

Code 0-30: Sertest2reg

5. Objektorientierte Kapselung der seriellen Schnittstelle

(Saal-) ÜBUNG
  • Entwerfen Sie eine Klasse, die die oben verwendete Funktionalität der Register-Befehle birgt.
  • Orientieren Sie sich bei dem Entwurf geeigneter Methoden an der Arduino-Library.

6. Auslesen eines MPU6050 über die I2C-Schnittstelle mittels Library

Der MPU6050 vereinigt einen dreiachsigen Beschleunigungssensor und einen dreiachsiges Gyroskopsensor. Er findet Anwendung in mobilen Devices, steht aber auch für die Verwendung mit Arduino auf kleinen Zusatzplatinen zur Verfügung. Er kann über den I2C-Bus angesprochen und ausgelesen werden.

MPU6050 ---- Arduino

VCC     ---- 3.3V
GND     ---- GND
SCL     ---- SCL
SDA     ---- SDA

Code 0-31: Verbindung MPU6050 mit Arduino

Schematischer Versuchsaufbau zum Testen des MPU6050.

Bild 0-11: Schematischer Versuchsaufbau zum Testen des MPU6050.

Neben einem dreiachsigen Beschleunigungssensor und einem Gyriskop besitzt der MPU6050 auch einen Temperaturfühler, der sich so kalibrieren läßt, dass die Werde gleich in Grad Celsius zur Verfügung stehen.

Die Abfrage dieser Temperaturwerte ist nicht ganz einfach, da sie über ein Bussystem (SPI-Bus) erfolgt. Es gibt bestimmte Befehlsfolgen, die den Baustein konfigurieren und in setup() stehen und andere, die die Daten abfragen.

Schaltplan zur Verbindung des MPU6050 mit dem Arduino Micro (LED-Teil wurde hier weggelassen).

Bild 0-12: Schaltplan zur Verbindung des MPU6050 mit dem Arduino Micro (LED-Teil wurde hier weggelassen).

Darstellung des Aufbaus auf dem Steckboard.

Bild 0-13: Darstellung des Aufbaus auf dem Steckboard.

#include<math.h>
#include<Wire.h>
const int MPU=0x68;  // I2C address of the MPU-6050
int16_t AcX=0,AcY=0,AcZ=0,Tmp=0,GyX=0,GyY=0,GyZ=0;
double TEMPERATUR = 0.0;
void setup() 
{
  Serial.begin(9600);
  Wire.begin();
  Wire.beginTransmission(MPU);
  Wire.write(0x6B);  // PWR_MGMT_1 register
  Wire.write(0);     // set to zero (wakes up the MPU-6050)
  Wire.endTransmission(true);
  delay(500);
}

void loop() 
{
  Wire.beginTransmission(MPU);
  Wire.write(0x3B);  // starting with register 0x3B (ACCEL_XOUT_H)
  Wire.endTransmission(false);
  Wire.requestFrom(MPU,14,true);  // request a total of 14 registers
  AcX=Wire.read()<<8|Wire.read();  // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)     
  AcY=Wire.read()<<8|Wire.read();  // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
  AcZ=Wire.read()<<8|Wire.read();  // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
  Tmp=Wire.read()<<8|Wire.read();  // 0x41 (TEMP_OUT_H) & 0x42 (TEMP_OUT_L)
  GyX=Wire.read()<<8|Wire.read();  // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L)
  GyY=Wire.read()<<8|Wire.read();  // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L)
  GyZ=Wire.read()<<8|Wire.read();  // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L)

  TEMPERATUR = Tmp/340.00+36.53;
  
  Serial.print("AcX = "); Serial.print(AcX);
  Serial.print(" | AcY = "); Serial.print(AcY);
  Serial.print(" | AcZ = "); Serial.print(AcZ);
  Serial.print(" | Tmp = "); Serial.print(TEMPERATUR);  //equation for temperature in degrees C from datasheet
  Serial.print(" | GyX = "); Serial.print(GyX);
  Serial.print(" | GyY = "); Serial.print(GyY);
  Serial.print(" | GyZ = "); Serial.print(GyZ);
  
  delay(500);
}

Code 0-32: Testprogramm, um alle Sensordaten, die der MPU6050 liefert im Serial Monitor ansehen zu können.

TemperaturAbfrage.zip -- Sketch mit obigem Testprogramm als .zip-File -- Sie können das .zip-File herunterladen und in Ihren Sketch-Ordner legen (Name Arduino). Nach einem Neustart der IDE erscheint auch dieser Sketch im Sketchbook.
Aufgaben
  • Bemühen Sie sich um ein grundsätzliches Verständnis des Testprogramms. Dieses wird im gemeinsamen Gespräch während der LV noch vertieft werden. Fragen Sie bitte proaktiv nach.
(Saal-) ÜBUNG

Emulieren Sie in folgender einfacher Weise eine serielle Schnittstelle mit Hilfe eines gewöhnlichen digitalen Ausgangs:

Die Hardwareanordnung entspricht der von oben bei Sertest1 und Sertest2. Die Aufeinanderfolge von Low und High-Pegeln soll auf dem gleichen digitalen Ausgang für den zweiten Mikrocontroller so erfolgen, dass dadurch seriell mit einem Start-Bit und acht Datenbits zyklisch der Buchstabe "A" an den ersten Mikrocontroller gesendet wird. Nutzen Sie den Timer 1 im CTC-Mode, um die Dauer der Low- und Highpegel zu steuern.


#12 Mi 01.06.2022

Studentische Lösung zu Lauflicht mit OOP:
Lauflicht_klasse.zip
Übung heute:
  • Bearbeiten Sie die mit einer orange-farbenen Überschrift gekennzeichneten Aufgaben aus der letzten Lehrveranstaltung.
  • Bauen Sie ein Funktionsmuster des in der letzten LV besprochenen elektronischen Würfel, der mit Hilfe des MPU6050 die Augenzahl je nach Winkel-Orientierung anzeigt.
mpu6050.zip -- studentische Lösung.
...geschickterweise wurde der Sensor von der LED-Platine getrennt.

Bild 0-14: ...geschickterweise wurde der Sensor von der LED-Platine getrennt.


#13 Mi 08.06.2022 Probe-E-Test


#14 Mi 15.06.2022

Hinweise zur Prüfung in einer Woche:

  • Form: elektronisch am PC
  • Beginn Mittwoch 22.06.,8:30 IWZ140.
  • Keine Hilfsmittel erlaubt.
  • Erlaubt sind: Stift, 3 Blatt Papier, einfacher Taschenrechner
  • Nötige Datenblattauszüge werden in den elektronischen Test hinein kopiert.

Die Lösungen zu den nachfolgenden Aufgaben können NACH dem E-Test in einer Woche besprochen werden.



Insofern die Lösungen zu den Aufgaben über den aktuellen Wissensstand hinausgehen, sind sie nicht prüfungsrelevant.



Verstehen Sie die nachfolgenden Aufgaben einfach als ein Übungsangebot, das Ihnen mehr Sicherheit in den behandelten Themen vermitteln kann.


Themen

Möglichst alle zentralen Inhalten der Lehrveranstaltung sollen nachfolgend noch einmal erwähnt und zu ihnen teilweise auch neue kleine Übungsaufgaben, bzw. Varianten zu alten Aufgaben und Fragen gegeben werden.

  1. Quiz
  2. Digitale Ein- und Ausgänge mittels Arduino-Befehlen
  3. Digitale Ein- und Ausgänge über Registerkonfigurationen
  4. Bitshift-Operationen in der Programmierung: Lauflicht
  5. PWM-Signale: Einen Modellbauservo über Arduino-Library-Befehle ansteuern
  6. PWM-Signale mit Timer 1 erzeugen und einen Servo ansteuern
  7. Bussysteme: Serielle Schnittstelle über Arduino-Befehle ansprechen
  8. Bussysteme: Serielle Schnittstelle Serial1 über Registerbefehle ansprechen
  9. Bussysteme: Serielle Schnittstelle an einem digitalen Ausgang emulieren
  10. Bussysteme: I2C-Schnittstelle im Zusammenhangm mit dem Sensorchip MPU6050
  11. Umgang mit dem Datenblatt: Modi des Timers 1 konfigurieren, Normaler Betrieb
  12. Umgang mit dem Datenblatt: Modi des Timers 1 konfigurieren, CTC-Mode
  13. Objektorientierte Programmierung
1. Quiz
  1. Eine Software emuliert den Computer C64 mit einem PC. Was bedeutet der Begriff emulieren?
  2. Welche Register sind an der Konfiguration der Digitalen Ein- und Ausgänge bei Port B beteiligt und welche Aufgaben haben diese im Einzelnen.
  3. Erkläre den Begriff "Interne Peripherie" durch einen Vergleich zwischen Mikrocontroller und PC.
  4. Wie in welchem Bereich in Millisekunden muß die Grundperiode bei einem Modellbauservo liegen und Welche Perioden haben im Vergleich die Servostelungen "ganz links", "ganz rechts", "Mitte"?
  5. Wie löscht man ein einzelenes Bit von einem Bitwort? -- Gib ein Beispiel.
  6. Wie setzt man ein einzelnes Bit von einem Bitwort? -- Gib ein Beispiel.
  7. Bei PINB sind an Bit0 und Bit2 Taster angeschlossen. Geben Sie if-Statements an, die wahr sind, wenn a) beide Eingänge LOW sind, b) beide HIGH, c) Bit0 LOW und Bit1 HIGH.
  8. Wieviele ASCII-Zeichen können mit einer seriellen Schnittstelle bei 1200 Baud pro Sekunde übertragen werden, wenn es 1 Start-Bit und 1 Stop-Bit gibt?
  9. Motore verursachen i.d.R. elektromagnetische Störungen. Warum ist das so und welche Maßnahmen kennen Sie, dem in einer Mikrocontrollerschaltung entgegen zu wirken?
  10. Welche Möglichkeiten hat man, Mikrocontrollerschaltungen Strom-sparender zu machen?
  11. Wieviele Bits hat der Datenbus, wieviele der Programmbus beim ATmega32u4?
  12. Wieviele Timer besitzt der ATmega32u4 (mit Blick auf das Pinlayout herausfinden)?
2. Digitale Ein- und Ausgänge mittels Arduino-Befehlen
96_Arduino/02_Taster -- zentrales Beispiel zum Thema

Aufgaben-Variante:

Realisieren Sie auf gleiche Weise ein UND-Gatter mit zwei Eingängen und einem Ausgang.

Verwendet werden sollen die digitalen Pins 9, 10 und 11. Nur dann, wenn beide Taster bei den digitalen Pins 9 und 10 die auf Masse liegen, soll eine LED, die bei digitalem Pin 11 angeschlossen ist leuchten, aber sonst ausgeschaltet sein.

3. Digitale Ein- und Ausgänge über Registerkonfigurationen

Zentrale Beispiele, vergleiche auf day_by_day:

  • Code 0-4: Variante 1
  • Code 0-5: Übersicht Shiftoperationen.
  • Code 0-6: Variante 2
  • Code 0-7: Varainte3: Bezeichnung der Shiftwerte gemäß Datenblatt.

Aufgaben-Variante:

Setzen Sie das Beispiel von oben ("2. Digitale Ein- und Ausgänge mittels Arduino-Befehlen") mit Registerbefehlen um.

4. Bitshift-Operationen in der Programmierung: Lauflicht

Zentrale Beispiele, vergleiche auf day_by_day:

  • Code 0-5: Übersicht Shiftoperationen.
  • Code 0-9: Lauflicht mit Auswahl.

...sowie:

96_Arduino/22_Universal/02_LED_Leiste

Aufgaben-Variante:

In dem Spielfilm Kampfstern Galactica kommen s.g. "Zylonen" vor, bei denen eine rote Lampe im Gesichtsfeld hin und her läuft.

  • Realisieren Sie ein solches Lauflicht mit 8 LEDs unter Verwendung von Bitshift-Operationen.
  • Pro neue LED-Position muß loop() einmal durchlaufen werden. D.h.: Vermeiden Sie Schleifen mit delay() innerhalb der loop()-Funktion.
https://www.youtube.com/watch?v=BqcrGNr6xWQ -- Zur Vedeutlichung.

Zusatzfrage: Wie hieß der Roboterhund, der in dem ersten Spielfilm für eines der geflüchteten Kinder gebaut wurde?

Zusatzaufgabe: Bauen Sie den Rest des Zylonen fertig auf und testen ihn.

5. PWM-Signale: Einen Modellbauservo über Arduino-Library-Befehle ansteuern
40_Mikrocontroller/04_PWM/01_Prinzip -- Prinzip eines PWM-Signals
40_Mikrocontroller/04_PWM/08_LoesungUE3 -- PWM beim Servo

Hierzu gibt es zwei Beispiele auf der Arduino-Webseite:

https://www.arduino.cc/reference/en/libraries/servo/
https://docs.arduino.cc/learn/electronics/servo-motors -- siehe "Knob" und "Sweep".

Aufgaben-Variante:

Das Beispiel "Sweep" wurde zwar so kodiert, dass es leicht nachvollziehbar, jedoch blockieren die beiden for-Schleifen den schnellen Durchlauf durch loop() und verunmöglichen so, dass der Mikrocontroller noch andere Aufgaben erledigen kann, wie beispielsweise Eingangspins einzulesen.

Beheben Sie diesen Mangel.

6. PWM-Signale mit Timer 1 erzeugen und einen Servo ansteuern

Zentrale Beispiele, vergleiche auf day_by_day:

  • Code 0-11: Modellbau-Servos auf OC1A und OC1B mittels Timer 1 mit hoher Genauigkeit ansteuern.
  • Code 0-12: SWEEP OHNE FOR-SCHLEIFE.

Aufgaben-Variante: siehe weiter unten (10. Bussysteme: I2C-Schnittstelle im Zusammenhangm mit dem Sensorchip MPU6050)

7. Bussysteme: Serielle Schnittstelle über Arduino-Befehle ansprechen

Zentrale Beispiele, vergleiche auf day_by_day:

  • 3. Verwendung der seriellen Schnittstelle mittels der Arduino-Library
8. Bussysteme: Serielle Schnittstelle Serial1 über Registerbefehle ansprechen

Zentrale Beispiele, vergleiche auf day_by_day:

  • 4. Verwendung der seriellen Schnittstelle mittels Registerkonfiguration
9. Bussysteme: Serielle Schnittstelle an einem digitalen Ausgang emulieren

Aufgabe dazu: siehe 11.

10. Bussysteme: I2C-Schnittstelle im Zusammenhangm mit dem Sensorchip MPU6050

Zentrale Beispiele, vergleiche auf day_by_day:

  • Code 0-32: Testprogramm, um alle Sensordaten, die der MPU6050 liefert im Serial Monitor ansehen zu können.

Aufgaben-Variante in Kombination mit 6.:

  • Befestigen Sie den MPU6050 mit Isolierklebeband oder Ähnlichem an der drehenden Achse der im Unterricht verwendeten Kleinservos.
  • Setzen Sie ein Projekt um, bei der die Daten des MPU6050 dazu verwendet werden, den Sensor immer waagerecht zu halten, wenn der Servo um die Drehachse gedreht wird.
  • D.h. über den möglichen Winkelbereich des Servos, soll eine Drehung der Servoachse eine Änderung der Orientierung des Servos im Raum ausgleichen.
  • Dies entspricht von der prinzipiellen Funktionalität her einem einachsigen Gimbal.

Frage: Was ist ein Kameragimbal und wieviele Achsen Haben typische Gimbals?

11. Umgang mit dem Datenblatt: Modi des Timers 1 konfigurieren, Normaler Betrieb
  • Emulieren Sie mit Hilfe des Timers 1 im normalen Zählbetrieb das Senden über die serielle Schnittstelle.
  • Es soll lediglich zyklisch der Buchstabe A (ASCII-Code 65) mit einem Start-, einem Stop-Bit und einer Pause (High-Pegel) von einem zu einem anderen Mikrocontroller übertragen werden.
  • Der sendende Mikrocontroller verwendet OC1A.
  • Der Empfangende Mikrocontroller ist ganz normal konfiguriert und leitet das auf RX empfangene Signal von Serial1 nach Serial weiter, damit man des sich am seriellen Monitor ansehen kann.
  • Zur Orientierung, wie die zeitliche Steuerung mittels CTC erfolgen kann, sehen Sie sich bitte folgendes Beispiel an:
40_Mikrocontroller/10_Peripherie/05_Zaehler -- siehe Code 0-1: Beispiel.
12. Umgang mit dem Datenblatt: Modi des Timers 1 konfigurieren, CTC-Mode

Zentrales Beispiel, vergleiche auf day_by_day:

  • Code 0-14: 1Hz über fest konfiguriertes Toggeln auf Pin9 und 10, kein Code in loop() nötig!

Aufgaben-Variante:

Die LED soll mit 3Hz blinken.

13. Objektorientierte Programmierung

Hinweis: Einige der nachfolgend angegebenen Inhalte wurden nicht behandelt. Sie werden hier aber angeführt, als Vertiefungsmöglichkeit für Sie in die Thematik OOP.

Hinweise zu OOP mit Arduino:

96_Arduino/24_OOP

Hinweise zu OOP mit einem Mikrocontroller:

40_Mikrocontroller/08_OOP/05_RS232/03_Umsetzung
40_Mikrocontroller/08_OOP/05_RS232/04_Vererbung
40_Mikrocontroller/08_OOP/05_RS232/05_Statisch
40_Mikrocontroller/08_OOP/05_RS232/06_Performance

OOP allgemein:

30_Informatik3/01_Vom_struct_zur_Klasse/06_Objektabstraktion
30_Informatik3/01_Vom_struct_zur_Klasse/07_OO_Kundenverwaltung
30_Informatik3/01_Vom_struct_zur_Klasse/08_Objektfaehigkeiten

Zentrale Beispiele, vergleiche auf day_by_day:

  • Code 0-16: Hauptprogramm.
  • Code 0-17: Servo.h
  • Code 0-19: OOP Lauflicht Hauptprogramm
  • Code 0-20: OOP Lauflicht.h (Klassendefinition)
  • Beispiel: Elektronisches Würfelspiel

Aufgaben-Variante:

Modifizieren Sie das Gimbal-Beispiel weiter oben so, dass es eine Klasse "Gimbal" gibt, in der alles Notwendige zusammengetragen ist, um einen Servo als Einachsigen Gimbal zu verwenden. Diskutieren Sie das Konzept unteriander und versuchen sich an einer Implementierung.


#15 Mi 22.06.2022 E-Test