Forum: Mikrocontroller und Digitale Elektronik Capture will nicht beim PIC


von Thorsten R. (Firma: Robert Bosch GmbH) (thorsten_roh)


Lesenswert?

Hallo,

ich möchte mit einem PIC18F46K22 mittels Capture die Differenz zwischen 
zwei steigenden Flanken bestimmen und auf einem LC-Display ausgeben.

Hier erstmal der Ablauf meiner main():

- Capturemodul ist CCP1 und dazu dann Timer1

- Timer1 wird auf 0x0000 gesetzt

- Variablen für Zeiten werden auf 0 gesetzt

- Timer1 wird gestartet

- Capture wird eingeschaltet

- eine While-Schleife wartet bis die erste Flanke erkannt wird (über das
  Interrupt-Flag

- Interrupt-Flag wird gelöscht

- Zeitwert CCPR1 wird in Hilfsvariable 1 gepackt

- wieder While-Schleife bis steigende Flanke

- Capture ausschalten

- Timer 1 stoppen

- CCPR1 in zweite Hilfsvariable

- Differenz der beiden Hilfsvariablen bilden

- Differenz auf LCD ausgeben

Zum testen gebe ich mit einem Funktionsgenerator Frequenzen von 50-500 
Hz auf den Capture-Eingang. Bei 50Hz ist die Periodendauer 20msec.
HFINTOSC steht auf 4Mhz, daraus ergibt sich wenn ich das richtig 
verstanden habe FOSC/4 von 1MHZ, oder?
Das hieße das die Zeitdirfferenz zwischen 2 Timerwerten 1µsec ist. Bei 
einem 16-Bit-Timer habe ich also 65 Millisekunden bevor er überläuft.
Die Periodendauer (entspricht ja der "Messung") liegt also bei >=50Hz 
immer innerhalb von Tmax des Timers.
Wenn ich nun Timer1 Interrupt nutze um eine LED umzuschalten ist diese 
von 50Hz bis ca. 245Hz schön am blinken und die Werte die mir das LCD 
anzeigt liegen immer 4.294.960.000
Ab ca 245Hz kommt zwar kein Interrupt mehr, aber die angezeigten Werte 
liegen weiterhin in genanntem Bereich.

Hier mein Quellcode in der Hoffnung das mir noch geholfen werden kann:
1
#include <p18f46k22.h>
2
#include <delays.h>
3
#include <timers.h>
4
#include <stdio.h>
5
#include <string.h>
6
#include "Lcd.h"
7
8
9
#pragma config FOSC = INTIO7   // interner Takt
10
unsigned int uint_period1 = 0;
11
unsigned int uint_period2 = 0;
12
unsigned long int ulint_period = 0; //Differenz
13
unsigned char uchar_i = 0;
14
char char_Zeit[];
15
unsigned char i = 0;
16
17
18
/*----------------------------------------------------------------------------
19
 -----                                                                   -----
20
 -----                      InterruptHandler                             -----
21
 -----                                                                   -----
22
 ---------------------------------------------------------------------------*/
23
24
#pragma interruptlow InterruptHandler
25
void InterruptHandler (void)
26
{
27
    // nur um zu sehen ob Timer überläuft
28
    LATBbits.LATB1=~LATBbits.LATB1;
29
    PIR1bits.TMR1IF=0;          //setze Timer3 Interrupt Flag zurück
30
}
31
/*----------------------------------------------------------------------------
32
 -----                                                                   -----
33
 -----                      Interruptvektoren setzen                     -----
34
 -----                                                                   -----
35
 ---------------------------------------------------------------------------*/
36
#pragma code _HIGH_INTERRUPT_VECTOR = 0x000008
37
void _high_ISR (void)
38
{
39
    _asm
40
        goto InterruptHandler       // Sprung zur Interruptroutine
41
    _endasm
42
}
43
44
#pragma code _LOW_INTERRUPT_VECTOR = 0x000018
45
void _low_ISR (void)
46
{
47
    _asm
48
        goto InterruptHandler       // Sprung zur Interruptroutine
49
    _endasm
50
}
51
#pragma code
52
53
54
55
/*----------------------------------------------------------------------------
56
 -----                                                                   -----
57
 -----                      Initialisierung                              -----
58
 -----                                                                   -----
59
 ---------------------------------------------------------------------------*/
60
void init (void)
61
{
62
    OSCCONbits.IRCF=0b101;  //HFINTOSC interner Takt auf 4MHz -> CLKOUT = 1MHz
63
    T1CON = 0x00; //Timer 1 mit FOSC/4, kein Prescaler für Capture, 16 Bit in eins lesen und STOP
64
    ANSELCbits.ANSC2 = 0; // RC2 als digitaler
65
    TRISCbits.TRISC2 = 1; // RC2 als Eingang für CCP1
66
    PIE1bits.CCP1IE = 0; // kein Interrupt von CCP1
67
    PIR1bits.CCP1IF = 0; // Interrupt Flag löschen
68
69
    //LCD starten PORTD als Ausgang damit LCD funktioniert
70
    LATD=0x00;
71
    ANSELD=0x00;
72
    TRISD=0x00;
73
    LCDInit();
74
    
75
    LATB=0x00;
76
    ANSELB=0x00;
77
    TRISB=0x00;
78
79
    //Interrupt für TIMER1 um zu sehen das Timer überläuft
80
    PIE1bits.TMR1IE = 1;  //TIMER1 Interrupt Enable
81
82
    INTCONbits.GIEH=1;  //
83
    INTCONbits.GIEL=1;  //
84
    INTCONbits.GIE = 1; //enable unmasked Interrupts Global
85
}
86
87
88
/*----------------------------------------------------------------------------
89
 -----                                                                   -----
90
 -----                      MAIN                                         -----
91
 -----                                                                   -----
92
 ---------------------------------------------------------------------------*/
93
void main(void)
94
{
95
    init();
96
    while(1)
97
    {
98
        TMR1L = 0x00;                    //setze Timer auf 0
99
        TMR1H = 0x00;
100
        uint_period1=0;
101
        uint_period2=0;
102
        ulint_period=0;
103
        
104
        T1CONbits.TMR1ON = 1;           //starte Timer 1
105
        CCP1CONbits.CCP1M = 0b111; // Capture bei jeder steigenden Flanke
106
        
107
        while (!(PIR1bits.CCP1IF));     //Warte auf erste steigende Flanke
108
        PIR1bits.CCP1IF = 0;
109
        uint_period1 = (unsigned int) CCPR1H;            //speichere Zeitpunkt erster Flanke (16Bit-Wert)
110
        uint_period1 = uint_period1<<8;
111
        uint_period1 = uint_period1 + CCPR1L;
112
113
        while (!(PIR1bits.CCP1IF));     //Warte auf zweite steigende Flanke
114
        CCP1CON = 0x00;                 //schalte Capture aus
115
        T1CONbits.TMR1ON = 0;           //stoppe Timer 1
116
117
        uint_period2 = (unsigned int) CCPR1H;            //speichere Zeitpunkt zweiter Flanke (16Bit-Wert)
118
        uint_period2 = uint_period2<<8;
119
        uint_period2 = uint_period2 + CCPR1L;
120
121
        ulint_period = (long) uint_period2 - (long) uint_period1; //Zeitdifferenz bilden
122
        
123
        LCDClear();
124
        LCDGoto(0,1);
125
        LCDWriteStr("Zeit");
126
        LCDGoto(0,0);
127
128
        ultoa(ulint_period, char_Zeit);         // unsigend Long to Ascii (String)
129
130
        i=0;
131
        while (char_Zeit[i] != 0x00)             // Solange nicht NUL
132
        {
133
             LCDPutChar(char_Zeit[i]);
134
             i++;
135
        }
136
137
138
        Delay1KTCYx(100);
139
    }
140
}

von Max H. (hartl192)


Lesenswert?

Thorsten Rohde schrieb:
> ulint_period = (long) uint_period2 - (long) uint_period1;
> //Zeitdifferenz bilden
Versuch mal die Differenz als 16bit unsigned Operation zu berechnen.

von Thorsten R. (Firma: Robert Bosch GmbH) (thorsten_roh)


Lesenswert?

Das gleiche, nur das der Wert jetzt nicht mehr bei 4.x Millionen liegt 
sondern um 65.500.
Auch weiterhin mit Blinkender LED für Timer1 Interrupt

von Max H. (hartl192)


Lesenswert?

Thorsten Rohde schrieb:
> CCP1CONbits.CCP1M = 0b111; // Capture bei jeder steigenden
> Flanke
Datenblatt des PIC18F46k22 schrieb:
> CCPxM<3:0>: ECCPx Mode Select bits
> [...]
> 0100 = Capture mode: every falling edge
> 0101 = Capture mode: every rising edge
> 0110 = Capture mode: every 4th rising edge
> 0111 = Capture mode: every 16th rising edge
http://ww1.microchip.com/downloads/en/DeviceDoc/41412F.pdf
Seite 205

P.S. Wenn die Kommentare falsch sind, kann man sie auch weglassen. 
Besser ohne Kommentare als mit falschen...

von Thorsten R. (Firma: Robert Bosch GmbH) (thorsten_roh)


Lesenswert?

Hallo Max,

Danke, war aber eigentlich kein Fehler im Kommentar sondern im Code.

Muss natürlich
1
CCP1CONbits.CCP1M = 0b101; // Capture bei jeder steigenden Flanke
sein.

von Thorsten R. (Firma: Robert Bosch GmbH) (thorsten_roh)


Lesenswert?

Aber er springt nach wie vor um die 65000 umher, dafür bekomme ich aber 
keinen Interrupt mehr vom Timer :-)

von Karl H. (kbuchegg)


Lesenswert?

Du hast ein LCD?


Das darf man auch benutzen, um sich mal die WErte von uint_period1 und 
uint_period2 anzusehen.


Ich würde auch hier
1
       
2
        T1CONbits.TMR1ON = 1;           //starte Timer 1
3
        CCP1CONbits.CCP1M = 0b111; // Capture bei jeder steigenden Flanke
4
        
5
        while (!(PIR1bits.CCP1IF));     //Warte auf erste steigende Flanke
6
        PIR1bits.CCP1IF = 0;

sicherheitshalber das Interrupt Flag löschen, bevor ich auf die erste 
Flanke warte.

Das würde ich insofern auf jeden Fall tun, weil du hier nach der 2. ten 
Flanke
1
        while (!(PIR1bits.CCP1IF));     //Warte auf zweite steigende Flanke
2
        CCP1CON = 0x00;                 //schalte Capture aus
3
        T1CONbits.TMR1ON = 0;           //stoppe Timer 1

dieses Flag nicht löscht.
Du schaltest zwar den ganzen Timer-Krempel ab (wozu eigentlich?), aber 
ob damit auch das Flag gelöscht wird?


Sicher ist sicher. Ein
1
        T1CONbits.TMR1ON = 1;           //starte Timer 1
2
        CCP1CONbits.CCP1M = 0b111; // Capture bei jeder steigenden Flanke
3
        PIR1bits.CCP1IF = 0;
4
        
5
        while (!(PIR1bits.CCP1IF));     //Warte auf erste steigende Flanke
6
        PIR1bits.CCP1IF = 0;
kostet nichts und du kannst dann sicher gehen, dass die while Schleife 
tatsächlich erst auf die nächste Flanke anspricht und nicht aufgrund 
eines übrig gebliebenen Interrupt Flags sofort wieder verlassen wird.

von Thorsten R. (Firma: Robert Bosch GmbH) (thorsten_roh)


Lesenswert?

Hallo Max,

Dein Kommentar mit while(1); ist zwar weg und ich will ja auch keine 
leer Endlosschleife da ich das ohne Interrupts machen will.
Habe aber zum ausprobieren was er nach nem Reset macht trotzdem mal 
statt dem Delay am Ende ein while(1); gesetzt.

Ergebnis ist nach jedem Reset anders (jeweils bei 100Hz, also eigentlich 
mit ner Periodendauer von 10.000 µsec was ja angezeigt werden müsste)

nach 1. Reset: 152
nach 2. Reset: 7314
nach 3. Reset: 4888
nach 4. Reset: 6161
nach 5. Reset: 3583
nach 6. Reset: 10008
nach 7. Reset: 9024
nach 8. Reset: 2141
nach 9. Reset: 7358

Hier nochmal das Programm dazu mit den eingebrachten Änderungen:
1
#include <p18f46k22.h>
2
#include <delays.h>
3
#include <timers.h>
4
#include <stdio.h>
5
#include <string.h>
6
#include "Lcd.h"
7
8
9
#pragma config FOSC = INTIO7   // interner Takt
10
unsigned int uint_period1 = 0;
11
unsigned int uint_period2 = 0;
12
unsigned int uint_period = 0; //Differenz
13
unsigned long ul_period_ultoa = 0;
14
unsigned char uchar_i = 0;
15
char char_Zeit[];
16
unsigned char i = 0;
17
18
19
/*----------------------------------------------------------------------------
20
 -----                                                                   -----
21
 -----                      InterruptHandler                             -----
22
 -----                                                                   -----
23
 ---------------------------------------------------------------------------*/
24
25
#pragma interruptlow InterruptHandler
26
void InterruptHandler (void)
27
{
28
    
29
}
30
/*----------------------------------------------------------------------------
31
 -----                                                                   -----
32
 -----                      Interruptvektoren setzen                     -----
33
 -----                                                                   -----
34
 ---------------------------------------------------------------------------*/
35
#pragma code _HIGH_INTERRUPT_VECTOR = 0x000008
36
void _high_ISR (void)
37
{
38
    _asm
39
        goto InterruptHandler       // Sprung zur Interruptroutine
40
    _endasm
41
}
42
43
#pragma code _LOW_INTERRUPT_VECTOR = 0x000018
44
void _low_ISR (void)
45
{
46
    _asm
47
        goto InterruptHandler       // Sprung zur Interruptroutine
48
    _endasm
49
}
50
#pragma code
51
52
53
54
/*----------------------------------------------------------------------------
55
 -----                                                                   -----
56
 -----                      Initialisierung                              -----
57
 -----                                                                   -----
58
 ---------------------------------------------------------------------------*/
59
void init (void)
60
{
61
    OSCCONbits.IRCF=0b101;  //HFINTOSC interner Takt auf 4MHz -> CLKOUT = 1MHz
62
    T1CON = 0x02; //Timer 1 mit FOSC/4, kein Prescaler für Capture, 16 Bit in eins lesen und STOP
63
    ANSELCbits.ANSC2 = 0; // RC2 als digitaler
64
    TRISCbits.TRISC2 = 1; // RC2 als Eingang für CCP1
65
    PIE1bits.CCP1IE = 0; // kein Interrupt von CCP1
66
    PIR1bits.CCP1IF = 0; // Interrupt Flag löschen
67
    CCP1CONbits.CCP1M = 0b101;      // Capture bei jeder steigenden Flanke
68
69
    //LCD starten PORTD als Ausgang damit LCD funktioniert
70
    LATD=0x00;
71
    ANSELD=0x00;
72
    TRISD=0x00;
73
    LCDInit();
74
}
75
76
77
/*----------------------------------------------------------------------------
78
 -----                                                                   -----
79
 -----                      MAIN                                         -----
80
 -----                                                                   -----
81
 ---------------------------------------------------------------------------*/
82
void main(void)
83
{
84
    init();
85
    while(1)
86
    {
87
        T1CONbits.TMR1ON = 1;           //starte Timer 1
88
89
        
90
        while (!(PIR1bits.CCP1IF));     //Warte auf erste steigende Flanke
91
        PIR1bits.CCP1IF = 0;
92
        uint_period1 = (unsigned int) CCPR1H;            //speichere Zeitpunkt erster Flanke (16Bit-Wert)
93
        uint_period1 = uint_period1 << 8;
94
        uint_period1 = uint_period1 + CCPR1L;
95
96
        while (!(PIR1bits.CCP1IF));     //Warte auf zweite steigende Flanke
97
        T1CONbits.TMR1ON = 0;           //stoppe und cleare Timer 1
98
99
        uint_period2 = (unsigned int) CCPR1H;            //speichere Zeitpunkt zweiter Flanke (16Bit-Wert)
100
        uint_period2 = uint_period2 << 8;
101
        uint_period2 = uint_period2 + CCPR1L;
102
103
        uint_period = uint_period2 - uint_period1; //Zeitdifferenz bilden
104
        ul_period_ultoa = (long) uint_period;
105
        LCDClear();
106
        LCDGoto(0,1);
107
        LCDWriteStr("Zeit");
108
        LCDGoto(0,0);
109
110
        ultoa(ul_period_ultoa, char_Zeit);         // unsigend Long to Ascii (String)
111
112
        i=0;
113
        while (char_Zeit[i] != 0x00)             // Solange nicht NUL
114
        {
115
             LCDPutChar(char_Zeit[i]);
116
             i++;
117
        }
118
119
        while(1);
120
//        Delay1KTCYx(100);
121
    }
122
}

von Karl H. (kbuchegg)


Lesenswert?

Wie hast du denn das
1
char char_Zeit[];
durch den Compiler gekriegt.

Das kannst du so nicht machen.
In C gibt es keine Arrays, die selbsttätig aufgrund der Anforderung
1
        ultoa(ulint_period, char_Zeit);         // unsigend Long to Ascii (String)
ihre Größe auf diese Anforderung anpassen.
Du gibst eine Array-Größe vor und bist dafür verantwortlich, dass diese 
Größe auch ausreicht.
1
char char_Zeit[15];


und dafür
1
        i=0;
2
        while (char_Zeit[i] != 0x00)             // Solange nicht NUL
3
        {
4
             LCDPutChar(char_Zeit[i]);
5
             i++;
6
        }
schreibst du dir gleich mal eine Funktion. Einen String am LCD ausgeben 
zu können, ist etwas das jede LCD Funktionalität können muss. Das 
braucht man so oft, das schreibt man nicht jedes mal neu.
Genauso wie Funktionen, die einen int, long, unsigned int, unsigned long 
ausgeben können. Wenn die LCD-Bibliothek so etwas nicht vorrätig hat, 
dann schreibt man sich diese Funktionen eben selber. Aber man schreibt 
sie sich 1 mal, und programmiert das nicht jedes mal neu in dem Programm 
welches man gerade baut.

von Karl H. (kbuchegg)


Lesenswert?

Thorsten Rohde schrieb:
> Hallo Max,
>
> Dein Kommentar mit while(1); ist zwar weg


Die war auch Quatsch.

von Max H. (hartl192)


Lesenswert?

Man könnte versuchen, auf das OSCCONbits.HFIOFS zu warten. Ich hatte 
ähnliches Mal mit der PLL: Am Anfang stimmte das Timing überhaupt nicht.

von Thorsten R. (Firma: Robert Bosch GmbH) (thorsten_roh)


Lesenswert?

Hallo Karl Heinz , Du hast mich auf die Lösung gebracht :-)

Karl Heiz schrieb:
> Du hast ein LCD?
>
>
> Das darf man auch benutzen, um sich mal die WErte von uint_period1 und
> uint_period2 anzusehen.
>
da es in der stdlib vom C18 kein uitoa gibt kann ich nur int oder long 
in ascii wandeln ohne mir eine Funktion zu schreiben, also gebe ich 
erstmal nur int oder long aus.

>
> Ich würde auch hier
>
1
> 
2
>         T1CONbits.TMR1ON = 1;           //starte Timer 1
3
>         CCP1CONbits.CCP1M = 0b111; // Capture bei jeder steigenden 
4
> Flanke
5
> 
6
>         while (!(PIR1bits.CCP1IF));     //Warte auf erste steigende 
7
> Flanke
8
>         PIR1bits.CCP1IF = 0;
9
>
>
> sicherheitshalber das Interrupt Flag löschen, bevor ich auf die erste
> Flanke warte.
>

Da war der entscheidende Hinweis :-)
Ich hatte erst den Capture nach capturen des 2. Wertes abgeschaltet was 
ich aber nach etwas Recherche rausgenommen hatte. Also muss ich 
natürlich auch das Interruptflag löschen bevor ich es wieder abfragen 
kann.
Vielen Dank für diesen Hinweis :-)

von Karl H. (kbuchegg)


Lesenswert?

Thorsten Rohde schrieb:
> Hallo Karl Heinz , Du hast mich auf die Lösung gebracht :-)
>
> Karl Heiz schrieb:
>> Du hast ein LCD?
>>
>>
>> Das darf man auch benutzen, um sich mal die WErte von uint_period1 und
>> uint_period2 anzusehen.
>>
> da es in der stdlib vom C18 kein uitoa gibt kann ich nur int oder long
> in ascii wandeln

Und? Wen störts?

`>  ohne mir eine Funktion zu schreiben

Aha.
ALso lieber im Nebel stochern und alles 3 mal schreiben, anstatt sich 
ein für alle mal eine Funktion ...
1
void LCDWriteULong( unsigned long wert )
2
{
3
  char txt[15];
4
5
  ultoa( wert, txt );
6
  LCDWriteStr( txt );
7
}
... zu schreiben, mit der man sich dann bei Bedarf auch andere Variablen 
aus dem Programm einfach ausgeben könnte.

Dieser Funktion darf man einen unsigned int übergeben. Der Compiler muss 
dann während des Funktionsaufrufs den unsigned int zu einem unsigned 
long machen, die Funktion gibt ihn aus und der Zahlenwert steht am LCD.


wäre ein
1
....
2
        uint_period = uint_period2 - uint_period1; //Zeitdifferenz bilden
3
4
        LCDClear();
5
6
        LCDGoto(0,1);
7
        LCDWriteStr("Zeit");
8
9
        LCDGoto(0,0);
10
        LCDWriteULong( uint_period );
11
12
        Delay1KTCYx(100);
13
      }
... zu einfach? Zu wartbar? Zu leicht zu lesen? Zu leicht um andere 
Ausgaben zu ergänzen?

Oder ein
1
....
2
       LCDGoto(0,0);
3
       LCDWriteULong( uint_period );
4
       LCDWriteStr( " " );
5
       LCDWriteULong( uint_period1 );
6
       LCDWriteStr( " " );
7
       LCDWriteULong( uint_period2 );
8
9
       Delay1KTCYx(100);
10
     }
wenn man sich alle 3 Werte ansehen will.

Ich werds nie verstehen, warum sich die Leute so sehr davor fürchten, 
sich selbst einen Vorrat an Funktionen zu verschaffen, mit dem man 
Standardaufgaben schnell und einfach lösen kann. Statt dessen stochern 
sie lieber stundenlang im Nebel.
Na, ja. Wers braucht.

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.