Atmega/Arduino - Input Capture funktioniert nicht

#7644739
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
}
#7644751
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
Beitrag #7644752 wurde von einem Moderator gelöscht.
#7644785
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
}
#7644789
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!

#7644819
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.

#7644868
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
}
#7644916
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

#7644931
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.

#7644942
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
}
#7645015
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

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

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren