www.mikrocontroller.net

Forum: Compiler & IDEs Mega128 Timer1 PWM


Autor: Alfred Chicken (alfred-c)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi Leute,

ich brauche für ein Projekt eine Routine die mir mit einer Genauigkeit 
von 8Bit 4 Software 100Hz-PWM-Kanäle mit nur einem Timer auf PD0-3 
rausgibt.

Nun möchte ich aus Zeitgründen nicht 256*4*100/sec einen Interrupt 
auslösen, sondern die Kanäle nach Pulsweite zu soriteren, um dann die 
Abstände der sortierten Pulsweiten zueinander zu ermitteln.

Ich dachte mir mit einem 16Bit Timer (Prescaler8) im CTC Mode könnte ich 
die IRQs somit auf nur 5*100/sec reduzieren.
Dazu habe ich zunächst eine Berechnungsroutine geschrieben, die mir vier 
frei wählbare 8Bit variablen in 16Bit Werte umwandelt, sortiert und die 
Abstände zueinader dann berechnet. All diese Berechnungen finden ein Mal 
je Sekunde außerhalb der Interrupt-Routine statt.

Sind die Eingangsparameter ungleich 0 und sind die Eingangsparameter 
mind. 1 voneinander entfernt läuft das ganze zieimlich gut. Aber eben 
nur dann. Nun weiß ich nicht weiter. Ich habe versucht das TCNT1 
Register jedes Mal zu "nullen" bzw auf Max zu setzen aber irgendwie löst 
der "Output Compare Match" ununterbrochen aus oder er verpasst den OCM 
und versaut damit die PWM :-(.
Ich bin mir sicher es liegt an einem allgemeinen Verständnisproblem für 
Timer, aber ich komme nicht drauf. Kann mir einer von euch helfen?

Hier der Code:
volatile uint8_t   AnaOut1;
volatile uint8_t   AnaOut2;
volatile uint8_t   AnaOut3;
volatile uint8_t   AnaOut4;

volatile uint16_t  PWM[5];
volatile uint8_t  Prio[4];
volatile uint8_t  cycle;

//***************** Timerinitialisierung:******************************

TCCR1B|=(1<<WGM12);   //CTC Mode0
TCCR1B|=(1<<CS11);      //Prescaler = 8, start timer      
TIMSK|=(1<<OCIE1A);  //Output compare match1

//***************** Berechnung der vier PWM Kanäle:********************

void anaOUTupdate (void)
{
uint16_t  PWMtemp[4];
uint16_t  PWMordered[4];
PWMordered[0] = 0;
PWMordered[1] = 0;
PWMordered[2] = 0;
PWMordered[3] = 0;
Prio[0] = 2; // needs to be plus 2 because 1 is reserved for start of PWM
Prio[1] = 2;
Prio[2] = 2;
Prio[3] = 2;
int i;
i = 0;


//PWM value conversion
PWMtemp[0] = AnaOut1*78;   //(20000/255=78.4 approx)
PWMtemp[1] = AnaOut2*78;   
PWMtemp[2] = AnaOut3*78;   
PWMtemp[3] = AnaOut4*78;  

//PWM rise and fall order
for (i=0;i<4;i++) //biggest value is 3, smallest is 0
  {
  if (PWMtemp[0]>PWMtemp[i])
    Prio[0] = 1+Prio[0];
  if (PWMtemp[1]>PWMtemp[i])
    Prio[1] = 1+Prio[1];
  if (PWMtemp[2]>PWMtemp[i])
    Prio[2] = 1+Prio[2];
  if (PWMtemp[3]>PWMtemp[i])
    Prio[3] = 1+Prio[3];
  };

//ordering from small to big in help array "PWMordered"
PWMordered[0] = 0;
PWMordered[1] = 0;
PWMordered[2] = 0;
PWMordered[3] = 0;

for (i=0;i<4;i++)
  {
  if (2==Prio[i])
    PWMordered[0]=PWMtemp[i];
  if (3==Prio[i])
    PWMordered[1]=PWMtemp[i];
  if (4==Prio[i])
    PWMordered[2]=PWMtemp[i];
  if (5==Prio[i])
    PWMordered[3]=PWMtemp[i];
  }

//PWM spacing calculation
PWM[0] = PWMordered[0];
PWM[1] = PWMordered[1]-PWMordered[0];
PWM[2] = PWMordered[2]-PWMordered[1];
PWM[3] = PWMordered[3]-PWMordered[2];
PWM[4] = 20000-PWMordered[3];
};

//********************************Timer IRQ************************

ISR (TIMER1_COMPA_vect) //PWM Routine Link
  {
cycle++;

  if (cycle == 1){OCR1A = PWM[0]; 
     PORTD|= (1<<PD0) | (1<<PD1) | (1<<PD2) | (1<<PD3);}
  if (cycle == 2)  OCR1A = PWM[1];
  if (cycle == 3)  OCR1A = PWM[2];
  if (cycle == 4)  OCR1A = PWM[3];

  if (cycle == Prio[0]) PORTD &= ~(1<<PD0);
  if (cycle == Prio[1]) PORTD &= ~(1<<PD1);
  if (cycle == Prio[2]) PORTD &= ~(1<<PD2);
  if (cycle == Prio[3]) PORTD &= ~(1<<PD3);

  if (cycle == 5)  {OCR1A = PWM[4]; cycle = 0;}

        };

Autor: Alfred Chicken (alfred-c)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hat wirklich keiner eine Idee oder waren meine Angaben zu ungenau?

Autor: Rolf Pfister (rolfp)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Alfred Chicken schrieb:
> Hat wirklich keiner eine Idee oder waren meine Angaben zu ungenau?

Ich bin auch grade dran etwas mit PWM auszuprobieren. Irgendwie ist mir 
aber dein Programm zu kompliziert als dass ich da durchblicken würde was 
du machen willst. Ich verstehe z.B. nicht warum du OCR1A in 
TIMER1_COMPA_vect veränderst. Wäre es nicht viel einfacher OCR1B für den 
PWM zu verwenden?
Also irgendwie so:
ISR (TIMER1_COMPA_vect)
{
 PORTD |= 0x0F; //alle PWM setzen
 OCR1B = ersterwert;
}

ISR (TIMER1_COMPB_vect)
{
 int n=TCNT1;
 if(pwm1==n) PORTD &= ~(1<<PD0); //entsprechender PWM ruecksetzen
 if(pwm2==n) PORTD &= ~(1<<PD1);
 if(pwm3==n) PORTD &= ~(1<<PD2);
 if(pwm4==n) PORTD &= ~(1<<PD3);
 OCR1B = naechsterwert;
}

Autor: Alfred Chicken (alfred-c)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Vielleicht habe ich im Datenblatt was überlesen, aber im CTC Mode gibt 
es doch keinen COMPB Interrupt oder? Wenn es doch geht würde das die 
Rechenzeit je Interruptroutine schon etwas verkürzen, aber das 
eigentliche Problem ist damit glaube ich nicht gelöst.

Also ich stelle mir das dann so vor:

Mit einem Prescaler von 8 würde ich den COMPA alle 20000 timer counts 
auslösen lassen. Damit könnte super die Periodendauer T der PWM 
eingestellt werden. Denn 16000000Hz/(20000*8 Takte) = 100Hz.

Dann würden in der Zwischenzeiten die vier Interrupts ausgelöst werden 
und zu gegebener Zeit den jeweiligen PortD-Pin auf low ziehen. Wäre aber 
beispielsweise PWM1 = 0; dann würde schonmal der erste Interrupt 
verpasst werden und der jeweilige Portpin für die ganze Zeit auf high 
bleiben. Denn laut Datenblatt wird doch der Compare fuer einen 
Timercount ausgesetzt, wenn OCR veraendert wird, oder habe ich das 
falsch verstanden?

Das Gute an deiner Routine wäre aber, das es abgesehen von dem Problem 
mit der 0 egal wäre, wenn zwei PWMs genau den gleichen Wert hätten. Wäre 
also "erster wert" und "zweiter wert" = 50, so würden beide auf low 
gehen, der zweite OCR Wert würde dann zwar verpasst werden aber das ist 
ja egal denn der Port is ja schon low.
Dann bin ich mir aber auch nicht sicher was passieren würde, wenn 
"erster wert" = 50 und "zweiter wert" = 51 wäre, denn die 
Interruptroutine würde unter Umständen so lange dauern, dann der OCR 
Wert für "zweiter Wert" auch verpasst wird, das wäre dann aber schlimm, 
denn der Pin wird dann nicht auf low gezogen...

das wird immer wirrer alles ^^. Wer soll denn da noch durchblicken ? :-)

Danke aber für deinen Vorschlag.

Autor: Alfred Chicken (alfred-c)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
OK, laeuft alles :-), ist nahezu der Code wie oben. Jetzt gehen je 
Sekunde ca. 35.000 Takte fuer die 4 100Hz PWMs drauf (= 0,22% CPU 
Auslastung), das ist zu verschmerzen...
Rolf, falls du den Code haben möchtest, schreib mich an.

Autor: Rolf Pfister (rolfp)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Alfred Chicken schrieb:
> Vielleicht habe ich im Datenblatt was überlesen, aber im CTC Mode gibt
> es doch keinen COMPB Interrupt oder? Wenn es doch geht würde das die

Warum sollte der COMPB Interrupt nicht gehen? Wenn ich nicht was 
wesentliches übersehen habe müsste der in jedem Modus gehen.

Alfred Chicken schrieb:
> OK, laeuft alles :-), ist nahezu der Code wie oben. Jetzt gehen je

Wie oben dein Code, oder wie oben mein Vorschlag? Vielleicht schreibst 
du kurz was denn dann der Trick war um das Problem zu lösen?

> Rolf, falls du den Code haben möchtest, schreib mich an.
Wenn du den Code hier posten kannst, schau ich ihn mir mal an.

Autor: Alfred Chicken (alfred-c)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Also ich hab die Funktion getestet, vielleicht muss noch minimal an den 
Timings gearbeitet werden, aber die reine Funktionalitaet ist 
erfuellt... Timer laeuft im CTC mode wie vorher... (WMG12)
/********************************************************************
Analogue Output: D0 = AO1, D1 = AO2, D2 = AO3, D3 = AO4
Resolution: 8Bit

Description: this function may be fairly complicated but it minimizes
the afford of calcualations per IRQ quite substantially. This function
should not be executed more than a few times per second.
1.   Convert 255 8-Bit Resolution Values) into values between 0-20000
2.   Every value needs a priority value as this will be used in IRQ. If
  two values equal eachother there are only 3 Priority numbers instead
  of 4. For instace: 
  255 30 70 70 --> 5 2 3 3   (offset = 2)
  10  20 30 40 --> 2 3 4 5 
  30  30 30 30 --> 2 2 2 2
3.  The values now need to be ordered from small to big in a help array.
4.  Now the distance between the PWMs needs to calculated.
  Example: 
  0----2000(PWM1)-------5000(PWM2)------15000(PWM3/4)---20000/0 RESTART
  0----2000(PWM1)--4000(PWM2)-5000(PWM3)----12000PWM(4)-20000/0 RESTART
  Instead reduces the need for IRQs from
  255(Resolution)*4PWMs*100Hz  =102000 IRQs
  to
  (4PWMs+1Stop)*100Hz      =500IRQs
********************************************************************/
void anaOUTupdate (void)
{
uint16_t  PWMtemp[4];
uint16_t  PWMordered[4];
PWMordered[0] = 0;
PWMordered[1] = 0;
PWMordered[2] = 0;
PWMordered[3] = 0;

int i;
i = 0;


//PWM value conversion
PWMtemp[0] = AnaOut1*78;   //(20000/255=78.4 approx)
PWMtemp[1] = AnaOut2*78;   //Analogue Output must be converted into value 0...20000
PWMtemp[2] = AnaOut3*78;   //it's time consuming but there is no need for rush
PWMtemp[3] = AnaOut4*78;  //this action may be interrupted by ISR
/*
uart_debug("Unsorted1: ",PWMtemp[0]);
uart_debug("Unsorted2: ",PWMtemp[1]);
uart_debug("Unsorted3: ",PWMtemp[2]);
uart_debug("Unsorted4: ",PWMtemp[3]);
*/

//PWM rise and fall order
Prio[0] = 2; // needs to be plus 2 because 1 is reserved for start of PWM
Prio[1] = 2;
Prio[2] = 2;
Prio[3] = 2;

for (i=0;i<4;i++) //biggest value is 3, smallest is 0
  {
  if (PWMtemp[0]>PWMtemp[i])
    Prio[0] = 1+Prio[0];
  if (PWMtemp[1]>PWMtemp[i])
    Prio[1] = 1+Prio[1];
  if (PWMtemp[2]>PWMtemp[i])
    Prio[2] = 1+Prio[2];
  if (PWMtemp[3]>PWMtemp[i])
    Prio[3] = 1+Prio[3];
  };
/*
PWMtemp[1]=PWMtemp[1]-1;
PWMtemp[2]=PWMtemp[2]-2;
PWMtemp[3]=PWMtemp[3]-3;
*/
/*
uart_debug("Prio1: ",Prio[0]);
uart_debug("Prio2: ",Prio[1]);
uart_debug("Prio3: ",Prio[2]);
uart_debug("Prio4: ",Prio[3]);
*/
//ordering from small to big in help array "PWMordered"

for (i=0;i<4;i++)
  {
  if (2==Prio[i])
    PWMordered[0]=PWMtemp[i];
  if (3==Prio[i])
    PWMordered[1]=PWMtemp[i];
  if (4==Prio[i])
    PWMordered[2]=PWMtemp[i];
  if (5==Prio[i])
    PWMordered[3]=PWMtemp[i];
  }

if (PWMordered[1]<PWMordered[0])
  PWMordered[1] = PWMordered[0];
if (PWMordered[2]<PWMordered[1])
  PWMordered[2] = PWMordered[1];
if (PWMordered[3]<PWMordered[2])
  PWMordered[3] = PWMordered[2];

/*
uart_debug("Ordered1: ",PWMordered[0]);
uart_debug("Ordered2: ",PWMordered[1]);
uart_debug("Ordered3: ",PWMordered[2]);
uart_debug("Ordered4: ",PWMordered[3]);
*/

//PWM spacing calculation
//probably timer must be killed for those 5 equations

if (PWMordered[1]>PWMordered[2])
  {  while(1)
  {
  };
  }
PWM[0] = PWMordered[0];
PWM[1] = PWMordered[1]-PWMordered[0];
PWM[2] = PWMordered[2]-PWMordered[1];
PWM[3] = PWMordered[3]-PWMordered[2];
PWM[4] = 20000-(PWM[0]+PWM[1]+PWM[2]+PWM[3]);
//PWM must not be 0 and values must not be the same!

if ( PWM[0] < 78)             {PWM[0] = 15+PWM[0];}
if ((PWM[1] < 78) || (PWM[0]==PWM[1]))  {PWM[1] = 30+PWM[1];}
if ((PWM[2] < 78) || (PWM[1]==PWM[2]))   {PWM[2] = 45+PWM[2];}
if ((PWM[3] < 78) || (PWM[3]==PWM[4]))  {PWM[3] = 60+PWM[3];PWM[4]=8+PWM[4];}

/*
uart_debug("Spacing0-1: ",PWM[0]);
uart_debug("Spacing1-2: ",PWM[1]);
uart_debug("Spacing2-3: ",PWM[2]);
uart_debug("Spacing3-4: ",PWM[3]);
uart_debug("Spacing4-0: ",PWM[4]);
*/
};

ISR (TIMER1_COMPA_vect) //PWM Routine
  {
cycle++;

  if (cycle == 1)  {PORTD|= (1<<PD0) | (1<<PD1) | (1<<PD2) | (1<<PD3); OCR1A = PWM[0];}
  if (cycle == 2) OCR1A = PWM[1];
  if (cycle == 3)  OCR1A = PWM[2];
  if (cycle == 4)  OCR1A = PWM[3];

  if (cycle == Prio[0]) PORTD &= ~(1<<PD0);
  if (cycle == Prio[1]) PORTD &= ~(1<<PD1);
  if (cycle == Prio[2]) PORTD &= ~(1<<PD2);
  if (cycle == Prio[3]) PORTD &= ~(1<<PD3);

  if (cycle == 5)  {OCR1A = PWM[4]; cycle = 0;}
  };

Autor: Rolf Pfister (rolfp)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Alfred Chicken schrieb:
> Also ich hab die Funktion getestet, vielleicht muss noch minimal an den
> Timings gearbeitet werden, aber die reine Funktionalitaet ist
> erfuellt... Timer laeuft im CTC mode wie vorher... (WMG12)

Ich hätte noch ein paar Optimierungsvorschläge:

Alfred Chicken schrieb:
> uint16_t  PWMordered[4];
> PWMordered[0] = 0;
> PWMordered[1] = 0;
> PWMordered[2] = 0;
> PWMordered[3] = 0;

das könnte man auch so machen:
uint16_t  PWMordered[4] = {0,0,0,0};

Alfred Chicken schrieb:
> Prio[0] = 1+Prio[0];
Einfacher so:
  Prio[0] += 1;
oder so:
  Prio[0]++;

Alfred Chicken schrieb:
> for (i=0;i<4;i++)
>   {
>   if (2==Prio[i])
>     PWMordered[0]=PWMtemp[i];
>   if (3==Prio[i])
>     PWMordered[1]=PWMtemp[i];
>   if (4==Prio[i])
>     PWMordered[2]=PWMtemp[i];
>   if (5==Prio[i])
>     PWMordered[3]=PWMtemp[i];
>   }

Einfacher und schneller so:
 for (i=0;i<4;i++)
   {
    PWMordered[Prio[i]-2] = PWMtemp[i];
   }

Alfred Chicken schrieb:
> if (PWMordered[1]>PWMordered[2])
>   {  while(1)
>   {
>   };
>   }

Das ist wohl noch ein Überbleibsel vom Testen. Also weglassen.

Autor: Alfred (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hey, vielen Dank! Das werd ich alles uebernehmen :-)... Die 
Schreibweisen mit dem + sehen natürlich besser aus. Da ich aber noch 
nicht so oft mit solchen arrays gearbeitet habe war ich mir nicht 
sicher, ob man die so wie normale Variablen behandeln kann.

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.