Forum: Compiler & IDEs Mega128 Timer1 PWM


von Alfred C. (alfred-c)


Lesenswert?

Hi Leute,

ich brauche für ein Projekt eine Routine die mir mit einer Genauigkeit 
von 8Bit 4 Software 100Hz-PWM-Kanäle mit nur einem Timer auf PD0-3 
rausgibt.

Nun möchte ich aus Zeitgründen nicht 256*4*100/sec einen Interrupt 
auslösen, sondern die Kanäle nach Pulsweite zu soriteren, um dann die 
Abstände der sortierten Pulsweiten zueinander zu ermitteln.

Ich dachte mir mit einem 16Bit Timer (Prescaler8) im CTC Mode könnte ich 
die IRQs somit auf nur 5*100/sec reduzieren.
Dazu habe ich zunächst eine Berechnungsroutine geschrieben, die mir vier 
frei wählbare 8Bit variablen in 16Bit Werte umwandelt, sortiert und die 
Abstände zueinader dann berechnet. All diese Berechnungen finden ein Mal 
je Sekunde außerhalb der Interrupt-Routine statt.

Sind die Eingangsparameter ungleich 0 und sind die Eingangsparameter 
mind. 1 voneinander entfernt läuft das ganze zieimlich gut. Aber eben 
nur dann. Nun weiß ich nicht weiter. Ich habe versucht das TCNT1 
Register jedes Mal zu "nullen" bzw auf Max zu setzen aber irgendwie löst 
der "Output Compare Match" ununterbrochen aus oder er verpasst den OCM 
und versaut damit die PWM :-(.
Ich bin mir sicher es liegt an einem allgemeinen Verständnisproblem für 
Timer, aber ich komme nicht drauf. Kann mir einer von euch helfen?

Hier der Code:
1
volatile uint8_t   AnaOut1;
2
volatile uint8_t   AnaOut2;
3
volatile uint8_t   AnaOut3;
4
volatile uint8_t   AnaOut4;
5
6
volatile uint16_t  PWM[5];
7
volatile uint8_t  Prio[4];
8
volatile uint8_t  cycle;
9
10
//***************** Timerinitialisierung:******************************
11
12
TCCR1B|=(1<<WGM12);   //CTC Mode0
13
TCCR1B|=(1<<CS11);      //Prescaler = 8, start timer      
14
TIMSK|=(1<<OCIE1A);  //Output compare match1
15
16
//***************** Berechnung der vier PWM Kanäle:********************
17
18
void anaOUTupdate (void)
19
{
20
uint16_t  PWMtemp[4];
21
uint16_t  PWMordered[4];
22
PWMordered[0] = 0;
23
PWMordered[1] = 0;
24
PWMordered[2] = 0;
25
PWMordered[3] = 0;
26
Prio[0] = 2; // needs to be plus 2 because 1 is reserved for start of PWM
27
Prio[1] = 2;
28
Prio[2] = 2;
29
Prio[3] = 2;
30
int i;
31
i = 0;
32
33
34
//PWM value conversion
35
PWMtemp[0] = AnaOut1*78;   //(20000/255=78.4 approx)
36
PWMtemp[1] = AnaOut2*78;   
37
PWMtemp[2] = AnaOut3*78;   
38
PWMtemp[3] = AnaOut4*78;  
39
40
//PWM rise and fall order
41
for (i=0;i<4;i++) //biggest value is 3, smallest is 0
42
  {
43
  if (PWMtemp[0]>PWMtemp[i])
44
    Prio[0] = 1+Prio[0];
45
  if (PWMtemp[1]>PWMtemp[i])
46
    Prio[1] = 1+Prio[1];
47
  if (PWMtemp[2]>PWMtemp[i])
48
    Prio[2] = 1+Prio[2];
49
  if (PWMtemp[3]>PWMtemp[i])
50
    Prio[3] = 1+Prio[3];
51
  };
52
53
//ordering from small to big in help array "PWMordered"
54
PWMordered[0] = 0;
55
PWMordered[1] = 0;
56
PWMordered[2] = 0;
57
PWMordered[3] = 0;
58
59
for (i=0;i<4;i++)
60
  {
61
  if (2==Prio[i])
62
    PWMordered[0]=PWMtemp[i];
63
  if (3==Prio[i])
64
    PWMordered[1]=PWMtemp[i];
65
  if (4==Prio[i])
66
    PWMordered[2]=PWMtemp[i];
67
  if (5==Prio[i])
68
    PWMordered[3]=PWMtemp[i];
69
  }
70
71
//PWM spacing calculation
72
PWM[0] = PWMordered[0];
73
PWM[1] = PWMordered[1]-PWMordered[0];
74
PWM[2] = PWMordered[2]-PWMordered[1];
75
PWM[3] = PWMordered[3]-PWMordered[2];
76
PWM[4] = 20000-PWMordered[3];
77
};
78
79
//********************************Timer IRQ************************
80
81
ISR (TIMER1_COMPA_vect) //PWM Routine Link
82
  {
83
cycle++;
84
85
  if (cycle == 1){OCR1A = PWM[0]; 
86
     PORTD|= (1<<PD0) | (1<<PD1) | (1<<PD2) | (1<<PD3);}
87
  if (cycle == 2)  OCR1A = PWM[1];
88
  if (cycle == 3)  OCR1A = PWM[2];
89
  if (cycle == 4)  OCR1A = PWM[3];
90
91
  if (cycle == Prio[0]) PORTD &= ~(1<<PD0);
92
  if (cycle == Prio[1]) PORTD &= ~(1<<PD1);
93
  if (cycle == Prio[2]) PORTD &= ~(1<<PD2);
94
  if (cycle == Prio[3]) PORTD &= ~(1<<PD3);
95
96
  if (cycle == 5)  {OCR1A = PWM[4]; cycle = 0;}
97
98
        };

von Alfred C. (alfred-c)


Lesenswert?

Hat wirklich keiner eine Idee oder waren meine Angaben zu ungenau?

von Rolf P. (rolfp)


Lesenswert?

Alfred Chicken schrieb:
> Hat wirklich keiner eine Idee oder waren meine Angaben zu ungenau?

Ich bin auch grade dran etwas mit PWM auszuprobieren. Irgendwie ist mir 
aber dein Programm zu kompliziert als dass ich da durchblicken würde was 
du machen willst. Ich verstehe z.B. nicht warum du OCR1A in 
TIMER1_COMPA_vect veränderst. Wäre es nicht viel einfacher OCR1B für den 
PWM zu verwenden?
Also irgendwie so:
1
ISR (TIMER1_COMPA_vect)
2
{
3
 PORTD |= 0x0F; //alle PWM setzen
4
 OCR1B = ersterwert;
5
}
6
7
ISR (TIMER1_COMPB_vect)
8
{
9
 int n=TCNT1;
10
 if(pwm1==n) PORTD &= ~(1<<PD0); //entsprechender PWM ruecksetzen
11
 if(pwm2==n) PORTD &= ~(1<<PD1);
12
 if(pwm3==n) PORTD &= ~(1<<PD2);
13
 if(pwm4==n) PORTD &= ~(1<<PD3);
14
 OCR1B = naechsterwert;
15
}

von Alfred C. (alfred-c)


Lesenswert?

Vielleicht habe ich im Datenblatt was überlesen, aber im CTC Mode gibt 
es doch keinen COMPB Interrupt oder? Wenn es doch geht würde das die 
Rechenzeit je Interruptroutine schon etwas verkürzen, aber das 
eigentliche Problem ist damit glaube ich nicht gelöst.

Also ich stelle mir das dann so vor:

Mit einem Prescaler von 8 würde ich den COMPA alle 20000 timer counts 
auslösen lassen. Damit könnte super die Periodendauer T der PWM 
eingestellt werden. Denn 16000000Hz/(20000*8 Takte) = 100Hz.

Dann würden in der Zwischenzeiten die vier Interrupts ausgelöst werden 
und zu gegebener Zeit den jeweiligen PortD-Pin auf low ziehen. Wäre aber 
beispielsweise PWM1 = 0; dann würde schonmal der erste Interrupt 
verpasst werden und der jeweilige Portpin für die ganze Zeit auf high 
bleiben. Denn laut Datenblatt wird doch der Compare fuer einen 
Timercount ausgesetzt, wenn OCR veraendert wird, oder habe ich das 
falsch verstanden?

Das Gute an deiner Routine wäre aber, das es abgesehen von dem Problem 
mit der 0 egal wäre, wenn zwei PWMs genau den gleichen Wert hätten. Wäre 
also "erster wert" und "zweiter wert" = 50, so würden beide auf low 
gehen, der zweite OCR Wert würde dann zwar verpasst werden aber das ist 
ja egal denn der Port is ja schon low.
Dann bin ich mir aber auch nicht sicher was passieren würde, wenn 
"erster wert" = 50 und "zweiter wert" = 51 wäre, denn die 
Interruptroutine würde unter Umständen so lange dauern, dann der OCR 
Wert für "zweiter Wert" auch verpasst wird, das wäre dann aber schlimm, 
denn der Pin wird dann nicht auf low gezogen...

das wird immer wirrer alles ^^. Wer soll denn da noch durchblicken ? :-)

Danke aber für deinen Vorschlag.

von Alfred C. (alfred-c)


Lesenswert?

OK, laeuft alles :-), ist nahezu der Code wie oben. Jetzt gehen je 
Sekunde ca. 35.000 Takte fuer die 4 100Hz PWMs drauf (= 0,22% CPU 
Auslastung), das ist zu verschmerzen...
Rolf, falls du den Code haben möchtest, schreib mich an.

von Rolf P. (rolfp)


Lesenswert?

Alfred Chicken schrieb:
> Vielleicht habe ich im Datenblatt was überlesen, aber im CTC Mode gibt
> es doch keinen COMPB Interrupt oder? Wenn es doch geht würde das die

Warum sollte der COMPB Interrupt nicht gehen? Wenn ich nicht was 
wesentliches übersehen habe müsste der in jedem Modus gehen.

Alfred Chicken schrieb:
> OK, laeuft alles :-), ist nahezu der Code wie oben. Jetzt gehen je

Wie oben dein Code, oder wie oben mein Vorschlag? Vielleicht schreibst 
du kurz was denn dann der Trick war um das Problem zu lösen?

> Rolf, falls du den Code haben möchtest, schreib mich an.
Wenn du den Code hier posten kannst, schau ich ihn mir mal an.

von Alfred C. (alfred-c)


Lesenswert?

Also ich hab die Funktion getestet, vielleicht muss noch minimal an den 
Timings gearbeitet werden, aber die reine Funktionalitaet ist 
erfuellt... Timer laeuft im CTC mode wie vorher... (WMG12)
1
/********************************************************************
2
Analogue Output: D0 = AO1, D1 = AO2, D2 = AO3, D3 = AO4
3
Resolution: 8Bit
4
5
Description: this function may be fairly complicated but it minimizes
6
the afford of calcualations per IRQ quite substantially. This function
7
should not be executed more than a few times per second.
8
1.   Convert 255 8-Bit Resolution Values) into values between 0-20000
9
2.   Every value needs a priority value as this will be used in IRQ. If
10
  two values equal eachother there are only 3 Priority numbers instead
11
  of 4. For instace: 
12
  255 30 70 70 --> 5 2 3 3   (offset = 2)
13
  10  20 30 40 --> 2 3 4 5 
14
  30  30 30 30 --> 2 2 2 2
15
3.  The values now need to be ordered from small to big in a help array.
16
4.  Now the distance between the PWMs needs to calculated.
17
  Example: 
18
  0----2000(PWM1)-------5000(PWM2)------15000(PWM3/4)---20000/0 RESTART
19
  0----2000(PWM1)--4000(PWM2)-5000(PWM3)----12000PWM(4)-20000/0 RESTART
20
  Instead reduces the need for IRQs from
21
  255(Resolution)*4PWMs*100Hz  =102000 IRQs
22
  to
23
  (4PWMs+1Stop)*100Hz      =500IRQs
24
********************************************************************/
25
void anaOUTupdate (void)
26
{
27
uint16_t  PWMtemp[4];
28
uint16_t  PWMordered[4];
29
PWMordered[0] = 0;
30
PWMordered[1] = 0;
31
PWMordered[2] = 0;
32
PWMordered[3] = 0;
33
34
int i;
35
i = 0;
36
37
38
//PWM value conversion
39
PWMtemp[0] = AnaOut1*78;   //(20000/255=78.4 approx)
40
PWMtemp[1] = AnaOut2*78;   //Analogue Output must be converted into value 0...20000
41
PWMtemp[2] = AnaOut3*78;   //it's time consuming but there is no need for rush
42
PWMtemp[3] = AnaOut4*78;  //this action may be interrupted by ISR
43
/*
44
uart_debug("Unsorted1: ",PWMtemp[0]);
45
uart_debug("Unsorted2: ",PWMtemp[1]);
46
uart_debug("Unsorted3: ",PWMtemp[2]);
47
uart_debug("Unsorted4: ",PWMtemp[3]);
48
*/
49
50
//PWM rise and fall order
51
Prio[0] = 2; // needs to be plus 2 because 1 is reserved for start of PWM
52
Prio[1] = 2;
53
Prio[2] = 2;
54
Prio[3] = 2;
55
56
for (i=0;i<4;i++) //biggest value is 3, smallest is 0
57
  {
58
  if (PWMtemp[0]>PWMtemp[i])
59
    Prio[0] = 1+Prio[0];
60
  if (PWMtemp[1]>PWMtemp[i])
61
    Prio[1] = 1+Prio[1];
62
  if (PWMtemp[2]>PWMtemp[i])
63
    Prio[2] = 1+Prio[2];
64
  if (PWMtemp[3]>PWMtemp[i])
65
    Prio[3] = 1+Prio[3];
66
  };
67
/*
68
PWMtemp[1]=PWMtemp[1]-1;
69
PWMtemp[2]=PWMtemp[2]-2;
70
PWMtemp[3]=PWMtemp[3]-3;
71
*/
72
/*
73
uart_debug("Prio1: ",Prio[0]);
74
uart_debug("Prio2: ",Prio[1]);
75
uart_debug("Prio3: ",Prio[2]);
76
uart_debug("Prio4: ",Prio[3]);
77
*/
78
//ordering from small to big in help array "PWMordered"
79
80
for (i=0;i<4;i++)
81
  {
82
  if (2==Prio[i])
83
    PWMordered[0]=PWMtemp[i];
84
  if (3==Prio[i])
85
    PWMordered[1]=PWMtemp[i];
86
  if (4==Prio[i])
87
    PWMordered[2]=PWMtemp[i];
88
  if (5==Prio[i])
89
    PWMordered[3]=PWMtemp[i];
90
  }
91
92
if (PWMordered[1]<PWMordered[0])
93
  PWMordered[1] = PWMordered[0];
94
if (PWMordered[2]<PWMordered[1])
95
  PWMordered[2] = PWMordered[1];
96
if (PWMordered[3]<PWMordered[2])
97
  PWMordered[3] = PWMordered[2];
98
99
/*
100
uart_debug("Ordered1: ",PWMordered[0]);
101
uart_debug("Ordered2: ",PWMordered[1]);
102
uart_debug("Ordered3: ",PWMordered[2]);
103
uart_debug("Ordered4: ",PWMordered[3]);
104
*/
105
106
//PWM spacing calculation
107
//probably timer must be killed for those 5 equations
108
109
if (PWMordered[1]>PWMordered[2])
110
  {  while(1)
111
  {
112
  };
113
  }
114
PWM[0] = PWMordered[0];
115
PWM[1] = PWMordered[1]-PWMordered[0];
116
PWM[2] = PWMordered[2]-PWMordered[1];
117
PWM[3] = PWMordered[3]-PWMordered[2];
118
PWM[4] = 20000-(PWM[0]+PWM[1]+PWM[2]+PWM[3]);
119
//PWM must not be 0 and values must not be the same!
120
121
if ( PWM[0] < 78)             {PWM[0] = 15+PWM[0];}
122
if ((PWM[1] < 78) || (PWM[0]==PWM[1]))  {PWM[1] = 30+PWM[1];}
123
if ((PWM[2] < 78) || (PWM[1]==PWM[2]))   {PWM[2] = 45+PWM[2];}
124
if ((PWM[3] < 78) || (PWM[3]==PWM[4]))  {PWM[3] = 60+PWM[3];PWM[4]=8+PWM[4];}
125
126
/*
127
uart_debug("Spacing0-1: ",PWM[0]);
128
uart_debug("Spacing1-2: ",PWM[1]);
129
uart_debug("Spacing2-3: ",PWM[2]);
130
uart_debug("Spacing3-4: ",PWM[3]);
131
uart_debug("Spacing4-0: ",PWM[4]);
132
*/
133
};
134
135
ISR (TIMER1_COMPA_vect) //PWM Routine
136
  {
137
cycle++;
138
139
  if (cycle == 1)  {PORTD|= (1<<PD0) | (1<<PD1) | (1<<PD2) | (1<<PD3); OCR1A = PWM[0];}
140
  if (cycle == 2) OCR1A = PWM[1];
141
  if (cycle == 3)  OCR1A = PWM[2];
142
  if (cycle == 4)  OCR1A = PWM[3];
143
144
  if (cycle == Prio[0]) PORTD &= ~(1<<PD0);
145
  if (cycle == Prio[1]) PORTD &= ~(1<<PD1);
146
  if (cycle == Prio[2]) PORTD &= ~(1<<PD2);
147
  if (cycle == Prio[3]) PORTD &= ~(1<<PD3);
148
149
  if (cycle == 5)  {OCR1A = PWM[4]; cycle = 0;}
150
  };

von Rolf P. (rolfp)


Lesenswert?

Alfred Chicken schrieb:
> Also ich hab die Funktion getestet, vielleicht muss noch minimal an den
> Timings gearbeitet werden, aber die reine Funktionalitaet ist
> erfuellt... Timer laeuft im CTC mode wie vorher... (WMG12)

Ich hätte noch ein paar Optimierungsvorschläge:

Alfred Chicken schrieb:
> uint16_t  PWMordered[4];
> PWMordered[0] = 0;
> PWMordered[1] = 0;
> PWMordered[2] = 0;
> PWMordered[3] = 0;

das könnte man auch so machen:
1
uint16_t  PWMordered[4] = {0,0,0,0};

Alfred Chicken schrieb:
> Prio[0] = 1+Prio[0];
Einfacher so:
  Prio[0] += 1;
oder so:
  Prio[0]++;

Alfred Chicken schrieb:
> for (i=0;i<4;i++)
>   {
>   if (2==Prio[i])
>     PWMordered[0]=PWMtemp[i];
>   if (3==Prio[i])
>     PWMordered[1]=PWMtemp[i];
>   if (4==Prio[i])
>     PWMordered[2]=PWMtemp[i];
>   if (5==Prio[i])
>     PWMordered[3]=PWMtemp[i];
>   }

Einfacher und schneller so:
1
 for (i=0;i<4;i++)
2
   {
3
    PWMordered[Prio[i]-2] = PWMtemp[i];
4
   }

Alfred Chicken schrieb:
> if (PWMordered[1]>PWMordered[2])
>   {  while(1)
>   {
>   };
>   }

Das ist wohl noch ein Überbleibsel vom Testen. Also weglassen.

von Alfred (Gast)


Lesenswert?

Hey, vielen Dank! Das werd ich alles uebernehmen :-)... Die 
Schreibweisen mit dem + sehen natürlich besser aus. Da ich aber noch 
nicht so oft mit solchen arrays gearbeitet habe war ich mir nicht 
sicher, ob man die so wie normale Variablen behandeln kann.

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.