Forum: Mikrocontroller und Digitale Elektronik Verteilung von X Einheiten in 100 Einheiten(PWM)


von Anton G. (vergil8)


Lesenswert?

Hallo Leute,

Ich möchte eine eigene "PWM" auf meinem Atmega16A schreiben. Hier soll 
nicht wie üblich ein zusammenhängender Puls am Anfag der PWM entstehen 
sonder die Pulse über die ganze Breite verteilt werden. Z.B. bei 100 
maximaler Pulsbreite und 50 Sollpulseinheiten soll jeder 2. Wert der PWM 
high sein.
Gibt es hier schon einen relativ genauen Algorithmus? Ich habe jetzt 
schon einige Zeit gesucht aber ich finde keinen passendend der Schnell 
und einfach ist. Meine eigene Idee dazu ist diese:
Maximale Pulsbreite: 100 Einheiten
Sollpulseinheiten: 73 Einheiten
Programm beim Ausführen:
AUFRUNDEN(100/73)= 2  (jeder 2. Einheit muss 1 sein)
73-(100/2) = 23       (Rest)
AUFRUNDEN(100/23) = 5 (jeder 5. Einheit muss 1 sein)
23-(100/5) = 3        (Rest)

Fehlerbetrachtung:
Der letzte Rest von 3  bleibt übrig und es fehlen somit 3 Einheiten.
Bei dem 5er Schritten wird jeder 2. Schritt bereits von dem 2er Schritt 
abgedeckt.
Folglich: 3+2=5
5 Einheiten Fehler!


Habt ihr vielleicht einen intelligenteren Algorithmus oder einfach nur 
eine Anregung/Idee?

Vielen Dank im Voraus!
Vergil8

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Bin mir nicht sicher. Aber ich denke 'Bit Angle Modulation' wäre was für 
dich.

von Karl H. (kbuchegg)


Lesenswert?


von Anton G. (vergil8)


Lesenswert?

Danke :) ich schau mal!

von Anton G. (vergil8)


Lesenswert?

Das ist auf jeden Fall schon besser aber nicht ganz das was ich möchte. 
Ich suche nach einem Algorithmus der es möglichst gleich verteilt. Bei 
der Lösung von dir bleibt z.B. bei 50%-high-values der erste Teil 
durchgehend auf 0 und wäre folglich 50% am Stück high und 50% am Stück 
low und nicht gleich verteilt.

: Bearbeitet durch User
von Detlef _. (detlef_a)


Lesenswert?

Kann man da nicht den Bresenham-Algorithmus zum Zeichnen von Linien 
zweckentfremden?

Das Problem des TO und dieses sind doch äquivalent:
Ich zeichne eine Linie von(0,0) nach (100,73) und möchte wissen, an 
welchen x Koordinaten ich in y-Richtung hochspringen muss.

Cheers
Detlef

von Thorsten O. (Firma: mechapro GmbH) (ostermann) Benutzerseite


Lesenswert?

Ich denke der Bresenham-Algorithmus ist das was du suchst. Ob du nun 
Kästchen auf Zeichenpapier malst oder Takte in einem PWM-Zyklus 
verteilst ist vom Prinzip her das gleiche.

Mit freundlichen Grüßen
Thorsten Ostermann

von Michael S. (rbs_phoenix)


Lesenswert?

Anton Gohlke schrieb:
> Meine eigene Idee dazu ist diese:
> Maximale Pulsbreite: 100 Einheiten
> Sollpulseinheiten: 73 Einheiten
> Programm beim Ausführen:
> AUFRUNDEN(100/73)= 2  (jeder 2. Einheit muss 1 sein)
> 73-(100/2) = 23       (Rest)
> AUFRUNDEN(100/23) = 5 (jeder 5. Einheit muss 1 sein)
> 23-(100/5) = 3        (Rest)
>
> Fehlerbetrachtung:
> Der letzte Rest von 3  bleibt übrig und es fehlen somit 3 Einheiten.
> Bei dem 5er Schritten wird jeder 2. Schritt bereits von dem 2er Schritt
> abgedeckt.
> Folglich: 3+2=5
> 5 Einheiten Fehler!
>
> Habt ihr vielleicht einen intelligenteren Algorithmus oder einfach nur
> eine Anregung/Idee?

Das wird so denke ich nicht gehen. Wenn jede 2. Einheit high ist, somit 
auch die 10., was passiert dann, wenn jede 5. Einheit auch auf high 
gesetzt wird? Jede 2. 5. Einheit ist dann schon High, somit gewinnst du 
kein Impuls mehr.
Ich hab das testhalber mal mit 7 aus 10 Einheiten gemacht:
10/7=1,4285...
Jetzt kann man mit 0 beginnend Multiplizieren, um die High zu setzenden 
Bits/Elemente zu ermitteln:
0x1,4285=0
1x1,4285=1,43
2x1,4285=2,86
3x1,4285=4,29
4x1,4285=5,71
5x1,4285=7,14
6x1,4285=8,57

Rundet man nun die Ergebnisse, kommt raus: 0, 1, 3, 4, 6, 7, 9. Dies 
sind die Elemente, die High gesetzt werden müssen. Sollte auch mit 73 
aus 100 gehen, doch dass wollte ich nich aufm Zettel rechnen ;)
Jetzt muss man nur überlegen, wie man anhand der Rechnung auch die 
ermittelten Einheiten High setzt. Entweder mit 2 Zählern (einer zum 
Einheiten zählen und einer für die Multiplikation) oder aber erst 
rechnen und anhand der Ergebnisse dann High setzen.


Sofern genug Flash vorhanden ist, könnte man auch ein großes Array 
anlegen, wo man dann einfach nur den entsprechenden Eintrag laden und 
rauswerfen muss. Spart man nen bisl Rechenleistung. Je nach dem, was 
wichtiger ist. Sprich: Entweder rechnest du beim Rausschicken der 
Bitfolge, dann bleibt die Einheit aber die Rechenzeit anliegend, oder 
aber man rechnet am PC, legt ein Array an und liest einfach nur das 
Array aus, sodass die Einheiten schneller rausgehauen werden können.

: Bearbeitet durch User
von Anton G. (vergil8)


Lesenswert?

Genau eine Antwort wie deine habe ich gebraucht!! Danke!!! Ich setzte es 
gleich mal in Code um und Poste dann meinen Ergebniscode!

von Anton G. (vergil8)


Lesenswert?

Hier ist jetzt mein C-Code:

Die Funkion zum setzen der prozentualen Pulsansteuerung(0%-100%)
Bei 0 ist es unendlich also setze ich den Wert der Schrittweite einfach 
auf 101 da der Wert nie erreicht werden kann.
1
void HeatPercent(int HeatSetpoint)
2
{
3
  if (HeatSetpoint == 0)
4
  {
5
    StepWidth = 101;
6
  }
7
  else
8
  {
9
    StepWidth = 100/HeatSetpoint;  
10
  }
11
  ZeroCrossingCounter = 1;
12
  StepWidthIncrement = 1;
13
}


Hier ist die ISR, die die Ausäange ein oder ausschaltet. Vorsicht: In 
dem Fall ist der Ausgang low-aktiv.
cbi - löschen des Bits,
sbi - setzen des Bits.
ZeroCrossingCounter - uint8_t, ist der Taktzähler.
Der rest dürfte am Namen erkennbar sein.
1
ISR(INT1_vect)
2
{
3
  if (((int)StepWidthIncrement*StepWidth) == ZeroCrossingCounter)
4
  {
5
    StepWidthIncrement++;
6
    cbi(PORTB, PB0);
7
  }
8
  else
9
  {
10
    sbi(PORTB, PB0);
11
  }
12
  ZeroCrossingCounter++;
13
  if (ZeroCrossingCounter > 100)
14
  {
15
    ZeroCrossingCounter = 1;
16
    StepWidthIncrement = 1;
17
  }
18
}


Vielen Dank nochmal für die Hiflfe und falls es Verbesserungsvorschläge 
gibt bitte melden =)

von M. S. (elpaco)


Lesenswert?

Hallo,

verstehe ich das richtig, dass du keine bestimmte Frequenz anpeilst (da 
die ja durch das "Aufsplitten" zerrissen wird), sondern es dir um die 
"Normalverteilung" geht?

Meine Idee wäre folgende:

Duty-Cycle: x/100
1
uint8_t i=0;
2
i+=x;
3
if(i>=100)
4
{
5
PORTX |= (1<<PXN);
6
i-=100;
7
}
8
else
9
{
10
PORTX &= ~(1<<PXN);
11
}

Beispiel 70/100:

i        Ausgang
70       0
140      1            -> i=40
110      1            -> i=10
80       0
150      1            -> i=50
120      1            -> i=20
90       0
160      1            -> i=60
130      1            -> i=30
100      1            -> i=0


Das Rechnen mit ganzen Zahlen erspart dem µC eine Menge Arbeit.

: Bearbeitet durch User
von Anton G. (vergil8)


Lesenswert?

Mit der ersten Funktion lege ich den Wert fest der angestuert werden 
soll.
Die ISR wird immer ausgeführt (im 100HZ Takt; Netzfrequenz mit 
Nullpunktauslöser)

von M. S. (elpaco)


Lesenswert?

Den Code kannst du in die ISR rein packen. i müsste natürlich 
Programmweit definiert sein, nicht nur innerhalb der ISR. Die ISR wird 
den Ausgang dann entsprechend toggeln.

von Anton G. (vergil8)


Lesenswert?

Gut ich probier es mal aus =)

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

M. S. schrieb:
> Den Code kannst du in die ISR rein packen. i müsste natürlich
> Programmweit definiert sein, nicht nur innerhalb der ISR.

Nö, da i ausschließlich in der ISR benutzt wird. schreibst Du in der 
ISR() einfach zu Anfang:

   static uint8_t i;    // = 0 kann man sich bei static sparen

Dann kann da keine andere Funktion rumpfuschen und die Variable bleibt 
trotzdem am Leben.

: Bearbeitet durch Moderator
von M. S. (elpaco)


Lesenswert?

Kannst du bspw so machen:
1
#define DutyCycle 70
2
3
uint8_t i;
4
5
void init(void)
6
{
7
   ...
8
   i=0;
9
   ...
10
}
11
12
ISR (TIMER0_comp_vect)
13
{
14
15
   i+=DutyCycle;
16
   if(i>=100)
17
   {
18
      PORTX |= (1<<PXN);
19
      i-=100;
20
   }
21
   else
22
   {
23
      PORTX &= ~(1<<PXN);
24
   }
25
26
}

Das dürfte vom Prinzip her dasselbe sein wie oben bereits beschrieben 
wurde, nur der Ansatz ist iterativ was Kommazahlen, Multiplikation und 
Division spart.

von M. S. (elpaco)


Lesenswert?

Frank M. schrieb:
> M. S. schrieb:
>> Den Code kannst du in die ISR rein packen. i müsste natürlich
>> Programmweit definiert sein, nicht nur innerhalb der ISR.
>
> Nö, da i ausschließlich in der ISR benutzt wird. schreibst Du in der
> ISR() einfach zu Anfang:
>
>    static uint8_t i;    // = 0 kann man sich bei static sparen
>
> Dann kann da keine andere Funktion rumpfuschen und die Variable bleibt
> trotzdem am Leben.

Sicher? Der Wert von i muss ja auch erhalten bleiben, wenn die ISR 
abgearbeitet ist.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

M. S. schrieb:
> Sicher?

Sehr sicher sogar.

> Der Wert von i muss ja auch erhalten bleiben, wenn die ISR
> abgearbeitet ist.

Genau das macht das Zauberwort "static".

Und zusätzlich ist die Variable vor versehentlichem Zugriff aus anderen 
Funktionen geschützt.

: Bearbeitet durch Moderator
von M. S. (elpaco)


Lesenswert?

Okay, auch wieder was dazu gelernt.

von Ralph (Gast)


Lesenswert?

Mir erschließt sich noch nicht der Sinn des ganzen.
Also was der TO damit bewirken will.


Um einfach nur Bitpattern auszugeben lässt sich zb auch die SPI 
Schnittstelle recht gut nutzen. Sogar um einiges besser als die PWM.

Irgendwie hab ich so das Gefühl das dieser Lösungsansatz das eigentliche 
Problem nicht löst.
Sonst wären auch andere bei Nutzung der PWM schon darüber gestolpert und 
es wäre eine Lösung verfügbar.
Im besten Fall als HW.

Nur das eigentliche Problem das gelöst werden soll ist ja nicht 
beschrieben.

von Udo S. (urschmitt)


Lesenswert?

Wofür ist das Ganze eigentlich gedacht?

von M. S. (elpaco)


Lesenswert?

Das Problem an der Methode hier ist, dass es ziemlich Leistungsintensiv 
ist. Die gewöhnliche PWM benötigt ja nur einen Zähler und muss nicht bei 
jedem Tick neu berechnen, wie der Ausgang gesetzt werden muss. Und wenn 
man die normale PWM sehr hochfrequent macht, kommt es auf das selbe 
Ergebnis wie die Methode hier, sogar mit noch besserer Gleichverteilung.

von Anton G. (vergil8)


Lesenswert?

Also das ganze ist zur Ansteuerung einer Heizmatte gedacht. Da ich das 
ganze über die Nulldurchgäge der Netzspannung dimensioniere(50Hz==> 100 
Nulldruchgänge) brauche ich eine Sekunde um 100 verschiedene Werte 
einstellen zu könne(eig wäre 101 Werte perfekt, 0-100%). Da bei einer 
normalen PWM z.B. bei 50/100 nur in den ersten 0,5s die Matte voll 
erwärmt wird und danach quasi 0,5s abkühlt kommt ein recht unlineares 
Verhalten raus. Zudem möchte ich den Teil später für etwas anderes gerne 
wieder verwenden. :)

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Anton G. schrieb:
> Also das ganze ist zur Ansteuerung einer Heizmatte gedacht.

Naja, eine Heizmatte ist aber viel viel träger in der 
Temperaturänderung. Da spielt es überhaupt keine Rolle, ob Du nun 0,6 
Sekunden heizt und dann 0,4 Sekunden kühlst oder Du das in kleineren 
Zeitabständen machst.

von Anton G. (vergil8)


Lesenswert?

Ja klar, aber später will ich das vielleicht auch für kleine 
Heizelemente verwenden und wenn ich jetzt schon gute vorarbeit leieste 
muss ich es dann später nicht mehr machen.

von Udo S. (urschmitt)


Lesenswert?

Anton G. schrieb:
> Also das ganze ist zur Ansteuerung einer Heizmatte gedacht.

Ich habs geahnt.
Deine Heizmatte ist 100 mal zu träge um eine 100Hz PWM mitzukriegen und 
du machst trotzdem den Aufwand noch 100 Impulse pro PWM grundfrequenz 
möglichst gleichmässig zu verteilen?

Das ist in etwa so sinnvoll wie deinen Kaffeetasseninhalt auf 1mg genau 
abzuwiegen :-(

von Udo S. (urschmitt)


Lesenswert?

Anton G. schrieb:
> Ja klar, aber später will ich das vielleicht auch für kleine
> Heizelemente verwenden

Wie klein sollen die sein? Heizen die dann einen 10tel Fingerhut?
Und selbst dann nimmst du halt 1KHz als PWm Frequenz.

von Michael S. (rbs_phoenix)


Lesenswert?

Für be Heizmatte ist das tatsächlich unnötig.

Ich will jetzt aber nicht umsonst drangesessen haben ;)
daher meine Idee:
1
#define MAX_WERT 100
2
3
uint8 neuer_Wert=0;
4
uint8 aktueller_Wert;
5
uint8 taktzaehler=0;
6
uint8 multi_zaehler=1;
7
float multiplikator=1;
8
9
void isr_100hz (){
10
  uint8 temp;
11
  
12
  if(taktzaehler==0){
13
    aktueller_Wert = neuer_Wert; 
14
    if(aktueller_Wert==0){  // Wenn 0%, Ausgang nicht setzen
15
      Output = 0;
16
      multiplikator = 0;
17
    }
18
    else{                   // Sonst Ausgang setzen
19
      Output = 1;
20
      multiplikator = (float) MAX_WERT / aktueller_Wert; // Bis zum nächsten Interrupt kann der Multiplikator berechnet werden
21
    }
22
    taktzaehler++; 
23
  }
24
  
25
  else{
26
    temp = (uint8)(multi_zaehler * multiplikator + 0.5); // +0.5, damit kaufmännisch gerundet wird, ist aber nicht wirklich erforderlich
27
    if(temp==taktzaehler){
28
      Output = 1;
29
      multi_zaehler++;
30
    }
31
    else{
32
      Output = 0;
33
    }
34
    taktzaehler++;
35
  }
36
  
37
  if(taktzaehler>=MAX_WERT){
38
    taktzaehler = 0;
39
    multi_zaehler = 1;
40
  }
41
}

Außerhalb kann dann neuer_Wert gesetzt werden, die dann nach Vollendung 
der Ausgabe geladen wird.

Der Code ist aber nicht getestet und ggf. können auch manche globalen 
Variablen als static in die isr..

In deinem Fall würde ich aber auch eine PWM nehmen.

: Bearbeitet durch User
von jonas biensack (Gast)


Lesenswert?

>Das ist in etwa so sinnvoll wie deinen Kaffeetasseninhalt auf 1mg genau
>abzuwiegen :-(

:D Ich habs vorher schon geahnt - dachte aber Karl-Heinz checkt das 
ab...

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Ohne jetzt den Code auf Funktionalität gecheckt zu haben, stößt mir 
direkt folgendes auf:

  multiplikator = (float) MAX_WERT / aktueller_Wert;

Einen Interrupt-Takt später dann:

  temp = (uint8)(multi_zaehler * multiplikator + 0.5);

Auch wenn Deine ISR() nur 100 mal pro Sekunde aufgerufen wird, halte ich 
hier die Verwendung von einem Float (was auf dem AVR richtig viel 
Rechenzeit kostet) für eine Kanone auf Spatzen.

Das kann man auch so machen:

  static uint8_t letzter_Wert;
  ...
  letzter_Wert = aktueller_Wert;

Einen Interrupt-Takt später dann:

  temp = (multi_zaehler * MAX_WERT) / letzter_Wert;

Dadurch, dass ich erst multipliziere und dann dividiere, verliere ich 
gegenüber der float-Division + float-Multpilikation überhaupt keine 
Genauigkeit. Denn was Du nachher haben willst, ist sowieso eine ganze 
Zahl.

: Bearbeitet durch Moderator
von Tom (Gast)


Angehängte Dateien:

Lesenswert?

Willst Du das im Anhang? Also Zeile (Y) je nach PWM-Wert wählen, 
periodisch von links nach rechts durch die Zeile wandern (ein x pro 
ISR-Aufruf) und den Pin nach der Farbe auf 0/1 setzen.

Das wäre nämlich die vorgeschlagene PWM a la Bresenham. Das war ein 
interessantes Nachmittagstee-Problem...

von Anton G. (vergil8)


Lesenswert?

Haha es tut mir Leid es war zu einem großen Teil ein Intresse-Ding :P

von Tom (Gast)


Lesenswert?

Anton G. schrieb:
> großen Teil ein Intresse-Ding

Bei mir auch ;)

von Anton G. (vergil8)


Lesenswert?

Tom schrieb:
> Willst Du das im Anhang?

Gerne. Ich schaue jetzt mal wie ich es genau mache aber danke euch allen 
;)

Die Lösung mit der normalen PWM hatte ich schon vor dem Forumeintrag

: Bearbeitet durch User
von Ralph (Gast)


Lesenswert?

Anton G. schrieb:
> Da ich das
> ganze über die Nulldurchgäge der Netzspannung dimensioniere(50Hz==> 100
> Nulldruchgänge) brauche ich eine Sekunde um 100 verschiedene Werte
> einstellen zu könne(eig wäre 101 Werte perfekt, 0-100%).

Also du willst eine variable Phasenanschnittschaltung.

Das gibt es schon länger.

Nahezu jeder Dimmer für Wechselstrom ( die Typen die auch Induktive 
Lasten vertragen) arbeitet nach dem Prinzip.

Eine µC PW; mit dem Nulldurchgang zu synchronisieren geht aber macht 
nicht wirklich Sinn.

von Tom (Gast)


Lesenswert?

Anton G. schrieb:
> Ich schaue jetzt mal wie ich es genau mache

http://en.wikipedia.org/wiki/Bresenham's_line_algorithm#Algorithm_with_Integer_Arithmetic
Ansatz: dx ist die Auflösung/Anzahl der Perioden (oben 32), dy der 
(variable) PWM-Wert.

von Tom (Gast)


Lesenswert?

Ralph schrieb:
> Eine µC PW; mit dem Nulldurchgang zu synchronisieren geht aber macht
> nicht wirklich Sinn.

Nuhr. http://de.wikipedia.org/wiki/Schwingungspaketsteuerung ist bei 
Heizungen genau richtig. OP sollte aber ganze Schwingungen und nicht 
Halbwellen schalten, sonst pumpt er bei genau 50% reichlich DC ins Netz.


"Ziel ist die Ansteuerung mit einer Pulsfolge, die kaum niederfrequente 
Signalanteile enthält. Erreicht wird dies durch Verwendung von 
Delta-Sigma-Modulatoren zur Ansteuerung."

Ist Delta-Sigma-Modulation hier nicht eigentlich das gleiche wie das, 
was der Bresenham macht?

von Anton G. (vergil8)


Lesenswert?

Tom schrieb:
> OP sollte aber ganze Schwingungen und nicht
> Halbwellen schalten, sonst pumpt er bei genau 50% reichlich DC ins Netz.

Oh stimmt daran habe ich noch garnicht gedacht! Danke!

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.