Liebe AVR'ler,
ich steh grad irgendwie auf der Leitung - finde den Fehler nicht:
Ich möchte mit untenstehendem Code einen Puls, der
zwischen ca. 16us - 30 us schwankt, vermessen.
Dazu möchte ich nicht die pulseIn() Funktion von Arduino verwenden (die
hat nur 1us Auflösung), sondern den Input Capture Mode von Timer1 meines
Arduino Nano Clones (hat einen Atmega328p).
Leider erhalte ich auf meinem OLED-Display "kippende" Werte angezeigt:
z.B. abwechselnd 14 und 65522,
oder 13 und 65523,
ab und an auch schon mal nur 0.
Neben den kippenden Werten wundert mich auch, dass ich nicht Werte um
16us/(1/16Hz)= 254 herum erhalte.
Bitte schaut Euch einmal den Code an - ich bin irgendwie zu blind, den
Fehler zu finden.
Viele Grüße
Igel1
1
#include <Arduino.h>
2
#include <U8g2lib.h>
3
#include <SPI.h>
4
#include <Wire.h>
5
6
#include <avr/io.h>
7
#include <util/delay.h>
8
#include <avr/interrupt.h>
9
10
11
// Set pinout
12
int pin = 8; // pin for puls input from oscillator
13
14
// Constructor - for OLED display library - see u8g2 documentation
Arduino:
Der Timer ist für PWM vorbereitet
Man sollte bevor man ihn neu konfiguriert alle wichtigen Register auf
Null setzen.
z.B.
TCCR1A = 0;
TCCR1B = 0;
in setup()
Andreas S. schrieb:> Leider erhalte ich auf meinem OLED-Display "kippende" Werte angezeigt:> z.B. abwechselnd 14 und 65522,
Da TCNT1 nicht auf 0 gesetzt wird (ist auskommentiert) tritt das
beschrieben Verhalten auf, wenn der Zähler während der Messung
überläuft.
1
pulse_width=falling_capture-rising_capture;
Dann ist die Differenz negativ, das ist unsigned 655xx.
Andreas S. schrieb:> Neben den kippenden Werten wundert mich auch, dass ich nicht Werte um> 16us/(1/16Hz)= 254 herum erhalte.
siehe Vorredner, TCCR1B muss erst auf 0 gesetzt werden, wahrscheinlich
sind CS11 oder CS12 gesetzt, das ergibt dann einen anderen Prescaler.
Andreas S. schrieb:> Neben den kippenden Werten wundert mich auch, dass ich nicht Werte um> 16us/(1/16Hz)= 254 herum erhalte.>> Bitte schaut Euch einmal den Code an - ich bin irgendwie zu blind, den> Fehler zu finden.
Dein Ansatz ist richtig, die Umsetzng in einigen wichtigen Details
nicht.
Variabelen, welche in einer ISR und im Hauptprogramm gelesen oder
geschrieben werden, müssen sowohl volatile deklariert werden (hast du)
als auch im Hauptprogramm atomar gelesen und geschrieben werden. SIehe
Interrupt. Wnn Variablen aber NUR in der ISR benutzt werden, müssen
sie nicht volatile sein.
Die Initialisierung des Timers macht man EINMALIG in setup() und nicht
dauernd in loop()! Bei einer Bool-Variablen nimmt man im Normalfall kein
Switch, denn außer true und false gibt es da nix, ein if/else reicht.
Bei Input Capture läßt man den Zähler in den meisten Fällen immer
laufen. Man muss ihn NICH auf Null setzen, um die Pulsbreite zu
bestimmen! Die Differenz falling_capture-rising_capture stimmt auch beim
Überlauf!
TCCR1B &= ~(1 << ICES1); // set input capture for falling edge
Das kann schief gehen. Du invertierst hier das Bit. Damit kann man aber
Pech haben, wenn sie deine Logik desynchronisiert. Hier ist es besser,
die Bits explizit zu setzen. Das Gleiche gilt für deine Variable
rising_edge. Hier is es besser und einfacher, die aktuellen Bits des
Steuerregisters zu nutzen. Am Ende kann man die Pulsbreite auch gleich
in der ISR berechnen, so eine einfache Differenz ist sehr schnell.
1
#include<Arduino.h>
2
#include<U8g2lib.h>
3
#include<SPI.h>
4
#include<Wire.h>
5
#include<avr/io.h>
6
#include<util/delay.h>
7
#include<util/atomic.h>
8
#include<avr/interrupt.h>
9
10
// Set pinout
11
12
intpin=8;// pin for puls input from oscillator
13
14
// Constructor - for OLED display library - see u8g2 documentation
Helmut H. schrieb:> Da TCNT1 nicht auf 0 gesetzt wird (ist auskommentiert) tritt das> beschrieben Verhalten auf, wenn der Zähler während der Messung> überläuft. pulse_width = falling_capture - rising_capture;> Dann ist die Differenz negativ, das ist unsigned 655xx.
Nein, eben nicht. Im Falle des Zählerüberlauf stimmt die Differenz
trotzdem!
rising_capture = 65000
Zählerüberlauf
falling_capture = 1000
pulse_width = falling_capture - rising_capture
= 1000 - 65000 = -64000 = 1536;
Die -64000 erzeugen selber wieder einen Überlauf, wodurch daraus ein
positives, richtiges Ergebnis wird! Einzige Randbedingung. Die Differenz
zwischen den Werten darf nicht größer als der halbe Zählerumfang sein,
als max. 32767!
Andreas S. schrieb:> Ich möchte mit untenstehendem Code einen Puls, der> zwischen ca. 16us - 30 us schwankt, vermessen.
Da kommt es aber auch noch auf das Tastverhältnis bzw. Wiederholfrequenz
an.
Wenn nur 1 µs Abstand besteht, kann es nicht klappen, da die ISR nicht
hinterherkommt.
Vielleicht läßt Du Dir die +PW und -PW anzeigen, um mehr Durchblick zu
erhalten.
Arduino F. schrieb:> Arduino:> Der Timer ist für PWM vorbereitet
Bist Du Dir da sicher? Soweit ich es verfolgt habe, wird erst dann
'vorbereitet', wenn PWM auch verwendet wird.
Falk B. schrieb:> TCCR1B &= ~(1 << ICES1); // set input capture for falling edge>> Das kann schief gehen. Du invertierst hier das Bit.
Was hab ich denn da für einen Unsinn geschrieben? War wohl noch zu früh
am Morgen! Es ist natürlich ein korrektes Bitlöschen!
Mi N. schrieb:> Arduino F. schrieb:>> Arduino:>> Der Timer ist für PWM vorbereitet>> Bist Du Dir da sicher? Soweit ich es verfolgt habe, wird erst dann> 'vorbereitet', wenn PWM auch verwendet wird.
Hallo,
die Timer werden teilweise mittels setup() vorbereitet. Nur das was mit
dem Pin direkt zu tun hat noch nicht. Viele Worte ohne Sinn, schau dir
den Testcode an was wann passiert.
Erst einmal 1000 Dank an Euch alle, die Ihr Euch so viele Mühen mit
Euren Antworten gemacht habt - einfach toll.
Auf die Schnelle habe ich Arduino F.'s Hinweis ausprobiert und die
Steuerregister genullt:
TCCR1A = 0;
TCCR1B = 0;
Das hat die Werte zumindest schon mal in die richtige Region gebracht -
nämlich zwischen ca. 240 und 1200 (je nach Frequenz meines Oszillators,
den ich an Pin 8 (nach Arduino-Zählweise) hängen habe - Tastverhältnis
ist ca. 50%).
Trotzdem flippen die Werte noch irgendwie herum - beim aktuell
eingestellten Oszillatorwert/Eingangssignal z.B. zwischen
1189,
0,
394,
784
Da das alles in der Region von Vielfachen von 394 liegt, habe ich die
Vermutung, dass der Capture nicht alle Flanken richtig mitbekommt.
Leider habe ich aktuell Null Zeit und kann Eure weiteren Tipps erst
heute Abend oder morgen in Ruhe lesen und ausprobieren.
Dann werde ich mir nochmals die Register genau anschauen, ob da ggf.
noch etwas genullt werden muss und ich werde Falk's und Veit D.'s
Hinweise nochmals genau lesen und umsetzen.
Daher: bitte habt noch etwas Geduld - ich werde hier in jedem Fall
berichten, wie die Sache weitergeht.
Viele Grüße
Igel1
Arduino F. schrieb:> Aber sowas von!>> Der Beweis:> https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/wiring.c#L297
Gut. Wiring.c soll intern sogar noch vor setup() aufgerufen werden. Das
soll der 'normale' Benutzer aber garnicht wissen.
Vermeiden kann man all dies, indem man kein setup() aufruft und main()
verwendet ;-)
Letztens hatte ich Arduino für den RP2040 am Wickel, wo die
Initialisierungen wohl erst nach Bedarf zur Laufzeit erfolgen. Da die
ISR-Vektoren im RAM liegen ist das kein Problem.
Wow - hier fliegen einem ja die gebratenen Gänse in den Mund (... die
alle von Falk ferngesteuert werden ...).
Fertiger, mundgerechter Code - das ist ja mehr als Service!
Bei so viel Engagement musste ich dann doch noch etwas Zeit von unserem
Ausflug abknappsen:
Habe Falks Code schnell kopiert, kompiliert und gebrannt -> läuft out of
the box wie am Schnürchen. Toll!
Respekt an Falk (aber auch alle anderen für Ihre super Hinweise). Den
Rest arbeite ich in Ruhe heute Abend oder morgen durch.
Viele Grüße und nochmals dickes Dankeschön!
Igel1
Hmm, obwohl es so funktioniert und ich so auch schon ein Projet gemacht
habe, muss man nach der Doku von Atmel/Microchip das Flag ICF1 im
Register TIFR1 löschen, wenn die Flanke vom ICP1 geändert wird.
Also sicherheitshalber noch ein
TIFR1 = (1<<ICF1);
Ans Ende der ISR.
Siehe Datenblatt ATmega 328
16.6.3 Using the Input Capture Unit
"Measurement of an external signal’s duty cycle requires that the
trigger edge is changed after each capture. Changing the edge sensing
must be done as early as possible after the ICR1 Register has been read.
After a change of the edge, the Input Capture Flag (ICF1) must be
cleared by software writing a logical one to the I/O bit location). For
measuring frequency only, the clearing of the ICF1 Flag is not required
(if an interrupt handler is used)."
Danke, Falk, für den Nachtrag - ist eingebaut.
Ich habe auch nochmals all Eure Anmerkungen schön durchgelesen und
nachvollzogen - inklusive Link von Falk.
Danke für die Tipps - sie haben mir gut aufs Pferd geholfen!
Viele Grüße
Igel1