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
Bin mir nicht sicher. Aber ich denke 'Bit Angle Modulation' wäre was für dich.
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
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
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
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
Genau eine Antwort wie deine habe ich gebraucht!! Danke!!! Ich setzte es gleich mal in Code um und Poste dann meinen Ergebniscode!
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 =)
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
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)
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.
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
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.
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.
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
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.
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.
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
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.
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.
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 :-(
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.
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
>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...
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
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...
Haha es tut mir Leid es war zu einem großen Teil ein Intresse-Ding :P
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
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.
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.
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?
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.