Forum: Mikrocontroller und Digitale Elektronik Input Capture Probleme ATmega328P


von alex (Gast)


Lesenswert?

Hallo Leute,

habe ein Problem mit meinem unten angehängten Programm. Das Programm 
soll ein PWM Signal am Input Capture Pin ICR1 einlesen und daraus die 
DutyCycle messen und per UART ausgeben. Ich habe Teile des Codes aus dem 
Internet herauskopiert, da ich noch nicht ganz verstehe wie der Input 
Capture Pin, bzw. dessen Programmcode auszusehen hat oder funktioniert. 
Zum Programmcode: Es werden steigende bzw. fallende Flanken erfasst, die 
Zeitpunkte "gemessen" und die Zeitdifferenz der Flanken berechnet. Man 
kann sich dann daraus zwei Zeitdifferenzen errechnen, einmal die High 
Pulslänge und einmal die Gesamtlänge. Dann werden diese beiden ins 
Verhältnis gesetzt, also HighPulslänge/Gesamtlänge, raus kommt die 
DutyCycle. Und jetzt kommt mein Problem. Es kommen zwei verschiedene 
Wertebereiche heraus, einmal Werte von 37-42 und einmal 3-8. Darüber 
hinaus ist es so, dass 3=37 entspricht, 4=38, 5=39, usw. . Hab dieses 
Problem relativ pragmatsch mit den if-Anweisungen:
1
if(DutyCycle == 42){DutyCycle = 8;}

usw. gelöst. Hab da noch kein Muster erkennen können, wann welcher 
Wertebereich dran ist und wie lange Werte aus dem einen Wertebereich 
folgen und wann aus dem Anderen. Das springt auch teilweise sehr schnell 
hin und her. Also es scheint mir so, als ob das "willkürlich" passiert.
Nun meine erste Frage. Warum kommen da zwei verschiedene Wertebereiche 
raus? Liegt es daran, dass am Schluss der while Schleife der Zähler 
zurückgesetzt wird? Oder liegt der Fehlern in den Interrupt Funktionen 
begraben? Vielleicht kann mir da ein Profi weiterhelfen, glaube da kann 
nicht viel falsch sein...

Des Weiteren will ich nicht den DutyCycle, sondern auch die 
High-Pulslänge alleine messen. Dazu haben ich bei der Formel für den 
DutyCycle nur den Nenner verwendet und ausgeben lassen, also
1
DutyCycle=(uint8_t)(((uint32_t)(Capt2-Capt1)+((uint32_t)T1Ovs1*0x10000L))*100L);

Da kann ich jetzt gar kein Muster mehr feststellen, da kommen 
irgendwelche Werte raus, zwischen 0 und 250. Vielleicht wird dieses 
Problem auch gelöst, wenn mein erstes Problem behoben. Es kann aber auch 
sein, dass da noch was anderes faul ist. Vielleicht hat da jemand eine 
Idee.

Ach ja, und falls das jemand ausprobieren sollte. Als PWM Signal habe 
ich ein Modellbau Servo PWM Signalgenerator verwendet, das ist nicht das 
typische Signal mit 100% DutyCycle, das hat nur einen DutyCycle von 0 
bis 10 Prozent und eine geringe Frequenz, deshalb wahrscheinlich die 
Werte von 3 bis 8.

Vielen Dank schon mal an alle die sich meinem Problem heranwagen :-)

1
#define F_CPU 8000000UL
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
#include <util/delay.h>
5
#include <stdio.h>
6
7
volatile uint16_t T1Ovs1, T1Ovs2; //Zähler Überlauf
8
volatile uint16_t Capt1, Capt2, Capt3; //Variable der drei Zeitstempel
9
volatile uint8_t Flag; //Flag
10
11
#define BAUD 9600UL
12
#define UBRR0_Value ((F_CPU/(16*BAUD))-1)
13
14
int uart_putc(unsigned char c)
15
{
16
  while (!(UCSR0A & (1<<UDRE0))) {}
17
  
18
  UDR0 = c; //Zeichen senden
19
  return 0;
20
}
21
22
void uart_puts (char *s)
23
{
24
  while (*s)
25
  {   // so lange *s != '\0' also ungleich dem "String-Endezeichen" 
26
    uart_putc(*s);
27
    s++;
28
  }
29
}
30
31
32
void InitTimer1(void) //Timer initialisieren
33
{
34
  TCCR1B|=(1<<ICES1); //steigende Flanke erfassen
35
  TCNT1=0; //Timer mit 0 initialisieren
36
  TIMSK1|=(1<<ICIE1)|(1<<TOIE1); //Input Capture und Überlauf Interrupts erlauben
37
}
38
void StartTimer1(void)
39
{
40
  TCCR1B|=(1<<CS10); //Timer starten ohne Prescaler
41
  sei(); //globale Interrupts erlauben
42
}
43
44
ISR(TIMER1_CAPT_vect) 
45
{
46
  if (Flag==0)
47
  {
48
    TCCR1B&=~(1<<ICES1); //erfassen der fallenden Flanke
49
    Capt1=ICR1; //Zeitstempel speichern
50
    T1Ovs2=0; //Überlauf auf 0 setzen
51
  }
52
  
53
  if (Flag==1)
54
  {
55
    TCCR1B|=(1<<ICES1); //erfassen der steigenden Flanke
56
    Capt2=ICR1; //Zeitstempel speichern
57
    T1Ovs1=T1Ovs2; //erster Überlauf Zähler
58
  }
59
  
60
  if (Flag==2)
61
  {
62
    Capt3=ICR1; //Zeitstempel speichern
63
    TIMSK1&=~((1<<ICIE1)|(1<<TOIE1)); //Input Capture und Überlauf Interrupts stoppen
64
  }
65
  Flag++; //Flag Zähler
66
}
67
68
ISR(TIMER1_OVF_vect) //Überlauf Service Routine
69
{
70
  T1Ovs2++; //Überlauf Zähler
71
}
72
73
74
int main(void)
75
{
76
  volatile uint8_t DutyCycle; //var DutyCycle
77
  InitTimer1();
78
  StartTimer1();
79
  
80
      UBRR0H = (UBRR0_Value>>8);
81
      UBRR0L = UBRR0_Value;
82
      
83
      UCSR0B = (1<<TXEN0) | (1<<RXEN0);
84
      UCSR0C = (1<<UCSZ00) | (1<<UCSZ01);
85
    
86
  
87
  while(1)
88
  {
89
    if (Flag==3) //alle Timestamps erfasst?
90
    {
91
      DutyCycle=(uint8_t)((((uint32_t)(Capt2-Capt1)+((uint32_t)T1Ovs1*0x10000L))*100L)/((uint32_t)(Capt3-Capt1)+((uint32_t)T1Ovs2*0x10000L)));
92
      
93
      
94
      if(DutyCycle == 42){DutyCycle = 8;}
95
        
96
      if(DutyCycle == 41){DutyCycle = 7;}
97
        
98
      if(DutyCycle == 40){DutyCycle = 6;}
99
        
100
      if(DutyCycle == 39){DutyCycle = 5;}
101
        
102
      if(DutyCycle == 48){DutyCycle = 4;}
103
        
104
      if(DutyCycle == 37){DutyCycle = 3;}
105
      
106
      
107
      char Buffer[20];//Buffer für UART, hier kommt DutyCycle rein
108
      int i = DutyCycle;
109
      sprintf(Buffer, "%i", i);
110
      uart_puts(Buffer);
111
      uart_puts("\r\n");
112
      _delay_ms(500);
113
      
114
115
      Flag=0; //Flag löschen bzw. 0 setzen
116
117
      T1Ovs1=0; //Überlaufzähler löschen bzw. 0 setzen;
118
      T1Ovs2=0; //Überlaufzähler löschen bzw. 0 setzen
119
      
120
      TIFR1=(1<<ICF1)|(1<<TOV1); //clear interrupt flags to avoid any pending interrupts
121
      
122
      TIMSK1|=(1<<ICIE1)|(1<<TOIE1); //enable input capture and overflow interrupts
123
    } 
124
  }
125
}

von Stefan F. (Gast)


Lesenswert?

Hast du die drei Variablen Capt1, Capt2 und Capt3 überprüft, ob sie 
immer plausible Werte haben? Falls nicht, hole das bitte nach.

Da das 16bit variablen sind, der µC aber nur eine 8bit CPU hat, muss er 
diese in zwei Schritten lesen. Zwischen diesen Schritten könnte die ISR 
dazwischen "funken" und Werte verändern. Dann kommen zufällige 
Ergebnisse heraus. Das musst du mit verhindern, zum Beispiel so:
1
if (Flag==3) //alle Timestamps erfasst?
2
{
3
    cli(); // interrupts sperren
4
    uint16_t a=Capt1; // timer-variablen kopieren
5
    uint16_t b=Capt2;
6
    uint16_t c=Capt3;
7
    sei();
8
    ...
9
}

Und dann rechnest du mit a,b und c weiter. Das volatile brauchst du 
trotzdem.

Weiter geht es mit der Berechnung, da befürchte ich einen Überlauf oder 
falsche Klammern. Da ich mit der Komplexität des Ausdrucks überfordert 
bin,  zerlege ich ihn in übersichtlichere Teile:

Dein Ausdruck:
1
uint8_t DutyCycle=(uint8_t)((((uint32_t)(Capt2-Capt1)+((uint32_t)T1Ovs1*0x10000L))*100L)/((uint32_t)(Capt3-Capt1)+((uint32_t)T1Ovs2*0x10000L)));

DutyCycle muss übrigens nicht volatile sein, weil es in der ISR nicht 
benutzt wird.

Schritt 1:
1
uint8_t DutyCycle=(uint8_t)(
2
    (((uint32_t)(Capt2-Capt1) + ((uint32_t)T1Ovs1*0x10000L))*100L)
3
    /
4
    ((uint32_t)(Capt3-Capt1) + ((uint32_t)T1Ovs2*0x10000L))
5
);

Schritt 2:
1
uint32_t high_zeit = (uint32_t)(Capt2-Capt1) + ((uint32_t)T1Ovs1*0x10000L);
2
3
uint32_t gesamt_zeit = (uint32_t)(Capt3-Capt1) + ((uint32_t)T1Ovs2*0x10000L);
4
5
uint8_t DutyCycle=(uint8_t)(
6
    (high_zeit * 100L) / gesamt_zeit
7
);

> high_zeit * 100L
ist in Ordnung. Da der rechte Operand ein long Integer ist, wird die 
Multiplikation mit einem long integer Algorithmus durchgeführt.

> [long integer] / gesamt_zeit
ist in Ordnung, dabei wird ein abgerundeter Integer (Prozent-Zahl) 
heraus kommen.

> DutyCycle=(uint8_t)(...)
Ist in Ordnung, weil wir wissen, dass das Ergebnis nicht größer als 100 
sein kann.

Kommen wir zu den anderen beiden Ausdrücken, die beide den gleichen 
Aufbau haben:

> (uint32_t) (Capt2-Capt1) + ((uint32_t)T1Ovs1*0x10000L)

Ich habe hier das Gefühl, dass das Casten auf uint32_t unnötig ist, weil

> Capt2-Capt1
Das sind beides 16bit Integer und es wird niemals mehr werden, wegen der 
Subtraktion.

> T1Ovs1*0x10000L
Der rechte Operand ist ein long integer, als wird ohnehin ein 
Algorithmus für long Integer verwendet.

Bleibt also (falls ich mich nicht irre) das als notwendig übrig:

> uint32_t high_zeit = (Capt2-Capt1) + (T1Ovs1*0x10000L);
> uint32_t gesamt_zeit = (Capt3-Capt1) + (T1Ovs2*0x10000L);

Ein Überlauf wird hier wohl ausgeschlossen sein.

So wie ich das verstehe berechnest du (links vom +) die Zeitspanne der 
HIGH Phase bzw die gesamte Pulslänge. Rechts addierst du noch etwas, um 
den 16bit Zähler per Software auf einen 32bit Zähler zu erweitern.

Das sieht für mich alles soweit Ok aus.

Vielleicht musst du nur cli() und sei() hinzufügen. Versuche das mal.

von Stefan F. (Gast)


Lesenswert?

Mir ist doch noch etwas aufgefallen:

> uint32_t high_zeit = (Capt2-Capt1) + (T1Ovs1*0x10000L);
> uint32_t gesamt_zeit = (Capt3-Capt1) + (T1Ovs2*0x10000L);

Ist das wirklich richtig, die capt Werte zuerst zu subtrahieren und dann 
die Zeitspannen der Überläufe zu addieren? Müsste man nicht erst den 
Zähler auf 32bit erweitern und dann subtrahieren? Also so:

> uint32_t high_zeit = (Capt2 + T1Ovs1*0x10000L) - Capt1;
> uint32_t gesamt_zeit = (Capt3 + T1Ovs2*0x10000L) - Capt1;

(Anmerkung: Für Capt1 gibt es keine Erweiterung auf 32bit, denn T1Ovs0 
hätte immer den Wert 0).

Ehrlich gesagt würde ich die Erweiterung auf 32bit (falls überhaupt 
nötig) in die ISR packen. Dann wären Capt1, 2 und 3 32bit Variablen, 
deren Wert man leichter überprüfen kann und man hätte auch besser 
lesbaren Code.

von alex (Gast)


Lesenswert?

Vielen, vielen Dank für die schnelle und ausführliche Antwort. Jetzt 
funktioniert es perfekt. Du ahnst nicht wie sehr mir das weiterhilft.

Wie du anfangs gleich erwähnt hast, könnte die ISR "dazwischenfunken". 
Darin lag der Fehler versteckt. Jetzt bekomme ich Werte raus, die ich 
weiterverarbeiten kann.

Deinen zweiten Post muss ich mir nochmal durch den Kopf gehen lasse, 
aber ich vermute du hast damit auch recht!

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.