PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : 6 fach 16 Software-PWM - Timer quälend langsam



Bääääär
31.07.2009, 03:48
Hallo Community!

Ich hab mal wieder ein Problem:

Ich habe hier einen ATMEGA32 und habe eine PWM-Software geschrieben. Da ich 6 (Später mal 7) Kanäle brauche, entfällt Hardware PWM also...
Folgender Code geht wunderbar:

#define F_CPU 16000000UL

volatile uint16_t TimerCounter;
volatile int LEDState[6][2];



uint16_t pwmtable[64] PROGMEM = {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, 29, 32, 36, 40, 44, 49, 55,
61, 68, 76, 85, 94, 105, 117, 131, 146, 162,
181, 202, 225, 250, 279, 311, 346, 386, 430,
479, 534, 595, 663, 739, 824, 918, 1023};

// PWM-timer ISR
ISR(TIMER2_OVF_vect) {

TimerCounter++;

if (TimerCounter > 1023 ) {
TimerCounter = 0;
if (LEDState[0][0] != 0) { PREVR_Port |= (1 << PREVR_Pin);}
if (LEDState[1][0] != 0) { PREVG_Port |= (1 << PREVG_Pin);}
if (LEDState[2][0] != 0) { PREVB_Port |= (1 << PREVB_Pin);}
if (LEDState[3][0] != 0) { REALR_Port |= (1 << REALR_Pin);}
if (LEDState[4][0] != 0) { REALG_Port |= (1 << REALG_Pin);}
if (LEDState[5][0] != 0) { REALB_Port |= (1 << REALB_Pin);}
}
if (TimerCounter > LEDState[0][0]) {
PREVR_Port &= ~(1 << PREVR_Pin);
}
if (TimerCounter > LEDState[1][0]) {
PREVG_Port &= ~(1 << PREVG_Pin);
}
if (TimerCounter > LEDState[2][0]) {
PREVB_Port &= ~(1 << PREVB_Pin);
}
if (TimerCounter > LEDState[3][0]) {
REALR_Port &= ~(1 << REALR_Pin);
}
if (TimerCounter > LEDState[4][0]) {
REALG_Port &= ~(1 << REALG_Pin);
}
if (TimerCounter > LEDState[5][0]) {
REALB_Port &= ~(1 << REALB_Pin);
}
}



void SetLED(int number, int i) {
LEDState[number][1] = i;
LEDState[number][0] = pgm_read_word(pwmtable + i);
}

int main(void) {

// Init global variables
TimerCounter = 0;

// Init LED-Pins for I/O use
PREVR_DDR |= (1 << PREVR_Pin);
PREVG_DDR |= (1 << PREVG_Pin);
PREVB_DDR |= (1 << PREVB_Pin);
REALR_DDR |= (1 << REALR_Pin);
REALG_DDR |= (1 << REALG_Pin);
REALB_DDR |= (1 << REALB_Pin);

// All Interrupts off
cli();

// Init the timer for Software-PWM
TCCR2 |= (1<<CS20);
TIMSK |= (1<<TOIE2);

sei();

// Init the LEDState flag-array
for (int i=0; i<8; i++) {
LEDState[i][0] = pgm_read_word(pwmtable + 63); // Here is the value from the pwmtable stored; this is the value used by pwm
LEDState[i][1] = 63; // This is the "seen" brightness of the LEDs, from 0 to 63. We start with full brightness
}


while(1) {
//Tolle Sachen machen... Faden... Farben durchschalten usw.
}
return 0;
}


(Der Umstand, dass ich einmal den Wert aus dem PWM-Table und einmal den "gesehenen" Wert speichere, hängt damit zusammen, dass ich damit noch Fade...)

Jetzt dachte ich, 10Bit ist cool, 16Bit ist besser. Also los:


uint16_t pwmtable_16[256] PROGMEM = {0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6,
6, 7, 7, 7, 8, 8, 8, 9, 9, 10, 10, 10, 11,
11, 12, 12, 13, 13, 14, 15, 15, 16, 17, 17,
18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
29, 31, 32, 33, 35, 36, 38, 40, 41, 43, 45,
47, 49, 52, 54, 56, 59, 61, 64, 67, 70, 73,
76, 79, 83, 87, 91, 95, 99, 103, 108, 112,
117, 123, 128, 134, 140, 146, 152, 159, 166,
173, 181, 189, 197, 206, 215, 225, 235, 245,
256, 267, 279, 292, 304, 318, 332, 347, 362,
378, 395, 412, 431, 450, 470, 490, 512, 535,
558, 583, 609, 636, 664, 693, 724, 756, 790,
825, 861, 899, 939, 981, 1024, 1069, 1117,
1166, 1218, 1272, 1328, 1387, 1448, 1512,
1579, 1649, 1722, 1798, 1878, 1961, 2048,
2139, 2233, 2332, 2435, 2543, 2656, 2773,
2896, 3025, 3158, 3298, 3444, 3597, 3756,
3922, 4096, 4277, 4467, 4664, 4871, 5087,
5312, 5547, 5793, 6049, 6317, 6596, 6889,
7194, 7512, 7845, 8192, 8555, 8933, 9329,
9742, 10173, 10624, 11094, 11585, 12098,
12634, 13193, 13777, 14387, 15024, 15689,
16384, 17109, 17867, 18658, 19484, 20346,
21247, 22188, 23170, 24196, 25267, 26386,
27554, 28774, 30048, 31378, 32768, 34218,
35733, 37315, 38967, 40693, 42494, 44376,
46340, 48392, 50534, 52772, 55108, 57548,
60096, 62757, 65535};


// PWM-timer ISR
ISR(TIMER2_OVF_vect) {
TimerCounter++;

if (TimerCounter == 0) { // Beim Überlauf
if (LEDState[0][0] != 0) { PREVR_Port |= (1 << PREVR_Pin);}
if (LEDState[1][0] != 0) { PREVG_Port |= (1 << PREVG_Pin);}
if (LEDState[2][0] != 0) { PREVB_Port |= (1 << PREVB_Pin);}
if (LEDState[3][0] != 0) { REALR_Port |= (1 << REALR_Pin);}
if (LEDState[4][0] != 0) { REALG_Port |= (1 << REALG_Pin);}
if (LEDState[5][0] != 0) { REALB_Port |= (1 << REALB_Pin);}
}
if (TimerCounter > LEDState[0][0]) {
PREVR_Port &= ~(1 << PREVR_Pin);
}
if (TimerCounter > LEDState[1][0]) {
PREVG_Port &= ~(1 << PREVG_Pin);
}
if (TimerCounter > LEDState[2][0]) {
PREVB_Port &= ~(1 << PREVB_Pin);
}
if (TimerCounter > LEDState[3][0]) {
REALR_Port &= ~(1 << REALR_Pin);
}
if (TimerCounter > LEDState[4][0]) {
REALG_Port &= ~(1 << REALG_Pin);
}
if (TimerCounter > LEDState[5][0]) {
REALB_Port &= ~(1 << REALB_Pin);
}
}

void SetLED(int number, int i) {
LEDState[number][1] = i;
LEDState[number][0] = pgm_read_word(pwmtable_16 + i);
}

int main(void) {

TimerCounter = 0;


// Init LED-Pins for I/O use
PREVR_DDR |= (1 << PREVR_Pin);
PREVG_DDR |= (1 << PREVG_Pin);
PREVB_DDR |= (1 << PREVB_Pin);
REALR_DDR |= (1 << REALR_Pin);
REALG_DDR |= (1 << REALG_Pin);
REALB_DDR |= (1 << REALB_Pin);

// All Interrupts off
cli();

// Init the timer for Software-PWM
TCCR2 |= (1<<CS20);
TIMSK |= (1<<TOIE2);

sei();

// Init the LEDState flag-array
for (int i=0; i<6; i++) {
LEDState[i][0] = pgm_read_word(pwmtable_16 + 255); // Here is the value from the pwmtable be stored; this is the value used by pwm
LEDState[i][1] = 256; // This is the "seen" brightness of the LEDs, from 0 to 255. We start with full brightness
}


while(1) {
// Wieder ne Menge tolle Farben durchschalten
}
return 0;
}


Hmm, wenn der Timer bei einem Prescaler von Null zählt, dann habe ich
16MHz/256 = 62500 Interrupte pro Sekunde.

Wenn ich helles Gelb erzeugen will (255,255,100) geht mein Timer aber so quälend langsam... Gelb (Rot+Grün) ist wie erwartet durchweg an, aber Blau blinkt alle Sekunde kurz auf.
Eben, die 62500 Interrupte reichen eben nicht aus, um x-mal/s 16Bit hochzuzählen...

Was mach ich jetzt?

Also Timer in den Clear-Timer-On-Compare-Match-Modus versetzt. Ich schreibe in das OCR2 Register 20 rein und er zählt, bis er 20 erreicht hat und dann kommt der Interrupt. Also 800.000 Interrpupt pro Sekunde. Heißt aber auch, ich hab nur 20 Takte Zeit, um all die LEds zu Schalten und eigentlich soll neben dem PWM ja auch noch Rechenzeit dasein für andere Sachen...
Jedenfalls hab ich folgende Änderungen am Code gemacht:

ISR (TIMER2_COMP_vect) {
...

// Init the timer for Software-PWM
TCCR2 |= (1<<CS20) | (1<<WGM21);
TCCR2 &= ~(1<<CS21);
TCCR2 &= ~(1<<CS22);
TIMSK |= (1<<OCIE2);
OCR2 = 20;

...

Leider ist das Resultat alles andere als gut: Immernoch sehe ich meine Blaue LED blinken, wenn auch schon in etwas weniger als einer Sekunde.

Welche Möglichkeiten bleiben mir noch?

Danke Vielmals,
Bääääär

Besserwessi
31.07.2009, 10:43
Der Code in der ISR ist so lang, das er µC auch etwa die 256 Zyken für den code braucht. Wenn man den Timer schneller stellt, werden einfach ein paar Interrupts verschluckt und die Geschwindigkeit wird duch den Code bestimmt.

Man kann den Code noch etwas beschleunigen:
Der Zugriff auf das 2 D Array ist ungünstig. Da lieber getrennt array nehmen. Der 2 te Punkt ist das Setzen der Bits. Es sollte schneller gehen, erst den wert für den kompletten port zusammentzstellen und dann in eins auszugeben.

Wenn das alles nichts hilft, bliebe noch inline ASM. Beim Soft-PWM ist der Geschwindigkeitsvorteil ziehmlich groß, weil man da das varry flag effektiv nutzen kann. Trotzdem wird man da nicht auf 20 Zyklen für die ISR kommen. Die Grenze sollte bei etwa 40-50 Zyklen liegen für 6 Kanäle mit 16 Bit PWM.

Hubert.G
31.07.2009, 10:44
Bei einem Interrupt werden in C standard gemäss auch alle verwendete Register gesichert. Ich habe noch nie nachgezöhlt, aber es sollten bis zu 70 Takte für das sichern und zurückschreiben notwendig sein.
Meiner Erfahrung nach, gibt es in einem Code bei einem Interrupt <100 Takte bereits Zeitprobleme.
Die einzige Lösung wird, abgesehen von Umstieg auf einen anderen schnelleren Prozessor, nur Assembler sein, da du dort wesentlich zeitoptimierter arbeiten kannst.

yaro
31.07.2009, 12:26
Um eine hoehere aufloesung zu erhalten, solltest du dein Software-PWM anders gestalten.
WarChild het dazu ne ganz gute Idee gehabt, die sich in deinem Fall sehr gut anwenden laesst.
hier kanstes nachlesen: https://www.roboternetz.de/phpBB2/zeigebeitrag.php?t=49193&postdays=0&postorder=asc&highlight=18+pwm&start=0

Grus, yaro

Besserwessi
31.07.2009, 18:53
Die Vorschläge bei de Thread oben, sind für Servosignale mit einem entsprechend asymetrischen PWM Signal. Für PWM Signale über den ganzen Bereich geht das nicht so gut.

Es gäbe da noch eine Lösung mit etwas anderer Hardware: der Mega324 hat 6 Hardware PWM Kanäle. Davon 4 zwar nur 8 Bit, aber da kann man mit einer zusätzlichen Modulation diese um 8 Bits erweitern um zwischenerte zu bekommen.

Bääääär
31.07.2009, 20:35
Hallo alle zusammen!

Erstmal: WOW, da haben aber ganz schön viele Leute gepostet! Danke!

Zum Thema: Ich bin zu dem Entschluss gekommen, dass es besser ist, die 6 PWM-Kanäle per Hardware zu betreiben. So habe ich weniger Stress und außerdem läuft dann auch noch das Restprogramm erträglich schnell. In der Zielanwendung werde ich einen ATMEGA64 nehmen, der hat 2 16Bit-Timer mit je 3 Output-Compare Pins. Genau Richtig für meine Anwendung. Den 7. PWM-Kanal kann ich auch per 10bit-software machen, da brauche ich keine besonders gute Auflösung.

Dennoch ist es gut zu wissen, wie man weiter optimieren kann. Dass mit den gesicherten Registern war mir gar nicht so klar, aber leuchtet ein. Dass ich erst alle Bits setzen und dann erst auf den PORT schieben kann und dabei ein Plus an Speed bekomme wusste ich gar nicht - warum geht das schneller? Ich werds mir aber merken und künftig so anwenden.

Danke an alle!
Bääääär

Besserwessi
31.07.2009, 21:44
Der Vorteil in der Geschwindigkeit ist nicht unbedingt groß wenn man die Bits zusammen setzt. Es hängt auch vom µC ab. Wenn der Port ein niedreige Addresse hat, kann der Compiler die Befehle SBI und CBI nutzen. Dann ist das Setzen in eins nicht mal unbedingt schneller.
Der Hauptvorteil kommt dadurch das beim Zählerstand 0 die ganzen If Bedingungen wegfallen. Das ISR Timing muß sich ja nach dem ungünstgsten Fall richten.

Nur wenn die Adresse höher ist (z.B. mega128 einige Ports) muß der Compiler mit LDS ORI und STS arbeiten. Vor allem dann wird es schneller.

Statt direkt ein Software PWM für 10 Bit zu programmieren, kann man auch schon einen 8 Bit PWM kanal nutzen und da dann ähnlich wie bei Soft PWM zwischen 2 Werten umschalten. Das gibt deutlich weniger Rechenzeitbelastung, denn man braucht nur alle 256 Schritte eine ISR. Auch darf die PWM Freuquenz dabei oft sogar noch niedriger sein als bei einem echten PWM Signal.