mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik PWM Flackern


Autor: Marcel Holle (multiholle)
Datum:

Bewertung
0 lesenswert
nicht 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.
#define F_CPU          20000000UL
#define F_PWM         150L               // PWM-Frequenz in Hz
#define PWM_PRESCALER 8                  // Vorteiler für den Timer
#define PWM_STEPS     1024               // PWM-Schritte pro Zyklus(1..256)
#define PWM_PORT      PORTD              // Port für PWM
#define PWM_DDR       DDRD               // Datenrichtungsregister für PWM
#define PWM_CHANNELS  8                  // Anzahl der PWM-Kanäle
 
// ab hier nichts ändern, wird alles berechnet
 
#define T_PWM (F_CPU/(PWM_PRESCALER*F_PWM*PWM_STEPS)) // Systemtakte pro PWM-Takt
//#define T_PWM 1   //TEST
 
#if ((T_PWM*PWM_PRESCALER)<(111+5))
    #error T_PWM zu klein, F_CPU muss vergrösst werden oder F_PWM oder PWM_STEPS verkleinert werden
#endif
 
#if ((T_PWM*PWM_STEPS)>65535)
    #error Periodendauer der PWM zu gross! F_PWM oder PWM_PRESCALER erhöhen.   
#endif
// includes
 
#include <stdint.h>
#include <string.h>
#include <avr/io.h>
#include <avr/interrupt.h>
 
uint16_t pwm_timing[PWM_CHANNELS+1];          // Zeitdifferenzen der PWM Werte
uint16_t pwm_timing_tmp[PWM_CHANNELS+1];      
uint16_t  pwm_mask[PWM_CHANNELS+1];            // Bitmaske für PWM Bits, welche gelöscht werden sollen
uint16_t  pwm_mask_tmp[PWM_CHANNELS+1];        
uint16_t  pwm_setting[PWM_CHANNELS];           // Einstellungen für die einzelnen PWM-Kanäle
uint16_t  pwm_setting_tmp[PWM_CHANNELS+1];     // Einstellungen der PWM Werte, sortiert
 
volatile uint16_t pwm_cnt_max=1;               // Zählergrenze, Initialisierung mit 1 ist wichtig!
volatile uint16_t pwm_sync;                    // Update jetzt möglich
 
// Pointer für wechselseitigen Datenzugriff
 
uint16_t * isr_ptr_time  = pwm_timing;
uint16_t * main_ptr_time = pwm_timing_tmp;
uint16_t *  isr_ptr_mask  = pwm_mask;
uint16_t *  main_ptr_mask = pwm_mask_tmp;
 
// Zeiger austauschen
// das muss in einem Unterprogramm erfolgen,
// um eine Zwischenspeicherung durch den Compiler zu verhindern
 
void tausche_zeiger(void) {
    uint16_t * tmp_ptr16;
    uint16_t * tmp_ptr8;
 
    tmp_ptr16 = isr_ptr_time;
    isr_ptr_time = main_ptr_time;
    main_ptr_time = tmp_ptr16;
    tmp_ptr8 = isr_ptr_mask;
    isr_ptr_mask = main_ptr_mask;
    main_ptr_mask = tmp_ptr8;
}
 
// PWM Update, berechnet aus den PWM Einstellungen
// die neuen Werte für die Interruptroutine
 
void pwm_update(void) {
    uint16_t i, j, k, min;
    uint16_t tmp;
 
    // PWM Maske für Start berechnen
    // gleichzeitig die Bitmasken generieren und PWM Werte kopieren
 
    tmp=0;
    j = 1;
    for(i=1; i<=(PWM_CHANNELS); i++) {
        main_ptr_mask[i]=~j;                        // Maske zum Löschen der PWM Ausgänge
        pwm_setting_tmp[i] = pwm_setting[i-1];
        if (pwm_setting_tmp[i]!=0) tmp |= j;        // Maske zum setzen der IOs am PWM Start
        j <<= 1;
    }
    main_ptr_mask[0]=tmp;                           // PWM Start Daten 
 
    // PWM settings sortieren; Einfügesortieren
 
    for(i=1; i<=PWM_CHANNELS; i++) {
        min=255;
        k=i;
        for(j=i; j<=PWM_CHANNELS; j++) {
            if (pwm_setting_tmp[j]<min) {
                k=j;                                // Index und PWM-setting merken
                min = pwm_setting_tmp[j];
            }
        }
        if (k!=i) {
            // ermitteltes Minimum mit aktueller Sortiertstelle tauschen
            tmp = pwm_setting_tmp[k];
            pwm_setting_tmp[k] = pwm_setting_tmp[i];
            pwm_setting_tmp[i] = tmp;
            tmp = main_ptr_mask[k];
            main_ptr_mask[k] = main_ptr_mask[i];
            main_ptr_mask[i] = tmp;
        }
    }
 
    // Gleiche PWM-Werte vereinigen, ebenso den PWM-Wert 0 löschen falls vorhanden
 
    k=PWM_CHANNELS;             // PWM_CHANNELS Datensätze
    i=1;                        // Startindex
 
    while(k>i) {
        while ( ((pwm_setting_tmp[i]==pwm_setting_tmp[i+1]) || (pwm_setting_tmp[i]==0))  && (k>i) ) {
 
            // aufeinanderfolgende Werte sind gleich und können vereinigt werden
            // oder PWM Wert ist Null
            if (pwm_setting_tmp[i]!=0)
                main_ptr_mask[i+1] &= main_ptr_mask[i];        // Masken vereinigen
 
            // Datensatz entfernen,
            // Nachfolger alle eine Stufe hochschieben
            for(j=i; j<k; j++) {
                pwm_setting_tmp[j] = pwm_setting_tmp[j+1];
                main_ptr_mask[j] = main_ptr_mask[j+1];
            }
            k--;
        }
        i++;
    }
    
    // letzten Datensatz extra behandeln
    // Vergleich mit dem Nachfolger nicht möglich, nur löschen
    // gilt nur im Sonderfall, wenn alle Kanäle 0 sind
    if (pwm_setting_tmp[i]==0) k--;
 
    // Zeitdifferenzen berechnen
    
    if (k==0) { // Sonderfall, wenn alle Kanäle 0 sind
        main_ptr_time[0]=(uint16_t)T_PWM*PWM_STEPS/2;
        main_ptr_time[1]=(uint16_t)T_PWM*PWM_STEPS/2;
        k=1;
    }
    else {
        i=k;
        main_ptr_time[i]=(uint16_t)T_PWM*(PWM_STEPS-pwm_setting_tmp[i]);
        j=pwm_setting_tmp[i];
        i--;
        for (; i>0; i--) {
            main_ptr_time[i]=(uint16_t)T_PWM*(j-pwm_setting_tmp[i]);
            j=pwm_setting_tmp[i];
        }
        main_ptr_time[0]=(uint16_t)T_PWM*j;
    }
 
    // auf Sync warten
 
    pwm_sync=0;             // Sync wird im Interrupt gesetzt
    while(pwm_sync==0);
 
    // Zeiger tauschen
    cli();
    tausche_zeiger();
    pwm_cnt_max = k;
    sei();
}
 
// Timer 1 Output COMPARE A Interrupt
 
ISR(TIMER1_COMPA_vect) {
    static uint16_t pwm_cnt;
    uint16_t tmp;
 
    OCR1A += isr_ptr_time[pwm_cnt];
    tmp    = isr_ptr_mask[pwm_cnt];
    
    if (pwm_cnt == 0) {
        PWM_PORT = tmp;                         // Ports setzen zu Begin der PWM
        pwm_cnt++;
    }
    else {
        PWM_PORT &= tmp;                        // Ports löschen
        if (pwm_cnt == pwm_cnt_max) {
            pwm_sync = 1;                       // Update jetzt möglich
            pwm_cnt  = 0;
        }
        else pwm_cnt++;
    }
}


int main(void) {
 
    // PWM Port einstellen
    
    PWM_DDR = 0xFF;         // Port als Ausgang
    
    // Timer 1 OCRA1, als variablen Timer nutzen
 
    TCCR1B = 2;             // Timer läuft mit Prescaler 8
    TIMSK1 |= (1<<OCIE1A);   // Interrupt freischalten
 
    sei();                  // Interrupts gloabl einschalten

    pwm_setting[3] = 372; 
    pwm_setting[6] = 1023; 
    pwm_setting[7] = 0; 
    pwm_update();
    while (1);
} 

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Marcel Holle (multiholle)
Datum:

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

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Marcel Holle (multiholle)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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.

Autor: Marcel Holle (multiholle)
Datum:

Bewertung
0 lesenswert
nicht 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...

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Marcel Holle (multiholle)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht 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.

  min = PWM_STEPS-1;

MfG
Falk

Autor: Marcel Holle (multiholle)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Vielen Dank! Funktioniert...

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@  Marcel Holle (multiholle)

>Vielen Dank! Funktioniert...

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

MFG
Falk

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.