Forum: Mikrocontroller und Digitale Elektronik AVR, Pointer auf Flash-Speicher per Assembler


von Peder (Gast)


Lesenswert?

Hallo,

ich "möchte gern" mit Assembler ein Array im Flash-Speicher eines 
XMega256 auslesen, bislang jedoch erfolglos. Bevor ich jetzt aber mit 
detaillierteren Code-Beispielen komme, möchte ich erst einmal wissen, ob 
mein Ansatz prinzipiell richtig ist:

Ich habe ein Array mit x Werten im Flash-Speicher abgelegt. Diese Werte 
sollen an das DAC-Modul des XMegas gegeben werden. Das funktioniert 
unter C auch tadellos mit Hilfe von PROGMEM und pgm_read_*(). Nun ist 
aber die Ausgabe an den DAC zeitkritisch geworden und muss mit Assembler 
gemacht werden.

Also caste ich zunächst die (Start-)Adresse, auf die der Array-Zeiger 
zeigt, zu einem Integer und übergebe diesen an ein 
Assembler-Unterprogramm. Dort gebe ich diesen Integer an den Z-Pointer, 
der ja eigentlich zum Flash zeigen soll, und lasse mir mit "lpm" den 
Wert an dieser Adresse ausgeben. Zum Schluss geht dieser Wert an das 
DAC-Datenregister. Der Z-Pointer wird nun inkrementiert und der nächste 
Wert des Arrays ausgelesen und an den DAC gegeben, bis das Array 
abgearbeitet ist.

Da ich hier nach Hilfe suche, ist klar, dass es nicht funktioniert. Aber 
wie gesagt, bevor ich das verkompliziere: ist der Ansatz denn 
prinzipiell so machbar?


Danke schon mal und Grüße

Peter

von spess53 (Gast)


Lesenswert?

Hi

>Dort gebe ich diesen Integer an den Z-Pointer,
>der ja eigentlich zum Flash zeigen soll, und lasse mir mit "lpm" den
>Wert an dieser Adresse ausgeben.

Deine Adresse dürfte ein Pointer auf ein Word im im Flash sein. LPM 
braucht aber eine Byte-Adresse. Also einmal nach links shiften.

MfG Spess

von Oliver S. (oliverso)


Lesenswert?

spess53 schrieb:
> Deine Adresse dürfte ein Pointer auf ein Word im im Flash sein.

Ein Pointer ist ein Pointer ist ein Pointer...

Da gcc das Konzept "Flash" überhaupt nicht kennt, sind C-Pointer immer 
Byte-Pointer. Das lässt ich aber einfach prüfen, in dem man sich den 
Wert mal ansieht.

Der Simulator eines der Studios ist bei solchen Fargestellungen auch 
immer sehr hilfreich.

Prinzipiell ist das Vorgehen aber richtig - einfach den Wert ins 
Z-Register laden, und die Daten lpm rx, Z+ auslesen. Den cast nach int 
braucht es dafür nicht mal.

Oliver

: Bearbeitet durch User
von Peder (Gast)


Lesenswert?

Ich bin nicht sicher, ob ich dich richtig verstehe.
Im Array sind 16-Bit-Werte gespeichert. Laut dem "Assembler Instruction 
Set"-Manual müsste "lpm" den Inhalt eines Bytes in ein gegebenes 
Register laden. Da ich 16-Bit habe, muss ich das also zweimal machen 
(und dazwischen einmal den Zeiger inkrementieren).

Der Adresswert, den ich von C zu Assembler gebe, sollte im Registerpaar 
r25:24 liegen. r27:26 sind hier als 16-Bit Zählregister gedacht.

1
ldi r27, 0x00               ; High-Byte der Loop-Zählers (0x002D=45) in r27
2
ldi r26, 0x2D               ; Low-Byte  des Loop-Zählers (0x002D=45) in r26
3
4
  movw r30, r24             ; Kopiere Addresse aus der 'main.c' ins Z-register
5
6
loop:
7
  lpm   r16, Z              ; Lade Inhalt an der Adresse nach r16
8
  sts  DACB_CH0DATA,   r16  ; Schreibe Low-Byte  ins DAC-Register
9
  adiw ZL,1                 ; Erhöhe Z-pointer um 1
10
  sts  DACB_CH0DATA+1, r16  ; Schreibe High-Byte ins DAC-Register
11
  adiw ZL,1                 ; Erhöhe Z-pointer um 1
12
13
14
  sbiw r26, 1               ; Dekrementiere Loop-Zähler (r27:26) um 1
15
  mov  r16, r26             ; Diese und nächste Zeile ermöglich einen...
16
  or   r16, r27             ; ...effizienten "Test-for-zero" für 16-Bit
17
18
  brne loop                 ; Springe zu loop:, wenn Zähler nicht null war
19
20
  ret

In meiner Theorie sollte das wunderbar funktionieren, aber irgendwas -- 
oder eben sehr viel -- ist daran nicht in Ordnung...

von Falk B. (falk)


Lesenswert?

@ Peder (Gast)

>ich "möchte gern" mit Assembler ein Array im Flash-Speicher eines
>XMega256 auslesen, bislang jedoch erfolglos. Bevor ich jetzt aber mit

>Ich habe ein Array mit x Werten im Flash-Speicher abgelegt. Diese Werte
>sollen an das DAC-Modul des XMegas gegeben werden. Das funktioniert
>unter C auch tadellos mit Hilfe von PROGMEM und pgm_read_*(). Nun ist
>aber die Ausgabe an den DAC zeitkritisch geworden und muss mit Assembler
>gemacht werden.

Dann nimmt man so oder so DMA, das geht auch unter C problemlos.

von chris (Gast)


Lesenswert?

so könnte man es machen:
1
read_flash:
2
      rcall  load_db1              ;datenpointer laden
3
read_flash2:
4
      lpm    r16,z+                ;von z laden in r16 und z+1
5
      cpi    r16,$ff               ;wenn wert =$ff dann abbruch
6
      brne   read_flash2           ;sprung bei ungleich
7
      ret
8
9
;Pointer setzen
10
load_db1:
11
      ldi    zh,high(test*2)
12
      ldi    zl,low(test*2)
13
      ret
14
15
;Tabbel mit Abbruchswert bei $ff
16
test: .db $07,$7fe,$7a,"Beipspiel",$ff

: Bearbeitet durch User
von c-hater (Gast)


Lesenswert?

Peder schrieb:

> Im Array sind 16-Bit-Werte gespeichert. Laut dem "Assembler Instruction
> Set"-Manual müsste "lpm" den Inhalt eines Bytes in ein gegebenes
> Register laden. Da ich 16-Bit habe, muss ich das also zweimal machen
> (und dazwischen einmal den Zeiger inkrementieren).

Oh mein Gott, ich dachte, es soll schneller werden, als der C-Compiler 
es kann. Das wird dir mit diesem Code aber nicht gelingen, so Scheiße 
sind die C-Compiler dann auch nicht mehr. Außerdem enthält der Code auch 
noch einen fetten Fehler.

> ldi r27, 0x00               ;    1
> ldi r26, 0x2D               ;    1

> movw r30, r24               ;    1

> loop:
>   lpm   r16, Z              ; 3
>   sts  DACB_CH0DATA,   r16  ; 2
>   adiw ZL,1                 ; 2

Hier ist der Fehler, es fehlt ein:

    lpm r16,Z                 ; 3

>   sts  DACB_CH0DATA+1, r16  ; 2
>   adiw ZL,1                 ; 2
>
>   sbiw r26, 1               ; 2
>   mov  r16, r26             ; 1
>   or   r16, r27             ; 1
>
>   brne loop                 ; 2/ 1
                              ;-----
                              ;20/19

Also für jeden Schleifendurchlauf außer dem letzten 20 Takte, im letzten 
19 und einmalig nochmal drei Takte für das Vorspiel.

44*20+19+3=902

Und so macht man das richtig:

Erstmal überlegt man sich, daß das Runterzählen von 45 überhaupt kein 
Zählregister in Wort-Breite benötigt, sondern ein Byte völlig 
ausreichend ist. Das spart einen Takt im Vorspann und drei Takte in der 
Schleife. Will man den Code so schreiben, daß er auch mit größeren 
Bytezahlen funktioniert und man deshalb zur Sicherheit einen 
16Bit-Zahler vorsieht, macht man das trotzdem anders. Man berechnet im 
Vorspann die Endadresse und vergleicht die laufende Adressen mit dieser. 
Das kostet zwar im Vorspann einmalig zwei Takte mehr, spart aber dafür 
dann in jedem Schleifendurchlauf zwei Takte.
Tja und dann noch zum Schleifenkern: Da benutzt man natürlich eine 
effizientere Adressierungsart, nämlich indirekt, entweder mit 
postincrement oder mit predecrement. Das spart gegenüber deiner Routine 
in jedem Schleifendurchlauf vier Takte.

Also, entweder so:

  ldi R26,$2d            ;    1
  movw R30,R24           ;    1

loop:
  lpm R16,Z+             ; 3
  sts DACB_CH0DATA,R16   ; 2
  lpm R16,Z+             ; 3
  sts DACB_CH0DATA+1,R16 ; 2

  dec R26                ; 1
  brne loop              ; 2/ 1
                         ;-----
                         ;13/12

44*13+12+2=586  ->  (902-586)*100/902=35% gespart

oder so:

  ldi R26,Low($2d*2)     ;    1
  ldi R27,High($2d*2)    ;    1
  add R26,R24            ;    1
  adc R27,R25            ;    1
  movw R30,R24           ;    1

loop:
  lpm R16,Z+             ; 3
  sts DACB_CH0DATA,R16   ; 2
  lpm R16,Z+             ; 3
  sts DACB_CH0DATA+1,R16 ; 2

  cp ZL,R26              ; 1
  cpc ZL,R27             ; 1
  brcs loop              ; 2/ 1
                         ;-----
                         ;14/13

44*14+13+5=634 -> (902-634)*100/902=30% gespart

So geht Assembler richtig.

von der alte Hanns (Gast)


Lesenswert?

Und ein 'lpm' reicht unter allen Umständen auf einem XMega256?

von c-hater (Gast)


Lesenswert?

der alte Hanns schrieb:

> Und ein 'lpm' reicht unter allen Umständen auf einem XMega256?

Sicher nicht. Genausowenig wie ein 16Bit-Zeiger übrigens...

Wenn aber der 16Bit Zeiger zur Adressübergabe reicht, dann reicht auch 
das lpm zum Auslesen der Daten.

von der alte Hanns (Gast)


Lesenswert?

Verstehe ich Ihre Antwort richtig dahingehend, dass ein C-Compiler 
solche Tabellen immer in die untersten 64 kiB legt?

von Sascha W. (sascha-w)


Lesenswert?

der alte Hanns schrieb:
> Verstehe ich Ihre Antwort richtig dahingehend, dass ein C-Compiler
> solche Tabellen immer in die untersten 64 kiB legt?
nein, es gibt ja auch noch elpm und die höheren Adressbits kommen in das 
Register RAMPZ.

Sascha

von der alte Hanns (Gast)


Lesenswert?

Das heißt, Herr Weber, dass ein C-Compiler solche Tabellen wahlweise 
über den gesamten verfügbaren Adressraum verteilt? Dann allerdings 
verstehe ich weder den Programmeinstieg von Peder noch die Antwort von 
c-hater.

von c-hater (Gast)


Lesenswert?

der alte Hanns schrieb:

> Verstehe ich Ihre Antwort richtig dahingehend, dass ein C-Compiler
> solche Tabellen immer in die untersten 64 kiB legt?

Nein, nicht wenn es ein brauchbarer Compiler ist.

von Falk B. (falk)


Lesenswert?

@ der alte Hanns (Gast)

>Verstehe ich Ihre Antwort richtig dahingehend, dass ein C-Compiler
>solche Tabellen immer in die untersten 64 kiB legt?

Nein, EIN C-Compiler nicht, aber DER avr gcc. Will man mehr, muss man 
workarounden ;-)

AVR-GCC-Tutorial

-> Variablenzugriff >64kB

von der alte Hanns (Gast)


Lesenswert?

> Nein, EIN C-Compiler nicht, aber DER avr gcc. Will man mehr, muss man
> workarounden ;-)
>
> AVR-GCC-Tutorial
>
> -> Variablenzugriff >64kB


Hier wendet sich der Gast mit Grausen...

Entschuldigen Sie die Störung, ich bleibe bei Assembler.

von c-hater (Gast)


Lesenswert?

der alte Hanns schrieb:

> Das heißt, Herr Weber, dass ein C-Compiler solche Tabellen
> wahlweise
> über den gesamten verfügbaren Adressraum verteilt?

Wenn er was taugt: ja.

> Dann allerdings
> verstehe ich weder den Programmeinstieg von Peder noch die Antwort von
> c-hater.

Man kann auch steuern, was der Compiler oder vielmehr der Linker tut.

In reinem unverfälschten Assembler, unbehelligt von jeglichen Compiler- 
und Linker-Befindlichkeiten stellt sich die Frage allerdings erst 
garnicht, da hat man sowieso jederzeit die volle Kontrolle über das 
Speicher-Layout und bemüht sich natürlich, das so zu organisieren, daß 
alle Routinen mit Vmax laufen können.

von Peter S. (cbscpe)


Lesenswert?

Im Kernel von RSX-11 hatte sich digital noch etwas anderes einfallen 
lassen um möglichst schnell Daten herum zuschaufeln. Das könnte man hier 
natürlich auch noch machen. Anstelle in einem Schlaufendurchgang nur ein 
16-Bit Wort auszulesen/verschieben, kann man den Teil
1
  lpm R16,Z+             ; 3
2
  sts DACB_CH0DATA,R16   ; 2
3
  lpm R16,Z+             ; 3
4
  sts DACB_CH0DATA+1,R16 ; 2


ein paar mal (16,32,64) wiederholen, man muss natürlich den 
Schlaufenzähler vorgängig anpassen. Und bei der Division der Anzahl 
bytes die man übertragen muss durch die Anzahl der Bytes die in der 
Schleife mit einem Rutsch übertragen werden muss man natürlich den Rest 
noch berücksichtigen. Ein ijmp fällt hier leider weg. Der braucht auch 
das Z register.

von c-hater (Gast)


Lesenswert?

c-hater schrieb:

>   cpc ZL,R27             ; 1

Mist, da hat sich doch glatt ein Fipptehler eingeschlichen. Das muß 
natürlich richtig heißen:

>   cpc ZH,R27             ; 1

von Oliver S. (oliverso)


Lesenswert?

Peter Schranz schrieb:
> Im Kernel von RSX-11 hatte sich digital noch etwas anderes
> einfallen
> lassen um möglichst schnell Daten herum zuschaufeln. Das

kann man auch in C, siehe Duff's Device.

Oliver

von c-hater (Gast)


Lesenswert?

Peter Schranz schrieb:

> Im Kernel von RSX-11 hatte sich digital noch etwas anderes
> einfallen
> lassen um möglichst schnell Daten herum zuschaufeln. Das könnte man hier
> natürlich auch noch machen. Anstelle in einem Schlaufendurchgang nur ein
> 16-Bit Wort auszulesen/verschieben, kann man den Teil  lpm R16,Z+
> ; 3
>   sts DACB_CH0DATA,R16   ; 2
>   lpm R16,Z+             ; 3
>   sts DACB_CH0DATA+1,R16 ; 2
>
> ein paar mal (16,32,64) wiederholen, man muss natürlich den
> Schlaufenzähler vorgängig anpassen.

Richtig, das klassische loop-unrolling sollte man auch immer in Erwägung 
ziehen. Die Indikatoren für diese Optimierung sind: wenige Takte im 
Nutzcode der Schleife und eine geringe Zahl von Schleifendurchläufen. 
Dann lohnt das richtig und bläht den Code auch nicht allzusehr auf.

Im Speziellen kann es aber durchaus auch mal sein, daß man sogar massive 
Codeblähungen hinnimmt, wenn der Code dadurch timingmäßig die Grenze 
zwischen "geht" und "geht nicht" durchbrechen kann...

von Peder (Gast)


Lesenswert?

Okay... Das ist jetzt doch deutlich mehr geworden, als ich erhofft 
hatte. Ich werde mir das alles mal in den nächsten Tagen zu Gemüte 
führen. Über die Feiertage komme ich leider nicht mehr dazu, das Ganze 
umzusetzen, aber ich werde auf jeden Fall berichten, ob und was 
funktioniert hat oder auch, wenn es nicht läuft.

Danke erstmal an alle und @c-hater: Ich arbeite mit Ausnahme einer 
Vorlesung zum ersten Mal produktiv mit Assembler. Es lässt sich einfach 
nicht vermeiden, dass mein Code nicht den Ansprüchen eines Profis 
entspricht. Deshalb frage ich hier nach Rat, um dem zumindest etwas 
näher kommen zu können.

von Peder (Gast)


Lesenswert?

Also die Zieladresse vorher zu bestimmen, ist keine schlechte Idee und 
funktioniert auch. Die 45 Schleifendurchläufe habe ich deshalb in 16 Bit 
gesteckt, weil es später deutlich mehr sein werden.
Das fehlende "lpm" war in meinem Code auch enthalten, ich habe es hier 
wahrscheinlich aus Versehen gelöscht. Ich verstehe allerdings das (*2) 
nicht, ist das ein Hinweis auf zwei Bytes?

Ich habe den Vormittag lang ein paar Tests gemacht und es scheint, dass 
ich R17:16 nicht benutzen sollte, ohne sie zu sichern, deshalb nutze ich 
erstmal R27:26. Selbst wenn ich R17:16 mit Konstanten beladen habe, 
zeigte das Oszi nicht die erwarteten Spannungen an, also denke ich, dass 
R17:16 schon benutzt wurden.

Folgendes (interessantes) habe ich aber in der lss-Datei gefunden:
1
1fc:  00 00 06 00 0c 00 12 00 18 00 1e 00 24 00 2a 00 ; Das sind die ersten 8 Werte des Arrays
2
[...]
3
28e:  fc 01  movw  r30, r24 ; Das ist die Übergabe des Parameters aus C in das Z-Register
Daraus kann ich doch schließen, dass die Adressübergabe funktioniert, 
oder?


Mit meinem jetzigen Code (vorsichtshalber diesmal die komplette Datei) 
sehe ich die assemblerproduzierten Werte des DACs aber immer auf 100%, 
das hat mit den Werten des Arrays nichts zu tun. Richtige Werte 
produziert er aber, wenn ich nicht aus dem Array abzufragen versuche, 
sondern Konstanten einsetze.
1
#define __SFR_OFFSET 0
2
#include <avr/io.h>
3
4
.global asm_set_DAC
5
6
.section .text
7
8
asm_set_DAC:
9
  ldi  r26, 0x2D        ; load low  byte of loop-counter (45 = 0x002D) to r26
10
  ldi  r27, 0x00        ; load high byte of loop-counter (45 = 0x002D) to r27
11
  add  r26, r24         ; add address to loop-counter for...
12
  adc  r27, r25         ; ...pre-calculating the last address of flash-table
13
  movw r30, r24         ; load address to z-register
14
15
loop:
16
  lpm   r24, Z+             ; load content of the address to which the 2byte-Z-pointer is pointing into r24
17
  sts  DACB_CH0DATA,   r24  ; write to DAC - Low  Byte
18
  lpm   r24, Z+             ; load content of the address to which the 2byte-Z-pointer is pointing into r24
19
  sts  DACB_CH0DATA+1, r24  ; write to DAC - High Byte
20
  cp   ZL, r26              ; compare low  byte of Z with low  byte of final address
21
  cpc  ZH, r27              ; compare high byte of Z (with previous carry) with high byte of final address
22
  brcs loop                 ; check is carry flag is set (by the two last operations) - if so, branch
23
24
  ret

Gibt es vielleicht noch andere Dinge, die ich bisher nicht 
berücksichtigt habe, wie etwaige Eigenarten der XMEGAs oder ähnliches?

von Peder (Gast)


Lesenswert?

Ich habe mir gerade beim Debuggen den Speicher angesehen und 
festgestellt, dass der EEPROM mit 0xFF gefüllt ist. Quasi genau das, was 
der DAC ausgibt und nicht soll. Kann es sein, dass der Z-Pointer nicht 
auf den Flash zeigt, sondern auf den EEPROM? Nach dem, was ich so auf 
die Schnelle im Netz finde, kann man den Z-Pointer zumindest für den 
EEPROM nutzen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Die Konstante kann man besser leserlich so laden, egal ob hex oder 
dezimal:
1
asm_set_DAC:
2
  ldi  r26, lo8(45)  ;; X = 45
3
  ldi  r27, hi8(45)
4
  ...

Dann addierst du W zu X und subtrahierst es später wieder (beim 
Vergleich von X gegen Z).  In der Schleife ist folgendes zwar nicht 
schneller aber besser verständlich:
1
  ...
2
  movw r30, r24
3
4
loop:
5
  lpm   r24, Z+
6
  sts  DACB_CH0DATA,   r24
7
  lpm   r24, Z+
8
  sts  DACB_CH0DATA+1, r24
9
  sbiw r26, 1   ;; while (--X)
10
  brne loop
11
  ...

Was die Verwendung von R16/R17 angeht musst du das ABI beachten:

http://gcc.gnu.org/wiki/avr-gcc#Register_Layout

Für die beiden Register bedeutet das, daß sie in einer Funktion nicht 
einfach überschrieben werden dürfen — egal ob die Funktion in Assembler 
steht oder in C.

von Peder (Gast)


Lesenswert?

Die Schleife sagt mir von der Lesbarkeit auch nicht besonders zu, aber 
hier geht es mir vorrangig um Geschwindigkeit.
Dein Link ist i.Ü. sehr informativ. Auch wenn es mein momentanes Problem 
noch nicht löst, ist diese Art Information ist genau das, was ich 
normalerweise suche, aber was meistens nirgendwo steht.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peder schrieb:
> Die Schleife sagt mir von der Lesbarkeit auch nicht besonders zu, aber
> hier geht es mir vorrangig um Geschwindigkeit.

SBIW ist genauso schnell wie CP + CPC, nämlich 2 Ticks.  Das einzige was 
geht, ist bei Arraygröße von bis zu 256 Werten (wobei 256 in der 
Schleifenvariable als 0 dargestellt wird) das SBIW + BRNE durch DEC + 
BRNE zu ersetzen.  Das spart dann 1 Tick und ist das Ende der 
Fahnenstange...

von c-hater (Gast)


Lesenswert?

Peder schrieb:

> Ich verstehe allerdings das (*2)
> nicht, ist das ein Hinweis auf zwei Bytes?

Ja, klar. Bei jedem Schleifendurchlauf werden zwei Bytes gelesen, der 
Zeiger also auch um zwei inkrementiert.

> Daraus kann ich doch schließen, dass die Adressübergabe funktioniert,
> oder?

Nicht unbedingt. Wie schon im Thread Thema war, reicht für Flash>64k ein 
16Bit-Pointer nicht unbedingt aus. Wenn du erzwingen willst, daß er 
ausreicht, mußt du die Tabelle vollständig in den Bereich unter 64k 
zwingen. Du kannst dich nicht darauf verlassen, was der Compiler tut, 
der macht, was er will, nicht das, was sinnvoll ist oder gar das, was du 
willst.

Alternativ kannst du auch den Rest des Zeigers benutzen, den der 
Compiler höchstwahrscheinlich in RAMPZ übergibt. Aber dann wird's wieder 
langsamer, jedenfalls, wenn du auf die Nasen hörst, die dich von der 
Idee mit dem Vergleich mit der Endadresse abbringen wollen.

Wenn du nämlich einen Zähler verwendest, brauchst du dann einen 
24Bit-Zähler und mußt obendrein in jedem Schleifendurchlauf einmal RAMPZ 
in ein Rechenregister holen.

Bei dem Ansatz mit der Endadresse hingegen brauchst du bloß das "brcs" 
durch ein "brne" ersetzen und es funktioniert. Jedenfalls solange die 
Größe deiner Tabelle unter 64k ist.

> Gibt es vielleicht noch andere Dinge, die ich bisher nicht
> berücksichtigt habe, wie etwaige Eigenarten der XMEGAs oder ähnliches?

Du hättest den ganzen Thread lesen sollen...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

c-hater schrieb:
>> Daraus kann ich doch schließen, dass die Adressübergabe funktioniert,
>> oder?
>
> Nicht unbedingt. Wie schon im Thread Thema war, reicht für Flash>64k ein
> 16Bit-Pointer nicht unbedingt aus.

Flash > 64k will er vermutlich eh nicht verwenden, da es um 
Geschwindigkeit geht und ELPM langsamer ist als LPM.

> Du kannst dich nicht darauf verlassen, was der Compiler tut,
> der macht, was er will,

Wasn das für'n Schmarrn?

Ein Compiler ist kein Zufallsgenerator, und in diesem Fall der Linker 
auch nicht. Die Ablage der Daten im Flash (Section .progmem, die man 
z.B. mittels PROGMEM oder __flash erreicht) folgt direkt auf .vectors. 
Siehe die entsprechenden Linker-Skripte, welche die Code- und 
Datenablage beschreiben:
1
  .text   :
2
  {
3
    *(.vectors)
4
    KEEP(*(.vectors))
5
    /* For data that needs to reside in the lower 64k of progmem.  */
6
    *(.progmem.gcc*)
7
    *(.progmem*)

Daten in .progmem gelangen also nur dann in Bereiche > 64k , wenn 
.vectors + .progmem > 64k ist.

Da das alles in C bereits funktioniert hat, es dort lediglich zu langsam 
war, ist davon auszugehen, daß .progmem Problemlos in die unteren 64k 
passt.

: Bearbeitet durch User
von c-hater (Gast)


Lesenswert?

Johann L. schrieb:

> Geschwindigkeit geht und ELPM langsamer ist als LPM.

Seit wann? In Atmels instruction set reference brauchen beide 
gleichermaßen 3 Takte. Und ich kann aus umfassender Erfahrung mit 
zeitkritischen Asm-Programmen bestätigen, daß Atmel hier völlig richtig 
liegt.

Aber du bist ja der Überflieger, du weißt wahrscheinlich was, was 
nichtmal Atmel von den eigenen Cores weiß...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

c-hater schrieb:
> Johann L. schrieb:
>
>> Geschwindigkeit geht und ELPM langsamer ist als LPM.
>
> Seit wann? In Atmels instruction set reference brauchen beide
> gleichermaßen 3 Takte.

Ja, ist auch das, was ich in Erinnerung hatte.  Hab aber beim 
Nachschlagen wohl nen blöden Fehler gemacht.  Mea Culpa.

von Peder (Gast)


Lesenswert?

Ich schließe meine Themen immer gern ab, wenn ich eine vollständige 
Lösung habe, falls jemand irgendwann über etwas ähnliches stolpern 
sollte:
Was ist die Fragestellung? Es sollen 16-Bit-Werte an das 
DA-Wandler-Modul eines XMEGA256 ausgeben. Diese Werte stehen als Array 
im Flash-Speicher - und zwar auch im Bereich > 64k.


Zunächst der C-Code, mit dem ich die Assembler-Routine aufrufe:
1
extern void asm_set_DAC(uint16_t address);       // include "asm_set_DAC" from assembler file
2
const uint16_t *flash_pointer = flash_table;     // pointer to flash_table
3
uint16_t address = (uint16_t) &flash_pointer[0]; // address of first array element
4
asm_set_DAC(address);                            // pass address into r25:24-register

Einer meiner Fehler bestand in der Annahme, dass "&flash_pointer" ohne 
"[0]" mir die Startadresse des Arrays geben würde. Was genau
1
uint16_t address = (uint16_t) &flash_pointer;
 macht, verstehe ich immer noch nicht.


Der Assembler-Code:
1
#define __SFR_OFFSET 0
2
#include <avr/io.h>
3
4
.global asm_set_DAC         ; makes asm_set_DAC visible in other source files
5
.section .text              ; defines a code section
6
7
asm_set_DAC:                ; start of asm_set_DAC routine
8
  ldi  r26, lo8(15840*2)    ; load low  byte of loop-counter into r26
9
  ldi  r27, hi8(15840*2)    ; load high byte of loop-counter into r27
10
  add  r26, r24             ; add address to loop-counter for...
11
  adc  r27, r25             ; ...precalculating the last address of flash-table
12
  movw r30, r24             ; load address to z-register
13
14
loop:
15
  lpm   r24, Z+             ; load content of the current flash-address into r24 and increment Z
16
  lpm   r25, Z+             ; load content of the current flash-address into r24 and increment Z
17
  sts  DACB_CH0DATA,   r24  ; write to DAC - low  byte
18
  sts  DACB_CH0DATA+1, r25  ; write to DAC - high byte
19
  cp   ZL, r26              ; compare low  byte of Z with low byte of final address
20
  cpc  ZH, r27              ; compare high byte of Z (with previous carry) with high byte of last address
21
  brcs loop                 ; check is carry flag is set (by the two last operations) - if so, branch
22
23
ret

Zwei Dinge könnten hier den einen oder überraschen:
1. "elpm" ist nicht nötig, auch wenn das Array den kompletten Rest des 
256k-Flashs ausnutzt, also mehr als nur 64k.
2. Ich muss mich nicht um RAMPZ kümmern. Anscheinend - und das 
bestätigen mir andere Funde im Internet - wird beim Überlauf des 
Z-Registers das RAMPZ hardwaremäßig inkrementiert.


Nun handelt es sich bei dem DAC aber um einen 12Bit-DAC, damit sind 
16Bit-Werte reine Platzverschwendung. Die Idee, die in C bereits 
funktioniert, ist folgende: Die 8 kleineren Bits kommen in ein 
"Low-Byte"-Array und die 4 größeren Bits kommen in ein 
"High-Byte"-Array. Das ermöglicht eine um ein Drittel größere 
Wertemenge, wenn man sich per Bitshifting die 12-Bit-Werte 
zusammensetzt.

Vielleicht melde ich mich in einem neuen Thema nochmal, wenn ich das in 
Assembler nicht umzusetzen können sollte. :)


Danke für all die Hilfe!

von Peder (Gast)


Lesenswert?

Peder schrieb:
> die 4 größeren Bits kommen in ein "High-Byte"-Array.

Das wäre natürlich Blödsinn. Ich meinte natürlich, dass zweimal 4-Bit in 
ein 8-Bit-Platz kommen.

von spess53 (Gast)


Lesenswert?

Hi

>1. "elpm" ist nicht nötig, auch wenn das Array den kompletten Rest des
>256k-Flashs ausnutzt, also mehr als nur 64k.

Nur elpm nutzt die RAMZ-Bits zu Adresserweiterung. lpm ist auf den 
Adressbereich <64k beschränkt. Instruction Set zu lpm:

This
instruction can address the first 64K bytes (32K words) of Program 
memory.

>2. Ich muss mich nicht um RAMPZ kümmern. Anscheinend - und das
>bestätigen mir andere Funde im Internet - wird beim Überlauf des
>Z-Registers das RAMPZ hardwaremäßig inkrementiert.

Richtig. Aber nur bei elpm. Allerdings funktioniert

>  cp   ZL, r26
>  cpc  ZH, r27

nur bis 64k. Darüber musst du ein drittes Register benutzen.

MfG Spess

von c-hater (Gast)


Lesenswert?

spess53 schrieb:
> Allerdings funktioniert
>
>>  cp   ZL, r26
>>  cpc  ZH, r27
>
> nur bis 64k. Darüber musst du ein drittes Register benutzen.

Nein. Solange die Größe der Tabelle<=64k ist, geht es weiterhin mit dem 
16Bit-Vergleich. Genau das ist ja der Vorteil des Ansatzes mit dem 
Adreßvergleich.

Der Unterschied besteht darin, daß man dann statt des "brcs" aus dem 
Beispielcode "brne" zum Verzweigen auf den Anfang der Schleife benutzen 
muß.

Das schrieb ich übrigens bereits einmal in diesem Thread...

von Sascha W. (sascha-w)


Lesenswert?

Peder schrieb:
> Zwei Dinge könnten hier den einen oder überraschen:
> 1. "elpm" ist nicht nötig, auch wenn das Array den kompletten Rest des
> 256k-Flashs ausnutzt, also mehr als nur 64k.
das halte ich für ein Gerücht, lpm nutzt nur eine 64k-Adresse und nicht 
mehr!
Nach einem Überlauf wird wieder ab Adresse 0 gelesen.

> 2. Ich muss mich nicht um RAMPZ kümmern. Anscheinend - und das
> bestätigen mir andere Funde im Internet - wird beim Überlauf des
> Z-Registers das RAMPZ hardwaremäßig inkrementiert.
das stimmt, bei jedem Überlauf von Z wird RAMPZ incrementiert, aber nur 
bei Befehlen, die auch RAMPZ verwenden - also nicht bei lpm.

Sascha

von spess53 (Gast)


Lesenswert?

Hi

>Nein. Solange die Größe der Tabelle<=64k ist,...

Das war eigentlich mit

> nur bis 64k.

gemeint.

MfG Spess

von Sascha W. (sascha-w)


Lesenswert?

spess53 schrieb:
> Hi
>
>>Nein. Solange die Größe der Tabelle<=64k ist,...
>
> Das war eigentlich mit
>
>> nur bis 64k.
>
> gemeint.
>
> MfG Spess
Peder schreibt in seinem Post aber
>und zwar auch im Bereich > 64k.

Sascha

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.