Forum: Mikrocontroller und Digitale Elektronik ATTiny25: unklare Interaktion PROGMEM <-> ADC


von HildeK (Gast)



Lesenswert?

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
2
//   _delay_us(25);
3
4
     ADCSRA |= 4; // ADC clock prescaler = 16 --> 500kHz ADC clock
5
//   ADCSRA |= 3; // ADC clock prescaler = 8  --> 1MHz ADC clock
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.

von Stefan F. (Gast)


Lesenswert?

> 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.

von HildeK (Gast)


Lesenswert?

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 ...

von c-hater (Gast)


Lesenswert?

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...

von Stefan F. (Gast)


Lesenswert?

> 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.

von HildeK (Gast)


Lesenswert?

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.
1
adc_square_sum += pgm_read_word (&square_number[adcvalue]);
Der vom C-Compiler (ich weiß, ist ein rotes Tuch für dich :-)) erzeugte 
Code sieht dazu so aus:
1
57:         adc_square_sum += pgm_read_word (&square_number[adcvalue]);  // only 8 bit ADC used
2
+0000013C:   91800066    LDS       R24,0x0066     Load direct from data space
3
+0000013E:   2FE8        MOV       R30,R24        Copy register
4
+0000013F:   E0F0        LDI       R31,0x00       Load immediate
5
+00000140:   0FEE        LSL       R30            Logical Shift Left
6
+00000141:   1FFF        ROL       R31            Rotate Left Through Carry
7
+00000142:   5EE2        SUBI      R30,0xE2       Subtract immediate
8
+00000143:   4FFF        SBCI      R31,0xFF       Subtract immediate with carry
9
+00000144:   9125        LPM       R18,Z+         Load program memory and postincrement
10
+00000145:   9134        LPM       R19,Z          Load program memory
11
+00000146:   E040        LDI       R20,0x00       Load immediate
12
+00000147:   E050        LDI       R21,0x00       Load immediate
13
+00000148:   91800060    LDS       R24,0x0060     Load direct from data space
14
+0000014A:   91900061    LDS       R25,0x0061     Load direct from data space
15
+0000014C:   91A00062    LDS       R26,0x0062     Load direct from data space
16
+0000014E:   91B00063    LDS       R27,0x0063     Load direct from data space
17
+00000150:   0F82        ADD       R24,R18        Add without carry
18
+00000151:   1F93        ADC       R25,R19        Add with carry
19
+00000152:   1FA4        ADC       R26,R20        Add with carry
20
+00000153:   1FB5        ADC       R27,R21        Add with carry
21
+00000154:   93800060    STS       0x0060,R24     Store direct to data space
22
+00000156:   93900061    STS       0x0061,R25     Store direct to data space
23
+00000158:   93A00062    STS       0x0062,R26     Store direct to data space
24
+0000015A:   93B00063    STS       0x0063,R27     Store direct to data space

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.

von c-hater (Gast)


Lesenswert?

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...

von Stefan F. (Gast)


Lesenswert?

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.

von HildeK (Gast)


Lesenswert?

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
1
adc_square_sum += pgm_read_word (&square_number[adcvalue]);
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.

von c-hater (Gast)


Lesenswert?

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...

von Peter D. (peda)


Lesenswert?

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.

: Bearbeitet durch User
von HildeK (Gast)


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von c-hater (Gast)


Lesenswert?

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...

von HildeK (Gast)


Angehängte Dateien:

Lesenswert?

"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!

von Peter D. (peda)


Lesenswert?

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.

von Gunner (Gast)


Lesenswert?

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.

von HildeK (Gast)


Lesenswert?

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 ...

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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...

von HildeK (Gast)


Lesenswert?

Marc V. schrieb:
> warum er (sie) das trotzdem
>  von hinten macht, ist mir unklar.

Er! :-)

Was meinst du mit 'von hinten'?

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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...

: Bearbeitet durch User
von Axel R. (Gast)


Lesenswert?

Clever!

Sample n-1  in der Zeitspanne "vorn->hinten" verarbeiten


StromTuner

von HildeK (Gast)


Lesenswert?

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!

von Gunner (Gast)


Lesenswert?

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.

von HildeK (Gast)


Lesenswert?

Danke - da hatte ich doch etwas falsch verstanden. Das schau ich mir 
auch noch an.

von S. Landolt (Gast)


Lesenswert?

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.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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:
1
 If the trigger signal still is set when the conversion completes, a new conversion will not be started.
2
 If another positive edge occurs on the trigger signal during conversion, the edge will be ignored.

 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 ?

von HildeK (Gast)


Lesenswert?

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.

von S. Landolt (Gast)


Lesenswert?

> ... 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?

von HildeK (Gast)


Lesenswert?

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.

von c-hater (Gast)


Lesenswert?

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...

von HildeK (Gast)


Lesenswert?

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.

von Dieter F. (Gast)


Lesenswert?

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 :-)

von Peter D. (peda)


Lesenswert?

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.

von neuer PIC Freund (Gast)


Angehängte Dateien:

Lesenswert?

Komisch dass noch niemand die modula-operation angezweifelt hat. Mit 
einem bisschen trace und der ISR als funktion in main daueraufgerufen 
zeigt sich
1
 i %= 13;                // 12 LEDs + OFF

als böser Übeltäter. Tut es nicht auch ein
1
 i = (i > 12) ? 0 : i;
?

Und beim peak_hold_counter wird wieder __udivmodqi4 angesprungen.

von guest (Gast)


Lesenswert?

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!

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

neuer PIC Freund schrieb im Beitrag #4937299:
> als böser Übeltäter. Tut es nicht auch ein
>  i = (i > 12) ? 0 : i; ?

 Nein.

von HildeK (Gast)


Lesenswert?

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.

von HildeK (Gast)


Lesenswert?

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!

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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:
1
 i %= 13;                // 12 LEDs + OFF

 Und dies:
1
 i = (i > 12) ? 0 : i;


 Ist und kann niemals dasselbe sein.

von Peter D. (peda)


Lesenswert?

Marc V. schrieb:
> Ist und kann niemals dasselbe sein.

Wenn man einfach nur ein paar Zeilen weiter lesen würde:
1
  i %= 13;                // 12 LEDs + OFF
2
// ...
3
  i++;
merkt man schon, daß hier der Vergleich dasselbe bewirkt.

von Gunner (Gast)


Lesenswert?

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 :(

von neuer PIC Freund (Gast)


Lesenswert?

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.

von HildeK (Gast)


Lesenswert?

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_t i,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.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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 ?

von HildeK (Gast)


Lesenswert?

Marc V. schrieb:
> Zufrieden ?

Ja :-)

von Axel R. (Gast)


Lesenswert?

HildeK schrieb:
> Falls jemand zur der Warnung bez. 'naked' mehr weiß: es interessiert
> mich!

Ich werde auffa Arbeit sicher nicht nach "naked" googlen, hehe :))

von Dieter F. (Gast)


Lesenswert?

Axel R. schrieb:
>> Falls jemand zur der Warnung bez. 'naked' mehr weiß: es interessiert
>> mich!

http://www.atmel.com/webdoc/avrlibcreferencemanual/group__avr__interrupts.html

von HildeK (Gast)


Lesenswert?

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 ... :-)

von Lothar G. (Gast)


Lesenswert?

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"

von c-hater (Gast)


Lesenswert?

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...

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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...

von HildeK (Gast)


Lesenswert?

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 ...

von Thomas E. (thomase)


Lesenswert?

HildeK schrieb:
> und jetzt funktioniert es sogar in C ...

Natürlich tut es das und mit ein paar Umstellungen passt es auch locker 
auf einen Attiny13.

von HildeK (Gast)


Lesenswert?

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' :-)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von Thomas E. (thomase)


Lesenswert?

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.

von HildeK (Gast)


Lesenswert?

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 :-)!

von Thomas E. (thomase)


Lesenswert?

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.

: Bearbeitet durch User
von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

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...

: Bearbeitet durch User
von neuer PIC Freund (Gast)


Lesenswert?

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
  push  a
3
  call  _adc_isr
4
  pop  a

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.

von Peter D. (peda)


Lesenswert?

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.

von Thomas E. (thomase)


Lesenswert?

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.

von HildeK (Gast)


Lesenswert?

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 :-).

von neuer PIC Freund (Gast)


Lesenswert?

>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.

von Thomas E. (thomase)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.