Angeregt duch eine Vorstellung eines VU-Meters in Assembler in
Beitrag "VU-Meter mit Attiny13a statt LM3916" habe ich versucht, dies auf
einem Tiny25 in C zu implementieren.
Grundsätzlich funktioniert das ganz gut, obwohl ich die gewünschte
Abtastrate aus ungeklärter Ursache mit 500kHz ADC-Takt nicht erreiche.
Mit 1MHz jedoch schon. Ich hätte jedoch erwartet, dass die auch mit
500kHz gehen würde.
Der Ansatz ist folgender:
- Prozessortakt 8MHz (interner RC)
- ADC im Autotriggermode mit 500kHz Takt, getriggert durch
Timer/Counter0 Overflow (Prescaling 1). Das passiert dann alle 32µs.
- ADC benötigt 13 ADC-Takte für die Konverierung, also 13*1/500kHz =
26µs
- zur Ergebnisberechnung lese ich jedes Mal einen Wert aus einer
Tabelle, die im Programmspeicher abgelegt ist (PROGMEM). Die
Zugriffsdauer auf diesen Wert ist rechnerisch und auch gemessen ca.
4.6µs
Mit noch einigen anderen Befehlen wäre das Zeitbudget dann schon
ausgeschöpft und würde auch das Verhalten erklären, dass der ADC nur
alle 64µs wieder losrennt. Diese Überlegung ist vermutlich nicht
richtig, weil:
Wenn ich anstatt des Zugriffs auf die Tabelle in PROGMEM ein einfaches
Delay mit 25µs einbringe, dann geht das problemlos.
Ich denke deshalb (weiß es aber nicht sicher), dass der ADC beim
Eintritt in die ADC ISR nur seinen Wert abliefert und sofort mit einer
neuen Wandlung beginnt, die es aber erlaubt, dass parallel dazu anderer
Code abgearbeitet werden kann. Ist das an sich richtig?
Wenn ja, warum bremst ein an sich zeitlich kurzer Zugriff mittels
PROGMEM den ADC oder den Trigger des ADC aus? Welche Information fehlt
mir hier?
Anbei der Code sowie drei Screenshots.
Für die Screenshots wurden im Code jeweils geeignet die Zeilen
1
adc_square_sum+=pgm_read_word(&square_number[adcvalue]);// only 8 bit ADC used
auskommentiert bzw. aktiviert, um das Verhalten zu messen.
In den Screenshots ist der Eintritt in die ISR mit einer fallenden
Flanke von PB0 markiert, das Ende mit einer steigenden.
Speziell im Bild "mit&ohne-adc_square_sum@500kHz.png" sieht man, dass
nach 32µs kein neuer Zyklus beginnt.
Noch verwirrender (für mich): es wird nur manchmal eine Triggerung
ausgelassen, selbst wenn ich 10µs oder 20µs delay hinzufüge.
> die es aber erlaubt, dass parallel dazu anderer> Code abgearbeitet werden kann. Ist das an sich richtig?
Ja.
> Wenn ja, warum bremst ein an sich zeitlich kurzer Zugriff mittels> PROGMEM den ADC oder den Trigger des ADC aus?
Wenn du den ADC im Free running Modus verwendest, gibt es keinen
Trigger. Und dann wird ganz sicher auch nichts ausgebremst.
> Anbei der Code
Darf ich davon ausgehen, dass dein Programm aus mehr als diesen drei
Zeilen besteht? Wenn DU ernsthaft Hilfe erwartest, dann solltest du das
Programm auf die kleinste mögliche Variante reduzieren, wo das Problem
noch nachvollziehbar ist und das dann hier posten.
Stefan U. schrieb:>> Anbei der Code>> Darf ich davon ausgehen, dass dein Programm aus mehr als diesen drei> Zeilen besteht? Wenn DU ernsthaft Hilfe erwartest, dann solltest du das> Programm auf die kleinste mögliche Variante reduzieren, wo das Problem> noch nachvollziehbar ist und das dann hier posten.
Der auf das Problem reduzierte Code ist als C-File im Anhang. Der ist
kompilierbar und mit dem Code wurden die Screenshots erstellt.
Stefan U. schrieb:> Wenn du den ADC im Free running Modus verwendest, gibt es keinen> Trigger. Und dann wird ganz sicher auch nichts ausgebremst.
Den Trigger wollte ich haben, um eine definierte Abtastrate zu bekommen.
Free-Running werde ich mal testen ...
HildeK schrieb:> - zur Ergebnisberechnung lese ich jedes Mal einen Wert aus einer> Tabelle, die im Programmspeicher abgelegt ist (PROGMEM). Die> Zugriffsdauer auf diesen Wert ist rechnerisch und auch gemessen ca.> 4.6µs
Viel zuviel. Ungefähr 4..5 mal so viel wie nötig. Da stimmt was nicht.
Oder es besteht zumindest massives Optimierungspotential...
> Ich denke deshalb (weiß es aber nicht sicher), dass der ADC beim> Eintritt in die ADC ISR nur seinen Wert abliefert und sofort mit einer> neuen Wandlung beginnt
Dann wäre er nicht im Timer-Triggermodus, sondern im FreeRunning-Modus.
Oder es wird fälschlicherweise irgendwo "manuell" eine Wandlung
gestartet.
> die es aber erlaubt, dass parallel dazu anderer> Code abgearbeitet werden kann.
Natürlich kann immer Code parallel zur AD-Wandlung abgearbeitet
werden. Ganz egal, wie die Wandlung gestartet wurde. Denn die Wandlung
erfolgt in eigener Hardware, völlig unabhängig von allem, was die MCU
tut.
> warum bremst ein an sich zeitlich kurzer Zugriff mittels> PROGMEM den ADC oder den Trigger des ADC aus?
Tut er nicht! Damit kannst du alle deine Überlegungen, die das irgendwie
als Basis haben, ganz getrost in den Orkus entsorgen.
Anders denken und die Fehleranalyse noch mal von vorn beginnen...
> Der auf das Problem reduzierte Code ist als C-File im Anhang
Oh, sorry. Den habe ich übersehen.
> Den Trigger wollte ich haben, um eine definierte Abtastrate> zu bekommen. Free-Running werde ich mal testen ...
Eine regelmäßige Abtastung mit so hohen Frequenzen bekommst du nur im
Free Running Modus hin. Alleine schon deswegen, weil Timer Interrupts
mit einem gewissen Jitter aufgerufen werden.
c-hater schrieb:> Viel zuviel. Ungefähr 4..5 mal so viel wie nötig. Da stimmt was nicht.> Oder es besteht zumindest massives Optimierungspotential...
Mag sein. Es ist ja nicht nur der Zugriff, sondern auch die
Aufsummierung - sorry für unsaubere Bezeichnung. Es war die Zeit des
Befehls, wo natürlich mehr dabei ist.
c-hater schrieb:> Dann wäre er nicht im Timer-Triggermodus,
Auch hier habe ich mich schlecht ausgedrückt - der Trigger erfolgt durch
den Timer.
Stefan U. schrieb:> Eine regelmäßige Abtastung mit so hohen Frequenzen bekommst du nur im> Free Running Modus hin. Alleine schon deswegen, weil Timer Interrupts> mit einem gewissen Jitter aufgerufen werden.
Ich verwende ja keinen Timer Interrupt, sondern nur den Autotrigger
durch den Timer Überlauf. Der sollte doch recht äquidistant sein, da er
einfach den Systemtakt durch 256 teilt - oder verstehe ich wieder was
falsch?
Und ohne den Zugriff und die Berechnung auf die im Progmem abgelegte
Tabelle passt ja alles. Mit 1MHz ADC-Takt auch.
Stefan U. schrieb:>> Den Trigger wollte ich haben, um eine definierte Abtastrate>> zu bekommen. Free-Running werde ich mal testen ...>> Eine regelmäßige Abtastung mit so hohen Frequenzen bekommst du nur im> Free Running Modus hin. Alleine schon deswegen, weil Timer Interrupts> mit einem gewissen Jitter aufgerufen werden.
Unsinn.
Man kann den ADC per Timer triggern, ohne dafür irgendeinen Interrupt
beschäftigen zu müssen. Das passiert direkt in der Hardware. Sehr
nützlich z.B. für geregelte Schaltwandler in Software, um die Messung
mit der Generierung zu synchronisieren. Vor allem deswegen gibt es wohl
diese Funktionalität, ihre Anwendung ist aber natürlich nicht darauf
beschränkt...
Ja, wenn du den ADC per Timer (ohne Interrupt-Routine) triggerst, dann
wird es wie gwünscht klappen - sollte jedenfalls, meine ich.
Ich hatte deine Beschreibung so verstanden, dass du den ADC im Free
Running Modus verwendest, aber irgendwie auch per Software triggern
wolltest.
Ja, der ADC wird jedes Mal dann getriggert, wenn der Timer/Counter0
einen Überlauf hat. Damit will ich für eine feste Abtastfrequenz von
8MHz/256 sorgen.
Das funktioniert ja auch wie erwartet, solange die
Quadratsummenberechnung
nicht drin ist. Dann kann ich mit einem Delay rund 20µs verbraten ohne
negativen Einfluss. Sieht man am Bild "mit_ohne_delay_500kHz.png" (gelb
zeigt die Zeit mit dem Delay, blau ohne).
An anderer Stelle hatte ich gemessen (und gerechnet), dass die genannte
Zeile eigentlich keine 5µs benötigt. Das zeigt das Bild
"mit_ohne-adc_square_sum_500kHz.png".
Nur: die besagte Zeile verhindert (zumindest in der Hälfte der Fälle),
dass der ADC nach dem nächsten Timerüberlauf wieder startet, sondern
erst nach dem übernächsten.
Das bekomme ich nicht unter einen Hut.
HildeK schrieb:>> Dann wäre er nicht im Timer-Triggermodus,>> Auch hier habe ich mich schlecht ausgedrückt - der Trigger erfolgt durch> den Timer.
Dass das beabsichtigt ist, war mir eigentlich schon klar, siehe auch die
anderen Postings im Thread, die sich mit deinem wahrscheinlich
überschnitten haben.
Aber ist auch die Initialisierung korrekt, um tatsächlich diesen
Triggermodus zu erreichen? Ich habe mir deinen Code nicht angesehen,
nach 8 Stunden C auf Arbeit habe ich schlicht mal wieder die Schnauze
voll davon...
> Der vom C-Compiler (ich weiß, ist ein rotes Tuch für dich :-))
Zu Recht, wie dieser Code ein weiteres Mal überzeugend darlegt. Den
Schaden richtet natürlich meist nicht der Compiler alleine an, sondern
der Compiler zusammen mit seinem unkritischen Benutzer, der der frechen
Lüge der C-Apologeten glaubt, dass der Compiler schon alles richten
wird...
> +0000013C: 91800066 LDS R24,0x0066 Load direct from data> space> +0000013E: 2FE8 MOV R30,R24 Copy register
Schonmal völlig sinnlos ein Takt verschenkt. Ein Fehler, der keinem
echten Asm-Programmierer an dieser Stelle jemals unterlaufen würde...
Hier müßte es NATÜRLICH heißen
lds R30,0x0066; 1
Das hätte wohl auch der Compiler hinbekommen, wenn du ihm verraten
hättest, dass diese Funktion nur von genau einer Stelle aus aufgerufen
werden wird. Leider geht das in C nur indirekt, indem du ihm sagst, dass
er jede Instanz der Funktion so übersetzen sollte, als wäre es die
einzige.
Das Zauberwort heisst inline...
> +0000013F: E0F0 LDI R31,0x00 Load immediate> +00000140: 0FEE LSL R30 Logical Shift Left> +00000141: 1FFF ROL R31 Rotate Left Through> +00000142: 5EE2 SUBI R30,0xE2 Subtract immediate> +00000143: 4FFF SBCI R31,0xFF Subtract immediate> +00000144: 9125 LPM R18,Z+ Load program memory> +00000145: 9134 LPM R19,Z Load program memory
Das ist soweit OK. Könnte man höchstens dann besser machen, wenn man die
Freiheiten von Assembler bei der Organisation des Speichers nutzt. Dann
würde man die Tabelle in zwei Teile zerlegen (Low- und High-Bytes der
Daten getrennt) und sie auf eine 256-Byte-Grenze alignen. Dann sähe der
Code im weiteren Verlauf so aus:
ldi R31,High(squaretablebase) ;1
lpm R18,Z ;3
inc R31 ;1
lpm R19,Z ;3
Damit sind wir erstmal mit dem durch, was du ursprünglich gesagt hast,
worum es gehen würde (wobei allerdings nicht klar war, das die zu
lesenden Daten 16 Bit breit sind). However, aus den 13 Takten des
Compilers haben wir trotzdem schonmal nur 9 gemacht. -> ca. 30% mehr
Performance bei dem Teil bis hierhin...
Schauen wir uns mal das weitere Elend an...
> +00000146: E040 LDI R20,0x00 Load immediate> +00000147: E050 LDI R21,0x00 Load immediate> +00000148: 91800060 LDS R24,0x0060 Load direct from data> space> +0000014A: 91900061 LDS R25,0x0061 Load direct from data> space> +0000014C: 91A00062 LDS R26,0x0062 Load direct from data> space> +0000014E: 91B00063 LDS R27,0x0063 Load direct from data> space> +00000150: 0F82 ADD R24,R18 Add without carry> +00000151: 1F93 ADC R25,R19 Add with carry> +00000152: 1FA4 ADC R26,R20 Add with carry> +00000153: 1FB5 ADC R27,R21 Add with carry> +00000154: 93800060 STS 0x0060,R24 Store direct to data> +00000156: 93900061 STS 0x0061,R25 Store direct to data> +00000158: 93A00062 STS 0x0062,R26 Store direct to data> +0000015A: 93B00063 STS 0x0063,R27 Store direct to data
Hmmm... Da ist wohl auch nicht der Compiler schuld, sondern auch wieder
du. Aufsummiert wird offensichtlich ein 32Bit-Wert. Ich bezweifele
ernsthaft, dass das an dieser Stelle tatsächlich sinnvoll ist. Und
selbst wenn: In Assembler würde man natürlich für einen derart häufig
genutzen Wert einfach vier unwichtige Register exklusiv dafür
reservieren, z.B. R7:R4. Damit reduziert sich dieser Codeteil auf:
add R4,R18 ;1
adc R5,R19 ;1
adc R6,NULL ;1 (NULL ist ein reserviertes Register mit dem konstanten
adc R7,NULL ;1 Inhalt 0. Das gibt es eigentlich auch beim avr-gcc,
keine Ahnung, warum der Compiler das nicht benutzt hat
und statt dessen diesen Unsinn mit R21:R20
veranstaltet...
Im Endeffekt also 4 statt 22 Takten -> 80% mehr Perfomance für diesen
Teil der Sache...
HildeK schrieb:> Ich hätte jedoch erwartet, dass die auch mit> 500kHz gehen würde.
Rechne einfach mal nach.
Dein Timer kommt alle 256 Zyklen, der ADC-Interrupt aber erst nach
13*16=208 Zyklen. Da sind nur noch 48 Zyklen Zeit, das Überlaufbit zu
löschen und das ist knapp. Schon ein Interrupteinsprung dauert
mindestens 10 Zyklen und dann noch der Prolog.
Die Lösung ist einfach, enable auch den Timerinterrupt. Dann wird das
Flag bequem bis zur nächsten Wandlung gelöscht:
1
EMPTY_INTERRUPT(TIMER0_OVF_vect);
Das Delay braucht viel weniger Register, als die Rechnerei und
Pointerarithmetik, d.h. der Prolog wird deutlich kürzer.
Sich drüber aufzuregen, daß der Compiler hi und da mal nen Zyklus mehr
braucht, ist völlig sinnlos. Zielführend ist allein, sich den zeitlichen
Ablauf klarzumachen.
c-hater schrieb:> Das Zauberwort heisst inline...
Erst mal danke für deine Analyse.
Ich bin HW-Entwickler (gewesen: jetzt Altersteilzeit, passiv) und kann
leider nur C leidlich für den Hausgebrauch, Assembler eben nicht. Inline
kenne ich zwar als Begriff, aber ich weiß z.B. von C aus nicht, in
welchen Registern der Compiler tatsächlich die Daten verarbeitet um ev.
die von dir genannten Registerverarbeitungen zu optimieren.
Ich habe also keinen Plan, wie ich diese eine C-Zeile mit Inline
darunter mischen könnte (wie übergebe ich Daten, wie bekomme ich dann an
die Ergebnisse?).
Puren Assembler hätte ich schon vor 30 Jahren lernen sollen - ich
fürchte, da ist es jetzt zu spät für mich ... :-)
Ich möchte daher mit C auskommen können.
> Aufsummiert wird offensichtlich ein 32Bit-Wert.
Richtig.
> Ich bezweifele> ernsthaft, dass das an dieser Stelle tatsächlich sinnvoll ist.
Ich hatte den Code ja auf das Minimum zur Klärung meines Problems
reduziert. Die 32-Bit-Variable ist deshalb notwendig, weil ich die
16Bit-Werte aus square_number über 1024 Samples aufsummiere und dann
einen Mittelwert bilde. Das soll also schon so sein ...
Aber unabhängig davon: ob der Befehl 2µs oder 5µs benötigt, ist wohl
deshalb nicht das Problem, weil auch ein _delay_us(20) noch geht. Also
meine ich, dass ich auf jeden Fall diese 20µs mit irgendwelchen Befehlen
füllen kann.
Ich vermute eine Abhängigkeit zwischen dem Timer Overflow-ADC-Trigger
und dem Zugriff auf eine Array-Variable, die im PROGMEM abgelegt ist und
ev. der AD Konversion allgemein.
Ich würde gerne verstehen, was hier bremst.
Für die Funktion des VU-Meters kann ich mir mit vernachlässigbarem
Genauigkeitsverlust auch damit behelfen, dass der ADC grenzwertig mit
1MHz Takt rennt.
Peter D. schrieb:> Die Lösung ist einfach, enable auch den Timerinterrupt.
Danke! Ja, zumindest in meinem 'aufs nötigste' reduzierten Code wirkt
das!
Muss ich mir morgen nochmals genau anschauen...
> Schon ein Interrupteinsprung dauert> mindestens 10 Zyklen und dann noch der Prolog.
Meinst du die Registerpuscherei? Da habe ich mich auch schon gefragt,
warum die da ist, da doch im main() in der Endlosschleife nichts mehr
passiert.
Und, was meins du mit 'Prolog'? Ich weiß, da fehlt noch Wissen, auch
warum die 48 Takte nicht reichen ...
> Sich drüber aufzuregen, daß der Compiler hi und da mal nen Zyklus mehr> braucht, ist völlig sinnlos. Zielführend ist allein, sich den zeitlichen> Ablauf klarzumachen.
Aufregen tu ich mich nicht :-).
Den zeitlichen Ablauf hatte ich mir schon überlegt, nur haben wohl die
vorhandenen Informationen noch nicht gereicht.
c-hater schrieb:> [bla]
Hat alles mit dem Problem des TO ziemlich sicher nix zu tun — umso mehr
mit deinem Hass auf C, und da lässt du ja bekanntlich keine Möglichkeit
aus.
Johann L. schrieb:> Hat alles mit dem Problem des TO ziemlich sicher nix zu tun
Doch. Sein Problem ist hier tatsächlich, dass schlicht die Zeit nicht
reicht.
Und es gibt genau drei Sachen, die man gegen den Mangel an Zeit tun
kann:
1.a) Man besorgt mehr Zeit für eine bestimmten Stelle des Codes, z.B.
mit dem Ansatz, den PeDa hier im Thread vorschlug. Das dürfte für den
gezeigten Code locker reichen, um ihn in die Echtzeit zu bringen und hat
ausserdem den Vorteil, keine Extrakosten zu verursachen.
Allerdings hat der TO ja gesagt, dass der Code auf's Wesentliche
bezüglich des Problems reduziert ist. Es ist also nicht sicher, dass es
auch noch reicht, wenn wirklich der volle Code läuft.
1.b) Man besorgt mehr Zeit für eine bestimmten Stelle des Codes, z.B.
durch Pipelining. D.h.: man verlagert die Berechnung an eine Stelle, wo
mehr Rechenzeit zur Verfügung steht. Leider kostet das in C Extra-Takte,
weil man den ADC-Wert im RAM zwischenspeichern muss. In Assembler kostet
es wahlweise nur ein Register und keine Extra-Takte.
Das Grundproblem bleibt aber das gleiche wie bei 1.a), wird durch den
Ansatz nur verlagert, die Kosten aber bleiben gleich oder werden sogar
größer. Manchmal reicht so ein Workaround, manchmal halt nicht.
2. Man sorgt dafür, dass der Code tatsächlich schneller läuft. Das ist
auf jeden Fall die optimale Lösung. Und das geht eben am besten mit
Assembler, wie ich gezeigt habe. Und, wie ich ebenfalls gezeigt habe,
sind es beileibe nicht nur ein paar Takte, die so zu sparen sind,
sondern wirklich signifikante Mengen an Rechenzeit, für die Routine
insgesamt nämlich satte 63%. Oder anders ausgedrückt: Sie ist in
optimiertem Asm ca. 3 Mal (!!) schneller...
"c-hater", du hast natürlich vollkommen recht: ein guter
Assemblerprogrammierer schafft das besser. Ich bin aber gar kein
Assemblerprogrammierer und nur ein mäßiger C-Programmierer.
In dem im Eröffnungspost verlinkten Thread hat das 'Dergute Weka' auch
so gemacht. Daraus habe ich auch die Struktur meines Programms
entnommen, soweit ich den Code nachvollziehen konnte.
Mir war nicht klar, dass der ADC offenbar noch weitere Takte benötigt
für irgendwelche Sachen ("peda" nannte Prolog, Interupteinsprung und
Timerflag) und nicht max. mit den 13 ADC-Takten auskommt. Darüber muss
ich mich noch schlau machen und herausfinden, warum der vorgeschlagene
Timerinterrupt das so deutlich löst.
Den Vorschlag von "peda* habe ich in den vollständigen Code
eingearbeitet und siehe da: auch hier reicht es - mit Ausnahme der alle
1024 Samples erfolgenden Auswertung der aufsummierten 'adc_square_sum'.
Dann wird ein Zyklus ausgelassen, was für das gewünschte Ergebnis aber
absolut irrelevant ist.
Höchstwahrscheinlich wäre für die Aufgabe auch eine Unterabtastung kein
Problem gewesen - ich will ja das Signal nicht wieder rekonstruieren.
Ich habe dann nur eine längere Erfassungszeit oder müsste für die
Mittelwertbildung die Zahl der Samples halbieren.
Im Anhang das vollständige Programm.
Ich danke allen für die angeregte Diskussion und besonders 'peda' für
den entscheidenden Hinweis!
HildeK schrieb:> mit Ausnahme der alle> 1024 Samples erfolgenden Auswertung der aufsummierten 'adc_square_sum'.> Dann wird ein Zyklus ausgelassen
Du kannst die Fusebits auf PLL setzen, das ergibt F_CPU=16MHz.
Dann braucht der ADC 416 Zyklen, d.h der Timerinterrupt muß auf 512
Zyklen gesetzt werden.
Ich verfolge sehr intressiert den thread und versuche eine Erklärung.
HildeK schrieb:> Mir war nicht klar, dass der ADC offenbar noch weitere Takte benötigt> für irgendwelche Sachen ("peda" nannte Prolog, Interupteinsprung und> Timerflag) und nicht max. mit den 13 ADC-Takten auskommt. Darüber muss> ich mich noch schlau machen und herausfinden, warum der vorgeschlagene> Timerinterrupt das so deutlich löst.
Es geht um den Interrupteinsprung. Mit der Aufsummierung müssen mehr
Register gesichert werden und machen die Zeit bis zum Löschen des TOV
länger. Zu lang, so daß der Timerüberlauf, der eigentlich eine neue
Messung starten sollte, schon vor Löschen des TOV stattfindet und damit
keinen ADC-Auslösetrigger in diesem Zyklus liefern kann.
Laut Datenblatt sollte man bei Autotrigger mit 13,5 ADC Takten rechnen.
Wären dann schon 27µs statt 26 bei 500kHz und noch näher an der 32µs
Marke.
Der Prolog, also Zeit von ADIF erkannt bis Beginn Abarbeitung des ersten
Befehls in der ISR kann in den verschieden Assemblerlistings vergleichen
und immer noch die Zeit für Interrupteinsprung, die die HW braucht,
dazurechnen zB Programmcounter einlesen. Die ganzen POPs findet man vor
dem RETI. Die PUSHs der Registersicherungen dazu dann beim Hochscrollen.
Die Aufsummierung ist also nur indirekt Schuld dadurch daß sie "Prolog"
länger macht.
Das Löschen des TOV durch den eigenen Interrupt kann in der Zeit der
Messung stattfinden und wenn der 32µs Intervall im Prolog der ADC ISR
liegt trotzdem eine neue Massung starten.
Gunner schrieb:> Ich verfolge sehr intressiert den thread und versuche eine Erklärung.
Danke, ich glaube, du hast damit schon sehr nahe die Ursachen erklärt.
> Die ganzen POPs findet man vor> dem RETI. Die PUSHs der Registersicherungen dazu dann beim Hochscrollen.
Ja, die habe ich gesehen und auch die Zeit, die damit 'verbraten' wird.
In meinem Programm wären die Pushs und Pops gar nicht erforderlich, denn
in der Endlosschleife im main() wird ja nichts abgearbeitet. Das erkennt
der Compiler offenbar nicht.
Peter D. schrieb:> Du kannst die Fusebits auf PLL setzen, das ergibt F_CPU=16MHz.> Dann braucht der ADC 416 Zyklen, d.h der Timerinterrupt muß auf 512> Zyklen gesetzt werden.
Das werde ich noch ausprobieren. Die PLL hatte ich nicht im Fokus.
Wie jedoch schon erwähnt: dieser eine fehlende Sample von 1024 tut dem
VU-Meter nicht weh.
Aber, Timerinterrupt auf 512 Zyklen? Der Timer/Counter0 ist doch nur ein
8 Bit Timer und der Prescaler kann nicht durch 2 teilen. Und mit dem
Timer1 kann man den ADC-Trigger nicht starten.
Ev. Timer0 Prescaler auf 8 und OCRA im Interrupt auf 64 (bzw 256-64)
setzen für 64 Takte á 500ns (16MHz / 8) und den Compare Match Interrupt
verwenden? Könnte gehen ...
Peter D. schrieb:> Rechne einfach mal nach.> Dein Timer kommt alle 256 Zyklen, der ADC-Interrupt aber erst nach> 13*16=208 Zyklen. Da sind nur noch 48 Zyklen Zeit, das Überlaufbit zu> löschen und das ist knapp. Schon ein Interrupteinsprung dauert> mindestens 10 Zyklen und dann noch der Prolog.
Der TO hat das selber ausgerechnet - warum er (sie) das trotzdem
von hinten macht, ist mir unklar.
Der Vorschlag von peda funktioniert nur, weil dadurch Takte in der
ADC-ISR eingespart werden.
Und deswegen hat der c-hater vollkommen Recht mit seinen Ausführungen
über Geschwindigkeit.
Aber egal.
Ein Messzyklus dauert 256 Takte.
Ende der Conversion bis zum Timer Überlauf = 48 Takte.
Timer Überlauf bis zum Ende der Conversion = 208 Takte.
Wenn man den ganzen Zirkus in TIMER0_OVF_vect macht, kriegt man
dadurch 4 Mal so viel Zeit.
Auch wenn in TIMER0_OVF_vect schon eine neue Conversion gestartet hat,
kann man das Resultat der letzten Conversion ohne Probleme auslesen.
Ich jedenfalls hätte es so gemacht...
HildeK schrieb:> Was meinst du mit 'von hinten'?
TIM0_OVF ============================> ADC_ISR |
^ ^ ^
| | |
| | |
Vorne Hinten Ende
Das ;-)
P.S.
Das Resultat kriegst du ganze 48 Takte später...
Marc V. schrieb:> TIM0_OVF ============================> ADC_ISR |> ^ ^ ^> | | |> | | |> Vorne Hinten Ende
Ich habe versucht, die Berechnungen in der Timer-ISR zu machen und quasi
die ADC-ISR leer zu lassen. Das brachte keine Änderung. Vielleicht habe
ich dich auch nicht korrekt verstanden.
Der Zeitrahmen ist durch die Timer ISR vorgegeben - 32µs. Ob ich nun die
Berechnung dort oder in der ADC ISR mache: es muss die Zeit zwischen
zwei ADC Triggern dafür reichen.
In der ADC ISR wird ja auch nicht gewartet, bis die Wandlung fertig ist
(wie im manuellen Triggermode üblich), sondern der Timer startet den ADC
ja schon wieder, obwohl die ADC ISR noch abgearbeitet wird. Die wird ja
erst wieder aufgerufen, wenn der ADC fertig ist.
Und dann ist es, vielleicht von Feinheiten abgesehen, egal, ob ich die
Berechnung in der Timer oder ADC ISR mache.
Als ohne Probleme funktionierende Variante hat sich dieses
herausgestellt, wie Peter Dannegger das schon richtig erkannte:
- Takt über PLL auf 16MHz
- Timer0 Prescaler 8
- CTC IRQ mit OCR0A=63 und leerer ISR
- ADC Trigger auf Timer/Counter0 Compare Match A
- ADC Takt 500kHz
- alle Berechnungen in ADC ISR
und die ohne PLL mit nur einer kleinen Lücke nach der Berechnung (da
reichen eben die 32µs mit C-Programmierung nicht aus), die ich heute
morgen schon gepostet hatte.
Ich bin mit dem Ergebnis des Threads sehr zufrieden! Danke!
Marc könnte meinen, daß man in einer 32µs Timeroverflov ISR
- Eine ADC single conversion startet zur Auswerung in der nächsten
Timeroverflow ISR
- ADCH ausliest, dessen Inhalt von der vorherghehenden single conversion
stammt und den Wert wie auch immer verwurstet.
Der Abstand der Messungen könnte einen leichten Jitter bekommen, je
nachdem wann es zur ISR Ausführung und damit zu einem neuen Start der
single conversion kommt. Wenn keine andere ISR läuft sind das vielleicht
1 bis 2 Systemtakte. Die Messungen werden dann ja nicht mehr nur von der
HW gestartet.
Zu Beginn der Timer-ISR den ADC auslesen. Zu diesem Zeitpunkt läuft zwar
schon die nächste Wandlung, aber "When a conversion is complete, the
result is written to the ADC Data Registers ...", also müsste in
ADCH&ADCL noch der vorige Wert drinstehen.
HildeK schrieb:> Ich habe versucht, die Berechnungen in der Timer-ISR zu machen und quasi> die ADC-ISR leer zu lassen. Das brachte keine Änderung. Vielleicht habe> ich dich auch nicht korrekt verstanden.
Das weiss ich nicht, aber dass es mit Timer-ISR und Auto-Trigger
funktionieren muss, das weiss ich.
Nur 1 Sache muss man unbedingt beachten und zwar:
Und das war es, was die EMPTY_INTERRUPT(TIMER0_OVF_vect) gemacht hat.
Hat den Flag gleich nach Start der neuen ADC Conversion gelöscht und
dir genügend Zeit gegeben, die Berechnungen durchzuführen.
Aber das schlägt in deinem Program nur dann fehl, wenn du mehr als
48 Takte brauchst um den TOV-Flag zu löschen.
Ein löschen der TOV-Flags gleich nach Eintritt in die ADC-ISR hätte es
normalerweise auch getan. Warum deine Routine mehr als 48 Takte bis
zu diesem Zeitpunkt braucht, kann ich dir nicht sagen.
In der Timer0-OVF wird der TOV-Flag automatisch zurückgesetzt, hat
aber keinen Einfluss auf die gerade gestartete Messung, ermöglicht nur,
dass die nächste Messung getriggert werden kann.
ADCH gleich auslesen.
Und dann hast du mindestens 200 Takte Zeit bis ADC-ISR.
Was soll da nicht funktionieren ?
Ja, habe ich jetzt verstanden und auch mal kurz getestet. Da aber
alleine die Berechnung innerhalb des Blocks
1
if(sample_counter%1024==0)
2
{
3
.
4
.
5
}
über 26µs braucht und für den Rest die übrigen 6µs wohl nicht
ausreichen, hilft das nicht, um alle Werte zu erfassen.
Eine Lösung ist es trotzdem, zwischen den letzten 1024 erfassten Werten
und den nächsten 1024 fehlt nur ein Sample. Funktioniert daher auch wie
die anderen Lösungen.
Wenn man den unbedingt haben will, dann hilft nur die PLL.
> ... ADCH gleich auslesen.> Und dann hast du mindestens 200 Takte Zeit bis ADC-ISR.
Das habe ich jetzt nicht verstanden, wozu braucht man in diesem Fall
noch die ADC-ISR?
Die Posts hatten sich überschnitten. Meine letzte Antwort galt S.
Landolt.
Marc V. schrieb:> Ein löschen der TOV-Flags gleich nach Eintritt in die ADC-ISR hätte es> normalerweise auch getan.
Die ursprügliche ADC ISR hatte das Löschen des TOV0 drin. Das hatte
nicht gereicht, erst der EMPTY_INTERRUPT(TIMER0_OVF_vect) hat gepasst.
Peda hat es oben erklärt.
Marc V. schrieb:> Was soll da nicht funktionieren ?
Keine Missverständnisse: es funktioniert. Nur an einer Stelle reicht die
Zeit nicht, wie oben schon beschrieben, was auch überhaupt nicht
tragisch ist.
Nur die Tatsache, dass wenn ich die Berechnung in der Timer ISR anstatt
in der ADC ISR ausführe, jede Menge Zeit gewinne: das ist nicht so!
Die Varianten:
- Berechnung in ADC ISR und leere OVF
- Berechnung in OVF-ISR mit oder ohne ADC IRQ
sind praktisch gleichwertig vom Verhalten.
Nur mit der 16MHz-PLL kann man auch die alle 1024 samples erfolgende
Auswertung in den 32µs schaffen.
S. Landolt schrieb:> Das habe ich jetzt nicht verstanden, wozu braucht man in diesem Fall> noch die ADC-ISR?
War wohl falsch ausgedrückt von Marc: Die ADC-ISR braucht man dann
tatsächlich nicht.
HildeK schrieb:> Wenn man den unbedingt haben will, dann hilft nur die PLL.
Oder halt die Programmierung von wirklich effizientem Code -> Asm rules.
Und da du C offensichtlich auch nicht wirklich kannst, wäre es für dich
durchaus eine Option, einfach AVR-Assembler zu lernen. Die Sprache
selber ist nämlich sehr viel einfacher als C zu erlernen. Das liegt
allerdings natürlich vor allem auch daran, dass sie sehr viel weniger
kann als C...
Aber was sie kann, kann sie wenigstens richtig. Und sehr vieles von dem,
was sie nicht kann, kann auch C nicht wirklich leisten. Höchstens mit
einem Programmierer, der nicht nur C selber wirklich kann, sondern
obendrein auch noch die benutzte Toolchain aus dem effeff beherrscht
UND die Zielarchitektur.
Vor allem letzteres ist gewöhnlich nur Leuten gegeben, die das Target
auch locker in Asm programmieren können...
Oder anders ausgedrückt: C ermöglicht es, dass auch UNFÄHIGE Leute
UNKRITISCHE Anwendungen in endlicher Zeit irgendwie zum Laufen
kriegen. Nicht mehr und nicht weniger.
Dein Anwendung ist aber schon an der oberen Grenze des unkritischen. Das
merkst du ganz leicht daran, dass du sie eben nicht mehr selbstständig
zum Laufen bringen konntest...
c-hater schrieb:> Oder halt die Programmierung von wirklich effizientem Code -> Asm rules.
Das musste kommen :-)
Ohne Zweifel richtig; wie schon mehrfach angedeutet, wären die Pushs und
Pops in meinem Programm überflüssig gewesen, weil die Endlosschleife in
der main() eh nur ein RJMP PC-0x0000 ist. Das macht der Compiler
trotzdem.
> Und da du C offensichtlich auch nicht wirklich kannst,
Richtig - Profi bin ich nicht, habe ich auch nie behauptet.
Aber, ist mein Code sooo schlecht? Ja, ich halte mich nicht an die
typischen Coding Rules, die Profisoftwerker so verwenden, aber es muss
für mich übersichtlich bleiben.
Es fehlt eher das ganz tiefe Verständnis und die Erfahrung, was die
Ziel-HW so alles für mich noch unbekannte Seiteneffekte produziert und
warum.
> wäre es für dich> durchaus eine Option, einfach AVR-Assembler zu lernen. Die Sprache> selber ist nämlich sehr viel einfacher als C zu erlernen.
Dafür bin ich definitiv zu alt. Und mit C kann ich mich trotzdem schon
gut bewegen - meine ich.
Es sagte mal einer: "C ist wie ein offenes Rasiermesser in der
Hosentasche." Ich denke, Assembler ist da nicht besser ...
> Höchstens mit> einem Programmierer, der nicht nur C selber wirklich kann, sondern> obendrein auch noch die benutzte Toolchain aus dem effeff beherrscht> UND die Zielarchitektur.
Dass ich da Defizite habe, ist ja allen klar geworden. Ich mache nur
selten was mit dem µC und habe erst vor ein paar Jahren überhaupt damit
angefangen. Und, ich muss auch das was ich mache nicht verkaufen. An den
hardwarenahen Feinheiten arbeite ich noch, wie man sah.
> Dein Anwendung ist aber schon an der oberen Grenze des unkritischen. Das> merkst du ganz leicht daran, dass du sie eben nicht mehr selbstständig> zum Laufen bringen konntest...
Ich hatte sie am Laufen mit der halben Abtastfrequenz bzw. mit dem
grenzwertig schnellen ADC, einschließlich Charlie-Plexing innerhalb für
mich kurzer Zeit (ein Wochenende).
Da es aber in dem ganz oben verlinkten Beitrag mit dem Tiny13A und
Assembler besser ging, erwachte ein wenig Ehrgeiz. Da ich nur in C
programmieren kann, wollte ich sehen, wie weit ich damit komme.
Wie schon mal erwähnt, kann eine VU-Anzeige auch mit einer
Unterabtastung leben. Ich will ja die Samples nicht wieder zu einem
Audiosignal rekonstruieren.
Und jetzt, im Ergebnis und mit eurer Hilfe, bin ich in C etwa soweit
gekommen wie Dergute Weka in dem erwähnten Thread.
HildeK schrieb:> Und jetzt, im Ergebnis und mit eurer Hilfe, bin ich in C etwa soweit> gekommen wie Dergute Weka in dem erwähnten Thread.
Google doch einfach mal nach ISR und Prolog und Epilog - das hilft
schon. Die ISR ist nicht (unbedingt) so schnell, wie man vermutet. Es
lohnt sich, nach Alternativen Ausschau zu halten.
HildeK schrieb:> Dafür bin ich definitiv zu alt. Und mit C kann ich mich trotzdem schon> gut bewegen - meine ich.
Ich auch - aber man lernt immer noch dazu :-)
HildeK schrieb:> Und jetzt, im Ergebnis und mit eurer Hilfe, bin ich in C etwa soweit> gekommen wie Dergute Weka in dem erwähnten Thread.
Dann kannst Du es ja mal unter "Projekte & Code" veröffentlichen.
Wenn Dich die Push/Pop stören und die Mainloop leer ist, könnte man auch
mit Polling arbeiten.
Und laß Dir nichts einreden, die C-Hater sind hier eindeutig in der
Minderzahl, sowie auch die Polemisierer.
Dieter F. schrieb:> Google doch einfach mal nach ISR und Prolog und Epilog - das hilft> schon. Die ISR ist nicht (unbedingt) so schnell, wie man vermutet. Es> lohnt sich, nach Alternativen Ausschau zu halten.
Für solche Fälle gäbe es noch "__attribute__ ((naked))". Allerdings
sollte man dann schon sehr genau wissen was man sich damit einhandelt!
Dieter F. schrieb:> Google doch einfach mal nach ISR und Prolog und Epilog
Danke. Mir sind die Begriffe in dem Zusammenhang noch nicht begegnet,
aber die Push/Pop-Orgien schon.
neuer PIC Freund schrieb im Beitrag #4937299:
> Komisch dass noch niemand die modula-operation angezweifelt hat.
Ja, interessant :-).
Ich hätte nicht gedacht, dass die Modulooperation so schlecht übersetzt.
Werde ich mir merken!
Peter D. schrieb:> Dann kannst Du es ja mal unter "Projekte & Code" veröffentlichen.
Ja, ich denke, ich erweitere den bestehenden
Beitrag "VU-Meter mit Attiny13a statt LM3916".
Er ist ja erst 4 Wochen alt.
> Wenn Dich die Push/Pop stören und die Mainloop leer ist, könnte man auch> mit Polling arbeiten.
Das schaue ich mir mal an.
> Und laß Dir nichts einreden, die C-Hater sind hier eindeutig in der> Minderzahl, sowie auch die Polemisierer.
Nein, sowieso nicht. Die Hilfe war hier vorbildlich!
Und auch 'c-hater' bin ich für seine Diskussionsbeiträge dankbar, wenn
ich auch seine Vorliebe für ASM nicht teilen kann.
Marc V. schrieb:> neuer PIC Freund schrieb im Beitrag #4937299:>> als böser Übeltäter. Tut es nicht auch ein>> i = (i > 12) ? 0 : i; ?>> Nein.
Doch! Und bringt in meiner Routine eine überraschend große
Zeitersparnis!
@ neuer PIC Freund: Guter Tipp!
Ist selbst einen Takt besser als if(i>12)i=0; (wäre in dem Kontext ja
auch möglich). Wird der Modulooperator nur mit Zweierpotenzen gut
übersetzt?
guest schrieb:> Für solche Fälle gäbe es noch "__attribute__ ((naked))".
Danke für deinen Tipp, letztlich hat der dazu geführt, dass alles (auch
die lange Berechnung nach je 1024 samples) in der ADC ISR Zeit hat.
> Allerdings> sollte man dann schon sehr genau wissen was man sich damit einhandelt!
Ja, mal sehen :-). Zunächst mal ist die (hier!) nutzlose und
zeitraubende Push/Pop-Orgie (Prolog und Epilog, wie heute gelernt habe
:-)) weg und es sieht damit sehr gut aus.
Falls jemand zur der Warnung bez. 'naked' mehr weiß: es interessiert
mich!
HildeK schrieb:> Nein.>> Doch! Und bringt in meiner Routine eine überraschend große> Zeitersparnis!
Nein.
Modulo und das, was er geschrieben hat, haben miteinander soviel
gemeinsam wie Zulu und Deutsch.
Dass es so bei dir passt (oder auch nicht, ich habe deinen
Programm nicht angesehen), kann nur Zufall sein.
Entweder hast du Modulo falsch angewendet oder er.
Wie auch immer, dies:
Marc V. schrieb:> Dass es so bei dir passt (oder auch nicht, ich habe deinen> Programm nicht angesehen), kann nur Zufall sein.
Was bist du denn für einer?
> Entweder hast du Modulo falsch angewendet oder er.> Wie auch immer, dies:>
1
>i%=13;// 12 LEDs + OFF
2
>
>> Und dies:>
1
>i=(i>12)?0:i;
2
>
>>> Ist und kann niemals dasselbe sein.
Natürlich hast du das ausprobiert!? Nee, kannst du nicht ausprobiert
haben :(
Marc Vesely schrieb:
> ...ich habe deinen Programm nicht angesehen
Aaaah!
> Ist und kann niemals dasselbe sein.
Sehe ich genauso.
> Dass es so bei dir passt ... kann nur Zufall sein.
Eher andersrum. Der Modulo ist eine teure Operation für diese
Teilaufgabe. Ein popeliges if tut es genauso.
HildeK schrieb:
> Falls jemand zur der Warnung bez. 'naked' mehr weiß: es interessiert mich.
Ist interessant, wenn du die ISR in Assembler setzt. Dann darfst du
selbst für die Push-/Pop-Orgie sorgen.
Mit der leeren Mainloop würde ich auf die ISR komplett verzichten, und
das Bit pollen.
Marc V. schrieb:> Ist und kann niemals dasselbe sein.
Richtig, generell ist es nicht das selbe, kann aber das selbe Ergebnis
liefern. Ich verstehe deinen Einwand. Letztlich ist es so angewandt
worden:
1
uint8_ti,j;
2
i=0;j=0;
3
while(1)
4
{
5
j%=13;
6
i=(i>12)?0:i;
7
8
j++;
9
i++;
10
}
und da sind i und j am Ende jedes Schleifendurchlaufs gleich mit
deutlichem Nachteil beim Zeitverbrauch des Modulo-Operators.
Das hat "neuer PIC Freund" in seinem Trace gesehen.
neuer PIC Freund schrieb im Beitrag #4937692:
> Mit der leeren Mainloop würde ich auf die ISR komplett verzichten, und> das Bit pollen.
Ja, das hat peda auch schon vorgeschlagen. Ich fand es elegant, das in
der ISR abzuhandeln - bis ich die Push-/Pop-Orgie sah.
Aber, mit dem ISR-Aufruf
1
ISR(ADC_vect,ISR_NAKED)
2
{
3
.
4
.
5
reti();
6
}
verschwindet diese und die Zeit reicht dann.
Man darf allerdings reti() am Ende nicht vergessen und nichts weiteres
in der Endlosschleife der main() tun, das irgend welche Register belegen
würde.
neuer PIC Freund schrieb im Beitrag #4937692:
> Eher andersrum. Der Modulo ist eine teure Operation für diese> Teilaufgabe. Ein popeliges if tut es genauso.
So herum stimmt es wieder.
Peter D. schrieb:> merkt man schon, daß hier der Vergleich dasselbe bewirkt.
Dass der Vergleich dasselbe wie Modulo bewirkt kann schon sein - dass
aber Modulo und Vergleich dasselbe sind - das kann nicht sein.
Und damit stehen schon 2 Fakten fest:
1) Ich habe sein Programm nicht gelesen (Asche auf mein Haupt).
2) Der TO hat Modulo falsch angewendet (Schande über ihn).
Zufrieden ?
HildeK schrieb:> Falls jemand zur der Warnung bez. 'naked' mehr weiß: es interessiert> mich!
Ich werde auffa Arbeit sicher nicht nach "naked" googlen, hehe :))
Danke - die offensichtliche Wirkung des 'ISR_NAKED' kenne ich
inzwischen: es werden die Pushes und Pops weggelassen, das SREG wird
nicht gesichert und ich muss selbst für ein reti() sorgen.
In deinem Link ist ja vermerkt: "SREG must be manually saved if the ISR
code modifies it, and the compiler-implied assumption of _zero_reg_
always being 0 could be wrong"
Ich sichere hier das SREG nicht.
Das I-Flag vom SREG wird beim Eintritt in die ISR gelöscht und am Ende
wieder gesetzt, alle anderen Flags interessieren außerhalb nicht und der
EMPTY_INTERRUPT(TIMER0_OVF_vect) hat nur ein RETI, also sollte ich save
sein und ich habe auch noch keine Auffälligkeiten gesehen.
Nur, gibt es darüber hinaus Seiteneffekte?
Axel R. schrieb:> Ich werde auffa Arbeit sicher nicht nach "naked" googlen, hehe :))
Ja, bloß nicht! :-)
Es ging aber um ISR_NAKED ... :-)
HildeK schrieb:> Nur, gibt es darüber hinaus Seiteneffekte?
Wenn Dein assemblierter Code (ja, da hat c-hater wieder mal Recht, man
sollte zumindest den Assembler-Code interpretieren können) keine
Op-Codes verwendet, die das Status-Register oder andere bereits in
Verwendung befindliche Register beeinflussen könnten, bist Du fein raus.
Interrupts, die sich nicht daran halten sollte man jedoch sperren.
Siehe z.B. auch: Beitrag "Re: Nested Interrupt - Atmega2560"
HildeK schrieb:> c-hater schrieb:>> wäre es für dich>> durchaus eine Option, einfach AVR-Assembler zu lernen. Die Sprache>> selber ist nämlich sehr viel einfacher als C zu erlernen.> Dafür bin ich definitiv zu alt.
Wie alt bist du? 200?
Ich habe AVR8-Assembler so irgendwann um 2008 herum gelernt (weiß nicht
mehr genau). Da war ich auch schon ein gesetzter Herr in den sog.
"besten Jahren".
Jetzt bin ich nochmal fast zehn Jahre älter und lerne gerade wieder eine
neue Assemblersprache, nämlich ArmV9. Und stelle erneut fest: kennste
eine, kennste alle. Man braucht nur einmalig einen Überblick über das
Verfügbare und dann nur noch die Referenz als Gedächtnisstütze.
Denn das Gedächtnis läßt wirklich mit zunehmendem Alter deutlich nach,
die Fähigkeit zur Logik ist davon zum Glück nicht im gleichen Maße
betroffen. Das mit dem Gedächtnisverlust ist obendrein leider sehr
selektiv, denn 6502- und Z80-Assembler z.B. (womit ich mich in meinen
späten 10ern und frühen 20ern beschäftigt habe) kann ich immer noch im
Schlaf ohne jede Hilfe durch eine Referenz, brauche den ganzen Scheiß
heute aber garnicht mehr.
Für die AVR8 hingegen brauche ich doch zumindest gelegentlich mal die
Referenz und für den ARMv9 brauche ich sie zumindest derzeit noch
praktisch ständig. Scheiß-Gehirn. Biologischer Kram. Macht, was es will
und hält nichtmal ein Leben lang richtig durch...
c-hater schrieb:> selektiv, denn 6502- und Z80-Assembler z.B. (womit ich mich in meinen> späten 10ern und frühen 20ern beschäftigt habe) kann ich immer noch im> Schlaf ohne jede Hilfe durch eine Referenz, brauche den ganzen Scheiß> heute aber garnicht mehr.
LOL.
Mit Z80-Asm ist es hier genauso, kann es immer noch nicht verstehen,
dass Zilog keinen uC gemacht hat.
Ich würde heute noch drei AVR uC für einen Z80 uC tauschen (wenn es
einen geben würde).
Eine der besten 8-bit CPUs aller Zeiten...
c-hater schrieb:> Denn das Gedächtnis läßt wirklich mit zunehmendem Alter deutlich nach,
Eben darum!
> die Fähigkeit zur Logik ist davon zum Glück nicht im gleichen Maße> betroffen. Das mit dem Gedächtnisverlust ist obendrein leider sehr> selektiv, denn 6502- und Z80-Assembler z.B. (womit ich mich in meinen> späten 10ern und frühen 20ern beschäftigt habe) kann ich immer noch im> Schlaf ohne jede Hilfe durch eine Referenz, brauche den ganzen Scheiß> heute aber garnicht mehr.
Genau, und den Hintergrund habe ich nicht. Als mir der Z80 zum ersten
mal begegnet ist, war ich schon über 30 und den habe damals ich in BASIC
programmiert :-).
Mir geht es so, dass ich fast noch die Pinbelegung vieler TTL-ICs,
zumindest aber noch die Zuordnung Namen zur Funktion auswendig weiß.
Auch überflüssig geworden ...
Mit μCs bin ich erst seit etwa 5..7 Jahren unterwegs und war froh, das
von der C-Begegnung in ca. 1990 noch etwas verwendbares übrig blieb.
In die Referenztabelle zu schauen, wäre nicht das Problem, aber der
grundsätzliche Aufbau, die Akrobatik mit den Register usw. scheue ich
deutlich (ich weiß ja noch nicht mal, was ich fragen müsste). Es würde
lange dauern, bis ich mich da einigermaßen sicher und zügig bewegen
könnte. Vermutlich würde ich es auch schneller wieder vergessen, als
dass neue Projekte auftauchen, die von Assembler ernsthaft provitieren
könnten.
Dies wäre das erste gewesen und jetzt funktioniert es sogar in C ...
Thomas E. schrieb:> Natürlich tut es das
Ich meinte damit, dass es letztlich gerade noch ohne Assembler ging.
> und mit ein paar Umstellungen passt es auch locker> auf einen Attiny13.
Ja, ein paar Register heißen anders, ansonsten passt es direkt: 1012 von
1024 Byte belegt im Tiny13A, aber mangels Device ungetestet.
Also näher an 'gerade so' als an 'ganz locker' :-)
HildeK schrieb:> 1012 von 1024 Byte belegt im Tiny13A, aber mangels Device ungetestet.> Also näher an 'gerade so' als an 'ganz locker' :-)
Wobei der oben gezeigte, von avr-gcc erezgte Code nahelegt, dass du eine
ältere Compilerversion verwendest. Ich würde tippen nicht neuer als
avr-gcc v4.6, also z.B. ein WinAVR.
HildeK schrieb:> Also näher an 'gerade so' als an 'ganz locker'
Ich hab das gestern abend mal aufgebaut. Mit ein paar Umstellungen sind
es bei mir 872 Bytes. Ich hab aber auch die Interrupts rausgeschmissen.
Johann L. schrieb:> Wobei der oben gezeigte, von avr-gcc erezgte Code nahelegt, dass du eine> ältere Compilerversion verwendest.
Ich habe den WinAVR-20100110 im Einsatz zusammen mit Atmel Studio 4.18.
Ich habe mich nie nach Alternativen umgeschaut (Motto: never change a
running system). Hast du einen Tipp?
Thomas E. schrieb:> Mit ein paar Umstellungen sind es bei mir 872 Bytes.
Prima! Liegt es am Compiler oder 'nur' an deinen besseren sprachlichen
Kenntnissen in der Umsetzung? Wahrscheinlich an beidem :-)!
HildeK schrieb:> Prima! Liegt es am Compiler oder 'nur' an deinen besseren sprachlichen> Kenntnissen in der Umsetzung? Wahrscheinlich an beidem :-)!
Eher weder noch.
Ich verwende die letzte Version, bei der der Debugger ohne Klimmzüge
unter 4.19 läuft. Die ist schon ziemlich obsolet.
Es bringt einfach keinen Vorteil, in der main auf der Stelle zu trampeln
und auf einen, 1(!), Interrupt zu warten. Der zweite ist ja nur zum Flag
löschen.
Im Gegenteil. Das Pollen des einen Flags, nämlich das des Timers, ist
wesentlich effektiver. Daraus resultiert im wesentlichen die
"Ersparnis". Auch lassen sich die paar verwendeten Variablen lokal ohne
static anlegen, womit sie nicht ständig ins RAM geschrieben werden
müssen, sondern in Registern gehalten werden.
HildeK schrieb:> Prima! Liegt es am Compiler oder 'nur' an deinen besseren sprachlichen> Kenntnissen in der Umsetzung? Wahrscheinlich an beidem :-)!
Kaum.
Sonst hättet Ihr beide gesehen, dass deine Tabelle anstatt mit 512
Bytes mit 256 Bytes auskommen kann.
Das sind schon satte 256 Bytes weniger.
Thomas E. schrieb:> Im Gegenteil. Das Pollen des einen Flags, nämlich das des Timers, ist> wesentlich effektiver. Daraus resultiert im wesentlichen die> "Ersparnis".
Blödsinn.
Weder kann man damit so viel einsparen, noch stimmt das überhaupt.
P.S.
Am Ende müsste ich doch deinen Programm (aber nur oberflächlich)
anschauen...
P.P.S.
Die ganzen PUSH, POP und RETI brauchen zusammen 70 Byt + 70 Takte
und das auch nur weil in der ISR gerechnet wird, anstatt nur ADCH
einzulesen und einen Flag zu setzen.
Ergibt 326 Bytes weniger ohne irgendwelche Akrobatik.
Nur damit es nicht heisst, ich hätte es nicht nachgerechnet...
Für die Über-den-Tellerrand-Blicker habe ich es mal mit copy-paste auf
das stm8s-disco übertragen. Getrieben von einem 11.0592er Baudratenquarz
verbraucht das ganze Gerödel auf der Akkumaschine gerade mal 18us. Und
die Push-/Poporgie fällt natürlich kurz aus:
1
;main.c:402:adc_isr(adc);
2
pusha
3
call_adc_isr
4
popa
In den 18us fällt die Tabelle raus, es wird multipliziert. Zudem wird
mit einem gleitenden Mittelwert ein automatischer Abgleich erstellt, der
wiederum die externen Bauteile reduziert.
Mit dem leidenserzwingenden SPL Zeugs liegt der Flashbedarf zwar bei
über 6k, wobei das aber auf so ein 80ct-STM8S-Board passt.
Zudem ist das Chalieplexing auch nicht so der Bringer. 20mA/Port / 12
macht 1.67 mA pro LED. Meine alten Möhren bleiben da fast dunkel und
schaffen es kaum gegen das Tageslicht an. Rein daher würde ich eine
Kiste mit 12 Ausgängen bevorzugen.
neuer PIC Freund schrieb im Beitrag #4941966:
> Zudem ist das Chalieplexing auch nicht so der Bringer. 20mA/Port / 12> macht 1.67 mA pro LED. Meine alten Möhren bleiben da fast dunkel und> schaffen es kaum gegen das Tageslicht an.
Ja, Charlieplexing ist nur hell genug mit den dafür vorgesehen
Treibern-ICs.
Einfach nen ATtiny261 nehmen und man kann die LEDs parallel treiben.
Peter D. schrieb:> Ja, Charlieplexing ist nur hell genug mit den dafür vorgesehen> Treibern-ICs.
Das stimmt einfach nicht. Normale 2mA-Low-Current sind kein Problem und
die superhellen Dinger im klaren Gehäuse erst recht nicht.
Peter D. schrieb:> Einfach nen ATtiny261 nehmen und man kann die LEDs parallel treiben.
Mich hat der Ansatz mit einem 8-Beiner fasziniert.
Sonst hätte man ja gleich den LM3916 nehmen können :-).
>Normale 2mA-Low-Current sind kein Problem
Soweit klar. Nur sehen die runden LED im klaren Gehäuse nicht so schön
aus, wie Rechteckige im gefärbten Gehäuse. Und Rechteckige mit
Leuchtkraft scheint es nicht zu geben. Wohl kein Markt.
neuer PIC Freund schrieb im Beitrag #4942071:
>>Normale 2mA-Low-Current sind kein Problem>> Soweit klar. Nur sehen die runden LED im klaren Gehäuse nicht so schön> aus, wie Rechteckige im gefärbten Gehäuse. Und Rechteckige mit> Leuchtkraft scheint es nicht zu geben. Wohl kein Markt.
Ja, es ging mir auch mehr um die Pauschalaussage über das
Charlieplexing.