PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Warum funktioniert diese software PWM nicht?



Torrentula
09.02.2012, 18:10
Hallo RNler!

Ich habe es nun endlich mal geschafft ein Programm zu schreiben, welches die PWM per software erzeugen sollte. Timer 1 läuft mit 10kHz und die gewünschte PWM frequenz ist 1kHz. Ich habe eine 8bit auflösung gewählt.

In der ISR werden nur die Timer overflows gezählt und dann je nachdem ob der Comparewert erreicht ist werden die PWM-Steps um 1 erhöht. Wenn nun die PWM-Steps kleiner sind als der vorgebene Wert, wird der PORT auf High gesetzt, wenn nicht dann wird er auf Low gesetzt. Also alles so wie bei einer Hardware PWM auch.

Trotzdem messe ich nur ca. 4Hz und ein Tastverhältnis von 99%, obwohl der Vergleichswert 127, also 50% duty cycle, ist.



#ifndef F_CPU
#define F_CPU 16000000UL
#endif

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

#define F_CNT 10000
#define F_PWM 1000
#define PWM_RES 255
#define Prescaler 8

#define OCR1A_val ((F_CPU / F_CNT) / (2*Prescaler)) - 1

uint8_t PWM_setting[8];

volatile unsigned int pwm_steps = 0;
volatile unsigned int overflows = 0;

int main(void){

TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS11); // CTC mode prescaler 8
TIMSK1 |= (1<<OCIE1A); // compare match interrupt

OCR1A = OCR1A_val;

DDRC = 0xFF;

for(uint8_t i = 0; i < 8; i++){
PWM_setting[i] = 127; // 50% duty cycle
}

sei();

while(1){

if( pwm_steps < PWM_setting[0] ){
PORTC |= (1<<0);
}
else{
PORTC &= ~(1<<0);
}
}

return 0;
}

ISR(TIMER1_COMPA_vect){

if(overflows < (F_CNT / F_PWM)){
overflows++;
}
else{
pwm_steps++;
overflows = 0;
}

if(pwm_steps > PWM_RES){
pwm_steps = 0;
}
}


Habe schon mehere Anläufe genommen, jedoch weigerte es sich bis jetzt immer zu funktionieren :(

MfG

Torrentula

Besserwessi
09.02.2012, 18:36
Den genauen Fehler hab ich nicht gesehen, aber der Code ist ziemlich umständlich und unpraktisch. Wenn man software PWM schon im Interrupt macht, dann sollte man es auch ganz im Interrupt machen. Wozu noch die Extra Interrupts nur um noch einmal eine Teilung durch 10 hinzubekommen - den Interrupt braucht man nur, wenn sich wirklich etwas an PWM_Steps ändert. So wie es aussieht kommt das ja auch etwas hin, denn es braucht schon 256 Schritte für das 8 Bit PWM Signal entsprechend kommen da rund 1 kHz / 255 oder ca. 4 Hz raus.

Um ein 1 kHz PWM Signal mit 8 Bit Auflösung zu bekommen, braucht man schon 255 kHz als Interruptfrequenz, wenn man es mit Hochzählen per Hand machen will.

Torrentula
10.02.2012, 16:11
Ok also das mit den 255kHz leuchtet ein :)

Allerdings kann ich ja auch den Timer einfach hochzählen lassen und dann den einfach prüfen ob der Wert in TCNT1 kleiner oder größer des vorgegebenen Wertes ist. Habe hier den Code eben genau so umgebastelt. Die Frequenz stimmt, allerdings der duty cycle nicht :(

Laut Datenblatt ist der Top-Wert ja ICR1, folglich müsste ICR1 / 2 einen 50% duty cycle ergeben. Leider kommt dies nur bei ca. 5kHz hin :(



#ifndef F_CPU
#define F_CPU 16000000UL
#endif

#include <avr/io.h>
#include <util/delay.h>

#define F_PWM 1000
#define PWM_RES 255
#define Prescaler 8

#define ICR1_val ((F_CPU / F_PWM) / Prescaler) - 1

uint8_t PWM_setting[8];

int main(void){

TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS11); // CTC mode prescaler 8

ICR1 = (uint16_t)ICR1_val;

DDRC = 0xFF;

for(uint8_t i = 0; i < 8; i++){
PWM_setting[i] = ICR1_val / 2; // 50% duty cycle
}

while(1){

for(int i = 0; i < 8; i++){
if( TCNT1 < PWM_setting[i] ){
PORTC |= (1<<i);
}
else{
PORTC &= ~(1<<i);
}
}
}

return 0;
}

sternst
10.02.2012, 19:34
Laut Datenblatt ist der Top-Wert ja ICR1, folglich müsste ICR1 / 2 einen 50% duty cycle ergeben.Dazu müsste man aber auch diesen Wert in das Vergleichs-Array schreiben. Du schreibst dort aber "ICR1 + 1" rein.