Forum: Mikrocontroller und Digitale Elektronik Software PWM auf 8 Kanälen


von Heiko (Gast)


Lesenswert?

Guten Abend

Also ich habe vor einiger Zeit mit AVR angefangen.
So mit meinem neuen Wissen würde ich gern ne Idee von mir in die Tat
umsetzen d.h. u.a. SoftwarePWM auf 6 Kanälen ---> tja das ist nicht so
einfach.
Habe das Forum durchsucht, ohne Erfolg. Suche ein Beispiel/Tutorial wo
SoftwarePWM auf mehreren Kanälen erklärt wird und das in Assembler -->
mit C Codes (wie z.B. hier im Forum) komme ich nicht wirklich klar.
In meinem Buch "Franzis` AVR-RISC Mikrocontroller" ist ein Beispiel
drin, tja da würde ich 24Register verbrauchen --> nicht akzeptabel (der
AVR soll später noch mehr können d.h dazu brauche ich Register)

Habe auch schon unter Google gesucht --> tja der Erfolg war nett so
besonders.

Kann mir jemand weiter helfen ???

herzlichen Dank an alle User.

von ...HanneS... (Gast)


Lesenswert?

Hi...

Du brauchst erstmal einen Timer-Interrupt, der alle n Takte aufgerufen
wird. (Der Einfachheit halber ersmal Timer0-Überlauf.)

In der ISR dazu zählst du ein (oberes) Register (nennen wir es PWZ,
PWM-Zähler) hoch und vergleichst es mit dem Endwert (Konstante). Ist
der Endwert (Zählumfang) erreicht, so wird PWZ auf 0 gesetzt, damit von
vorn gezählt wird. Außerdem werden alle 8 PWM-Ausgänge aktiviert.

Nun wird in jedem Int-Durchlauf PWZ nacheinander mit den 8 Sollwerten
verglichen, die in 8 (unteren) Registern stehen, und bei Gleichheit
(besser beim Überschreiten) wird der zugehörige PWM-Ausgang
zurückgesetzt.

Das kostet ein Zählregister und 8 Sollwertregister, also 9 Register.
Dazu kommt noch ein Register für den Timer-Reload und ein Register zum
Sichern des SREG. Das sind dann 11 Register und immernoch keine 24...

Im Hauptprogramm kannst du dann die Sollwerte manipulieren, Sollten
diese per ADC (Potis) eingelesen werden, könnte das auch in der ISR
geschehen. Dazu bei jedem PWZ-Überlauf (also wenn PWZ auf 0 gesetzt
wird) ADC auslesen, PWM-Sollwert errechnen, per Y-Pointer (indiziert)
in das entsprechende Register schreiben und danach Y-Pointer und
ADC-Quelle (ADMUX) auf den nächsten einzulesenden Wert einstellen, so
dass beim nächsten PWZ-Überlauf der nächste ADC-Wert verfügbar ist.
Sind alle ADC-Eingänge abgeklappert, beginnt man von vorn (Vergleich
von YL mit Konstante).

...

von Peter D. (peda)


Lesenswert?


von ...HanneS... (Gast)


Angehängte Dateien:

Lesenswert?

Moin...

Ich kann das Addieren in den ASM-Beispielen (optimierte Lösung für AVR)
in Peters Link (noch) nicht nachvollziehen. Das "Einsammeln" der
Carry-Zustände ist aber verdammt gut, spart gewaltig Rechenzeit.

Ich stelle mal eine weitere 8-Kanal-Software-PWM im Anhang zur
Diskussion. Ist mit Mega8535, liest 8 Potis per ADC ein und erzeugt die
zugehörige PWM. Könnte für ernsthafte Anwendung mit einer Tabelle
nachgerüstet werden, mit der die PWM-Werte verändert werden können
(Kennlinie für Motordrehzahl oder Lampen-Helligkeit, je nach
Anwendung).

...

von Bernhard (Gast)


Lesenswert?

Hallöchen Hannes,

ich beschäftige mich gerade mit Deinem Programm,

es ist sehr interessant, sehr raffiniert und hat mir schon viele

Stunden gekostet.

Nun meine Frage?

Der Sollwert wird in das Sollwert-Register (SRAM) geschrieben
.
.
st y,wl               ;und ins Sollwert-Register

doch an welcher Stelle erfolgt dann die direkte Zuweisung

an "soll0" "soll1" usw?

Gruß Bernhard

von ...HanneS... (Gast)


Lesenswert?

Tach Bernhard...

Da wird nix ins SRAM geschrieben, sondern ins Register. Steht ja auch
im Kommentar.

Schau dir mal an, welchen Wert y hat. Die Pointer können nämlich nicht
nur auf SRAM zeigen.

Und bitte das Ergebnis deiner Analyse nicht in der Codesammlung
veröffentlichen. Denn so gut ist mein Programmbeispiel nun auch
wieder nicht.

...HanneS...

von Andi (Gast)


Lesenswert?

Hi!

Wieso muß eigentlich alles über nicht mehr anders verwendbare Register
laufen?
Und dann noch beim 8535?

Geht doch auch so:

 ldi zl,low(Sollwerte)
 ldi zl,high(Sollwerte)
 ldd r16,y+Soll0
 cp pwz,r16            ;Sollwert0 erreicht?
 ror wl                ;Ergebnis (Carry) sichern
 ldd r16,y+Soll1
 cp pwz,r16            ;Sollwert1 erreicht?
 ror wl                ;Ergebnis (Carry) sichern
 ldd r16,y+Soll2
 cp pwz,r16            ;Sollwert2 erreicht?
 ror wl                ;Ergebnis (Carry) sichern
 ldd r16,y+Soll3
 cp pwz,r16            ;Sollwert3 erreicht?
 ror wl                ;Ergebnis (Carry) sichern
 ldd r16,y+Soll4
 cp pwz,r16            ;Sollwert4 erreicht?
 ror wl                ;Ergebnis (Carry) sichern
 ldd r16,y+Soll5
 cp pwz,r16            ;Sollwert5 erreicht?
 ror wl                ;Ergebnis (Carry) sichern
 ldd r16,y+Soll6
 cp pwz,r16            ;Sollwert6 erreicht?
 ror wl                ;Ergebnis (Carry) sichern
 ldd r16,y+Soll7
 cp pwz,r16            ;Sollwert7 erreicht?
 ror wl                ;Ergebnis (Carry) sichern
 com wl                ;invertieren wegen L-aktiven Ausgängen
 out aus,wl            ;Ergebnisse ausgeben

.dseg
SollWerte: .byte 8
.equ Soll0=0
.equ Soll1=1
.equ Soll2=2
.equ Soll3=3
.equ Soll4=4
.equ Soll5=5
.equ Soll6=6
.equ Soll7=7

Möchte man für jeden Kanal dann noch andere Periodenzeiten, dann ist es
auch möglich, sich ein kleines Array mit 2 Parametern mal 8 Kanäle zu
machen:

.dseg
.equ Channels=8
PWMRegs=Channels * 2
.equ Sollwert=0
.equ PWZ=1

Mit einer Schleife läuft man dann alle durch:

 ldi yl,low(PWMRegs)
 ldi yl,high(PWMRegs)
loop:
  ldd r16,y+            ;Sollwert nach r16
  ldd pwz,y+            ;Periodenzeit nach pwz
  cp pwz,r16            ;Sollwert7 erreicht?
  ror wl                ;Ergebnis (Carry) sichern
  cpi zl,low(PWMRegs+2*Channels) ;Ist das letzte bearbeitet?
 brne loop
 com wl                ;invertieren wegen L-aktiven Ausgängen
 out aus,wl            ;Ergebnisse ausgeben

Sind dann halt 9 Takte je Kanal (72) aber man hat mehr Möglichkeiten.
Und wenn man in der Timer-ISR ein paar 100 Takte Zeit hat, ist as kein
Problem.

@Hannes: In dem Sammelregister w1 werden die Vergleichswerte
gespeichert welche beim OUT gleich den High- oder Low-Pegel für die
Hardware entsprechen.
Das COM ist nur dazu da, um aus einem high ein low zu machen und
umgekehrt.
Wenn man die angeschlossene Hardware auf aktiv-low auslegt, kann man
sich das COM sparen.

Gruß
Andi

von Andi (Gast)


Lesenswert?

Ach ja!
Die Zugriffe im .dseg-Bereich des ersten Beispieles erfolgen dann z. B.
mit "STS Sollwerte+3,r16" auf Kanal 3 oder "STS Sollwerte+7,r16" auf
Kanal 7.
Oder, bei mehreren Zugriffen sparsammer, mit

 ldi yl,low(Sollwerte)
 ldi yh,high(Sollwerte)
 ldi r16,nnn
 std y+Soll3,r16 ;Kanal 3 verändern
 ldi r16,nnn
 std y+Soll5,r16 ;Kanal 5 verändern
 ldi r16,nnn
 std y+Soll7,r16 ;Kanal 7 verändern

Im 2. Beispiel geht das dann z. B. mit "STS Channels+5*2,r16" für
Kanal 5.
Zum ändern der Periodenzeit (PWZ) für Kanal 5 dann mit
"STS Channels+5*2+1,r16".

Gruß
Andi

von Andi (Gast)


Lesenswert?

... mal wieder viiiel zu voreilig :-(

Erstes Beispile beginnt mit

 ldi yl,low(Sollwerte)
 ldi yh,high(Sollwerte)
 ldd r16,y+Soll0
 cp pwz,r16            ;Sollwert0 erreicht?
 ror wl                ;Ergebnis (Carry) sichern
.....

das zweite mit

 ldi yl,low(PWMRegs)
 ldi yh,high(PWMRegs)
loop:
.....

autsch...

von ...HanneS... (Gast)


Lesenswert?

Hi Andi...

Aber sicher geht das auch im SRAM.

Der Grund, warum ich das in Registern ablegte, war der, dass diese
Werte bei jedem Timer-Int gebraucht werden. Da oftmals eine hohe
PWM-Frequenz gewünscht wird, sollte die ISR möglichst schnell
abgearbeitet werden. Da lege ich mir lieber seltener gebrauchte
Variablen ins SRAM, falls die Register knapp werden.

Bei umfangreicheren Programmen geht diese Denkweise allerdings nicht.

Gruß...
...HanneS...

von Andi (Gast)


Lesenswert?

AUUUUUUUUUUUUUUUUUUUAAAAAA!

 ldi yl,low(PWMRegs)
 ldi yh,high(PWMRegs)
loop:
  ldd r16,y+            ;Sollwert nach r16
  ldd pwz,y+            ;Periodenzeit nach pwz
  cp pwz,r16            ;Sollwert7 erreicht?
  ror wl                ;Ergebnis (Carry) sichern
  cpi yl,low(PWMRegs+2*Channels) ;Ist das letzte bearbeitet?
 brne loop
 com wl                ;invertieren wegen L-aktiven Ausgängen
 out aus,wl            ;Ergebnisse ausgeben

.dseg
.equ Channels=8
PWMRegs: .byte Channels * 2
.equ Sollwert=0
.equ PWZ=1

In Zukunft lass ichs lieber bleiben!

Gruß
Andi

von Dirk Broßwick (Gast)


Lesenswert?

Hallo,

schaue mal ganz unten in dem beitrag nach, da hatte ich sowas mal mit
tabellen erklärt was sehr schnell ist und nicht viel rechenzeit
verbraucht und im timerinterupt läuft.

CA

von Andi (Gast)


Lesenswert?

@Hannes: Und wie hoch ist die gewünschte PWM-Frequenz?
Habe bei 10KHz, für meine Anwendung passend, und 16MHz 1600 Takte je
Timer-ISR.
Ginge mit der Schleifenmethode auch locker mit 100KHz.
Bei 200KHz muß man die Schleife in einzelne Codezeilen auflösen sofern
es nicht weniger 8 Kanäle sein sollen.

Gruß
Andi

von Dirk Broßwick (Gast)


Lesenswert?

den link sollte man auch schicken :-)

http://www.mikrocontroller.net/forum/read-1-31884.html#new

CA

von ...HanneS... (Gast)


Lesenswert?

Warum Aua??? - Ich habe dich doch nicht gehauen... ;-))
(würde ich niemals nie nicht tun...)

Allerdings habe ich auf die Schnelle deine Beispiele auch nicht näher
analysiert da ich ja wusste, was du damit ausdrücken wolltest.

Eine Bitte: Lass es bitte in Zukunft nicht bleiben, ich habe schon
manche gute Idee beim Lesen deiner Beiträge bekommen.

Gruß...
...HanneS...

von ...HanneS... (Gast)


Lesenswert?

Andi, die 10kHz (bei 16MHz und 1600 Takte) sind ja erstmal der
PWM-Zähltakt. Den muss man noch durch den PWM-Zählumfang teilen, dann
kommt es hin. Nimmt man eine feine Abstufung, so wird es doch relativ
langsam für einen Motor. Es gibt immer Leute, die mögen es nicht, wenn
die PWM im Motor pfeift.

Mir dem Beispiel wollte ich Einsteigern eigentlich zeigen, dass es auch
anders geht, als:
- ADC-Quelle umschalten,
- Messung wegwerfen,
- ADC-Flag abfragen, bis nächste Messung fertig ist,
- endlich (nach langem Warten) einen gültigen Messwert einlesen.

Und dann wollte ich die bei Peter gesehene Methode mit dem "Einsammeln
der Carrys" umsetzen.

Es war ein reines Anfängerbeispiel und muss daher nicht als "das
einzig Wahre" angesehen werden.

...HanneS...

von Andi (Gast)


Lesenswert?

Das mit dem AUA betraf mich von mir! :-)
Da hat man mal lust, schnell was reinzuhacken, und dann Fehler über
Fehler.
DAS TUT EINFACH WEH wenn mans nacher liest.
Aber wie war das mit den Frequenzen?
Wie hoch ist (war) bei Dir bisher die gewünschte PWM-Frequenz?
Für Licht sind ja 50Hz * z. B. 100 Regelstufen = 5KHz in Ordnung, auch
für Motoren.

@Dirk Broßwick: Deine Art der PWM-Erzeugung ist ja nicht schlecht aber
man benötigt für eine höhere Auflösung mehr RAM.
Und CBI und SBI sind auch nicht gerade fix (2 Takte) davor noch der
Vergleich mit cpi x,y und davor noch das lesen aus dem RAM.
Möchte nicht wissen, wieviel ASM-Code und Taktzyklen  aus Deinem C-Code
dann je Kanal anfallen.

Gruß
Andi

von Andi (Gast)


Lesenswert?

...das waren Sekunden!

Gruß
Andi

von Dirk Broßwick (Gast)


Lesenswert?

@Andi

ich weiss nicht ob du meinen asm-code richtig gelesen hast, aber ich
finde weder die besagten befehle CBI und SBI, weder ist der code in C
geschrieben. Und das du mit einer höheren PWM-auflösung mehr sram
verbrauchst ist denke ich mal vertretbar wenn man eine auflösung von
256 als max wählt und 256 byte verbraucht, soviel hat jeder atmega
über. Und als bemerkung noch, es werden pro interrupt 8 kanale
bearbeitet bei 29 Takte! und es werden keine Register belegt, man kann
also im hauptprogramm normal weiter arbeiten, das war ziel dieses
programm, weil anfänger meist über schnell hingebaute routinen stolpern
wenn sie eigene sachen dazubauen und über die belegten register sich
ärgern :-)

CA

von Andi (Gast)


Lesenswert?

Bin in Deinem Link versehentlich eins hoch gerutscht zu einem Post von
einem Dirk und habe da alles durcheinander gebracht.
Mein Fehler. Tschuldigung!

Die Idee mit den vorberechneten An/Aus-Phasen ist sehr gut.
Allerdings ist das wirklich nur was für die Megas wegen der RAM-Nutzung
und wenn jeder PWM-Kanal die selbe Periodenzeit haben kann.
Bei bestimmten Anwendungen ist das natürlich auch was für die Tinys mit
128Byte SRAM.

Gruß
Andi

von Tim L. (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,

ich habe eine PWM geschrieben, so wie sie ...HanneS... im 2. Beitrag
erklärt hat.
Wenn ich nur eine Ausgang setzt, läuft alles wie es soll. Verwende ich
aber mehrere Ausgänge, stimmt nix mehr. Die PWM Frequenz ist eine
andere und die Ein- und Ausschaltdauern stimmten auch nicht mit den
erwarteten überein.
Verwende ich z.B. für alle Ausgänge die gleich PWM Konstante, die dann
mit der Zählvariablen in jedem Interrupt verglichen wird , ist aber
trotzdem das PWM Signal an den Ausgängen unterschiedlich. Die Ausgänge
0-3 sind unterschiedlich und die Ausgänge 4-7 sind gleich, wobei die
Frequenz (bei 4-7) auch nicht die ist, die man erhält, wenn man nur
einen Ausgang verwendet.
Woran kann das liegen?
(Quelltext im Anhang)

Schönes Wochenende!

MfG Tim

von ...HanneS... (Gast)


Lesenswert?

Ich kann kein C, nehme aber an, dass dein Code zu lange dauern könnte
und dadurch Timer-INTs verschluckt werden können. Vielleicht sollte es
doch zeitoptimiert in ASM geschrieben werden.

...

von Tim L. (Gast)


Lesenswert?

Hmm, das könnte sein. Nur bin ich im Assembler nicht so firm... Die
Frage ist nur, ob der Code dann soooo viel schneller wird, dass nix
mehr verschluckt wird.

von ----- (Gast)


Lesenswert?

Der Code wird in ASM absolut schnell sofern man nicht durch gedankenlose
Programmierung Zeit verschenkt.

MfG
Andi

von Tim L. (Gast)


Lesenswert?

Hallo,

ich glaube es liegt nicht daran, dass ein Interrupt verschluckt wird.
Die ISR tritt alle 15µs auf und der ganze Interruptvorgang dauert 6µs.
Woran kann es noch liegen?

von Andi (Gast)


Lesenswert?

Und Du bist sicher, das der Code nur 6µS dauert?
OK, bei 16MHz wären das dann 96 Takte die das Prog max. in Anspruch
nimmt und dazwischen eine Pause von 144 Takte.
Aber mgst Du die Frequenz von 66,6KHz nicht mal herabsenken auf z. B.
30KHz?
Laufen noch andere, länger dauernde Interrups?
Wie schnell ist Dein µC und welcher ist es?

MfG
Andi

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.