Verteilung von X Einheiten in 100 Einheiten(PWM)

#3832673
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
#3832704
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.
#3832814
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.
#3832947
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 =)
#3832957
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.
Moderator Persönliche Seite #3832999
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.
#3833008
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.
#3833011
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.
Gast #3833033
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.
#3833046
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.
#3833071
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. :)
#3833082
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 :-(
#3833092
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.
Moderator Persönliche Seite #3833106
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.
Gast #3833109
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...
Angehängte Dateien:
Gast #3833148
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.
Gast #3833168
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?

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren