Hi Ihrz...
Ich habe mal eine schnelle Frage:
Die AVR-Assembler-Befehle (für Attiny) BST und BLD lassen sich auf die
Register R0-R31 anwenden. Ich möchte die Befehle direkt auf das $REG
anwenden. Kann ich das $REG in den Registern R0-R15 finden?
Befehl bisher:
cp r16,r17
in r18,$REG
bst r18,2
bld r19,0
Gewünscht:
cp r16,r17
bst $REG,2
bld R19,0
Meinst Du das SREG? Das ist ein I/O-Register und hat einen separaten
Platz im Controller-Adressraum. Es gibt spezielle Befehle, um einzelne
Bits im SREG zu setzen und zu löschen.
SEC Set Carry
CLC Clear Carry
SEN Set Negative Flag
CLN Clear Negative Flag
SEZ Set Zero Flag
CLZ Clear Zero Flag
SEI Global Interrupt Enable
CLI Global Interrupt Disable
SES Set Signed Test Flag
CLS Clear Signed Test Flag
SEV Set Two’s Complement Overflow
CLV Clear Two’s Complement Overflow
SET Set T in SREG
CLT Clear T in SREG
SEH Set Half Carry Flag
CLH Clear Half Carry Flag
@ Knut Ballhause
So weit war ich bereits. Trotzdem danke.
Was ich möchte ist statt
in R16,SREG
bst R16,2
direkt
bst SREG,2
schreiben können. Ich hatte jetzt die Hoffnung, dass das SREG in einen
der Register von r0-r15 (Attiny13A) steht, da ich bst auf die Register
r0-r31 anwenden kann.
Tiny10Nutzer schrieb:> @ Knut Ballhause>> So weit war ich bereits. Trotzdem danke.
Warum schreibst du Dödel dann *$REG* statt SREG? Das war die Frage,
die sich alle Vorposter gestellt haben.
> Was ich möchte ist statt>> in R16,SREG> bst R16,2>> direkt>> bst SREG,2>> schreiben können.
Erstens ist das nicht das gleiche. Und zweitens ist das Leben kein
Wunschkonzert.
> Ich hatte jetzt die Hoffnung, dass das SREG in einen> der Register von r0-r15 (Attiny13A) steht
Welchen Teil von
Knut Ballhause schrieb:> Das ist ein I/O-Register und hat einen separaten> Platz im Controller-Adressraum.
hast du nicht verstanden?
Oder von
Knut Ballhause schrieb:> Es gibt spezielle Befehle, um einzelne> Bits im SREG zu setzen und zu löschen.
?
Bit 2 in SREG ist das N Flag. Dafür gibt es SEN und CLN.
XL
spess53 schrieb:> Und was ist der tiefere Grund für diesen Wunsch?
Hab ich auch eine Weile überlegt.
Er will sich offenbar das Ergebnis des COmpares (wenn ich jetzt nur
wüsste, welche Bedeutung Bit 2 im SREG hat) im T-Bit speichern.
Beantwortet noch nicht die Frage nach dem 'wozu' im Detail, aber das
scheint wohl die Absicht zu sein.
Karl Heinz schrieb:> spess53 schrieb:>>> Und was ist der tiefere Grund für diesen Wunsch?>> Hab ich auch eine Weile überlegt.> Er will sich offenbar das Ergebnis des COmpares (wenn ich jetzt nur> wüsste, welche Bedeutung Bit 2 im SREG hat) im T-Bit speichern.>> Beantwortet noch nicht die Frage nach dem 'wozu' im Detail, aber das> scheint wohl die Absicht zu sein.
Langsam: der Code ging ja noch weiter
1
cpr16,r17
2
bst$REG,2
3
bldR19,0
er will offenbar das Negative Flag vom SREG nach R19 übertragen.
Wenn er sagen würde, ob ihm die restlichen Flags vom SREG in R19 egal
sind oder nicht, dann würde ich ja sagen: übertrag halt einfach das
komplette SREG ins R19.
Hi
>Wahrscheihlich moechte er sich die Takte sparen... oder eine>Codezeile...
Ist schon klar. Aber was will er mit dem N-Flag in einem Register? Es
gibt Befehle, die direkt auf diese Flag reagieren: brpl und brmi. Oder
SBIS SREG,2/SBIC SREG,2.
MfG Spess
Welches ist der Wertebereich von R16 und R17? Liegt er günstig, setzt
der CP-Befehl das C-Flag gleich wie das N-Flag, und folgender Code tut
das von dir Gewünschte mit der gleichen Zyklenzahl:
1
lsr r19
2
cp r16,r17
3
rol r19
Musst du tatsächlich das N-Flag verwenden, geht das nicht so einfach.
Wer sich jetzt hier so schnell durchfindet wird merken, dass ich hier
nicht nur eine Zeile (einen Befehl) einsparen möchte. Der Sinn des
Ganzen ist hier Offtoppic (und sagt mir nur, dass die Diskutierenden
darüber nicht die richtige Antwort haben). Ich suche hier nur einen Weg
den Befehl bst etwas zweck zu entfremden.
Im Attiny13A ist das SREG in Adresse 63 (0x3F). Wenn ich jetzt wüsste
wie die Register r0-r15 aufgerufen werden, könnte ich vieleicht einfach
die Adressen austauschen (darüber nachdenkend).
@ Yalu X
Es muss das N-Flag sein. Ich brauche nach jeder Auswertung das N-Flag an
einer bestimmten Position des PortB.
@ der alte
Der aktuelle Zustand des PortB ist nicht wichtig und darf aber auch
nicht verändert werden. Der zeitliche Abstand mit dem auf PortB
geschrieben wird muss zuverlässig konstant sein (weswegen ich nur einmal
auf PortB schreibe). Hin- und Herspringen möchte ich aus dem gleichen
Grund nicht. Das Ganze muss ohne Sprungbefehle gehen.
A. K. schrieb:> Robin schrieb:>> sbr R24,0>> So wird das nix. SBR (=ORI) braucht Maske, nicht Bit.
Ups, ist aber auch schon wieder ein weilchen her mit dem Assembler bei
mir. War da auch schon mal fitter. Aber das Prinzip stimmt doch
zumindest, der Rest ist feinschliff.
@ Robin
Das kommt dem was ich mir vorstelle schon sehr nahe. Die Zyklenzahl ist
mit N-Flag-Low und N-Flag-High die gleiche. Aber: Ich habe bisher noch
nicht mit SBR arbeiten dürfen. Aber beeinflusst das nicht auch die Bits
(in R24) die ich unberührt lassen möchte?
Ich hätte jetzt auch gesagt, dass Robins Code mit der Modifikation von
A. K. und dem alten Hanns den Job tun sollte. Zu Beginn der ganzen
Befehlskette sollte noch ein CLR R24 stehen. Zusammen mit dem OUT am
Ende sind das konstant 20 Zyklen
Tiny10Nutzer schrieb:> Der Teilabschnitt meines Code sieht etwa so aus:
Das sind 25 Zyklen, also bringt Robins Lösung doch eine ganze Menge :)
Tiny10Nutzer schrieb:> Ich habe bisher noch nicht mit SBR arbeiten dürfen. Aber beeinflusst> das nicht auch die Bits (in R24) die ich unberührt lassen möchte?
SBR ist einfach ein Synonym für ORI.
Die Verwendung von sbr und cbr hatte ich mir damals ganz schnell
abgewöhnt! Es handelt sich, wie A.K. und Yalu schreiben, nur um eine
andere Form von ori resp. andi, vermutlich um in der Werbung die
Befehlsanzahl künstlich aufzublähen.
Hi
>Ich habe bisher noch>nicht mit SBR arbeiten dürfen. Aber beeinflusst das nicht auch die Bits>(in R24) die ich unberührt lassen möchte?
Nein. Das ist eine andere Schreibweise für ORI. Es werden nur die Bits
gesetzt, die Eins sind.
MfG Spess
So werden die Bits in R24 aber invertiert gesetzt. Wenn jedes Bit dem
N-Flag des jeweiligen Vergleichs entsprechen soll, musst du wie in
Robins Vorschlag BRPL statt BRMI verwenden.
Und ein Tipp: das (1<<n) macht die Sache nicht nur übersichtlicher,
sondern auch sicherer. (Wenn ich da an SEPA mit den vielen Nullen
mittendrin denke)
Ja das meinte ich. War mir grade nicht mehr sicher.
Diese Variante hat auch den enormen Vorteil, dass ich mir R16 einspare.
@ Robin
Hast mir grade riesig geholfen.
Danke euch allen für die Hilfe.
spess53 schrieb:> Noch eine grundsätzlich Frage: Bist du sicher, das das N-Flag das> richtige ist?
Anscheinend ja (ich hatte das weiter oben auch schon gefragt):
Tiny10Nutzer schrieb:> @ Yalu X>> Es muss das N-Flag sein. Ich brauche nach jeder Auswertung das N-Flag an> einer bestimmten Position des PortB.
Ginge auch das C-Flag könnte man noch einmal 6 Zyklen einsparen.
Deswegen hast du wahrscheinlich auch danach gefragt :)
@ spess53
Ich bin mir absolut sicher. Ich habe die Routine ja auch schon mehrmals
im Einsatz gehabt. Wie ich aber grade so mal meine Ausdrucke untersucht
habe, hat mich die Länge des Programmabschnitts (Zyklen und Code) dann
doch sehr gestört. Ich fand auch die Lösung über ein nichtgebrauchtes
Register zu gehen nicht sonderlich elegant.
Hi
>Ginge auch das C-Flag könnte man noch einmal 6 Zyklen einsparen.>Deswegen hast du wahrscheinlich auch danach gefragt :)
Das N-Flag sagt nach einem CP nichts über das Verhältnit der Operatoren
aus:
0x01-0x03 = 0xFE -> N=1
0xFF-0x01 = 0xFE -> N=1
MfG Spess
Jetzt bin ich doch nochmal neugierig geworden
Das Ziel der Teilfunktion ist:
Die Register die mit CP verglichen werden, können Werte von 0 bis 255
annehmen. Ist das eine Register kleiner als das andere soll ein Bit (in
einem dritten Register) gesetzt werden. So meine Routine bisher. Ich
könne meine Routine aber auch etwas abändern, solange das Ergebnis das
gleiche bleibt.
Hi
>Ist das eine Register kleiner als das andere soll ein Bit (in>einem dritten Register) gesetzt werden.
Dafür ist das Carry-Flag zuständig. Das N-Flag sagt lediglich aus, das
beim Ergebnis Bit7 = 1 ist.
MfG Spess
spess53 schrieb:> Das N-Flag sagt nach einem CP nichts über das Verhältnit der Operatoren> aus:>> 0x01-0x03 = 0xFE -> N=1> 0xFF-0x01 = 0xFE -> N=1
Doch, manchmal schon. Das hängt davon ab, ob die Registerinhalte als
vorzeichenlose oder oder vorzeichenbehaftete Zahlen betrachtet werden.
Offensichtlich haben wir es hier mit vorzeichenbehafteten Zahlen zu tun,
weswegen das N-Flag die passendere Lösung ist.
Korrektur: Da habe ich etwas verwechselt. Nicht das N-Flag, sondern das
S-Flag ist für Vergleiche vorzeichenbehafteter Zahlen gedacht.
Ich bin mir aber fast sicher, dass man, wenn man den Rest des Programms
etwas anpassen würde, das Ganze auch mit vorzeichenlosen Zahlen rechnen
könnte, so dass man auch das C-Flag verwenden könnte ;-)
Edit:
Tiny10Nutzer schrieb:> Die Register die mit CP verglichen werden, können Werte von 0 bis 255> annehmen. Ist das eine Register kleiner als das andere soll ein Bit (in> einem dritten Register) gesetzt werden.
Hmm, also doch vorzeichenlos? Dann ist aber das C-Flag richtig.
Tiny10Nutzer schrieb:> Ich bin mir absolut sicher.
Ist denn die Anwendung so geheim, daß Du sie nicht nennen darfst?
Mit dem C-Flag würde ich denken, das ist ne Mehrkanal-PWM.
Mit dem N-Flag fällt mir keine praktische Anwendung ein. Vielleicht ein
Mehrkanal-Phasenschieber.
Die Kommentare sind lange nicht mehr aktuell. Der restliche Code ist
weder fertig noch wichtig. Ich kann nur sagen, dass das Programm bis
hier richtig funktioniert hat (die logische Abfolge stimmt).
Wenn ich das richtig sehe, ist das kein PWM-Generator, sondern, wie
Peter schon angedeutet hat, ein Phasenschieber, es sei denn, softpwmband
wird an anderer Stelle noch auf einen Wert kleiner 128 gesetzt.
Für größer/kleiner Vergleiche gibt es doch schönere branch befehle:
BRSH/BRLO für vorzeichenlose Zahlen und BRGE/BRLT für
vorzeichenbehaftete Zahlen.
Damit wäre dann das ganze noch schöner ;)
tiny10nutzer schrieb:> Ich kann nur sagen, dass das Programm bis> hier richtig funktioniert hat (die logische Abfolge stimmt).
Sicher?
Wenn ein Wert durchzählt und der andere irgendeine Konstante ist, dann
ist das Ergebnis immer zu 50% negativ, d.h. Deine PWM hat konstant 50%.
Nur die Phase verschiebt sich.
Oft ist aber der scheinbar einfache Weg weder effektiv noch effizient.
Schau Dir mal die BAM an:
http://www.batsocks.co.uk/readme/art_bcm_3.htm
Mir ist keine bessere Software-PWM bekannt.
Und natürlich ist noch immer das offen, was Yalu X. anriss: wenn statt
des N- das C-Flag abgefragt werden kann (warum eigentlich nicht?), dann
ist noch mehr möglich.
tiny10nutzer schrieb:> Wenn Ihr mir jetzt sagt "Das geht noch kürzer" hör ich gern zu.
Damit man später noch mal was erkennen kann solltest du Sprungmarken
anstelle von PC+xx verwenden. Man muss ja nicht alles 'kopieren'.
Sascha
> Damit man später noch mal was erkennen kann solltest du Sprungmarken
Bei mir ist es genau umgekehrt, vor lauter (unnützen) Sprungmarken würde
ich nichts mehr erkennen.
Falls es dir nur um eine Takteinsparung in der Timer_ISR geht, dann
kannst du deine Daten auch vorsortieren und in einer Liste die OCCRA
Werte und die PortB Werte speichern und dann nurnoch diese in der ISR
laden.
Dann bekommst du die Timer ISR innerhalb von ca.10 Takten abgearbeitet.
Allerdings brauchst du in deinem, Hauptcode dann einen Bubblesort, der
einmal pro Timer Überlauf durchlaufen wird.
Dann wird das ganze aber noch schneller ;)
der alte Hanns schrieb:> Bei mir ist es genau umgekehrt, vor lauter (unnützen) Sprungmarken würde> ich nichts mehr erkennen.
Huch?! Was schreibst du denn für Code?
Klar, es gibt durchaus Situationen, wo die Ziele kurzer Sprünge
unwichtig sind. Vor allem dann, wenn diese kurzen Sprünge nur
existieren, um über den wegen der Beschränkungen der Sprungweite der
branch-Instruktionen sozusagen "ausgelagerten" weiten Sprung
hinwegzuspringen.
Wenn das bei dir aber so häufig vorkommt, daß du es für die Regel
hältst, dann schreibst du offensichtlich ziemlich ineffizienten Code...
Bei mir liegt die Quote solcher durch die Beschränkungen des
Befehlssatzes erzwungenen "Sprung-Umleitungen" jedenfalls im Allgemeinen
unter einem Prozent.
In allen anderen Fällen arbeite ich natürlich mit Sprungmarken. Deren
Bezeichner sind natürlich sprechend und ersparen somit den Kommentar an
der Verzweigung. Alles andere wäre doch ziemlich idiotisch!
tiny10nutzer schrieb:> Iggit C-Code...
Ach komm, der Algorithmus ist doch so einfach, das hat man ruckzuck auch
in Assembler hingefriemelt.
Hier mal der Interrupt:
1
INTHAND OC0Aaddr
2
in isreg, SREG
3
ld iwr0, X+
4
out PORTD, iwr0 ;output pattern
5
lsl pwm_time ;0, 2, 6, 14, 30, 62, 126, 254
6
inc pwm_time ;1, 3, 7, 15, 31, 63, 127, 255
7
out OCR0A, pwm_time
8
inc pwm_time ;2, 4, 8, 16, 32, 64, 128, 0
9
breq _oc01
10
dec pwm_time ;1, 3, 7, 15, 31, 63, 127
11
out SREG, isreg
12
reti
13
_oc01: ; 0
14
ldi xl, low(pwm_data)
15
ldi xh, high(pwm_data)
16
out SREG, isreg
17
reti
Der Rest im Anhang.
Die PWM ist nur 244Hz, das kann man aber noch tunen.
Ich habs auf nem ATmega48 getestet.
Peter Dannegger schrieb:> Die PWM ist nur 244Hz, das kann man aber noch tunen.
Siehe Soft-PWM
geht aber auch noch besser. Mit Jitterkorrektur kann man nocht bessere
Ergebnisse erreichen. Auch Soft-PWM (nicht BAM) kann man durch
Phasenverschiebung mit vollem Takt erzeugen, das ist aber deutlich
aufwändiger als BAM.
tiny10nutzer schrieb:> Dein Code macht mir grade gewaltig Angst, da ich genaugenommen überhaupt> nichts verstanden habe...
Versuche erstmal das Prinzip von BAM zu verstehen. Dann verstehst du
auch den Code.
Robin schrieb:> Falls es dir nur um eine Takteinsparung in der Timer_ISR geht, dann> kannst du deine Daten auch vorsortieren und in einer Liste die OCCRA> Werte und die PortB Werte speichern und dann nurnoch diese in der ISR> laden.>> Dann bekommst du die Timer ISR innerhalb von ca.10 Takten abgearbeitet.> Allerdings brauchst du in deinem, Hauptcode dann einen Bubblesort, der> einmal pro Timer Überlauf durchlaufen wird.>> Dann wird das ganze aber noch schneller ;)
Kann ich dafür irgendwo ein Beispiel sehen?
Robin schrieb:> Dann bekommst du die Timer ISR innerhalb von ca.10 Takten abgearbeitet.
10 Zyklen dauert allein schon der Einsprung, Sprung zum Handler und das
RETI.
Und wenn im Main gerade ein RET ausgeführt wird, nochmal 4 Zyklen.
Von atomic Zugriffen und anderen Interrupts ganz zu schweigen.
Bei der BAM besteht allerdings die Möglichkeit, für die ersten 2 oder 3
Bits den Interrupt garnicht erst zu verlassen. Dann könnten 10 Zyklen
Bitzeit durchaus möglich sein.
tiny10nutzer schrieb:> Kann ich dafür irgendwo ein Beispiel sehen?
Das gibt es leider (noch) nicht. Ich habe vor ein paar Tagen es
theoretisch getestet. Es ist auf jeden Fall möglich.
Peter Dannegger schrieb:> Dann könnten 10 Zyklen> Bitzeit durchaus möglich sein.
Ich wiederhole mich: Es sind Bitzeiten bis zum CPU-Takt möglich. Mit
Jitterkorrektur ist das gar kein Problem mehr.
an c-hater:
ldi output,0
cp softpwmcount,channel0value ; vergl. jeweils Zähler mit PWM-Einst.
brpl pc+2
ori output,(1<<0)
cp softpwmcount,channel1value
brpl pc+2
ori output,(1<<1)
cp softpwmcount,channel2value
brpl pc+2
ori output,(1<<2)
cp softpwmcount,channel3value
brpl pc+2
ori output,(1<<3)
cp softpwmcount,channel4value
brpl pc+2
ori output,(1<<4)
cp softpwmcount,channel5value
brpl pc+2
ori output,(1<<5)
out PORTB,output
gegenüber
ldi output,0
cp softpwmcount,channel0value ; Vergleiche Zähler mit PWM-Einstellung
brpl hopp_de_baese_0
ori output,0b00000001
hopp_de_baese_0:
cp softpwmcount,channel1value ; Vergleiche Zähler mit PWM-Einstellung
brpl hopp_de_baese_1
ori output,0b00000010
hopp_de_baese_1:
cp softpwmcount,channel2value ; Vergleiche Zähler mit PWM-Einstellung
brpl hopp_de_baese_2
ori output,0b00000100
hopp_de_baese_2:
cp softpwmcount,channel3value ; Vergleiche Zähler mit PWM-Einstellung
brpl hopp_de_baese_3
ori output,0b00001000
hopp_de_baese_3:
cp softpwmcount,channel4value ; Vergleiche Zähler mit PWM-Einstellung
brpl hopp_de_baese_4
ori output,0b00010000
hopp_de_baese_4:
cp softpwmcount,channel5value ; Vergleiche Zähler mit PWM-Einstellung
brpl hopp_de_baese_5
ori output,0b00100000
hopp_de_baese_5:
out PORTB,output ; auf das PortB schreiben
DAS meinte ich, und wenn es auch, zugegebenermaßen, Geschmackssache sein
mag, so wiederhole ich mich gerne: ich kann das Erste schneller
überblicken.
Mit einem macro gefiele es mir noch besser, aber dieses Umschreiben
überlasse ich Ihnen.
Dieses jedoch
> dann schreibst du offensichtlich ziemlich ineffizienten Code...
zeigt leider nur wieder einmal Ihren üblichen Stil.
Oder war es, da andernorts gerade über 'Lesevermögen' diskutiert wird,
nur ein Missverständnis?
Ich meinte natürlich nicht, dass jegliche Sprungmarken unnütz sind,
sondern dass ich solche Sprungmarken durch pc+- ersetze, die ich für
unnütz halte.
Trotzdem, Ihr Ton gefällt mir nicht.
> ziemlich idiotisch!
Während Ihr Euch ein klein wenig gestritten habt, kahm mir noch ein
Gedanke:
Im Beispiel oben hat das Software-PWM eine Auflösung von 250
Abstufungen. D.h. bei 250 Interruptaufrufen finden nur 2
High-Low-Wechsel am Pin statt. Sind also die anderen 248
Interruptaufrufe sinnfrei (auf den einzellnen Pin bezogen). Die kann man
doch bestimmt aussetzen.?
Ich hab die BAM noch auf 24 Zyklen Bitzeit gepimpt, indem der nächste
Timerwert auch aus einer Tabelle geladen wird.
Damit kommt man bei F_CPU = 8MHz auf 1,3kHz PWM, das sollte reichen.
> Während Ihr Euch ein klein wenig gestritten habt
Nun, junger Freund, alles will gelernt sein, auch zivilisiert zu
streiten, und, wenn ich meinen Blick etwas weiter schweifen lasse, so
möchte ich hinzufügen, dass es eine der (überlebens-) wichtigen
Fähigkeiten ist.
@ der alte Hanns
Korrekt...
@ Peter Dannegger
Ich habe mich jetzt in das BAM hineingelesen. So wie ich das System
verstehe setzt es (die Realität) vorraus, dass das niedrigste Bit für
die kleines Auflösung 1 steht, das Bit darüber für eine Auflösung
doppelt so groß wie das kleinste, usw. Für das höchste Bit braucht also
für 128 Durchläufe nur ein Interrupt ausgelösst werden, da dieses Bit
ohnehin für diese Größe steht. Das Bit darunter steht für 64 Durchläufe,
muss hier auch nur einmal aufgerufen werden und sparrt dabei 63
Durchläufe (oder Interrupts). Der nette Nebeneffekt ist, dass die BAM
während eines gesammten Durchlaufs die Flanke mehrmals auf High bzw. Low
setzt und so in Durchschnitt eine höhere Frequenz erwirkt wird, während
ein "nur" PWM während eines Durchlaufs die Flanke jeweils nur einmal auf
High bzw. Low setzt.
Ist das so richtig?
(Es steht aber auch in der Beschreibung, dass BAM absolut
Elektro-Motor-Ungeeignet ist.)
Während der 128 Bit-Phase hätte ich mehr als genug Zeit alle
channel?value Einstellungen umzusortieren, sodass ich im Interrupt
selber nur noch "out PortB,Output - Reti" schreiben muss. Der Vorteil
ist eine extrem höhere Frequenz, da ich für den Interrupt statt der
jetzigen 24-25 Zyklen nur noch 5 Zyklen brauche. Der Nachteil ist, dass
ich 7 zusätzliche Register brauche, da ist 8-mal Output für die Ausgabe
vorbereiten muss.
Ich rechne mal:
Mein PWM: Ich arbeite jetzt mit 1,2 MHz, rufe alle 30 Takte den
Interrupt auf und habe eine Auflösung von 250, macht 160 Hz.
Das Umsetzbare BAM: Vorrausgesetzt es bleibt bei den 1,2 MHz, ist der
kürzeste Interruptaufruf bei 5 Takten und bei einer Auflösung von 8 Bit
(256) komme ich auf 937,5 Hz. Da die Flanke mehr als nur einmal wechselt
habe ich eine durchschnittlich deutlich höhere Frequenz. Wie ich die
durchschnittlich höhere Frequenz berechnen soll, da habe ich aber grade
keinen Plan von.
Kommt das so hin, oder denke ich grade Falsch?
tiny10nutzer schrieb:> Kann ich dafür irgendwo ein Beispiel sehen?
Beispiel Code hab ich leider gerade keinen Zur hand, aber ich versuch es
mal schrittweise zu erklären.
Im RAM speicherst du eine Tabelle mit 3 Byte pro Kanal. bestehend aus 2
Byte für das Compare Register und einem Byte für den Ausgangsport. (bei
mehreren Ports müssen mehr Bytes gespeichert werden).
Dann brauchst du noch 2 Byte für den Z-Pointer, damit die ISR schneller
abgearbeitet werden kann.
Im Hauptprogramm wird eine Flag abgefragt, die nach einem Timer
Durchlauf gesetzt wird. Ist diese Flag gesetzt, werden die zu diesem
Zeitpunkt aktuellen PWM Werte sortiert und eine Maske für den Ausgabe
Port angelegt.
Bei gleichen Timerwerten für mehrere Kanäle, werden diese
zusammengefasst und alle doppelten bis auf einen Kanal auf maximum
gesezt, damit sie ans ende der Tabelle kommen.
Im Timer Overflow interrupt werden alle PWM-Pins auf High gesetzt, der
Z-Pointer auf den Anfang der Liste initialisiert und der Erste Wert der
Liste in das Timer Compare Register geladen. Und die Flag für das
Hauptprogramm gesetzt.
Bei einem Timer Compare interrupt, wird der Z-Pointer geladen, die
Portmaske am Ausgabe Port ausgegeben und der nächste wert in das Compare
Register geladen. Anschliesend wird der Z-Pointer wieder gesichert,
damit ein schnellerer Zugriff im nächsten Interrupt erfolgt, ohne lange
herum zu rechnen.
---------------------
Das sind die Bestandteile dieser Methode, hat natürlich Vorteile und
auch Nachteile.
Vorteile:
- maximal n Interrupt aufrufe pro Periode, (n = Anzahl der Kanäle)
- Wenn man etwas fehler in Kauf nimmt (mindest abstand zwischen zwei
Zeiten) kann man eine hohe Auflösung erreichen.
Nachteile:
- Recht aufwändige Vorverarbeitung der Daten
- Recht viel Speicherplatz benötigt (3*n + 2 Byte RAM und ein
Sortieralgorithmus im Programmcode)
- Eine Periode Verzögerung bei der Ausgabe der Werte (könnte aber
optimiert werden)
Hoffe das ist einigermaßen verständlich geschrieben. Die Einfachere
Möglichkeit zum Realisieren ist auf jedenfall die BAM. Die
Vorverarbeitung der Daten ist einfacher, dafür wird die ISR häufiger
aufgerufen. Speicherbedarf und Verzögerung sind bei beiden in etwa
gleich.
Tiny10Nutzer schrieb:> Während der 128 Bit-Phase hätte ich mehr als genug Zeit alle> channel?value Einstellungen umzusortieren, sodass ich im Interrupt> selber nur noch "out PortB,Output - Reti" schreiben muss.
Genauso würde man das machen. Tatsächlich würde man wohl die Bitmasken
vorberechnen (mehr als einmal pro Zyklus kann man die PWM-Werte sowieso
nicht ändern ohne Glitches zu verursachen). Die ISR muß dann nur die
jeweils nächste Bitmaske aus dem Buffer lesen und auf die Ausgabepins
schreiben.
Nicht vergessen: für LED will man die Intensitätswerte nicht direkt für
die PWM verwenden. Das Auge hat eine antilogarithmische
Empfindlichkeitskurve. Für 256 Helligkeitsstufen, die dem Auge
gleichmäßig erscheinen, braucht man mindestens 11, besser 12 Bit. Am
einfachsten macht man dafür eine Tabelle und schlägt auch die nur einmal
im PWM-Zyklus nach, wenn man die Bitmasken neu berechnet.
XL
Tiny10Nutzer schrieb:> Wie ich die> durchschnittlich höhere Frequenz berechnen soll, da habe ich aber grade> keinen Plan von.
Die Frequenz bei BAM richtet sich nach deiner geschwindigkeit der ISR
und deiner Auflösung des PWMs.
lässt sich dann etwa so berechnen:
F_CPU/(takte_ISR * Auflösung als Zahlenwert)
Also als Beispiel:
1200000 / (30 * 250) = 160 Hz
Durch Optimierungen bei den schnellen Frequenzen lässt sich dann aber
noch einiges mehr rausholen, denn eigentlich sind die ersten 3 Ausgaben
nur kritisch, danach sind die Zeiten der Ausgaben weit genug
auseinander.
Wenn man die beiden Compare Register des Timers verwendet. Könnte man
die Kleinen Werte von einer ISR ausgeben lassen und in der Anderen die
Restlichen.
Bei der getrennten Ausgabe hast du dann viel mehr Zeit zur Verfügung,
bis deine ISR Abgearbeitet sein muss.
So ist wenn die ersten 3 Werte getrennt ausgegeben werden, eine 8 fach
höhere PWM Frequenz möglich.
Tiny10Nutzer schrieb:> Ich arbeite jetzt mit 1,2 MHz
Gibt es einen Grund, warum Du nicht 9,6MHz benutzen möchtest?
Du mußt nur den Vorteiler auf 1 setzen (als Fusebit oder im Programm).
Peter Dannegger schrieb:> Gibt es einen Grund, warum Du nicht 9,6MHz benutzen möchtest?
Antwort: Faulheit...
Ich habe jetzt hier mal was im Konzept zusammengeschustert:
Ich finde Systemtakt durch 256 (8-Bit) hört sich als Frequenz doch echt
gut an. Aber: Wie ändere ich im laufenden Programm den Sprungvektor in
.org 0x0006?
out PortB,tmp ; 64 auf OCR0A schreiben
da stimmt was nicht ;)
tiny10nutzer schrieb:> Aber: Wie ändere ich im laufenden Programm den Sprungvektor in> .org 0x0006?
Warum machst du das nicht in der selben ISR und lädst die Werte über
einen Pointer aus dem RAM?
1
Variablen im RAM
2
Z_Pointer: .Byte 2
3
PWM: .Byte 8
4
5
OCCRA_ISR:
6
push Zh // Verwendete Register Sichern
7
push ZL
8
push R16
9
in R16, SREG
10
Push R16
11
12
lds Zl, Z_Pointer // Pointer auf PWM Werte Laden
13
lds Zh, Z_Pointer+1
14
15
ld R16,Z+ // Aktuellen PWM Wert laden und ausgeben
16
out PortB, R16
17
18
sts Zl, Z_Pointer // Veränderten Pointer sichern
19
sts Zh, Z_Pointer+1
20
21
in R16, OCCRA // OCCRA*2
22
lsl R16
23
out OCCRA, R16
24
25
pop R16 // Register wieder Herstellen
26
out SREG, R16
27
pop R16
28
pop Zl
29
pop Zh
30
reti
Im Timer_Overflow interrupt wird dann OCCRA und die Pointer Variable im
RAM immer wieder zurückgesetzt. Eine Flag gesetzt, die durch die dann im
Hauptprogramm die Nächsten PWM Werte verarbeitet werden.
Optimieren kann man das ganze aber noch, wie weiter oben geschrieben,
OCCRB für die kleinen Werte nehmen, oder gleich im Timer Overflow
Interrupt.
@ why_me
Dein Beispiel ist nicht schlecht, braucht aber vom Interrupt bis zum
"out PortB" satte 18 Zyklen. Das währe dann auch schon das KO-Kriterium
für mich. Aber dein Ansatz für die kleinen Bits OCR0A und für die hohen
Bits OCR0B zu verwenden gefällt mir. Wenn ich die Zyklen geschickt
ausmanövriere, kann ich ein, zwei Register einsparen, indem ich den
Sortiervorgang dazwischenschiebe.
Der Sortiervorgang selber würde grundlegend so aussehen:
1
rorChannel0Value
2
rorOutput128
3
rorChannel1Value
4
rorOutput128
5
rorChannel2Value
6
rorOutput128
7
rorChannel3Value
8
rorOutput128
9
rorChannel4Value
10
rorOutput128
11
rorChannel5Value
12
rorOutput128
13
rorOutput128
14
rorOutput128
15
16
rorChannel0Value
17
rorOutput64
18
rorChannel1Value
19
rorOutput64
20
rorChannel2Value
21
rorOutput64
22
rorChannel3Value
23
rorOutput64
24
rorChannel4Value
25
rorOutput64
26
rorChannel5Value
27
rorOutput64
28
rorOutput64
29
rorOutput64
30
31
rorChannel0Value
32
rorOutput32
33
rorChannel1Value
34
rorOutput32
35
rorChannel2Value
36
rorOutput32
37
rorChannel3Value
38
rorOutput32
39
rorChannel4Value
40
rorOutput32
41
rorChannel5Value
42
rorOutput32
43
rorOutput32
44
rorOutput32
45
46
rorChannel0Value
47
rorOutput16
48
rorChannel1Value
49
rorOutput16
50
rorChannel2Value
51
rorOutput16
52
rorChannel3Value
53
rorOutput16
54
rorChannel4Value
55
rorOutput16
56
rorChannel5Value
57
rorOutput16
58
rorOutput16
59
rorOutput16
60
61
rorChannel0Value
62
rorOutput8
63
rorChannel1Value
64
rorOutput8
65
rorChannel2Value
66
rorOutput8
67
rorChannel3Value
68
rorOutput8
69
rorChannel4Value
70
rorOutput8
71
rorChannel5Value
72
rorOutput8
73
rorOutput8
74
rorOutput8
75
76
rorChannel0Value
77
rorOutput4
78
rorChannel1Value
79
rorOutput4
80
rorChannel2Value
81
rorOutput4
82
rorChannel3Value
83
rorOutput4
84
rorChannel4Value
85
rorOutput4
86
rorChannel5Value
87
rorOutput4
88
rorOutput4
89
rorOutput4
90
91
rorChannel0Value
92
rorOutput2
93
rorChannel1Value
94
rorOutput2
95
rorChannel2Value
96
rorOutput2
97
rorChannel3Value
98
rorOutput2
99
rorChannel4Value
100
rorOutput2
101
rorChannel5Value
102
rorOutput2
103
rorOutput2
104
rorOutput2
105
106
rorChannel0Value
107
rorOutput1
108
rorChannel1Value
109
rorOutput1
110
rorChannel2Value
111
rorOutput1
112
rorChannel3Value
113
rorOutput1
114
rorChannel4Value
115
rorOutput1
116
rorChannel5Value
117
rorOutput1
118
rorOutput1
119
rorOutput1
Abgesehen davon, dass ich den Vorgang statt 8 Mal nur 1 Mal in einem
Unterprogramm durchführen kann, gibt es möglichkeiten das sinnvoller,
schneller und kürzer zu erledigen?
Be deinem Code fehlt ja auch noch etwas, pushs, pops, dann noch die
unterschiedlichen Sprünge, die wohl am schnellsten über eine Programm
Counter Manipulation.
Werden auch nochmal 10 Takte sein.
Schneller wird man die Verarbeitung der Werte nicht hinbekommen. Evtl in
die andere Richtung schieben und die letzten beiden Schiebe Operationen
weglassen, PB7 und 8 gibts beim Tiny eh nicht.
Und Kürze gehts nur über eine schleife, aber das dauert dann auch wieder
etwas länger.
Dürften noch der eine oder andere Schreibfehler drin sein. Aber ich habe
hier statt 8 Outputregister nur 4 verbraten, indem ich sie einfach
verschoben habe. Registerzahl hier 5+4+1=10 bleiben noch 6 die ich
weiter verwenden kann.
Sieht noch wer Optimierungsmöglichkeiten?
tiny10nutzer schrieb:> Der Sortiervorgang selber würde grundlegend so aussehen:ror> Channel0Valuetiny10nutzer schrieb:> Ich habe mein Konzept jetzt mal in diese Richtung gelenkt:Reset_Order:> ; ergänzende Befehle
Bring's mal auf den Punkt oder verwende die Anhang-Funktion. Sonst
bekommt man noch wunde Finger vom Scrollen.
Meinst Du, da sieht noch jemand durch?
Sende solche Monster wenigstens als Anhang.
Hier mal meine Routine:
1
ISR OC0Aaddr
2
ldd iwr0, Y+(pwm_time - pwm_data)
3
out OCR0A, iwr0 ;compare time
4
ld iwr0, Y+
5
out PORTD, iwr0 ;output pattern
6
ldi iwr0, low(pwm_data_end)
7
cpse yl, iwr0
8
reti
9
ldi yl, low(pwm_data)
10
ldi yh, high(pwm_data)
11
reti
Ein Bit habe ich auf 24 Zyklen festgelegt (3 * Vorteiler 8). Der
Interrupt braucht 18 Zyklen, da ist also noch Luft für ein RET (4
Zyklen) im Main.
Die letzte Bitzeit 384 paßt dann nicht mehr in 8 Bit. Macht aber
nichts, ich habe sie einfach auf 2 Interrupts aufgeteilt.
Im Anhang ein kleines Testprogramm.
Ich hab schon ewig nichts mehr in Assembler gemacht. Ich hab daher
erstmal einige Macros geschrieben, damit der Code nicht gar zu
unleserlich wird.
C ist doch wesentlich bequemer, die Bitzeit muß man dann aber etwas
erhöhen.
Als Alternative zur PWM funktioniert PDM auch ganz gut.
Hier ein Beispiel für 8 D/A-Kanäle.
R16 - R23 = Zu wandelndelde Werte
R8 - R15 = Merkregister
R7 = Zwischenregister
an Peter Dannegger
Sehe ich das richtig?
Ausgehend von Ihrem Programm könnte man auf 2*8 = 16 Takte Bitzeit
kommen, wenn man statt Interrupt-handling ein Polling (kennt jemand
griffige deutsche Ausdrücke?) durchführt, evtl. keine Schleife, sondern
achtmal hintereinandergeschrieben, und allfällige sonstige
Verarbeitungsblöcke in die größte, ggfs. noch zweitgrößte Zeitscheibe
verlegt.
(Damit würde auch die größte Zeitscheibe gerade noch in OCR0A passen)
@der alte Hanns (Gast)
>Ausgehend von Ihrem Programm könnte man auf 2*8 = 16 Takte Bitzeit>kommen, wenn man statt Interrupt-handling ein Polling (kennt jemand>griffige deutsche Ausdrücke?)
Interrupt ist ein Fremdwort, die deutsche Bezeichung
Unterbrechungsanforderung ist zu lang und zu staubig, als das es jemand
verwenden will.
Abfrage ist eine brauchbare deutsche Bezeichnung für Polling.
der alte Hanns schrieb:> Mich deucht, es sollten sogar 1*8 möglich sein!
Bitlänge 1 Takt ist möglich, indem die kritischen Bitzeiten in einen
Interrupt legt (hab ich schon 2 mal geschrieben).
an Falk Brunner
Danke. Bleibt es also bei den Anglizismen, hier wohl besser als
Fachterminologie bezeichnet.
an Sam
Wie sieht denn Ihr Beispielprogramm aus, besonders die Jitterkorrektur
(schon wieder, Falk Brunner) würde mich interessieren.
Ha - könnte man 'Unschärfekorrektur' dafür schreiben?
an Sam
Liegt nicht genau hier das Problem? Ohne vollständige Korrektur hat man
doch keine 8-bit-Auflösung mehr, oder verstehe ich das falsch?
@ der alte Hanns (Gast)
>Ha - könnte man 'Unschärfekorrektur' dafür schreiben?
Nein, das wäre zu unschaft ;-)
Jitter würde ich eher mit Zittern übersetzen, das ist es ja irgendwie
auch. Ein zeitliches Zittern eines Signals.
Hier ist meine Variante um den Jitter zu entfernen. Ich hab auch schon
welche mit ijmp gesehen, bei denen in eine nop Tabelle gesprungen wird.
Bei größeren auszugleichenden Latenzen braucht das aber unnötig viel
Speicherplatz.
INT_LATENCY: maximale Latenz des Interrupts (mindestens 4 Zyklen)
CYCLES_TO_SYNC: Takte bis sync vom Interrupt aufgerufen wird.
Die Routine sorgt dafür das beim Rücksprung das TCNT0 Register immer den
gleichen Wert hat (außer man überschreitet die maximale Latenz).
So weit, so klar. Unklar ist mir aber noch das genaue Zusammenspiel mit
dem restlichen Programm, vielleicht bin ich weniger versiert als Sie.
Wenn Sie also Ihr komplettes Programm hier einstellen könnten, so wie
Herr Dannegger?
Dann könnte man sich das Ganze auch in praxi anschauen, d.h. einfach
eine Überprüfung per Voltmeter und Oszilloskop vornehmen.
Bitte verstehen Sie mich recht, ich will Sie keineswegs ärgern, nur -
ich habe schon einige 'Trivialitäten' ("kein Problem, das lösen wir
später") gesehen, die dann als k.o.-Kriterium - nun, das Projekt eben
k.o. schlugen, zumindest in der ursprünglich geplanten Form.
Und eine 1-Takt-Auflösung erstaunt mich schon (und würde mich freuen,
wenn es möglich ist).
Sam .. schrieb:> Bitlänge 1 Takt ist möglich, indem die kritischen Bitzeiten in einen> Interrupt legt (hab ich schon 2 mal geschrieben).
Ja, aber nicht gezeigt.
Wird letzlich darauf hinauslaufen, daß die ersten 128 Bitzeiten komplett
im Interrupt laufen und nur das höchstwertige Bit dem Main etwa CPU-Zeit
abgibt. Im Prinzip sähe das so aus:
1
ISR OC0Aaddr
2
ld iwr0, Y+
3
ld iwr1, Y+
4
ld iwr2, Y+
5
out PORTD, iwr0
6
out PORTD, iwr1 ;1 cycle
7
nop
8
out PORTD, iwr2 ;2 cycle
9
ld iwr0, Y+
10
nop
11
out PORTD, iwr0 ;4 cycle
12
ld iwr0, Y+
13
rjmp pc+1
14
rjmp pc+1
15
nop
16
out PORTD, iwr0 ;8 cycle
usw.
Für die Latenzkompensation gabs auch mal ne App-Note.
Interessant wäre aber die Frage, wozu überhaupt die PWM schneller als
1,3kHz machen?
Selbst bei Erweiterung auf 10Bit sinds immer noch 325Hz.
> Interessant wäre aber die Frage, wozu überhaupt
Erkenntnisgewinn, vielleicht kann man es mal für etwas anderes
gebrauchen (ich vielleicht nicht mehr, aber die Jüngeren).
Peter Dannegger schrieb:> Interessant wäre aber die Frage, wozu überhaupt die PWM schneller als> 1,3kHz machen?
Motoren pfeifen bei 1,3kHz immer so herzerbärmlich :)
> und nur das höchstwertige Bit dem Main etwa CPU-Zeit abgibt.
Okay, wie schon beschrieben. Aber ist die Synchronisation mit dem Timer
auf 1 Takt genau möglich? Ich habe da zu wenig Erfahrung und Gespür.
> Für die Latenzkompensation gabs auch mal ne App-Note.
Wo, bzw. von wem?
> Motoren pfeifen bei 1,3kHz immer so herzerbärmlich :)
Vielleicht stören sich irgendwelche Tiere an 325 Hz-Licht?
der alte Hanns schrieb:>> Für die Latenzkompensation gabs auch mal ne App-Note.> Wo, bzw. von wem?
Ich kanns nicht mehr finden.
Es könnte so ähnlich gewesen sein:
1
ISR OVF0addr
2
in iwr0, TCNT0
3
sbrs iwr0, 0
4
rjmp pc+1
5
sbrc iwr0, 1
6
lpm iwr0, z
Beim Eintritt ist der Timer 6, 7, 8 oder 9.
6 ergibt sich aus dem Interruptaufruf (4) + RJMP (2) zum Handler.
Danke, Herr Dannegger, so allmählich bekomme ich eine vage Vorstellung.
Allerdings hatte ich auf etwas Fertiges von Sam gehofft, denn
> Ich habe vor ein paar Tagen es theoretisch getestet. Es ist auf jeden Fall> möglich.
Aber mit etwas Glück habe ich ab morgen Mittag Zeit, dann setze ich mich
dran, denn es interessiert mich durchaus, auch wenn ich mit dieser
ganzen LED-Geschichte ("Zaubern von Showeffekten", so hieß es vor kurzem
andernorts) sonst nicht viel anfangen kann.
Der Interupt dauert 152 Zyklen, d.h. belegt 60% CPU-Zeit.
Das Main läuft dann quasi mit 3MHz.
Ein Flackern ist auch beim schnellsten Schütteln des STK500 nicht mehr
zu erkennen.
Der komplette Code ist im Anhang.
Peter Dannegger schrieb:> Interessant wäre aber die Frage, wozu überhaupt die PWM schneller als> 1,3kHz machen?> Selbst bei Erweiterung auf 10Bit sinds immer noch 325Hz.
Für Led Fading sollte man mindestens 10-12 Bit zu Verfügung haben. Das
reicht dann auch für die nicht lineare Kennlinie des Auges, sowie für
Dot-Correction. Mehr schadet hier nicht. Wenn man dann noch eine Matrix
ansteuert braucht man schon ~1kHz PWM Frequenz.
Im Anhang 8 Kanal Bam für 9-16 Bit mit CPU-Frequenz.
der alte Hanns schrieb:> Allerdings hatte ich auf etwas Fertiges von Sam gehofft, denn>> Ich habe vor ein paar Tagen es theoretisch getestet. Es ist auf jeden Fall>> möglich.
Das bezog sich auf phasenverschobene PWM. Ich habe dazu nur Code
geschrieben, der die berechnet wie die PWM-Signale verschoben werden
müssen damit man es mit einer bestimmten Interrupt-Latenz schafft. Der
Code braucht auf dem Avr ~50k Zyklen, hat aber noch
Optimierungspotential (schätzungsweise Faktor ~3). Wenn da Interesse
besteht kann ich das auch hochladen.
Erstmal herzlichen Dank an die Autoren (beide offenbar Nachtarbeiter)!
Wann ich zu Sams Code komme, weiß ich noch nicht.
Pedas Code läuft auf einem 644 auf Anhieb, prima! Bei einem Schnelltest
sehe ich zwar ungleiche Sprünge bei 127-128-129, das mag aber an meinem
Analogteil liegen; gegen Abend habe ich mehr Zeit.
der alte Hanns schrieb:> Bei einem Schnelltest> sehe ich zwar ungleiche Sprünge bei 127-128-129
Naja, 8MHz Bitzeit sind 125ns kurz, da können sich Flankenzeiten
durchaus auswirken.
Schalte dochmal den CPU-Teiler auf 1MHz oder 500kHz.
> gegen Abend habe ich mehr Zeit
April, April! (Ich hasse diese Terminabstimmungen im 10-Minuten-Takt,
aber wir sind heutzutage ja so flexibel, flexibel bis zur
Selbstverbiegung)
an Sam
Der Titel 'Assembler-Frage' suggerierte mir genau dieses; Ihr Programm
ist in C, und in Sachen C fühle ich mich schlicht inkompetent.
Vielleicht hat Herr Dannegger Zeit und Lust, sich Ihr Programm
anzuschauen und ggfs. zu prüfen; es wäre auch etwas für c-hater (seien
Sie allerdings schon vorab vor dessen Umgangsformen gewarnt).
an Peter Dannegger
Ich arbeite bereits mit 1 MHz, genauer, ich hatte nie umgestellt.
Mir scheint, als wäre Ihr macro 'delay' für die größte Zeitscheibe einen
Takt zu lang.
Generell aber: feine Sache, ich bin wieder einmal erstaunt, was sich
alles machen lässt.
DANKE !
Hallo Hanns,
hast Du das hingekriegt mit dem Compare Interrupt statt Overflow?
Im Simulator im AVRStudio kann man sich schön ansehen, wie lang die
einzelnen Bits sind.
Ich bin grad an der 10Bit-PWM.
Allerdings wird mir das in Assembler langsam zu mühselig.
Ich werd daher nur die Interrupts in Assembler machen und die
Mainroutinen bequem in C. Dann kann man auch schönere Lichteffekte
machen (z.B. Knight Rider).
Also so ähnlich wie Sam.
Der Thread ist ja durch die riesen Listings leider total verbrannt, da
guckt keiner mehr drauf.
Ich werd mal nen neuen aufmachen.
Ja, hat sofort problemlos geklappt, danke der Nachfrage.
Etwas schade finde ich, dass von Tiny10Nutzer so gar keine Reaktion mehr
kommt. Aber ich habe einiges gelernt, und für Sie war es hoffentlich
nicht nur> mühselig
sondern auch mit etwas Spaß verbunden.
Peter Dannegger schrieb:> Der Thread ist ja durch die riesen Listings leider total verbrannt, da> guckt keiner mehr drauf.Ich guck' noch! Und ich lasse mich auch nicht abschrecken. ;-)
Ich bin mir sicher, dass da noch einige stille Mitleser sind. Danke für
das spannende Thema.
Hallo Herr Dannegger,
jetzt komme ich doch noch wie die alte Fastnacht hinterher.
Stimmt Ihr Code
sbrs iwr0, 0 ;compensate latency
rjmp pc+1 ;add 1 cycle
sbrc iwr0, 1
lpm iwr0, z ;add 2 cycle
?
Ich hätte eher
sbrs iwr0, 1
erwartet.
der alte Hanns schrieb:> Stimmt Ihr Code> sbrs iwr0, 0 ;compensate latency> rjmp pc+1 ;add 1 cycle> sbrc iwr0, 1> lpm iwr0, z ;add 2 cycle> ?> Ich hätte eher> sbrs iwr0, 1> erwartet.
Das kommt darauf an, in welchem Moment des Zyklus man zuschlägt. Nur bei
sehr speziellen Bedingungen, die Peter folgendermaßen formuliert hat:
>> Beim Eintritt ist der Timer 6, 7, 8 oder 9.
stimmt die Routine so wie gepostet. Das ist aber nur unter gewissen
Randbedingungen der Fall, von denen die wichtigsten sind:
-keine konkurrierenden Interrupts
-keine cli/sei-Blöcke (>4 Takte) in main
Nur wenn mindestens diese Bedingungen gegeben sind, ist der gepostete
Code die effizienteste denkbare Sync-Routine.
Die von Sam gepostete Variante ist deutlich flexibler (sie kann
praktisch auf jede Maximallatenz allein durch die Änderung einer
einzigen symbolischen Konstanten justiert werden), diese Flexibilität
bezahlt man allerdings damit, daß die Sync-Routine selber für ihre
Arbeit 6 Takte mehr verbraucht als die Minimalvariante.
Wenn man die beiden Routinen sinngemäß verschmilzt, kann man eine bauen,
die zwar die Flexibilität von Sams Variante hat, aber nur 4 Takte mehr
verbraucht als die von Peter, also gegenüber Sams Variante immerhin 2
Takte spart. Allerdings braucht diese Bastard-Lösung wiederum ein
Register mehr, lohnt also nur, wenn der eigentliche Job der ISR sowieso
mindestens zwei Register erfordert oder ein "freies" Register zur
Verfügung steht, ansonsten würde der Aufwand zum Sichern und
Wiederherstellen des zusätzlichen Registers den den Gewinn in einen
Verlust verwandeln.
Nun, ich bin habe ganz naiv die main-Schleife um einige rjmp pc+1 sowie
rcall mit ret erweitert und in der ISR auf das erste out PORTD einen
breakpoint gesetzt; dann war im Simulator ein jitter zu sehen.
Aber, und das war mein Fehlschluss: ich hatte vor drei Tagen die
Interruptroutine direkt an die Adresse gesetzt (jetzt nicht mehr dran
gedacht), in meiner Testversion fehlte also ein rjmp mit seinen 2
Takten.
an C-Hasser
Und bevor Sie sich nun wieder mit einem 'Huch' über meine Arbeitsweise
lustig machen, sag' ich's lieber gleich: ich halte mein Pseudonym in
Ehren und arbeite mit einem entsprechenden AVR-Studio, das aber
meckerte, dass '.org' in einem macro nicht zulässig sei. Also musste ich
schieben.
der alte Hanns schrieb:> Interruptroutine direkt an die Adresse gesetzt
Du meinst sicher: direkt auf den Interruptvektor.
> in meiner Testversion fehlte also ein rjmp mit seinen 2> Takten.
Jepp, und damit war die von Peter formulierte Bedingung halt schon nicht
mehr gegeben und der Code paßte nicht mehr.
Bei der Sam-Lösung hätte man nur vom Wert des Symbols INT_LATENCY zwei
abziehen müssen und alles wäre wieder in Butter gewesen. Das war, was
ich mit Flexibilität meinte.
Es geht nämlich weiter: Wenn die ISR einfach nur weiter als +-2kWords
vom Vektor entfernt ist und deswegen eines rjmp ein jmp zum Einsatz
kommen muss, wird die einfache Routine auch wieder nicht funktionieren.
Dann ändert sich die Latenz nämlich um +1 und diesmal muß zur Korrektur
an der ersten sbrX-Instruktion geschraubt werden.
Und es endet spätestens, wenn ein AVR mit 22Bit-PC zum Einsatz kommt.
Dann ist das Problem mit dieser Routine überhaupt nicht mehr lösbar,
weil schon dann die variable Latenz >4 ist.
Und das ist sie auch dann, wenn konkurrierende Interrupts und/oder
cli/sei-Konstrukte mit einer Länge von mehr als 4 Takten in's Spiel
kommen, was in nahezu jeder praktisch nützlichen Anwendung der Fall sein
wird.
Deswegen würde ich an deiner Stelle gleich auf die flexiblere
Sam-Variante und/oder die Bastard-Lösung setzen. Insbesondere, wenn der
Code als recyclebares Macro gedacht ist, wie ich dem zweiten Posting
entnehmen konnte...
Vielen Dank für die ausführlichen Erläuterungen.
> war die von Peter formulierte Bedingung halt schon nicht mehr gegeben
Und diese steht auch noch direkt darüber! (Es war auch sonst kein guter
Independence Day für mich) Entschuldigung, speziell an Peter Dannegger.
der alte Hanns schrieb:> das aber> meckerte, dass '.org' in einem macro nicht zulässig sei.
Das ist aber auch Versionsunabhängig völlig logisch ...
Gruß
Jobst
Jobst M. schrieb:> der alte Hanns schrieb:>> das aber>> meckerte, dass '.org' in einem macro nicht zulässig sei.>> Das ist aber auch Versionsunabhängig völlig logisch ...
Wieso sollte das logisch sein?
Der AVR-Assembler2 jedenfalls schluckt auch .org in Macros (zumindest im
.cseg). Und gelegentlich ist das sogar nützlich anwendbar.
c-hater schrieb:> Wieso sollte das logisch sein?
Aus dem einfachen Grund, dass dann mehrfach .org mit der selben Adresse
im Programmcode steht ...?
Oder habe ich da etwas völlig missverstanden?
Gruß
Jobst
Tiny10Nutzer schrieb:> Der Sinn des> Ganzen ist hier Offtoppic (und sagt mir nur, dass die Diskutierenden> darüber nicht die richtige Antwort haben).
Das ist etwas bescheiden formuliert. Die korrekte Antwort zur Frage kam
schon. Dir gefällt nur nicht wie sie lauet. Geht nicht so direkt. Darum
ist die Frage nach den Sinn und Zweck (Kontext) um eine Alternative zu
finden eben nicht Offtoppic.
Tiny10Nutzer schrieb:> @ Yalu X>> Es muss das N-Flag sein. Ich brauche nach jeder Auswertung das N-Flag an> einer bestimmten Position des PortB.>> @ der alte>> Der aktuelle Zustand des PortB ist nicht wichtig und darf aber auch> nicht verändert werden. Der zeitliche Abstand mit dem auf PortB> geschrieben wird muss zuverlässig konstant sein (weswegen ich nur einmal> auf PortB schreibe). Hin- und Herspringen möchte ich aus dem gleichen> Grund nicht. Das Ganze muss ohne Sprungbefehle gehen.Das ist jetzt die wesentliche Information, die man auch noch genauer
beschreiben könnte. Schön daß sie dann doch irgendwann ntsiegelt wird.
Immer diese Salamitaktik. "Helft mir, aber ich sag euch nicht wobei."
Wenn man etwas vergißt oder übersieht ist das eine Sache. Vorsätzlich
auch auf Rückfrage hinterm Berg zu halten und dann noch über die "nicht
passende Antwort" beklagen ist eine Andere...
Jobst M. schrieb:> c-hater schrieb:>> Wieso sollte das logisch sein?>> Aus dem einfachen Grund, dass dann mehrfach .org mit der selben Adresse> im Programmcode steht ...?>> Oder habe ich da etwas völlig missverstanden?
Du scheinst irrigerweise vorauszusetzen, daß das Makro
1. mehrfach aufgerufen wird und außerdem
2. die Adresse nach *.org* konstant ist
XL
Jobst M. schrieb:> Aus dem einfachen Grund, dass dann mehrfach .org mit der selben Adresse> im Programmcode steht ...?>> Oder habe ich da etwas völlig missverstanden?
Ja.
1) Macros können ersetzbare Parameter haben.
2) In Macros können Berechnungen vorgenommen werden.
3) Hinter '.org' können nicht nur numerische Literale stehen.
Als Beispiel zwei Macros, welche einen Datenbereich an der
nächstmöglichen 256Byte-Grenze ausrichten. Das ist z.B. nützlich, um
schnelle Zugriffe auf diese Daten zu realisieren, bei denen nur der
Low-Teil des Indexregisters angefaßt werden muss. Eine recht häufige
Aufgabe bei der Performance-Optimierung.
;->@0: Label für den Beginn des ausgerichteten Speicherblocks
;Macro funktioniert im DSEG und im ESEG
.MACRO PAGEALIGN
unaligned:
.SET aligned = ((unaligned+255)/256)*256
.EQU @0 = aligned
.ORG aligned
.ENDMACRO
;->@0: Label für den Beginn des ausgerichteten Speicherblocks
;Macro funktioniert im CSEG
.MACRO PM_PAGEALIGN
unaligned:
.SET aligned = (((unaligned<<1)+255)/256)*256
.EQU @0 = aligned
.ORG aligned
.ENDMACRO
Als Beispiel für die Benutzung vielleicht mal eine Subroutine, die das
nachste Sample aus einer Sinustabelle von 256 Byte Größe holt. (Würde
man natürlich so niemals implementieren, aber soll ja nur ein Beispiel
sein):
.CSEG
sintable:
.DB ... ;insgesamt 256 Bytes
;<-[Z]: Zeiger auf erstes Sample
init_sampleptr:
ldi ZL,Low(sintable<<1)
ldi ZH,High(sintable<<1)
ret
;->[Z]: Zeiger auf das nächste Sample
;<-R16: neues Sample
; [Z]: Zeiger auf nächstes Sample für nächsten Aufruf
getsample:
push tmpL ; 2
push tmpH ; 2
lpm R16,Z+ ; 3
ldi tmpL,Low((sintable<<1)+256) ; 1
ldi tmpH,High((sintable<<1)+256) ; 1
cp ZL,tmpL ; 1
cpc ZH,tmpH ; 1
brcs gs_done ; 2 1
subi ZH,1 ; 1
gs_done:
pop tmpH ; 2
pop tmpL ; 2
ret ;--
;17
Die Routine (ohne call-frame) braucht 17 Takte und davon tragen nur
magere 3 zur Lösung der eigentlichen Aufgabe bei, der ganze Rest wird
nur gebraucht, um den Pointer auf die Samples im Datenbereich zu halten.
Aligned man nun diese Daten, reduziert sich die Sache auf:
.CSEG
PM_PAGEALIGN sintable
.DB ... ;insgesamt 256 Bytes
;<-[Z]: Zeiger auf erstes Sample
init_sampleptr:
ldi ZL,Low(sintable)
ldi ZH,High(sintable)
ret
;->[Z]: Zeiger auf das nächste Sample
;<-R16: neues Sample
; [Z]: Zeiger auf nächstes Sample für nächsten Aufruf
getsample:
lpm R16,Z ; 3
inc ZL ; 1
ret ;--
; 4
Vorteil sichtbar? Nutzen des Macros klar?
Axel Schwenke schrieb:> Du scheinst irrigerweise vorauszusetzen, daß das Makro>> 1. mehrfach aufgerufen wird und außerdem
Dann verdient es aber eigentlich auch nicht den Namen Makro.
> 2. die Adresse nach *.org* konstant ist
Okay, das ist eine Erklärung.
Gruß
Jobst
Axel Schwenke schrieb:> Du scheinst irrigerweise vorauszusetzen, daß das Makro>> 1. mehrfach aufgerufen wird und außerdem
Natürlich kann das Macro "ISR" mehrmals aufgerufen werden, nämlich so
oft, wie der gewählte AVR Interruptvektoren hat.
Es versteht sich von selbst, daß jedem Vektor nur ein Handler zugewiesen
werden kann.
Okay...
Jetzt nach recht langer Zeit komme ich doch nochmal dazu dieses Thema
aufzugreifen. Jetzt habe ich Urlaub und darf mal nicht in der
Nachbarschaft einen Computer reparieren. Ich habe aber auch in wenigen
Monaten die Prüfung für meine Umschulung.
Ich habe jetzt mal den Berg an Beiträgen seit dem 28.06. gelesen und
kämpfe mich jetzt da durch die Codes zu verstehen. Jetzt nach einem Tag
muss ich sagen, habe ich grade mal kapiert, dass der Code mit den
Zugriffen auf das SRAM zu tun hat und das irgendwas addiert wird. Ich
stell mich grade beim Verstehen aber recht dumm an.
Vielleicht kann mich mal wer beim Verstehen an die Hand nehmen...?
> seit dem 28.06. gelesen
Dann haben Sie sicher gesehen, dass peda einen Folge-Thread eröffnet
hat.
Wenn wirklich BAM (bzw. BCM) zum Einsatz kommen soll, dann ist Sams Code
sicher eine gute oder sogar die beste Wahl; vermutlich auch ohne
tieferes Verständnis einsetzbar.
Ich möchte es so sagen:
Für mein Auge sieht das hoch professionell und funktionstüchtig aus. Ich
verstehe nur nicht was da im Programm überhaupt passiert. Ich würde es
aber gern verstehen, da das BAM, PWM oder was sonst in Frage kommt nur
ein Teil eines Mammut-Projektes ist. Da kommen noch Teile hinterher die
weitaus schlimmer um zu setzen sind. Da möchte ich natürlich Konflikte
vermeiden, bzw. sehen wo ich welche Möglichkeiten ausschöpfen kann.
Ne... da kann ich so lange drauf schauen wie ich will. Dieses
Mixed-C-ASM kann ich nicht lesen. Da bleibt mir grade nichts anderes
übrig, als meine eigene kleine Geschichte weiterzuschreiben. Ich hätte
jetzt im nächsten Schritt aber ohnehin auf das SRAM ausweichen müssen.
Dies macht im weiteren Verlauf meines Projektes auch Sinn.
Bugfix:
Ich lese mir den Komplettcode von Sam immer und immer wieder durch und
verstehe langsam immer mehr. Ich finde schon, dass das eine kleine
Meisterleistung ist. Trotzdem verstehe ich so die eine oder andere Zeile
nicht so ganz. Z. B.:
brtc .+0 Ich habe herausgefunden, dass dies bedingter Sprung PC+1
bedeutet. Gesehen habe ich das aber noch nie.
brmi 0b Soll wohl ein Bedingter Sprung PC-1 bedeuten.
0: 1: 2: Sollen wohl Labels sein. Habe ich so auch noch nie gesehen. Die
werden aber auch nirgends aufgerufen.
Lange Rede kurzer Unsinn habe ich alle Eindrücke von den Posts oben
mitgenommen und habe an meinem (kleinen) Programm weitergeschrieben. Ich
bin da aber auch einen Bug gestoßen, aus dem ich nicht schlau werde. Der
8-Bit-Timer 0 wird einmal gestartet und von da an in Ruhe gelassen.
Stattdessen wird der Interruptpunkt für OCR0A immer weiter nach hinten
geschoben. Im folgenden Code (unten) wird von Interrupt bis zur Rückkehr
in das Hauptprogramm 15 CPU-Zyklen verbraucht. Verschiebe ich den
aktuellen Interruptpunkt in OCR0A auf einen Punkt 16 Zyklen später,
sollte das Programm genau einen Zyklus im Hauptprogramm verweilen und
wieder in die ISR gehen. Aber das tut sie im Simulator erst 16+256
Zyklen später.
Kann mir wer sagen warum?
1
timer0_matchA:
2
ld itmp0,Z+ ; SRAM lesen - Zeiger erhöhen
3
out PortB,itmp0 ; Ausgabe der entsprechend wertigen Bits
4
5
in itmp4,SREG ; SREG sichern
6
cpi ZL, LOW(Bam_Outputs)+LOW(BAM_Width)-4 ; Zeiger auf Position überprüfen
7
breq Last_BAM_Cycle ; letzte BAMs ausführen
8
9
; Simple_BAM_Cycle:
10
11
lsr itmp6 ; logisches verschieben nach rechts bei überlauf bit-128 mitnehmen
12
in itmp0,OCR0A ; aktuellen Timerinterrupt-Punkt lesen
13
add itmp0,itmp6
14
; bewirkt, dass der nächste iterrupt in der hälfte der Zeit ausgelösst wird
15
out OCR0A,itmp0 ; Timerinterrupt-Punkt auf neue Position setzen
An der Stelle timer0_matchA sind doch bereits mindestens 4, vermutlich
aber 6 oder mehr Takte seit dem Interrupt (TCNT0 = OCR0A) vergangen.
(Auch wenn ich mich wiederhole: wäre Zeit und Energie nicht besser in
die Integration von Sams Lösung investiert?)
Nach Eintritt in den Interrupt wird OCR0A ja nicht absolut weiter nach
hinten versetzt. Die 16 werden zu der alten bereits vergangenem Position
dazu addiert. Die neue Position wird also in Relation zur alten Position
neu gesetzt, unabhängig davon wo diese Befehle stehen.
In den Interrupts vorher wird eine Verzögerung von 64 (0b01000000) und
eine mit 32 (0b0010000) dazu addiert. Die Interrupts werden auch mit
der korrekten Verzögerung aufgerufen. Nur mit der 16 gibt es Probleme.
Die daraus folgende Verzögerung von 16+256 zeigt ja auch, dass die
Verzögerung übernommen wurde. Sie wird nur viel zu spät wirksam und ich
habe keine Ahnung warum.
Sams Lösung würde ich wirklich gern übernehmen, bzw. für meine kleineren
Bedürfnisse anpassen. Das Problem bleibt aber, dass ich die Essenz des
Programms (noch) nicht erfassen kann und es mir daher schwer fällt die
einzelnen Stellen des Programms richtig einzuschätzen. Dazu kommt, dass
mich die vielen Verzögerungsschleifen stören, die an sich gut mit
sinnvollen Befehlen befüllt werden könnten (es wird CPU-Leistung
verbraucht ohne das was gemacht wird). Mein kleines Programm ist
ungleich weniger professionell, aber dafür stärker an meine Bedürfnisse
angepasst.
Wenn OCR0A mit dem Wert 'vorher+16' gesetzt wird, ist TCNT0 bereits
einen oder einige Zähler weiter, als wird die Interruptbedingung TCNT0 =
OCR0A erst einen Durchlauf später erfüllt.
Das sollte im Simulator eigentlich klar erkennbar sein!?
Hier kommt noch das fehlende 'o' fürs also.
Nochmal zu Sam: 'verbraten' wird ja nur in den kleinen Zeitscheiben (und
ich bezweifle, dass Ihnen das wesentlich besser gelingen wird), in den
großen bleibt viel zeitlicher Spielraum für sonstige Aktivitäten.
Allgemein habe ich jetzt aber ein Problem: mein Wunsch zu helfen geht
allmählich gegen Null, und ein Eigeninteresse, sprich Erkenntnisgewinn,
ist nicht in Sicht. Ich verabschiede mich.
Die wenigen Erkenntnisse die ich grade über Sams Komplettprogramm habe
ist, dass es auf den Timer_Overflow reagiert (davon ausgelöst wird) und
die Bits in der Reichen folge 1 2 4 8 16 32 64 128 ausgibt und erst nach
der Ausgabe von 128 die CPU wieder für das Hauptprogramm frei gibt. Da
ich noch eine andere sehr wichtige Aufgabe umsetzen möchte ist das so
für mich nicht direkt verwendbar.
Selbstverständlich können meine Erkenntnisse auch falsch sein.
Der Vollständigkeit halber möchte ich aber sagen, dass ich hier
niemanden auf den Schlipps treten möchte.