Forum: Mikrocontroller und Digitale Elektronik externes event über ISR und timer "präzise" messen


von madtulip (Gast)


Lesenswert?

Hallo.

Ich würde gernen aus einem gegebenen Takt von 2048Hz, Rechteck, 50% duty 
cycle einen schnelleren Takt generieren von 4*2048Hz, Rechteck, 50% duty 
cycle.

Vermutlich gibts da eine geschicktere analoge Schaltung, oder?

Ich verwende jedenfall gerade einen ATtiny4313 und habe das 2048Hz 
Signal an PD6 (ICP1) angelegt.

Ich möchte die Zeit zwischen zwei steigenden Flanken des 2048Hz Signales 
messen. Am besten mit voller 8MHz Auflösung (F_CPU). Ich gehe von einem 
idealem 2048Hz Takt aus. Ich möchte die Zeit zwischen zwei Flanken des 
langsameren Taktes nutzen um zwischen der letzten Flanke und der jeweils 
nächsten Flanke die kommen wird 3 zusätzliche steigende Flanken in 
gleichem Abstand zu setzen. Phasenwinkel ist irrelevant. Den Abstand der 
zu generierenden Flanken kann ich mit einem Fehler der Auflösung die mir 
die Timergeschwindigkeit aufzwingt aus der gemessenen Zeit zwischen den 
beiden letzen Flanken des 2048Hz Signales berechnen(Rundungsfehler durch 
Division ... ok).

So soll es aussehen.:
  2048Hz Takt: 1000000010000000....
=>
4*2048Hz Takt: 1010101010101010....

Ich verwende den 16-bit timer1 um über das ICIE1 Bit einen Interrupt 
auszulösen, wenn eine steigende Flanke an PD6 anliegt. Der Timer 
verwendet 1 als Prescaler um eine hohe zeitliche Auflösung zu erreichen.

Der Timer sollte also TCNT1 in jedem MCU Takt um eins erhöhen.

Das Input capture Register (ICR1) übernimmt den TCNT1 Wert bei Eintritt 
in die ISR.

Wenn ich es richtig verstanden habe, läuft TCNT1 weiter, auch wenn ich 
mich in einer ISR befinde.
1
ISR(/*ICIE1_zugehöriger_vektorname_gerade_vergessen*/){
2
TCNT1 = 0;// Rücksetzen des Zählers des Timers 1
3
/*
4
...
5
*/
6
// verlassen vor nächsterm ISR Ereigniss notwendig
7
}
8
9
int main(void){
10
/*
11
...
12
*/
13
while(1)}

Problematisch finde ich derzeit die Stelle.:
1
TCNT1 = 0;

Ich frage mich, ob ich das Register nicht auf 1 oder 2 setzen sollte, je 
nachdem wieviele CPU Zyklen vergehen zwischen dem setzen des ISR und dem 
inkrementieren von TCNT1 durch den timer, nachdem ich das register 
resetet habe. Wenn ich das nicht mache, dann messe ich zwischen 2 
flanken des 2048Hz Signales nicht den Abstand zwischen den Flanken, 
sondern den Abstand zwischen den Flanken minus die Anzahl der Taktzyklen 
der MCU zwischen ISR Eintritt und resetten des TCNT1 Registers. Muss ich 
diese Stelle in inline asm schreiben um unterschiedliche codelaengen 
generiert durch den compiler zu vermeiden oder wird eine solche simple 
Zuweisung immer gleich kompiliert? wieviele MCU Zyklen werden hier genau 
benötigt? auf welchen Wert sollte ich also TCNT1 setzen um nur, und auf 
einen MCU Zyklus genau, die Zeit zwischen 2 Flanken zu messen. Von 
anderen Ungenauigkeiten mal ganz abgesehen geht es mir hier leider um 
den Teufel und worin er steckt.

Beispiel.:
CPU Zyklus       Aktion
---------------------------------
100              Flanke/ISR/ ICR1=100
101              TCNT1 = 0;
102              TCNT1 wird von timer inkrementiert auf 1

wenn das so funktioniert sollte ich TCNT1 nicht auf 0 sondern auf 1 
setzen.

CPU Zyklus       Aktion
---------------------------------
100              Flanke/ISR/ ICR1=1
101              TCNT1 = 1;
102              TCNT1 wird von timer inkrementiert auf 2

Oder ist das, was ich "Flanke/ISR" nannte garnicht vorhanden, sondern an 
der ISR vektor Adresse steht direkt der erste Befehl der ISR Funktion?

CPU Zyklus       Aktion
---------------------------------
100              TCNT1 = 1; (ICR1=100 per hardware)
101              TCNT1 wird von timer inkrementiert auf 2

Oder braucht die Zuweisung an das TCNT1 Register mehr als einen F_CPU 
Zyklus ?

Vielen Dank für eure Hilfe und das ihr euch die Zeit genommen habt bis 
hierher zu lesen!

von madtulip (Gast)


Lesenswert?

So, ich glaube ich habs durch kürzen der Unwissenheit über die oben 
angefragten Timings gelöst. Evtl. kann jemand den code ja nochmal 
brauchen.:

Der folgende Code oversampled eine externe Clock an Pin PD6 des hier 
verwendeten ATtiny4313 um Faktor 4 und gibt sie an PA1 aus. Es wurde 
hier versucht die Intervalle zwischen den Taktzyklen an PD6 so genau wie 
möglich zu messen und die resultierenden Intervalle des langsamen Taktes 
so gleich wie möglich zu halten.

Der Oversamplefaktor kann über "CLK_multiplier_times_2" variiert werden.

Andere Einstellungen als 8 erfordern den
1
if (rest == 1)
2
/*...*/

Teil des Codes entsprechend anzupassen.

Triggern auf die langsamere Clock erzeugt auf dem verwendetem Osziloskop 
eine konstant stehende Welle der langsameren und der schnelleren Clock.
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
// ----- Pin maps (see scematics) -----
5
#define SATELIT_CLK        PA1
6
7
// ----- Makros -----
8
#define FASTER_CLK_ON      PORTA |=  (1<<SATELIT_CLK)      // high
9
#define FASTER_CLK_OFF      PORTA &= ~(1<<SATELIT_CLK)      // low
10
11
// ----- Timer -----
12
// number of clockcycles of equal length *2 you want to insert between to clock cycles on ICP1 input capture pin
13
#define CLK_multiplier_times_2  8
14
15
void Toggle_Clock(void)
16
{
17
  // static variables for faster access compared to global
18
  static unsigned int full_interval_length = 0;
19
  static char Pin_level_state = 0;
20
  static char Clock_state = 0;
21
  static unsigned int timing_vector[CLK_multiplier_times_2];
22
  
23
  // Toggle faster clock
24
  if (Pin_level_state){
25
    FASTER_CLK_OFF;
26
    Pin_level_state = 0;
27
  }
28
  else{
29
    FASTER_CLK_ON;
30
    Pin_level_state = 1;
31
  }
32
  
33
  if (Clock_state == 0){
34
    // new cycles starts
35
    // prepare timing vector
36
    // read measured interval length between two slower clock pulses
37
    full_interval_length = ICR1;
38
    
39
    char rest = full_interval_length % 8;// rest of division by 8
40
41
    timing_vector[0] = (1*full_interval_length)/CLK_multiplier_times_2;
42
    timing_vector[1] = (2*full_interval_length)/CLK_multiplier_times_2;
43
    timing_vector[2] = (3*full_interval_length)/CLK_multiplier_times_2;
44
    timing_vector[3] = (4*full_interval_length)/CLK_multiplier_times_2;
45
    timing_vector[4] = (5*full_interval_length)/CLK_multiplier_times_2;
46
    timing_vector[5] = (6*full_interval_length)/CLK_multiplier_times_2;
47
    timing_vector[6] = (7*full_interval_length)/CLK_multiplier_times_2;
48
    timing_vector[7] = 0xFFFE;// Dummy, timer should never reach this value. external slower clock ISR should appear before this happens.
49
    
50
    // divide rest of division "equaly" over the timers
51
    if (rest == 1){
52
      timing_vector[3]++;
53
    }
54
    else if (rest == 2){
55
      timing_vector[2]++;
56
      timing_vector[4]++;
57
    }
58
    else if (rest == 3){
59
      timing_vector[1]++;
60
      timing_vector[3]++;
61
      timing_vector[5]++;
62
    }
63
    else if (rest == 4){
64
      timing_vector[0]++;
65
      timing_vector[2]++;
66
      timing_vector[4]++;
67
      timing_vector[6]++;
68
    }
69
    else if (rest == 5){
70
      timing_vector[0]++;
71
      timing_vector[1]++;
72
      timing_vector[3]++;
73
      timing_vector[5]++;
74
      timing_vector[6]++;
75
    }
76
    else if (rest == 6){
77
      timing_vector[0]++;
78
      timing_vector[1]++;
79
      timing_vector[2]++;
80
      timing_vector[4]++;
81
      timing_vector[5]++;
82
      timing_vector[6]++;            
83
    }
84
    else if (rest == 7){
85
      timing_vector[0]++;
86
      timing_vector[1]++;
87
      timing_vector[2]++;
88
      timing_vector[3]++;
89
      timing_vector[4]++;
90
      timing_vector[5]++;
91
      timing_vector[6]++;
92
    }                    
93
  }
94
95
  OCR1A  = timing_vector[Clock_state];// set time for next satellite clk event
96
  
97
  Clock_state++;      // increment faster clk cycle counter
98
  if (Clock_state == 8){
99
    Clock_state = 0;  // reset cycle
100
  }
101
102
  // it is necessary to reach end of this before "timing_vector[Clock_state]" MCU cycles
103
}
104
105
ISR(TIMER1_CAPT_vect)
106
{
107
  // ICR1 is set to TCNT1 by hardware.
108
  TCNT1 = 0;    // reset current value of timer 1.
109
  Toggle_Clock();  // Toggle faster clock
110
}
111
112
ISR(TIMER1_COMPA_vect)
113
{
114
  
115
  OCR1A = 0xFFFE;// Dummy 16-bit access that "should" take as long as TCNT1 = 0;.Timer should never reach this value.
116
  Toggle_Clock();  // Toggle faster clock
117
}
118
119
int main(void)
120
{
121
  // ----- Hardware -----
122
  // GPIOs
123
    FASTER_CLK_OFF;
124
    DDRA   |=  (1 << SATELIT_CLK);
125
  
126
  // ----- Timer 1 -----
127
  TCCR1B |= (1<<ICES1);        // Trigger positive edge
128
129
  //CS12 CS11 CS10 Description
130
  //0 0 0 No clock source (Timer/Counter stopped).
131
  //0 0 1 clkI/O/1    (No prescaling)
132
  //0 1 0 clkI/O/8    (From prescaler)
133
  //0 1 1 clkI/O/64    (From prescaler)
134
  //1 0 0 clkI/O/256    (From prescaler)
135
  //1 0 1 clkI/O/1024    (From prescaler)
136
  //1 1 0 External clock source on T1 pin. Clock on falling edge.
137
  //1 1 1 External clock source on T1 pin. Clock on rising edge.
138
  
139
  // prescaler = 1
140
  //TCCR1B |=  (1<<CS12);
141
  TCCR1B &= ~(1 <<CS12);
142
  //TCCR1B |=  (1<<CS11);
143
  TCCR1B &= ~(1 <<CS11);
144
  TCCR1B |=  (1<<CS10);
145
  //TCCR1B &= ~(1 <<CS10);
146
147
  OCR1A  = 0xFFFE;    // Dummy, should never be reached - if slower clock is present
148
  
149
  // integrate overflow ISR depending on what you wnat to do when no slow clock is present
150
  //TIMSK   &= ~(1 <<TOIE1);  // overflow ISR
151
  TIMSK |=  (1 <<ICIE1 );  // Input Capture Interrupt Enable
152
  TIMSK |=  (1 <<OCIE1A);  // compare A match enable
153
while(1){}
154
}

von madtulip (Gast)


Lesenswert?

"und die resultierenden Intervalle des SCHNELLEREN Taktes
so gleich wie möglich zu halten."

von Stefan E. (sternst)


Lesenswert?

madtulip schrieb:
> Es wurde
> hier versucht die Intervalle zwischen den Taktzyklen an PD6 so genau wie
> möglich zu messen

Dann lass das "TCNT1 = 0;" in der ISR weg. Du hast im ersten Posting ja 
schon selber ausgeführt, warum das zu Ungenauigkeiten führt. Genaue 
Messung per Input-Capture macht man, indem man den Zähler durchlaufen 
lässt, sich den vorigen Capture-Wert merkt, und die Differenz bildet. 
Wenn man dafür Variablen mit dem richtigen Typ nimmt, braucht man bei 
der Differenzbildung noch nicht mal den möglichen Timer-Überlauf zu 
berücksichtigen.

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.