Forum: Mikrocontroller und Digitale Elektronik PWM Flackern


von Marcel H. (multiholle)


Lesenswert?

Ich möchte auf meinen Atmega 168 Soft PWM nutzen. Den Code habe ich von 
hier übernommen: http://www.mikrocontroller.net/articles/Soft-PWM. 
PWM_STEPS habe ich auf 1024 gestellt. Das ganze funktioniert auch ganz 
gut. Allerdings bekomme ich bei der Einstellung (0, 0, 0, 372, 0, 0, 
1023, 0) ein Flackern bei LED 3. Es gibt noch mehr Einstellungen bei 
denen das auftritt. Was mache ich falsch?

Hier nochmal der gesammte Code. Ich habe nur PWM_STEPS geändert und die 
Main-Methode ergänzt.
1
#define F_CPU          20000000UL
2
#define F_PWM         150L               // PWM-Frequenz in Hz
3
#define PWM_PRESCALER 8                  // Vorteiler für den Timer
4
#define PWM_STEPS     1024               // PWM-Schritte pro Zyklus(1..256)
5
#define PWM_PORT      PORTD              // Port für PWM
6
#define PWM_DDR       DDRD               // Datenrichtungsregister für PWM
7
#define PWM_CHANNELS  8                  // Anzahl der PWM-Kanäle
8
 
9
// ab hier nichts ändern, wird alles berechnet
10
 
11
#define T_PWM (F_CPU/(PWM_PRESCALER*F_PWM*PWM_STEPS)) // Systemtakte pro PWM-Takt
12
//#define T_PWM 1   //TEST
13
 
14
#if ((T_PWM*PWM_PRESCALER)<(111+5))
15
    #error T_PWM zu klein, F_CPU muss vergrösst werden oder F_PWM oder PWM_STEPS verkleinert werden
16
#endif
17
 
18
#if ((T_PWM*PWM_STEPS)>65535)
19
    #error Periodendauer der PWM zu gross! F_PWM oder PWM_PRESCALER erhöhen.   
20
#endif
21
// includes
22
 
23
#include <stdint.h>
24
#include <string.h>
25
#include <avr/io.h>
26
#include <avr/interrupt.h>
27
 
28
uint16_t pwm_timing[PWM_CHANNELS+1];          // Zeitdifferenzen der PWM Werte
29
uint16_t pwm_timing_tmp[PWM_CHANNELS+1];      
30
uint16_t  pwm_mask[PWM_CHANNELS+1];            // Bitmaske für PWM Bits, welche gelöscht werden sollen
31
uint16_t  pwm_mask_tmp[PWM_CHANNELS+1];        
32
uint16_t  pwm_setting[PWM_CHANNELS];           // Einstellungen für die einzelnen PWM-Kanäle
33
uint16_t  pwm_setting_tmp[PWM_CHANNELS+1];     // Einstellungen der PWM Werte, sortiert
34
 
35
volatile uint16_t pwm_cnt_max=1;               // Zählergrenze, Initialisierung mit 1 ist wichtig!
36
volatile uint16_t pwm_sync;                    // Update jetzt möglich
37
 
38
// Pointer für wechselseitigen Datenzugriff
39
 
40
uint16_t * isr_ptr_time  = pwm_timing;
41
uint16_t * main_ptr_time = pwm_timing_tmp;
42
uint16_t *  isr_ptr_mask  = pwm_mask;
43
uint16_t *  main_ptr_mask = pwm_mask_tmp;
44
 
45
// Zeiger austauschen
46
// das muss in einem Unterprogramm erfolgen,
47
// um eine Zwischenspeicherung durch den Compiler zu verhindern
48
 
49
void tausche_zeiger(void) {
50
    uint16_t * tmp_ptr16;
51
    uint16_t * tmp_ptr8;
52
 
53
    tmp_ptr16 = isr_ptr_time;
54
    isr_ptr_time = main_ptr_time;
55
    main_ptr_time = tmp_ptr16;
56
    tmp_ptr8 = isr_ptr_mask;
57
    isr_ptr_mask = main_ptr_mask;
58
    main_ptr_mask = tmp_ptr8;
59
}
60
 
61
// PWM Update, berechnet aus den PWM Einstellungen
62
// die neuen Werte für die Interruptroutine
63
 
64
void pwm_update(void) {
65
    uint16_t i, j, k, min;
66
    uint16_t tmp;
67
 
68
    // PWM Maske für Start berechnen
69
    // gleichzeitig die Bitmasken generieren und PWM Werte kopieren
70
 
71
    tmp=0;
72
    j = 1;
73
    for(i=1; i<=(PWM_CHANNELS); i++) {
74
        main_ptr_mask[i]=~j;                        // Maske zum Löschen der PWM Ausgänge
75
        pwm_setting_tmp[i] = pwm_setting[i-1];
76
        if (pwm_setting_tmp[i]!=0) tmp |= j;        // Maske zum setzen der IOs am PWM Start
77
        j <<= 1;
78
    }
79
    main_ptr_mask[0]=tmp;                           // PWM Start Daten 
80
 
81
    // PWM settings sortieren; Einfügesortieren
82
 
83
    for(i=1; i<=PWM_CHANNELS; i++) {
84
        min=255;
85
        k=i;
86
        for(j=i; j<=PWM_CHANNELS; j++) {
87
            if (pwm_setting_tmp[j]<min) {
88
                k=j;                                // Index und PWM-setting merken
89
                min = pwm_setting_tmp[j];
90
            }
91
        }
92
        if (k!=i) {
93
            // ermitteltes Minimum mit aktueller Sortiertstelle tauschen
94
            tmp = pwm_setting_tmp[k];
95
            pwm_setting_tmp[k] = pwm_setting_tmp[i];
96
            pwm_setting_tmp[i] = tmp;
97
            tmp = main_ptr_mask[k];
98
            main_ptr_mask[k] = main_ptr_mask[i];
99
            main_ptr_mask[i] = tmp;
100
        }
101
    }
102
 
103
    // Gleiche PWM-Werte vereinigen, ebenso den PWM-Wert 0 löschen falls vorhanden
104
 
105
    k=PWM_CHANNELS;             // PWM_CHANNELS Datensätze
106
    i=1;                        // Startindex
107
 
108
    while(k>i) {
109
        while ( ((pwm_setting_tmp[i]==pwm_setting_tmp[i+1]) || (pwm_setting_tmp[i]==0))  && (k>i) ) {
110
 
111
            // aufeinanderfolgende Werte sind gleich und können vereinigt werden
112
            // oder PWM Wert ist Null
113
            if (pwm_setting_tmp[i]!=0)
114
                main_ptr_mask[i+1] &= main_ptr_mask[i];        // Masken vereinigen
115
 
116
            // Datensatz entfernen,
117
            // Nachfolger alle eine Stufe hochschieben
118
            for(j=i; j<k; j++) {
119
                pwm_setting_tmp[j] = pwm_setting_tmp[j+1];
120
                main_ptr_mask[j] = main_ptr_mask[j+1];
121
            }
122
            k--;
123
        }
124
        i++;
125
    }
126
    
127
    // letzten Datensatz extra behandeln
128
    // Vergleich mit dem Nachfolger nicht möglich, nur löschen
129
    // gilt nur im Sonderfall, wenn alle Kanäle 0 sind
130
    if (pwm_setting_tmp[i]==0) k--;
131
 
132
    // Zeitdifferenzen berechnen
133
    
134
    if (k==0) { // Sonderfall, wenn alle Kanäle 0 sind
135
        main_ptr_time[0]=(uint16_t)T_PWM*PWM_STEPS/2;
136
        main_ptr_time[1]=(uint16_t)T_PWM*PWM_STEPS/2;
137
        k=1;
138
    }
139
    else {
140
        i=k;
141
        main_ptr_time[i]=(uint16_t)T_PWM*(PWM_STEPS-pwm_setting_tmp[i]);
142
        j=pwm_setting_tmp[i];
143
        i--;
144
        for (; i>0; i--) {
145
            main_ptr_time[i]=(uint16_t)T_PWM*(j-pwm_setting_tmp[i]);
146
            j=pwm_setting_tmp[i];
147
        }
148
        main_ptr_time[0]=(uint16_t)T_PWM*j;
149
    }
150
 
151
    // auf Sync warten
152
 
153
    pwm_sync=0;             // Sync wird im Interrupt gesetzt
154
    while(pwm_sync==0);
155
 
156
    // Zeiger tauschen
157
    cli();
158
    tausche_zeiger();
159
    pwm_cnt_max = k;
160
    sei();
161
}
162
 
163
// Timer 1 Output COMPARE A Interrupt
164
 
165
ISR(TIMER1_COMPA_vect) {
166
    static uint16_t pwm_cnt;
167
    uint16_t tmp;
168
 
169
    OCR1A += isr_ptr_time[pwm_cnt];
170
    tmp    = isr_ptr_mask[pwm_cnt];
171
    
172
    if (pwm_cnt == 0) {
173
        PWM_PORT = tmp;                         // Ports setzen zu Begin der PWM
174
        pwm_cnt++;
175
    }
176
    else {
177
        PWM_PORT &= tmp;                        // Ports löschen
178
        if (pwm_cnt == pwm_cnt_max) {
179
            pwm_sync = 1;                       // Update jetzt möglich
180
            pwm_cnt  = 0;
181
        }
182
        else pwm_cnt++;
183
    }
184
}
185
186
187
int main(void) {
188
 
189
    // PWM Port einstellen
190
    
191
    PWM_DDR = 0xFF;         // Port als Ausgang
192
    
193
    // Timer 1 OCRA1, als variablen Timer nutzen
194
 
195
    TCCR1B = 2;             // Timer läuft mit Prescaler 8
196
    TIMSK1 |= (1<<OCIE1A);   // Interrupt freischalten
197
 
198
    sei();                  // Interrupts gloabl einschalten
199
200
    pwm_setting[3] = 372; 
201
    pwm_setting[6] = 1023; 
202
    pwm_setting[7] = 0; 
203
    pwm_update();
204
    while (1);
205
}

von Falk B. (falk)


Lesenswert?

@  Marcel Holle (multiholle)

>1023, 0) ein Flackern bei LED 3. Es gibt noch mehr Einstellungen bei
>denen das auftritt. Was mache ich falsch?

Möglicherweise hast du die AVR Fuses nicht richtig eigestellt und 
dein AVR läuft nicht mit dem Quarz mit 20 MHz sondern mit dem internen 
RC-Oszillator und damit maximal 8 MHz. Das reicht nicht für 10 Bit.

MFg
Falk

von Marcel H. (multiholle)


Lesenswert?

Ich habe folgendes eingestellt für den Takt: CKSEL3 = CKSEL2 = CKSEL1 = 
CKSEL0 = 0

von Falk B. (falk)


Lesenswert?

@  Marcel Holle (multiholle)

>Ich habe folgendes eingestellt für den Takt: CKSEL3 = CKSEL2 = CKSEL1 =
>CKSEL0 = 0

kann nicht sein, bei einem ATmega168 heisst das, externer Takt, nicht 
externer Quarz. Hast du einen Externen Taktgeber? Ausserdem hat dieser 
AVR eine CLKDIV8 Fuse, da wird der Takt intern durch 8 geteilt! Die muss 
auf 1 sein.

Und besorg dir ein gescheites Programmierwerkzeug, das die Fuses im 
Klartext direkt anzeigt, dann muss man nicht so sinnlos Rätselraten.

MFG
Falk

von Marcel H. (multiholle)


Angehängte Dateien:

Lesenswert?

So sind die Fuses gesetzt. Habe mich vertan, CKSEL steht auf External 
Crystal Oscillator. Getaktet wird der Atmega 168 somit mit dem 20 MHz 
externen Quarz.

von Marcel H. (multiholle)


Lesenswert?

Ich glaube kaum das die Taktfrequenz nicht richtig stimmt. Der µC ist ja 
Teil von meinem Rumpus Entwicklungsboard und wurde bereits geflasht 
geliefert.

Wie kann ich den Fehler etwas eingrenzen? Bei 256 Schritten tritt der 
Fehler nicht auf, nur bei 1024...

von Falk B. (falk)


Lesenswert?

@  Marcel Holle (multiholle)

>Wie kann ich den Fehler etwas eingrenzen? Bei 256 Schritten tritt der
>Fehler nicht auf, nur bei 1024...

Und bei 512?
Bei 700?
Bei 900?

Probier mal.

von Marcel H. (multiholle)


Lesenswert?

Das Problem tritt ab einer Schrittanzahl von 258 auf. Evtl. findet 
irgendwo ein Überlauf statt, der im Original mit 256 Schritten nicht 
passiert ist. Ideen?

von Falk B. (falk)


Lesenswert?

@  Marcel Holle (multiholle)

>Das Problem tritt ab einer Schrittanzahl von 258 auf. Evtl. findet
>irgendwo ein Überlauf statt, der im Original mit 256 Schritten nicht
>passiert ist. Ideen?

Ahhhh, Mist. In pwm_update ist noch eine feste Zahl drin, die aber 
variabel über ein define sein muss.

> min=255;

Mach da mal das draus.

1
  min = PWM_STEPS-1;

MfG
Falk

von Marcel H. (multiholle)


Lesenswert?

Vielen Dank! Funktioniert...

von Falk B. (falk)


Lesenswert?

@  Marcel Holle (multiholle)

>Vielen Dank! Funktioniert...

Schön, dann werd ich den Fehler mal im Artikel korrigieren.

MFG
Falk

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.