1.1o 🚧 Oszilloskopieren
Synopsis: [de.wikipedia.org/wiki/Oszilloskop]
Ein Oszilloskop auch liebevoll Oszi genannt ist das (zweit) wichtigste Messgerät eines IT-Bastlers. Dabei werden Signale im zeitlichen Verlauf graphisch dargestellt. Wenn viele digitale Signale gleichzeitig gemessen werden sollen gibt es Logic-Analyzer.
- Mit digitalWrite() Pin-Zustand ändern
- Mit GPIOA->ODR die Hardware direkter ansprechen
- Mit GPIOA->BSRR noch schneller werden
- Prellen und Brummen messen
- Ausführungszeiten messen
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)
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);
}
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);
}
}
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
}
}
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
}
}
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.
Prellen und Brummen messen
Offene Eingänge sind empfindlich: Brummen
Ein offener Eingang ohne Widerstand ist hochohming, im Floating-Zustand. Ein Finger in der Nähe kann schon den Zustand verändern.
Den Tastkopf mit A1 (PA1) 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));
}
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:
Tasten-Prellen
Beim Schließen eines Tasters prallen Kontakte aufeinander und der Kontakt ist nicht gleich dauerhaft geschlossen [de.wikipedia.org/wiki/Prellen]. Dieses mehrmalige Schließen kann zu fehlerhaften Taster-Auswertungen führen.
Musste das Oszi auf Single-Shot umstellen und ein wenig rumprobieren bis mir diese Aufnahme gelungen ist.
150µs lang prellt es hier.
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..
Ausführungszeiten von Unterprogrammen und Interrupt Service Routinen (ISR) mit Oszi messen
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);
}
}
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);
}
}
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);
}
}