Forum: Mikrocontroller und Digitale Elektronik Atmega/Arduino - Input Capture funktioniert nicht


von Andreas S. (igel1)


Lesenswert?

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
15
U8G2_SSD1309_128X64_NONAME0_F_4W_HW_SPI u8g2(U8G2_R0, 10, 9, 7);  
16
17
volatile uint16_t rising_capture = 0;
18
volatile uint16_t falling_capture = 0;
19
volatile uint16_t pulse_width = 0;
20
volatile bool rising_edge = true;
21
volatile bool isFinished = false;
22
23
char buffer[32];
24
25
26
void setup() {
27
  pinMode(pin, INPUT);
28
  u8g2.begin();                // start the u8g2 library
29
  u8g2.setBitmapMode(1);
30
  u8g2.setFont(u8g2_font_helvB08_tr);
31
}
32
33
void loop() {
34
    pulse_width = 0;
35
    falling_capture = 0;
36
    rising_capture = 0;
37
    isFinished = false;
38
39
    TCCR1B |= (1 << ICES1);       // input capture set for rising edge
40
    TCCR1B |= (1 << CS10);        // no prescaler
41
    TIMSK1 |= (1 << ICIE1);       // input capture interrupt enable
42
   
43
    while(isFinished == false) {};
44
    
45
    pulse_width = falling_capture - rising_capture;
46
    
47
    u8g2.clearBuffer();
48
    sprintf(buffer, "Pulse: %u", pulse_width);
49
    u8g2.drawStr(10, 64, buffer);
50
    u8g2.sendBuffer();
51
    delay(1000);
52
}
53
54
55
ISR(TIMER1_CAPT_vect)
56
{
57
    switch(rising_edge){
58
        case true:
59
            //TCNT1 = 0;             // reset the timer counter 
60
            rising_capture = ICR1;   // take the input capture value and
61
                                     // save it in rising_capture 
62
            TCCR1B &= ~(1 << ICES1); // set input capture for falling edge
63
            rising_edge = false;     // make rising_edge variable false 
64
                                     // so next time it will react to 
65
                                     // case false
66
            break;
67
        
68
        case false:
69
            falling_capture = ICR1; // take input capture value and 
70
                                    // save it in falling edge
71
            TCCR1B |= (1 << ICES1); // set input capture for rising 
72
                                    // edge back
73
            rising_edge = true;     // make rising edge true for next 
74
                                    // pulse
75
            TIMSK1 &= ~(1 << ICIE1); // disable input capture interrupt
76
            isFinished = true;       // mark pulse measurement finished
77
            break;
78
    }
79
}

: Bearbeitet durch User
von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

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()

von Helmut H. (helmuth)


Lesenswert?

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.
1
   TCCR1B |= (1 << CS10);        // no prescaler

: Bearbeitet durch User
Beitrag #7644752 wurde vom Autor gelöscht.
von Falk B. (falk)


Lesenswert?

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
int pin = 8;                 // pin for puls input from oscillator
13
14
// Constructor - for OLED display library - see u8g2 documentation
15
16
U8G2_SSD1309_128X64_NONAME0_F_4W_HW_SPI u8g2(U8G2_R0, 10, 9, 7);  
17
volatile uint16_t pulse_width = 0;
18
volatile bool isFinished = false;
19
char buffer[32];
20
21
void setup() {
22
23
  pinMode(pin, INPUT);
24
  u8g2.begin();                // start the u8g2 library
25
  u8g2.setBitmapMode(1);
26
  u8g2.setFont(u8g2_font_helvB08_tr);
27
28
  isFinished = false;
29
  TCCR1A  = 0;
30
  TCCR1B  = (1<<ICNC1) | (1<<ICES1) | (1<<CS11);
31
  TIMSK1 |= (1<<ICIE1);
32
33
}
34
35
void loop() {
36
    uint16_t tmp;
37
 
38
    isFinished = false;
39
    while(!isFinished);
40
41
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
42
        tmp = pulse_width;
43
    }
44
45
    u8g2.clearBuffer();
46
    sprintf(buffer, "Pulse: %u", tmp);
47
    u8g2.drawStr(10, 64, buffer);
48
    u8g2.sendBuffer();
49
    delay(1000);
50
}
51
52
ISR(TIMER1_CAPT_vect)
53
{
54
    uint16_t tr, tf;
55
56
    if (TCCR1B & (1<<ICES1) ) {             // rising edge
57
        tr = ICR1;
58
        TCCR1B  = (1<<ICNC1) | (1<<CS11);   // next trigger on falling edge
59
    } else {                                // falling edge
60
        tf = ICR1;
61
        TCCR1B  = (1<<ICNC1) | (1<<ICES1) | (1<<CS11);  // next trigger on rising edge
62
        pulse_width = tf-tr;
63
        isFinished = true;
64
    }
65
}

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

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!

von Mi N. (msx)


Lesenswert?

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.

von Falk B. (falk)


Lesenswert?

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!

von Veit D. (devil-elec)


Lesenswert?

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.
1
#include <Streaming.h>  // https://github.com/janelia-arduino/Streaming
2
Stream &out {Serial};
3
4
const uint8_t pinOutM {12};   // OC1B Arduino Mega2560
5
const uint8_t pinOutU {10};   // OC1B Arduino Uno R3 
6
7
void setup(void)
8
{
9
  Serial.begin(9600);
10
  Serial.println(F("\nuC Reset ####\n"));  
11
  pinMode(pinOutM, OUTPUT);
12
  pinMode(pinOutU, OUTPUT);
13
  readT1Register(Serial);
14
  analogWrite(pinOutM, 128);
15
  analogWrite(pinOutU, 128);
16
  readT1Register(Serial);
17
}
18
19
void loop(void)
20
{ }
21
22
void readT1Register (Stream &out)
23
{
24
  out << "TCCR1A " << _BIN(TCCR1A) << endl;
25
  out << "TCCR1B " << _BIN(TCCR1B) << endl;
26
  out << "TCCR1C " << _BIN(TCCR1C) << endl;
27
  out << "TIMSK1 " << _BIN(TIMSK1) << endl;
28
  out << "TIFR1  " << _BIN(TIFR1)  << endl;
29
  out << "ICR1   " << ICR1  << endl;
30
  out << "OCR1A  " << OCR1A << endl;
31
  out << "OCR1B  " << OCR1B << endl;
32
  out << "OCR1C  " << OCR1C << endl;
33
  out.println();
34
}

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?


von Andreas S. (igel1)


Lesenswert?

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

von Sebastian W. (wangnick)


Lesenswert?

Falk B. schrieb:
> uint16_t tr

Falk B. schrieb:
> Was hab ich denn da für einen Unsinn geschrieben? War wohl noch zu früh
> am Morgen!

LG, Sebastian

von Mi N. (msx)


Lesenswert?

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.

von Falk B. (falk)


Lesenswert?

Falk B. schrieb:
> ISR(TIMER1_CAPT_vect)
> {
>     uint16_t tr, tf;

Da fehlt ein static, aua. Und beim Prescaler hab ich auch noch nen 
Fehler drin. Eher so
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
int pin = 8;                 // pin for puls input from oscillator
13
14
// Constructor - for OLED display library - see u8g2 documentation
15
16
U8G2_SSD1309_128X64_NONAME0_F_4W_HW_SPI u8g2(U8G2_R0, 10, 9, 7);  
17
volatile uint16_t pulse_width;
18
volatile bool isFinished;
19
char buffer[32];
20
21
void setup() {
22
23
  pinMode(pin, INPUT);
24
  u8g2.begin();                // start the u8g2 library
25
  u8g2.setBitmapMode(1);
26
  u8g2.setFont(u8g2_font_helvB08_tr);
27
28
  TCCR1A  = 0;
29
  TCCR1B  = (1<<ICNC1) | (1<<ICES1) | (1<<CS10);
30
  TIMSK1 |= (1<<ICIE1);
31
}
32
33
void loop() {
34
35
    uint16_t tmp;
36
37
    isFinished = false;
38
    while(!isFinished);
39
40
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
41
        tmp = pulse_width;
42
    }
43
    u8g2.clearBuffer();
44
    sprintf(buffer, "Pulse: %u", tmp);
45
    u8g2.drawStr(10, 64, buffer);
46
    u8g2.sendBuffer();
47
    delay(1000);
48
}
49
50
ISR(TIMER1_CAPT_vect)
51
{
52
    static uint16_t tr;
53
    uint16_t tf;
54
55
    if (TCCR1B & (1<<ICES1) ) {             // rising edge
56
        tr = ICR1;
57
        TCCR1B  = (1<<ICNC1) | (1<<CS10);   // next trigger on falling edge
58
    } else {                                // falling edge
59
        tf = ICR1;
60
        TCCR1B  = (1<<ICNC1) | (1<<ICES1) | (1<<CS10);  // next trigger on rising edge
61
        pulse_width = tf-tr;
62
        isFinished = true;
63
    }
64
}

von Andreas S. (igel1)


Lesenswert?

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

von Falk B. (falk)


Lesenswert?

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)."

von Andreas S. (igel1)


Lesenswert?

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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.