Forum: Mikrocontroller und Digitale Elektronik Frage zum SoftPWM-Artikel


von Peter K. (Gast)


Lesenswert?

Hallo,

ich habe mir die SoftPWM v3 von 
http://www.mikrocontroller.net/articles/Soft-PWM gerade etwas angepasst 
und sie läuft soweit auch bestens.

Könnte mir aber eventuell jemand erklären, warum die Funktion 
pwm_update(), in der main() aufgerufen, 100mal langsamer abgearbeitet 
wird, als wenn ich sie z.B. mittels eines zweiten Timers alle 10ms in 
dessen ISR aufrufe?
Die main() müßte doch schneller sein?

Schleife für die main():
1
xxx++;
2
SET_PWM(pgm_read_word(lookupTable64+xxx),0,0,0);
3
if(xxx>=63) xxx=0;

Zähler über die ISR: (das Inkrement in der main() fällt dann natürlich 
weg!)
1
// Konfiguration von 8-bit Timer/Counter0
2
TCCR2B = (1<<CS22)|(1<<CS20); // Vorteiler:1024
3
TIMSK2 |= (1<<TOIE2);         // Aktivierung des Timer2-Overflow-Interrupts
4
5
ISR(TIMER2_OVF_vect)
6
{
7
  TCNT2 = (uint8_t)(int16_t)-((F_CPU/(F_CPU/16000000)) / 1024 * 10e-3 + 0.5);
8
  xxx++;
9
}

Das ist die LookupTabelle:
1
uint16_t lookupTable64[64] PROGMEM=
2
{
3
  0, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 6, 6, 7, 8, 9, 10, 11, 12, 13, 15, 17, 19, 21, 23, 26,
4
  29, 32, 36, 40, 44, 49, 55, 61, 68, 76, 85, 94, 105, 117, 131, 146, 162, 181, 202, 225, 250, 279,
5
  311, 346, 386, 430, 479, 534, 595, 663, 739, 824, 918, 1023
6
};

SET_PWM ist nur eine übergeordnete Funktion von mir für pwm_update().

Liebe Grüße

von Peter K. (Gast)


Lesenswert?

Niemand einen Rat???

LG

von Stefan (Gast)


Lesenswert?

Peter K. schrieb:
> Niemand einen Rat???

Nö, im Moment nicht.

von Werner (Gast)


Lesenswert?

Peter K. schrieb:
> Könnte mir aber eventuell jemand erklären, warum die Funktion
> pwm_update(), in der main() aufgerufen, 100mal langsamer abgearbeitet
> wird, als wenn ich sie z.B. mittels eines zweiten Timers alle 10ms in
> dessen ISR aufrufe?

Das einfachste ist, wenn du deinem Programm im Simulator über die 
Schulter schaust, und guckst, wo er womit die Zeit verbringt.

von Karl H. (kbuchegg)


Lesenswert?

Wie wärs mit:
weil sie, von main aus aufgerufen, dauernd durch die Interrupts 
unterbrochen wird, während sie von einer ISR aus aufgerufen durchläuft?

von Falk B. (falk)


Lesenswert?

@  Karl Heinz Buchegger (kbuchegg) (Moderator)

>weil sie, von main aus aufgerufen, dauernd durch die Interrupts
>unterbrochen wird, während sie von einer ISR aus aufgerufen durchläuft?

Theoretisch ja, praktisch nein. Denn gerade die 3. Version macht SEHR 
wenig Interrupts, das ist ja der Witz. Die Aussage, 100 mal langsamer 
ist nicht haltbar, es sei denn, der OP hat sie verschlimmbessert.

Mit VOLLSTÄNDIGEM Code könnte man da was sehen . . .

MFG
Falk

von Axel S. (a-za-z0-9)


Lesenswert?

Peter K. schrieb:
> Könnte mir aber eventuell jemand erklären, warum die Funktion
> pwm_update(), in der main() aufgerufen, 100mal langsamer abgearbeitet
> wird

Wie kommst du darauf?

> Die main() müßte doch schneller sein?

Und wie kommst du darauf?

Was auch immer du gemessen hast, scheint der typische "wer mißt, mißt 
Mist" gewesen zu sein. Und syntaktisch kaputten Code zu posten, macht 
die Sache kein bisschen besser.


XL

von Peter K. (Gast)


Lesenswert?

Hi,

und danke für die schnellen Antworten.

Also folgendes habe ich in der ISR(in der SoftPWM) geändert:
1
if(pwm_cnt == 0)
2
  {
3
    // Setzen der Portbits zu Beginn der PWM
4
    if(tmp & 0b00000001) PWM1_PORT |= (1<<PWM1);
5
    if(tmp & 0b00000010) PWM2_PORT |= (1<<PWM2);
6
    if(tmp & 0b00000100) PWM3_PORT |= (1<<PWM3);
7
    if(tmp & 0b00001000) PWM4_PORT |= (1<<PWM4);
8
9
    pwm_cnt++;
10
  }
11
  else
12
  {
13
    // Löschen der Portbits
14
    if((tmp & 0b00000001) == 0) PWM1_PORT &= ~(1<<PWM1);
15
    if((tmp & 0b00000010) == 0) PWM2_PORT &= ~(1<<PWM2);
16
    if((tmp & 0b00000100) == 0) PWM3_PORT &= ~(1<<PWM3);
17
    if((tmp & 0b00001000) == 0) PWM4_PORT &= ~(1<<PWM4);
18
19
...
20
}

Und dies ist die Test-Funktion, einmal in ISR(TIMER2_OVF_vect) und in 
der main() aufgerufen:
1
void PowerFading(uint8_t nX) // simple Fading-Schleife
2
{
3
  uint16_t nR,nG,nB,nW;
4
5
  nG = nB = nW = 0;
6
7
  if(xxx<=63)
8
  {
9
    nR = pgm_read_word(lookupTable64+xxx);
10
    SET_PWM(nR,nG,nB,nW);
11
  }
12
  else
13
  {
14
    nR = pgm_read_word(lookupTable64+(63-xxx));
15
    SET_PWM(nR,nG,nB,nW);
16
  }
17
18
  if(xxx>=63) xxx=0;
19
}


@Axel:

Ich habe nichts gemessen, sondern einfach die Augen aufgemacht und eine 
angeschlossene LED beobachtet.

LG

von Axel S. (a-za-z0-9)


Lesenswert?

Peter K. schrieb:

> Also folgendes habe ich in der ISR(in der SoftPWM) geändert:

<schnipp>

Das ist nicht clever. Die ISR wird dadurch deutlich länger und die 
Makros zu Beginn des Codes stimmen nicht mehr. Wenn ich das richtig 
überschaue, ist die magische Konstante 111 die Anzahl Takte, die die ISR 
lang ist. Wenn du den Code der ISR änderst, mußt du das anpassen.

> Und dies ist die Test-Funktion, einmal in ISR(TIMER2_OVF_vect) und in
> der main() aufgerufen:
1
> void PowerFading(uint8_t nX) // simple Fading-Schleife
2
> {
3
>   uint16_t nR,nG,nB,nW;
4
> 
5
>   nG = nB = nW = 0;
6
> 
7
>   if(xxx<=63)
8
>   {
9
>     nR = pgm_read_word(lookupTable64+xxx);
10
>     SET_PWM(nR,nG,nB,nW);
11
>   }
12
>   else
13
>   {
14
>     nR = pgm_read_word(lookupTable64+(63-xxx));
15
>     SET_PWM(nR,nG,nB,nW);
16
>   }
17
> 
18
>   if(xxx>=63) xxx=0;
19
> }

Entschuldige bitte die harten Worte, aber das ist Bullsh*t. Der 
Parameter nX wird nicht verwendet, aber dafür eine unbekannte Variable 
xxx. Der else-Zweig kann beliebig außerhalb der Tabelle lesen (schau 
einfach mal, was für xxx=100 passiert) etc. pp. Wenn dein Compiler eine 
Faust machen könnte, dann hättest du jetzt eine blutige Nase ;)

Wir kennen auch SET_PWM() nicht. Und wir wissen auch nicht, wie du 
diese Funktion aus main() aufrufst (sicher mit irgendeiner Art von 
delay) oder wie du Timer2 eingestellt hast.

Nicht zu vergessen, daß Variablen, die innerhalb einer ISR verändert 
werden, als volatile deklariert sein müssen.

> @Axel:
>
> Ich habe nichts gemessen, sondern einfach die Augen aufgemacht und eine
> angeschlossene LED beobachtet.

Und daraus schließt du auf die Ausführungsgeschwindigkeit einer 
Funktion? Das einzige was du beobachtest, ist wie oft pro Sekunde die 
Funktion zum Ändern der PWM-Werte ausgeführt wird. Und nix anderes.


XL

von Peter K. (Gast)


Lesenswert?

Hi,

hab's auch grad erst gesehen. Ich hatte die Test-Funktion noch ein paar 
mal danach geändert und hab wohl einen älteren Speicherpunkt erwischt.
Vergiss dann einfach diese Funktion - alles andere siehst du, wenn du 
fit bist, etwas weiter oben. Dort ist eine dreizeilige Schleifenfunktion 
beschrieben, die auch mit xxx arbeitet.

Was SET_PWM() macht, steht auch schon weiter oben:

void SET_PWM(uint16_t data1,uint16_t data2,uint16_t data3,uint16_t 
data4)
{
  pwm_setting[0] = data1;
  pwm_setting[1] = data2;
  pwm_setting[2] = data3;
  pwm_setting[3] = data4;
  pwm_update();
}

Desweiteren deklariere ich in meinem Projekt keine Variablen in einer 
ISR, dass macht ausschließlich IHR.^^

LG

von Peter K. (Gast)


Lesenswert?

Ps.:

Die besagte ISR braucht etwas über 70 Takte. Umgeschrieben 2-6 weniger.
Warum ist dies denn so schlimm?

Lg

von Falk B. (falk)


Lesenswert?

@  Peter K. (Gast)

>Die besagte ISR braucht etwas über 70 Takte. Umgeschrieben 2-6 weniger.
>Warum ist dies denn so schlimm?

Schlimm ist deine Ignoranz. Poste VOLLSTÄNDIGEN Code.

MfG
Falk

von Peter K. (Gast)


Lesenswert?

Hallo Falk,

der Code war schon vollständig gepostet.
1
ISR(TIMER2_OVF_vect)
2
{
3
  TCNT2 = (uint8_t)(int16_t)-((F_CPU/(F_CPU/16000000)) / 1024 * 10e-3 + 0.5);
4
  xxx++;
5
}
6
7
uint16_t lookupTable64[64] PROGMEM=
8
{
9
  0, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 6, 6, 7, 8, 9, 10, 11, 12, 13, 15, 17, 19, 21, 23, 26,
10
  29, 32, 36, 40, 44, 49, 55, 61, 68, 76, 85, 94, 105, 117, 131, 146, 162, 181, 202, 225, 250, 279,
11
  311, 346, 386, 430, 479, 534, 595, 663, 739, 824, 918, 1023
12
};
13
14
15
main()
16
{
17
SoftPWM_INIT();
18
19
// Konfiguration von 8-bit Timer/Counter2
20
TCCR2B = (1<<CS22)|(1<<CS20); // Vorteiler:1024
21
TIMSK2 |= (1<<TOIE2);         // Aktivierung des Timer2-Overflow-Interrupts
22
23
while(1)
24
{
25
xxx++; // für den Vergleich hab ich dieses Inkrement in TIMER2_OVF_vect verschoben
26
SET_PWM(pgm_read_word(lookupTable64+xxx),0,0,0);
27
if(xxx>=63) xxx=0;
28
}}

und hier die SoftPWM:
1
/********************************************************************************************************
2
 SoftPWM Version3 von http://www.mikrocontroller.net/articles/Soft-PWM
3
********************************************************************************************************/
4
#ifndef SOFTPWM_H
5
#define SOFTPWM_H
6
/*******************************************************************************************************/
7
#define F_PWM      150L  // PWM-Frequenz in Hz
8
#define PWM_PRESCALER  8    // Vorteiler für den Timer
9
#define PWM_STEPS    1024  // PWM-Schritte pro Zyklus(1..256,1023,etc.)
10
#define PWM_CHANNELS  4    // Anzahl der PWM-Kanäle
11
/*******************************************************************************************************/
12
// Definition von Datenrichtungsregister, Port und auch Pin für den jeweiligen PWM-Kanal
13
#define PWM1_DDR  DDRD
14
#define PWM1_PORT  PORTD
15
#define PWM1    PD6
16
17
#define PWM2_DDR  DDRD
18
#define PWM2_PORT  PORTD
19
#define PWM2    PD5
20
21
#define PWM3_DDR  DDRD
22
#define PWM3_PORT  PORTD
23
#define PWM3    PD3
24
25
#define PWM4_DDR  DDRB
26
#define PWM4_PORT  PORTB
27
#define PWM4    PB1
28
/*******************************************************************************************************/
29
#define T_PWM (F_CPU/(PWM_PRESCALER*F_PWM*PWM_STEPS)) // Systemtakte pro PWM-Takt
30
31
#if((T_PWM*PWM_PRESCALER) < (111 + 5))
32
  #error T_PWM zu klein! F_CPU muss vergrössert, oder F_PWM bzw. PWM_STEPS verkleinert werden.
33
#endif
34
35
#if((T_PWM*PWM_STEPS) > 65535)
36
  #error Periodendauer der PWM zu gross! F_PWM oder PWM_PRESCALER muß erhöht werden.   
37
#endif
38
/*******************************************************************************************************/
39
// Einbindung von Standardbibliotheken...
40
#include <stdint.h>
41
#include <string.h>
42
#include <avr/io.h>
43
#include <avr/interrupt.h>
44
/*******************************************************************************************************/
45
// Deklaration globaler Variablen
46
uint16_t pwm_timing[PWM_CHANNELS+1];      // Zeitdifferenzen der PWM-Werte
47
uint16_t pwm_timing_tmp[PWM_CHANNELS+1];
48
49
uint8_t pwm_mask[PWM_CHANNELS+1];        // Bitmaske für PWM-Bits, welche gelöscht werden sollen
50
uint8_t pwm_mask_tmp[PWM_CHANNELS+1];      // Änderung auf uint16_t oder uint32_t für mehr Kanäle
51
52
uint16_t pwm_setting[PWM_CHANNELS];        // Einstellungen für die einzelnen PWM-Kanäle
53
uint16_t pwm_setting_tmp[PWM_CHANNELS+1];    // Einstellungen der PWM-Werte,
54
                        // Änderung auf uint16_t für mehr als 8-bit Auflösung
55
56
uint8_t pwm_cnt_max = 1;            // Zählergrenze(Initialisierung mit 1 ist wichtig!)
57
volatile uint8_t pwm_sync;            // Hilfsvariable für 'Update jetzt möglich'
58
59
// Pointer für wechselseitigen Datenzugriff
60
uint16_t *isr_ptr_time  = pwm_timing;
61
uint16_t *main_ptr_time = pwm_timing_tmp;
62
63
uint8_t *isr_ptr_mask  = pwm_mask;        // Bitmasken für die PWM-Kanäle
64
uint8_t *main_ptr_mask = pwm_mask_tmp;      // Änderung auf uint16_t oder uint32_t für mehr Kanäle
65
/*******************************************************************************************************/
66
// Diese Routine MUß in einem Unterprogramm erfolgen, um eine Zwischenspeicherung durch den Compiler
67
// zu verhindern!
68
void tausche_zeiger(void)
69
{
70
  uint16_t *tmp_ptr16;
71
  uint8_t *tmp_ptr8;               // Änderung auf uint16_t oder uint32_t für mehr Kanäle
72
73
  tmp_ptr16 = isr_ptr_time;
74
  isr_ptr_time = main_ptr_time;
75
  main_ptr_time = tmp_ptr16;
76
  tmp_ptr8 = isr_ptr_mask;
77
  isr_ptr_mask = main_ptr_mask;
78
  main_ptr_mask = tmp_ptr8;
79
}
80
/*******************************************************************************************************/
81
// Berechnung der neuen Werte für die Interruptroutine aus den PWM-Einstellungen 
82
void pwm_update(void)
83
{
84
  uint8_t i, j, k;
85
  uint8_t m1, m2, tmp_mask; // Änderung auf uint16_t oder uint32_t für mehr Kanäle    
86
  uint16_t min, tmp_set;    // Änderung auf uint16_t für mehr als 8-bit Auflösung
87
88
  // Berechnung und Generierung der Bitmasken für den PWM-Start
89
  m1 = 1;
90
  m2 = 0;
91
  for(i=1; i<=(PWM_CHANNELS); i++)
92
  {
93
    main_ptr_mask[i] = ~m1;          // Maske zum Löschen der PWM-Ausgänge
94
    pwm_setting_tmp[i] = pwm_setting[i-1];
95
    if(pwm_setting_tmp[i] != 0) m2 |= m1;  // Maske zum Setzen der I/Os am PWM-Start
96
    m1 <<= 1;
97
  }
98
  main_ptr_mask[0] = m2;            // PWM-Startdaten 
99
100
  // Sortierung der PWM-Settings
101
  for(i=1; i<=PWM_CHANNELS; i++)
102
  {
103
    min = PWM_STEPS-1;
104
    k = i;
105
    for(j=i; j<=PWM_CHANNELS; j++)
106
    {
107
      if(pwm_setting_tmp[j] < min)
108
      {
109
        k = j;              // Indexing und PWM-Setting merken
110
        min = pwm_setting_tmp[j];
111
      }
112
    }
113
    if(k != i)
114
    {
115
      // Tauschen des ermittelten Minimums mit aktueller Sortiertstelle
116
      tmp_set = pwm_setting_tmp[k];
117
      pwm_setting_tmp[k] = pwm_setting_tmp[i];
118
      pwm_setting_tmp[i] = tmp_set;
119
      tmp_mask = main_ptr_mask[k];
120
      main_ptr_mask[k] = main_ptr_mask[i];
121
      main_ptr_mask[i] = tmp_mask;
122
    }
123
  }
124
125
  // Vereinigung gleicher PWM-Werte und, falls vorhanden, den PWM-Wert 0 löschen
126
  k = PWM_CHANNELS; // PWM_CHANNELS Datensätze
127
  i = 1;        // Startindex
128
129
  while(k>i)
130
  {
131
    while(((pwm_setting_tmp[i] == pwm_setting_tmp[i+1]) || (pwm_setting_tmp[i] == 0)) && (k>i))
132
    {
133
      // Vereinigung von gleichen aufeinanderfolgenden Werten(oder PWM-Wert ist 0)
134
      if(pwm_setting_tmp[i] != 0)
135
        main_ptr_mask[i+1] &= main_ptr_mask[i]; // Vereinigung der Masken
136
137
      // Datensatz entfernen und alle Nachfolger eine Stufe hochschieben
138
      for(j=i; j<k; j++)
139
      {
140
        pwm_setting_tmp[j] = pwm_setting_tmp[j+1];
141
        main_ptr_mask[j] = main_ptr_mask[j+1];
142
      }
143
      k--;
144
    }
145
    i++;
146
  }
147
148
  // Extrabehandlung des letzten Datensatzes:
149
  // Vergleich mit dem Nachfolger nicht möglich, deshalb nur löschen
150
  // (gilt nur im Sonderfall, wenn alle Kanäle 0 sind)
151
  if(pwm_setting_tmp[i] == 0) k--;
152
153
  // Berechnung der Zeitdifferenzen
154
  if(k == 0) // ... wenn alle Kanäle auf 0 sind(Sonderfall)
155
  {
156
    main_ptr_time[0] = (uint16_t)T_PWM*PWM_STEPS/2;
157
    main_ptr_time[1] = (uint16_t)T_PWM*PWM_STEPS/2;
158
    k=1;
159
  }
160
  else
161
  {
162
    i = k;
163
    main_ptr_time[i] = (uint16_t)T_PWM*(PWM_STEPS - pwm_setting_tmp[i]);
164
    tmp_set = pwm_setting_tmp[i];
165
    i--;
166
    for (; i>0; i--)
167
    {
168
            main_ptr_time[i] = (uint16_t)T_PWM*(tmp_set - pwm_setting_tmp[i]);
169
      tmp_set = pwm_setting_tmp[i];
170
    }
171
    main_ptr_time[0] = (uint16_t)T_PWM*tmp_set;
172
  }
173
174
  // Warten auf Sync
175
  pwm_sync = 0; // Sync wird im Interrupt gesetzt
176
  while(pwm_sync == 0);
177
178
  // Zeiger tauschen
179
  cli();
180
  tausche_zeiger();
181
  pwm_cnt_max = k;
182
183
  sei();
184
}
185
/*******************************************************************************************************/
186
// Initialisierung der SoftPWM
187
void SoftPWM_INIT(void)
188
{
189
  // Konfiguration der I/Os als Ausgänge
190
  PWM1_DDR |= (1<<PWM1);
191
  PWM2_DDR |= (1<<PWM2);
192
  PWM3_DDR |= (1<<PWM3);
193
  PWM4_DDR |= (1<<PWM4);
194
195
  // Konfiguration von 16-bit Timer/Counter1 für die PWM-Generierung
196
  TCCR1B = (1<<CS11);     // Vorteiler:8
197
  TIMSK1 |= (1<<OCIE1A); // Aktivierung des Timer1-Output-Compare-A Interrupts
198
199
  sei();           // Aktivierung der Interrupts auf globaler Ebene
200
}
201
/*******************************************************************************************************/
202
// Übergabe neuer PWM-Werte an die Hauptroutine
203
void SET_PWM(uint16_t data1,uint16_t data2,uint16_t data3,uint16_t data4)
204
{
205
  pwm_setting[0] = data1;
206
  pwm_setting[1] = data2;
207
  pwm_setting[2] = data3;
208
  pwm_setting[3] = data4;
209
    pwm_update();
210
}
211
/*******************************************************************************************************/
212
// Auswertung des Timer1-Output-Compare-A-Interrupts
213
ISR(TIMER1_COMPA_vect)
214
{
215
  static uint16_t pwm_cnt; // Änderung auf uint16_t für mehr als 8-bit Auflösung
216
  uint8_t tmp;       // Änderung auf uint16_t oder uint32_t für mehr Kanäle
217
218
  OCR1A += isr_ptr_time[pwm_cnt];
219
  tmp    = isr_ptr_mask[pwm_cnt];
220
221
  if(pwm_cnt == 0)
222
  {
223
    // Setzen der Portbits zu Beginn der PWM
224
    if(tmp & 0b00000001) PWM1_PORT |= (1<<PWM1);
225
    if(tmp & 0b00000010) PWM2_PORT |= (1<<PWM2);
226
    if(tmp & 0b00000100) PWM3_PORT |= (1<<PWM3);
227
    if(tmp & 0b00001000) PWM4_PORT |= (1<<PWM4);
228
229
    pwm_cnt++;
230
  }
231
  else
232
  {
233
    // Löschen der Portbits
234
    if((tmp & 0b00000001) == 0) PWM1_PORT &= ~(1<<PWM1);
235
    if((tmp & 0b00000010) == 0) PWM2_PORT &= ~(1<<PWM2);
236
    if((tmp & 0b00000100) == 0) PWM3_PORT &= ~(1<<PWM3);
237
    if((tmp & 0b00001000) == 0) PWM4_PORT &= ~(1<<PWM4);
238
239
    if(pwm_cnt == pwm_cnt_max)
240
    {
241
      pwm_sync = 1; // 'Update jetzt möglich'
242
      pwm_cnt  = 0;
243
    }
244
    else pwm_cnt++;
245
  }
246
}
247
/*******************************************************************************************************/
248
#endif
249
/*******************************************************************************************************/

lg

von Axel S. (a-za-z0-9)


Lesenswert?

Peter K. schrieb:

>Die besagte ISR braucht etwas über 70 Takte. Umgeschrieben 2-6 weniger.

Wohl kaum. Wenn du aus einem simplen
1
    PWM_PORT = tmp;

ein
1
    if(tmp & 0b00000001) PWM1_PORT |= (1<<PWM1);
2
    if(tmp & 0b00000010) PWM2_PORT |= (1<<PWM2);
3
    if(tmp & 0b00000100) PWM3_PORT |= (1<<PWM3);
4
    if(tmp & 0b00001000) PWM4_PORT |= (1<<PWM4);

machst, dann wird der Code nicht kürzer, sondern länger.

>Warum ist dies denn so schlimm?

Weil - wenn die ISR zu lange dauert - der nächste Interrupt verpaßt 
wird. Bei genauerer Betrachtung des Codes ist die magische Grenze von 
111 nicht die Laufzeit der ISR alleine, sondern die Summe der Laufzeiten 
der ISR und der Zeiger-Tausch-Funktion. Bzw. ganz genau die Zeit die 
zwischen dem Timer1-Interrupt für die letzte PWM-Phase bis zum sei() in 
pwm_update() vergeht, wenn wir annehmen, daß pwm_update() in der 
while(pwm_sync == 0); Schleife hängt.

> der Code war schon vollständig gepostet.

Nein, war er nicht. main() fehlte z.B.
1
> ISR(TIMER2_OVF_vect)
2
> {
3
>   TCNT2 = (uint8_t)(int16_t)-((F_CPU/(F_CPU/16000000)) / 1024 * 10e-3 + 0.5);

Kannst du uns diese Zeile mal erläutern? Und wie hast du eigentlich 
F_CPU definiert? Ich komm da im Leben nicht auf 10ms.

[c]
> while(1)
> {
> xxx++; // für den Vergleich hab ich dieses Inkrement in TIMER2_OVF_vect 
verschoben
> SET_PWM(pgm_read_word(lookupTable64+xxx),0,0,0);
> if(xxx>=63) xxx=0;
> }}
[c]

Dieser Teil fehlte bisher.

Ein wesentlicher Punkt bei der ganzen Sache ist, daß PWM_UPDATE() nur 
einmal je PWM-Zyklus laufen kann, weil die Funktion darauf wartet, daß 
die PWM-ISR pwm_sync auf 1 setzt. Das passiert genau einmal im 
PWM-Zyklus.

Die Endlos-Schleife in main() läuft also ziemlich genau 150 mal pro 
Sekunde durch. In der Originalversion wird die LED dabei jedesmal eine 
Stufe heller gedimmt, was zu einem Blinken mit 150Hz/63 ~= 2.4Hz führt.

Die Timer2 ISR verändert xxx nun asynchron. Mit welcher Frequenz, hängt 
von F_CPU ab (was du uns nicht sagst). Auf jeden Fall kann sich die 
Variable zwischen aufeinanderfolgenen Aufrufen von PWM_UPDATE() gleich 
mehrfach ändern, was vermutlich nicht ist, was du willst. Dafür kannst 
du dir aber häßliche Aliasingeffekte einhandeln.

Ach ja, die Deklaration von xxx fehlt uns auch immer noch. Willst du uns 
veralbern? Wenn ich nicht schon so viel getippt hätte, würde ich jetzt 
glatt abbrechen...

Schließlich noch:

(du schriebst)
>> Könnte mir aber eventuell jemand erklären, warum die Funktion
>> pwm_update(), in der main() aufgerufen, 100mal langsamer abgearbeitet
>> wird, als wenn ich sie z.B. mittels eines zweiten Timers alle 10ms
>> in dessen ISR aufrufe?

Das geht schon mal gar nicht. Innerhalb einer ISR sind Interrupts 
gesperrt (es sei denn, man enabled sie explizit). Da pwm_update() aber 
auf eine volatile Variable wartet, die von einer anderen ISR gesetzt 
wird, wird das effektiv eine Endlosschleife.


XL

von Peter K. (Gast)


Lesenswert?

Hallo Axel,

danke und SORRY. Ich bin noch Anfänger und scheine das mit den 
Interrupts irgendwie nur teilweise zu verstehen.

Ich hab' bislang knapp 10 Dateien, über die meine Deklarationen und 
Funktionen verstreut sind und wollte deshalb (in meiner Ansicht) 
unnötiges Kopieren vermeiden. Ich dachte aber zudem auch, dass meine 
obigen Postings verständlich waren. Das es eine main() gibt, ist ja, 
dachte ich, klar und wie die obigen Funktionen dort eingebunden sind, 
hatte ich kurz aber deutlich erklärt. Nochmal sorry.

Diese Zeile:
TCNT2 = (uint8_t)(int16_t)-((F_CPU/(F_CPU/16000000)) / 1024 * 10e-3 + 
0.5); ist aus Peter's Tasterentprellung und die mittleren Klammern 
ergaben 16MHz. Ich hatte das irgendwann mal auf 20MHz umgebaut. In 
seiner kurzen Kommentierung wurden 10ms erwähnt, genaueres ist mir aber 
bislang unbekannt.

Diese Interrupts scheinen mir wirklich zuzusetzen.:(
Hab' ich das dann richtig verstanden, dass die main() allgemein nur 
150mal pro Sekunde abgearbeitet wird, weil die Interruptroutine so lange 
braucht bzw. wartet?
Beeinflussen sich dann eigentlich auch die verschiedenen Timer-ISRs 
untereinander?

Das einzige, was ich bräuchte, ist irgendein Signal mit 500 bzw. 1000Hz, 
um die PWM auch zügig zu aktualisieren. Aber seit diesen Interrupts 
versteh ich irgendwie gar nix mehr.

Achso,... und sorry, ich hatte aus dem Originalcode versehentlich ein 
volatile gelöscht - hab' ich leider gerad erst bemerkt.

LG

von Axel S. (a-za-z0-9)


Lesenswert?

Hallo Peter,

Peter K. schrieb:

> danke und SORRY. Ich bin noch Anfänger und scheine das mit den
> Interrupts irgendwie nur teilweise zu verstehen.

Dann lies. Und übe.

> Diese Zeile:
> TCNT2 = (uint8_t)(int16_t)-((F_CPU/(F_CPU/16000000)) / 1024 * 10e-3 +
> 0.5); ist aus Peter's Tasterentprellung und die mittleren Klammern
> ergaben 16MHz.

Dann hast du irgendwas falsch kopiert. Schon bei
1
(F_CPU/(F_CPU/16000000))
rollen sich einem die Fußnägel hoch. Bei F_CPU < 16MHz kriegt man gar 
"division by zero", weil der Präprozessor das als INT behandelt.

> Hab' ich das dann richtig verstanden, dass die main() allgemein nur
> 150mal pro Sekunde abgearbeitet wird, weil die Interruptroutine so lange
> braucht bzw. wartet?

Nicht die Interruptroutine. In pwm_setup() findest du diese 2 Zeilen:
1
  // Warten auf Sync
2
  pwm_sync = 0; // Sync wird im Interrupt gesetzt
3
  while(pwm_sync == 0);

Hier wird die Variable also auf 0 gesetzt und dann in einer (leeren) 
Schleife so lange gewartet, bis sie != 0 ist. Auf einen Wert != 0 wird 
diese Variable nur an einer Stelle gesetzt:
1
ISR(TIMER1_COMPA_vect)
2
{
3
    ...
4
    if(pwm_cnt == pwm_cnt_max)
5
    {
6
      pwm_sync = 1; // 'Update jetzt möglich'
7
      pwm_cnt  = 0;
8
    }
9
    ...
10
}

Diese ISR wird in jeder PWM-Periode (die du auf (1/150)s definiert hast) 
mindestens 1 mal (alle LEDs aus) und maximal 5 mal (alle 4 LEDs mit 
verschiedener Helligkeit an) aufgerufen. Im letzten Aufruf pro 
komplettem Zyklus wird die Variable auf 1 gesetzt.

Im Rahmen der Rechengenauigkeit und des glatten Aufgehens von 150Hz, 
1024 PWM-Stufen und Vorteiler 8 in F_CPU passiert das also ca. 150 mal 
in der Sekunde. Wenn du pwm_setup() irgendwo aufrufst, kommt das optimal 
sofort (na gut, fast sofort) und pessimal nach (1/150)s zurück.

> Beeinflussen sich dann eigentlich auch die verschiedenen Timer-ISRs
> untereinander?

Klar doch. Es kann immer nur eine ISR zu einer Zeit laufen. Nur wenn du 
in einer ISR
1
 sei()
machst, kann ein anderer Interrupt diese ISR unterbrechen. Andererseits 
ist es eine goldene Regel, eine ISR immer so kurz wie möglich zu halten, 
so daß das nur selten nötig ist. Insbesondere darf eine ISR sich 
niemals selbst unterbrechen. Sonst hast du ganz schnell einen 
Stacküberlauf.

> Das einzige, was ich bräuchte, ist irgendein Signal mit 500 bzw. 1000Hz,
> um die PWM auch zügig zu aktualisieren. Aber seit diesen Interrupts
> versteh ich irgendwie gar nix mehr.

Aktualisiert wird die PWM bestenfalls einmal alle (1/150) Sekunden. 
Durch pwm_setup(). Öfter geht auch nicht. Wie willst du denn die 
Helligkeit einer LED schneller ändern als nach einem PWM-Zyklus?

Du kannst zur Synchronisation die pwm_setup() Funktion verwenden, so wie 
das in dem Wiki-Artikel vorgesehen ist.

In meiner Soft-PWM Implementierung mache ich das anders: mit einer 
Callback-Funktion und einer Zählervariabe (ich habe sie "tick" genannt). 
Immer wenn tick==0 ruft main() die Aktualisierungsfunktion auf, die 
einen neuen Helligkeitswert setzen kann. Und auch tick entsprechend 
setzt. Denn normalerweise muß man die Helligkeit nicht nach jedem 
PWM-Zyklus verändern.

Meinen Soft-PWM Code findest du hier: 
Beitrag "noch ein AVR Moodlight"


XL

von Peter K. (Gast)


Lesenswert?

Hallo Axel,

vielen Dank für die vielen Infos und jepp, werd' ich auf jeden Fall 
machen.^^

Hmmmmm... deine SoftPWM gefällt mir wirklich sehr gut!!!(und 
funktionierte sogar auf Anhieb);-)
Der Hauptgrund dafür sind die 6 zusätzlichen Bits, da die 10Bit-Stufen 
bei den untersten 4 Werten nicht mehr so ganz mitkommen.

Könnte man deine PWM denn auch auf 4 LEDs umbauen? Ich bräuchte eine 
4.LED-Farbe auf Port.B1(also 4 LEDs, verteilt auf 2 Ports)?
Könnte man das Setzen des Ausgangsports dann event. auch wie in meinem 
Umbau der 10bit-PWM machen, um nicht benutzte Portbits so zu belassen 
wie sie sind?

Liebe Grüße

von Axel S. (a-za-z0-9)


Lesenswert?

Hallo Peter,

Peter K. schrieb:

> Hmmmmm... deine SoftPWM gefällt mir wirklich sehr gut!!!(und
> funktionierte sogar auf Anhieb);-)

Schön.

> Der Hauptgrund dafür sind die 6 zusätzlichen Bits, da die 10Bit-Stufen
> bei den untersten 4 Werten nicht mehr so ganz mitkommen.

Naja, eigentlich sind es gar nicht so viel mehr reale Bits. Aber in der 
Tat habe ich recht lange mit verschiedenen Faktoren für die Kennlinie 
experimentiert. Und ein bisschen Luft ist auch noch; ich wollte mich 
nicht davon abhängig machen, daß der Compiler die ISR immer so kurz wie 
möglich codiert.

> Könnte man deine PWM denn auch auf 4 LEDs umbauen? Ich bräuchte eine
> 4.LED-Farbe auf Port.B1(also 4 LEDs, verteilt auf 2 Ports)?

Es wäre viel einfacher, wenn das 4 Bits an einem Port wären. Daß die ISR 
so knackig kurz ist, beruht auf 2 Punkten:

1. alle LEDs an einem Port
2. die anderen Portleitungen haben einen festen Pegel

> Könnte man das Setzen des Ausgangsports dann event. auch wie in meinem
> Umbau der 10bit-PWM machen, um nicht benutzte Portbits so zu belassen
> wie sie sind?

Dafür würde ich mal mit spitzem Stift die Länge der ISR nachrechnen. 
Weit besser wäre es aber, die Bitmasken vorzuberechnen und mehrere 
pwm_data[] Arrays zu haben (1 je genutztem Port). Dann muß die ISR nur 
noch pro Port die passenden Bits rausmaskieren und setzen.

An sich hast du viel Luft, wenn dein µC mit 20MHz läuft. Leider hat 
Timer 1 aber keinen Prescaler 2:1 oder 4:1, sondern nur 1:1 oder 8:1. 
Ersteres gibt 300Hz PWM-Frequenz, letzteres nur magere 38Hz. Vermutlich 
würde ich das unter diesen Bedingungen anders schreiben. Z.B. mit 
Prescaler 8:1 und entweder 14 oder 15 Bit nominaler Auflösung 
(entsprechend 75 oder 150Hz). Die Timing-Anforderungen sind bei dem 
höheren Takt ja entspannter, da kann man die zusätzliche 16-Bit Addition 
in der ISR verschmerzen.


XL

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.