Forum: Mikrocontroller und Digitale Elektronik ATMega 48/168 PWM Verständnissprobleme.


von Sascha S. (Firma: None) (saschas)


Lesenswert?

Hallo, ich bin recht frisch bei µControllern.

Vorkenntnisse bestehen zwar ein paar aber nicht wirklich Berühmt.

Mein Ziel, Schalten, Dimmen, Messen-Fühlen... Verarbeitung via AVR 
Zugriff und Eingreifen via PC.

Im Moment hänge ich etwas an der PWM Geschichte, weil ich recht 
Kostengünstig ca 30 PWM Kanäle brauche (Akut nur 6, später mehr) welche 
auch nicht aus SMD Bauteilen bestehen sollte.

Hierbei bin ich dann auf diese Seite gestossen:

http://mino-elektronik.de/AVR_PWM_64/AVR_PWM_64.htm

64 PWM Kanäle an einer ATMega48, die ist recht Kostengünstig, die 
anderen Bauteile sind auch keine SMD Bauteile nur habe ich Verständniss 
Probleme...

Zum einen kann ich hiermit nichts Anfangen...

Zitat:
"
Damit werden für die Ausgabewerte 1 und 255 rund 80Hz Pulsfrequenz 
erzeugt. Auf Grund des speziellen PWM-Verfahrens wird die maximale 
Pulsfrequenz beim Wert 128 erreicht und beträgt dann ca. 8kHz.
"

Ich verstehe nicht warum die PWM Frequenz vom Wert abhängen sollte, 
immerhin sollte das Tastverhälniss und nicht die Frequenz geändert 
werden oder deute ich das Zitat falsch?.

Wie dem auch sei, 80Hz wären mir zu niedrig und 8Khz, das machen meine 
Konstantstromquellen nicht mit... aber dennoch dachte ich schaue mir das 
ganze mal genauer an, um daraus zu lernen.

Nun habe ich mir den Sourcecode angeschaut und festgestellt das Header 
included sind die wohl bei IAR und CrossWork Editoren dabei sind.

OK die Kickstart edition von IAR heruntergalden und nachgesucht... hier 
kommt mein zweites Verständnissproblem...

CODE (pwm64.c):
#include <ina90.h>  // fuer _WDR() + __enable_interrupt()

--------

So nun hab ich die Dateien die Included sind Gegoogelt...

Verstehe ich das Richtig das :
CODE(ina90.h--->avr):
#define _SEI() _asm__ __volatile_ ("sei")
...
#define _WDR() _asm__ __volatile_ ("wdr")
--------

bedeutet das der Call WDR () nichts anderes macht als einen watch dog 
reset?

UND das :
CODE(comp_a90.h--->IAR):

#ifndef _SEI
#define _SEI() __enable_interrupt()
#endif
--------

der Call __enable_interrupt() nichts anderes macht als das Interrupt 
auszulösen? Da der Call SEI() ja in ina90.h als inline Assembler befehl 
"sei" definiert ist?

Sorry für diese ggf. dumme Fragen.

Grüsse

von Krapao (Gast)


Lesenswert?

> der Call __enable_interrupt() nichts anderes macht als das Interrupt
> auszulösen?

Nicht Auslösen, sondern Enable d.h. Erlauben

von m.n. (Gast)


Lesenswert?

Zu den Programmen "pwm_sub.s90" und "pwm_64.c" gibt es Vorgänger, die Du 
Dir ansehen solltest:

http://www.mino-elektronik.de/soft-pwm/pwm_software.htm#l2
http://www.mino-elektronik.de/soft-pwm/pwm_software.htm#l3

Bezogen auf "pwm_sub.s90" wird für jeden Kanal der 'pwm_wert' zu 
'pwm_summe' addiert. Ensteht dabei ein Übertrag, wird der betreffende 
Ausgang gesetzt und gelöscht, wenn kein Übertrag auf getreten ist. Der 
Rest in 'pwm_summe' wird jedoch nicht gelöscht!

Angenommen, die pwm-Routine wird 256x aufgerufen:
Für 'pwm_wert' = 1 wird 255x der Ausgang gelöscht (bleibt auf '0') und 
bei der 256. Addition ein Übertrag erzeugt und der Ausgang gesetzt '1'.
Es ensteht eine Pulsfolge von 255x '0' und 1x '1'. Das Tastverhältnis 
ist 1/256.
Erhöht man 'pwm_wert' auf 2, so tritt der 1. Übertrag bei der 128. 
Addition und ein weiterer bei der 256. auf.
Es ergibt sich die Pulsfolge 127x '0', 1x '1', 127x '0', 1x '1'. Das 
Tastverhältnis ist nun 1/128 aber bei doppelter Ausgabefrequenz; der 
eff. Ausgabewert ist 2/256.

Im Gegensatz zur 'normalen' PWM, bei der der '1' Impuls entsprechend 
verlängert wird, werden hier die '1' Impulse möglichst gleichmäßig auf 
das ganze Intervall (hier 256) verteilt. Entspechend liefert der Ausgang 
bei 'pwm_wert' = 128 eine stetige Folge von '1' und '0' mit 128facher 
Ausgabefrequenz.

Sascha Sauren schrieb:
> immerhin sollte das Tastverhälniss und nicht die Frequenz geändert
> werden

Es ändert sich Beides mit dem Ziel einer möglichst hohen 
Ausgabefrequenz.

Die Compiler spezifischen Befehle SEI, WDR (oder ARD und ZDF) muß man 
nur einmal verstehen und an den eigenen Compiler (Fernseher) anpassen.

von Sascha S. (Firma: None) (saschas)


Lesenswert?

Hallo und Danke erstmal, ja das mit sei und so weiter hab ich inzwischen 
raus, Danke auch für die Email.

Bei dem Rest habe ich noch immer Verständnis Probleme...

Das mit dem Übertrag ist soweit angekommen, ganz genau hab ich es noch 
nicht kapiert (also noch kein Bild im Kopf) aber warum sich die Frequenz 
ändert habe ich begriffen.
Ich schaue mir die Vorgängerprogramme nochmal an und werde auch nochmal 
die Abschnitte unter Register lesen, Danke... Ich habe zu Atari Zeiten 
schon Assembler gehasst und nicht verstanden:(.

Allerdings bleibt eine Sache die jetzt damit nichts zu Tun hat 
unverständlich.

Wenn ich mir die pwm64.c anschaue dann sehe ich darin das "pwm_ausgabe" 
aufgerufen wird in Zeile 130, zu dem Zeitpunkt ist "kanal_summe" bekannt 
aber leer, demzufolge werden ja auch die Register auf 0 gesetzt, wie im 
Kommentar zu lesen ist.

Dann werden in "kanal_summe[]" unterschiedliche Anfangswerte abgelegt... 
diese werden auch nie wieder irgendwo verändert bleiben also IMMER 
gleich.

Dann wird pwm_strobe aufgerufen... für "pwm_summe" steht logischerweise 
alles auf 0.

Nun wird ja durch Timer Interrupt und Handler alle 150 (*8 Vorteiler) 
Tackte , erstmal der Timer wieder neu gesetzt, der Strobe zwecks 
Schreibsignal für die Schieberegister aufgerufen und dann, um alle in 
der Zeit angekommenen und in "kanal[]" zwischen gespeicherten Werte
in "pwm_wert" zu übertragen die "pwm_ausgabe" wieder aufgerufen... UND 
JETZT kommt das was ich hier nicht verstehe:

In "pwm_ausgabe" wird zwar pwm_summe aus dem Stack geholt aber dann 
sofort mit dem Y Register in dem "kanal_summe[]" mit unveränderlichen 
Werten steht, somit wird bei:

    ld pwm_summe,-Y    ; Kanal 0 ist letztes Bit !
    add pwm_summe, pwm_wert  ; zur Summe addieren

doch immer von "kanal_summe[]" Ausgegangen und da stehen die Werte 
0,4,8,12,16,20... bis 256 drin.

Ergo wird jeder neue Wert für die "pwm_Ausgabe" immer wieder mit dem 
selben "Ausgangswert" für den Kanal welcher in "kanal_summe" bei 
Programm initiierung festgelegt wurde Multipliziert...

Vielleicht muss ich mir das ganz nochmal nen Tag durch den Kopf gehen 
lassen.

Ich bedanke mich jedenfalls jetzt schon mal für die schnelle Antwort und 
die Erklärung.

Grüsse

von m.n. (Gast)


Lesenswert?

Ich kommentiere mal für Dich verständlicher:

ld pwm_summe,-Y          ; Y -=1 und lade pwm_summe indirekt über Y
add pwm_summe, pwm_wert  ; addieren
rol temp                 ; Uebertrag verwerten
st Y, pwm_summe          ; speicher pwm_summe indirekt über Y

Y ist ein Indexregister und zeigt auch nicht auf einen Stack!

Beim 6502 hätte man geschrieben:
DEY
LDA (kanal_summe),Y
CLC
ADC pwm_wert
STA (kanal_summe),Y
LDA temp
ROL A
STA temp

Ohne Gewähr :-)

von Sascha S. (Firma: None) (saschas)


Lesenswert?

Erstmal entschuldigung für den langen langen Text, aber Beispiele 
brauchen Platz.

Ja diesen "Rechen" Teil hatte ich soweit auch verstanden und als ich vom 
Stack redete meinte ich die push und pop am Anfang und Ende.

Aber was mich in Bezug aus "kanal_summe" bzw. "pwm_summe" meinte möchte 
ich an einem Beispiel Erklären:

Beispiel(jetzt nur das Wichtigste)->

Neustart...  (C++ arbeitet alles ab bis hier...)

pwm_64.c:
__
init_timer0();
  SPCR = 0x5c;    // SPI master-mode, pos. clock, 1/4 Takt
  init_twi();    // nur empfangen neuer PWM-Werte

  pwm_ausgabe();  // Register auf 0 setzen
__

Hier gehts dann in der asm weiter:

pwm_sub.s90

--
    ldi r28, LOW(kanal_summe+MAX_KANAL)
    ldi r29, HIGH(kanal_summe+MAX_KANAL)
--

Lade ohne Umwege den Inhalt der High Und Low Bytes aus den Adressen die 
du von kanal_summe her kennst.Adressen kanal_summe+MAX_KANAL

Dann noch ein Stück weiter kommt das oben angesprochene:
--
    ld pwm_summe,-Y
--
So und hier liegt der Knackpunkt. in c++ wird in der nächsten zeile:

__
k = 0;    // ungleiche Startwerte verringern den Spitzenstrom
  for(i = 0; i < MAX_KANAL; i++) {
    kanal_summe[i] = k;
    k += 4;             // 4er Abstand
  }
__

die kanal_summe die vorher zwar definiert aber leer war mit zahlen in 
4er Abständen gefüllt von 0 - 252 .


Also steht da nun 0,4,8,12,16... das bleibt auch so da drin stehen...

kanal_summe wird nun jedes mal beim Aufruf von pwm_ausgabe() erneut mit 
den selben zu Beginn festgelegten Zahlen ins Y Register geladen.

Und dieses dann :
--
    ld pwm_summe,-Y
--
in die pwm_summe


soweit so gut, nur eine Zeile danach:

--
    add pwm_summe, pwm_wert
--

Addiere pwm_wert zu pwm_summe und speichere das Ergebnis in pwm_summe,

Da pwm_summe aber vorher aus Y geladen welche kanal_summe speichert( 
also auch nach der xten Eingabe von anderen Werten noch immer 
0,4,8,12,16.... ist)wird sich auch immer das Ergebnis und viel wichtiger 
der Ausgabewert temp auf kanal_summe beziehen und nicht auf einen 
Refernzwert von vorher und dies auch noch bei jedem Kanals anders.


Also kanal_summe für die ersten 4 "Kanäle" nach der Initiierung...

00000000 -> 0
00000100 -> 4
00001000 -> 8
00001100 -> 12

Nun möchten wir überall den Wert "9" 00001001 einstellen also wir 
addiert:

00001001
00000000
______
00001001

also wär das erste Byte was ausgegeben werden würde:
00001001

auch beim zweiten Byte geben wollen wir "9" einstellen.

00001001
00000100
______
00001101

das sieht schon anders aus ...

Nummer 3:

00001001
00001000
______
00010101

und für 4:

00001001
00001100
______
00010101.

...

4 unterschiedliche Bytes bei der selben Eingabe.

Das ist das eine was ich nicht verstehe... warum hier kanal_summe einmal 
gesetzt, nie verändert und immer wieder benutzt wird, wodurch 
unterschiedliche werte raus kommen.

Das andere ist, das hier bei der Eingabe der neuen Werte via TWI. (ich 
weiss nicht ob das ein Protokoll ist ich nenns einfach mal so) zu 
ersehen ist das der erste Wert der Ankommt in "teste_twi()" die KANAL 
NUMMER ist...

Sagen wir ich möchte die LED´s welche über eine Konstantstromquelle am 
Ersten Ausgang des ersten Schieberegler (das nenne ich jetzt mal den 
Kanal also am ersten KANAL) mit 100% einschalten, das wäre dann (ich 
trenne mal mit "|" )

0x00    | 0xFF
00000000|11111111 (nur zur orientierung)

Kanalnummer | Wert

das geht dann auch so maximal 64 byte lang, das heisst das stimmt nicht 
ganz, wenn ich am Anfang als Kanalnummer nun einen Mittleren Kanal so 
Kanal 32 Angebe dann ist diese Erfüllung hier:


if(kanal_nr < MAX_KANAL) {
        kanal[kanal_nr] = TWDR;  // PWM-Werte uebernehmen
        kanal_nr++;    // auto-increment

doch schon nach weiteren 32 durchläufen (byte) nicht mehr erfüllt.
Kanal 63 bekäme genau 1 byte mögicher länge.

aber egal, gehen wir davon aus ich hab da nun meine 0xFF als wert drin, 
die wird nun erstmal in kanal[kanal_nr] drin.

Lassen wir das Programm weiter laufen, pwm_ausgabe wird Aufgerufen.
das erste Byte wird verabeitet und ausgegeben:

11111111

Anstelle des Wert 255  0xFF  11111111 auf dem ersten Anschluss hab ich 
da nun eine 1, ok ist auch 100%, würde ich aber 50% (0x7f -> 01111111) 
einstellen, hätte ich keine 50% sondern dauer 0, dafür wären kanäle 1-7 
dann auf 100% dabei wollt ich die gar nicht einschalten.

Verstehe ich nun auch Schieberegister nicht richtig?

Wie gesagt nochmal sorry für den langen Text und ich hoffe es ist nicht 
totaler humbug den ich da geschrieben habe.

Ich werde nochmal versuchen alleine hinter die kanal_summe Geschichte zu 
kommen und warum ich zwar Verstehe wie es laufen sollte aber nicht 
nachvollziehen kann das es das auch tut, für Hinweise bin ich natürlich 
sehr Dankbar.

Grüsse

von m.n. (Gast)


Lesenswert?

Sascha Sauren schrieb:
> Das ist das eine was ich nicht verstehe... warum hier kanal_summe einmal
> gesetzt, nie verändert und immer wieder benutzt wird, wodurch
> unterschiedliche werte raus kommen.

Nach der Initialisierung hängt das Programm in der do-while-Schleife 
ganz am Ende des Programmes fest. Die PWM läuft im Hintergrund per 
Timer0-Interrupt und verändert permanent die Werte von kanal_summe!

Dein TWI-Problem verstehe ich nicht. Du kannst das Programm doch nach 
Herzenslust Deinen Wünschen anpassen. Wenn Du einen Programmfehler 
findest, korrigiere ihn einfach.

Generell solltest Du Dich näher mit den AVR-Befehlen beschäftigen, um 
die Assembler-Routinen nachvollziehen zu können. Falls Du ein 
Simulationsprogramm hast, sieh Dir an, wie wo was passiert.

von Sascha S. (Firma: None) (saschas)


Lesenswert?

Hmm naja nichts für ungut, ich dachte halt sie hätten das schon mal 
gebaut und wüssten ob es funktioniert.

kanal_summe wird im gesammten code ein mal gesetzt und nie mehr 
verändert.

es wird in Register Y geladen
und von dort aus im pwm_summe.

Ich es gibt im ganzen code nur diese Zeile wo kanal_summe gesetzt wird:

kanal_summe[i] = k;

und das wars.

Wie dem auch sei, Danke für Zeit und Mühe.

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.