Forum: Mikrocontroller und Digitale Elektronik function c asm bedeutung


von whitenoise (Gast)


Lesenswert?

#if defined(RAMPZ)
  #define PROGMEM_UPPER _attribute_ ((section (".textupper")))
  #define SET_RAMPZ(value)  {RAMPZ = value;}

  #define pgm_read_byte_inc(addr) \
  (__extension__({                \
      uint8_t __result;           \
      _asm_                     \
      (                           \
          "elpm %0, Z+" "\n\t"    \
          : "=r" (__result),      \
            "=z" (addr)           \
          : "1" (addr)            \
      );                          \
      __result;                   \
  }))
#else
  #define PROGMEM_UPPER PROGMEM
  #define SET_RAMPZ(value)

  #define pgm_read_byte_inc(addr) \
  (__extension__({                \
      uint8_t __result;           \
      _asm_                     \
      (                           \
          "lpm %0, Z+" "\n\t"     \
          : "=r" (__result),      \
            "=z" (addr)           \
          : "1" (addr)            \
      );                          \
      __result;                   \
  }))


hi,

ich arbeite mich hier gerade in einen code ein, und hier wird diese 
funktion verwendet - ich frage mich was deren genaue funktion ist.

so wie es scheint wird ihr ein pointer vom typ const char * übergeben, 
und sie gibt dann ein byte zurück, ist das so richtig?

dann müsste ich sie in c doch in etwa so deklarieren, oder!?

unsigned char pgm_read_byte_inc (const char *) oder!?

zusätzlich wird dann "dem namen nach" noch der pointer um 1 
erhöht...sehe ich das richtig? damit könnte ich also ein array 
auslesen..

meine zweite frage, warum macht man das an der stelle so? zeiterparnis? 
oder hat es was mit dem internen/externen ram/flash zu tun?

ich würde diese funktion ertsmal ganz gern einfach in c schreiben, um 
damit ein lokales const unsigned char array auslesen zu lönnen.

gruß,
w.

von Johann (Gast)


Lesenswert?

Prinzipell ist es die gleiche Funktionalität wie
1
pgm_read_byte (addr++);

Das Problem damit ist jedoch, daß pgm_read_byte (ebenfalls ein 
Inline-Makro) immer nur ein LPM erzeugt, und das ++ nicht mit dem LPM 
kombiniert werden kann sondern zB mit adiw r30,1 separat erfolgt. Das 
kostet Zeit und Platz.

Die Makros von oben gehen übrigens nicht für alle AVRs; denn einige 
ATtiny kennen nur einen LPM, der implizit nach R0 lädt, und kein LPM Rn, 
Z.

Eine inline Funktion wird übrigens anderen Code geben. Entgegen der oft 
zu findenden Aussage, Inline-Funktionen lieferten gleichen oder 
gleichschnellen Code wie entsprechende Makros, ist das nicht der Fall. 
Grund dafür ist zB die Returnwert-Promotion einer geinlinten Funktion, 
so daß result immer auf 16 Bit expandiert wird -- im Gegensatz zum 
Makro. (avr-gcc ohne -mint8)

von Oliver (Gast)


Lesenswert?

>ich würde diese funktion ertsmal ganz gern einfach in c schreiben

Hätte der Autor dieses Codestücks vermutlich auch getan, wenn es denn 
möglich gewesen wäre. Das geht aber nicht.

Und ja, das hat was mit dem flash zu tun. Der gcc (um den es hier 
vermutlich geht) kann mit der Harvard-Architektur des AVR's (um den es 
hier vermutlich geht) nix anfangen, und kennt keinen Unterschied 
zwischen Programm- und Data-Speicher. Der wird durch nichts und 
niemanden dazu bewegen zu sein, den Befehl "lpm" zu verwenden, um auf 
den Programmspeicher zuzugreifen. Das geht nur in Assembler.

Oliver

von whitenoise (Gast)


Lesenswert?

ok ok, nochmal langsam:

ich will diese funktion ersetzen, mit ihr wird ein datensatz geladen, 
ein ganzer haufen bytes die dann weiterverarbeitet werden.

ich lege diesen datensatz einfach global an:

const unsigend char datensatz[]={0xff,....,0xab};

und will die oben genannten funktionen so in c ausprägen das ich (wie 
mit der funktion oben dies schon geschehen ist) die einzelnen werte aus 
dem datensatz[] auslesen.

ich kenne den avr asm nicht, leider - deswegen verstehe ich auch nicht 
genau was dort passiert und wo das problem liegt...

was ist ein LPM - tut mir leid, ich kenn mich da noch nicht so 
tiefgehend aus, in meiner welt gibts ram zellen, intern und extern 
(optional, können evtl. gemappt werden) am prozessor, stack, heap, und 
programmspeicher flash, am rande noch etwas eeprom speicher - egal - ich 
verstehe das problem bei der sache noch nicht.

w.

von whitenoise (Gast)


Lesenswert?

ein datensatz mit der alten funktion wurde so angelegt:

uint8_t _attribute_ ((progmem)) datensatz[] = {
    0x04,...,0xff};
(keine ahnung was da passiert)
im code wird sie so verwendet:

unsigned short  var;


var+=pgm_read_byte_inc(datensatz);
var=pgm_read_byte_inc(datensatz);
var-=pgm_read_byte_inc(datensatz);

folgend in schleifen usw... es wird ihr immer die adresse von datensatz 
übergeben....

es gibt auch noch die funkion:

pgm_read_byte(datensatz);

deren deklaration ich noch nicht gefunden habe, sie wird ebenfalls 
zwichendurch aufgerufen, ich schätze sie inkrementiert nur den 
"datenpointer" im gegensatz zu pgm_read_byte_inc(datensatz); nicht.

1) warum macht man das so? was bezweckt es genau?
2) wie kann ich es so umschreiben das ich auf mein kleines globales 
array zugreifen kann, ohne die gesamten funktionen die diese 
zugriffsfunktion nutzen zu ändern?

ich hoffe ich konnte es so etwas besser darstellen....

gruß,
w.

von whitenoise (Gast)


Lesenswert?

ich versuche es zz mit diesen defines...

#define     pgm_read_byte_inc(d)        *d;d++
#define     pgm_read_byte(d)            *d

w.

von Oliver (Gast)


Lesenswert?

LPM liest ein byte aus dem Programm Memory in ein Register- also dem 
Flash.

Der gcc kennt aber keine getrennten Speicher, ergo kommt der gar nicht 
auf die Idee, LPM zu verwenden.

Ohne große Umbauten am Compiler und der avr-libc gibt es keine 
Möglichkeit, "ganz normal" auf ein im Flash liegendes Array zuzugreifen. 
Das geht nur über die jeweilgen pgm_read-Funktionen, und damit nur für 
jeden Wert einzeln.

Oliver

von whitenoise (Gast)


Lesenswert?

...ich glaube wir reden aneinader vorei :-)...

ich kann doch schreiben:





const unsigend char datensatz[]={0xff,0xff,0xab};

void main (void)
{
    unsigned short x;

    x=datensatz[0];
    x+=datensatz[1];
    x+=datensatz[2];
}


oder ich schreibe:




uint8_t attribute ((progmem)) datensatz[] = {0xff,0xff,0xab};

void main (void)
{
    unsigned short x;

    x=pgm_read_byte_inc(datensatz);
    x+=pgm_read_byte_inc(datensatz);
    x+=pgm_read_byte_inc(datensatz);

}

ein unterschied ist, so wie ich es verstanden habe, das das array oben, 
nach dem laden wohl im ram liegt, während das im unteren bsp im flash 
liegt und da der compiler hier nicht unterscheiden kann, zwischen flash 
und ram, benötigt man diese read funktionen um ihm eben genau dies 
"aufzuzwingen".
was passiert also im oberen fall, es ist immerhin ein const array, wird 
jedoch demnach trotzdem im ram angelegt...
nebenbei, der inhalt des arrays MUSS jedoch irgentwann ja mal im 
programmcode gelegen haben, da das programm ja auch mal in den 
controller geflasht wurde - jedoch der compiler macht einen anderen 
zugriff daraus!?au mai..

dies ist mir jedoch egal - ich will von der hardware weg - ich will das 
untere main überall nutzen können, auf jedem prozessor, das datenarray 
muss NICHT im flash liegen!

was also genau macht diese read funktion mit dem array, gibt sie einfach 
nur einen nach dem anderen wert des arrays zurück!? bei jedem aufruf den 
nächsten!?

w.

von Johann (Gast)


Lesenswert?

Wenn es wirklich nur darum geht, ein möglichst einfach zu verstehendes 
und portierbares C-Programm zu bekommen, dann kannst Du ganz darauf 
verzichten.

Dazu nimmst Du wie bereits erwähnt die Abbildungen
1
#if defined (__AVR__) && defined (__GNUC__) // avr-gcc
2
#   include <avr/pgmspace.h>
3
#   define pgm_read_byte_inc(d) pgm_read_byte(((const unsigned char *) (d))++)
4
#else // kein avr-gcc
5
#   define pgm_read_byte(d) (*(d))
6
#   define pgm_read_byte_inc(d) (*((const unsigned char *) (d))++)
7
#   define PROGMEM
8
#endif // avr-gcc

Falls d etwa ein Pointer auf long ist, erhälst Du sonst mit
1
#define pgm_read_byte_inc(d) (*(d)++)

ein anderes Ergebnis, weil ++ immer um sizeof (*d) erhöht.

Ohne PROGMEM funktioniert das Programm genauso, nur daß mehr RAM 
verbraucht wird, weil die Konstanten nicht in FLASH leben wie mit 
PROGMEM, sondern im RAM.

avr-gcc hat wie gesagt keine Vorstellung davon, was LPM macht. Diese 
Instruktion wird nur in ein paar tablejumps textuell ausgegeben, zB in 
manchen Sprungtabellen für switch/case.

Leider gibt es in GCC 4 immer noch nicht die nötige Infrastruktur, um 
Harvard-Architekturen besser im Backend unterstützen zu können. Die 
Umsetzung von pgm_read* etc ist über Attribute implementiert, was aber 
die Aufgabe nicht adäquat löst. An der Stelle wären nämlich eigene 
Qualifier nötig (ähnlich wie const, volatile, unsigned, signed), die 
dann auch Ausdrücke wie
1
progmem char * foo1; // foo1 zeigt ins Flash und steht im RAM/GPR
2
char * progmem foo2; // foo2 zeigt ins RAM und steht im FLASH
3
void *a = foo1; // lädt foo1 per LDS/LDD (oder MOV falls schon im GPR)
4
void *b = foo2; // lädt foo2 per LPM

erlaubten.

von Johann (Gast)


Lesenswert?

Das PROGMEM-Zeig dient wie gesagt nur dazu, um RAM zu sparen.

Die const-Daten liegen so oder so im Flash, aber in einem Fall wird beim 
Lesen ins Flash gegriffen (avr-gcc + pgm_*) und im anderen Fall 
(Standard-C) kopiert der Startup-Code das Gerüffel ins RAM oder legt es 
in eine read-only Section je nach Compiler/µC und greift nach dem 
Startup nicht mehr auf diese Daten im Flash zu, sondern nur noch auf die 
Kopien.

von Oliver (Gast)


Lesenswert?

>ein unterschied ist, so wie ich es verstanden habe, das das array oben,
>nach dem laden wohl im ram liegt, während das im unteren bsp im flash
>liegt und da der compiler hier nicht unterscheiden kann, zwischen flash
>und ram, benötigt man diese read funktionen um ihm eben genau dies
>"aufzuzwingen".

So ist es.

>was passiert also im oberen fall, es ist immerhin ein const array, wird
>jedoch demnach trotzdem im ram angelegt...

So ist es beim gcc auf dem AVR. Bei anderen Compilern kann das anders 
sein.

>nebenbei, der inhalt des arrays MUSS jedoch irgentwann ja mal im
>programmcode gelegen haben, da das programm ja auch mal in den
>controller geflasht wurde - jedoch der compiler macht einen anderen
>zugriff daraus!?au mai..

Jein. Im Ram sind das initialisierte globale Variable. Diese werden beim 
Start des Programms, noch bevor main() aufgerufen wird, mit Werten 
gefüllt. Die Initialisierungswerte dazu stehen natürlich im Flash, und 
werden von dort ins Ram kopiert. In allen Programmteilen, die du 
programierst, greift der Compiler dann nur auf das Ram zu.

>dies ist mir jedoch egal - ich will von der hardware weg - ich will das
>untere main überall nutzen können, auf jedem prozessor, das datenarray
>muss NICHT im flash liegen!

Dann wirst du aber für jede Architektur eine passende Implementierung 
von pgm_read_byte_inc() erstellen müssen.

>was also genau macht diese read funktion mit dem array, gibt sie einfach
>nur einen nach dem anderen wert des arrays zurück!? bei jedem aufruf den
>nächsten!?

Im Prinzip schon. Die Funktion gibt den Wert, auf den addr zeigt, 
zurück, und erhöht danach addr dann um eins.

Oliver

von whitenoise (Gast)


Lesenswert?

@Johann und Oliver,

danke für die erklärungen und hintergrundinfos - damit komme ich 
zurecht.

schade das wenn man das schlüsselwort const verwendet dies nicht auch 
einfluss auf den ursprung der variablen nimmt sondern in diesem fall nur 
die variable vor dem zugriff schützt.

const heißt ja nun das die werte nicht verändert werden können, sollen 
müssen.

aber das ist sicher das was du damit sagen wolltest:

>>Leider gibt es in GCC 4 immer noch nicht die nötige Infrastruktur, um
>>Harvard-Architekturen besser im Backend unterstützen zu können. Die
>>Umsetzung von pgm_read* etc ist über Attribute implementiert, was aber
>>die Aufgabe nicht adäquat löst. An der Stelle wären nämlich eigene
>>Qualifier nötig (ähnlich wie const, volatile, unsigned, signed)...

...was mich etwas wundert ist, warum der compiler davon nichts weiß, 
dies ist dann auch der grund warum die werte aus dem flash vorm main ins 
ram kopiert werden, obwohl sie als const deklariert sind, demnach also 
eh nicht verändert werden, theoretisch müsste beim einsatz von const ja 
NUR eben dieser spezielle befehl verwendet werden um den zugriff ins 
flash an dieser stelle zu sichern, denn letztendlich werden die zellen 
ja sicher adressen haben, da dies aber alles viel komplizierter sein 
muss, wird es dann wohl so gemacht (ich meine, wer kopiert schon gern 
konstante werte ins ram) - schade.

ich werds testen (also die defines)

@oli
klar muss ich diese funktion dann immer neu implementieren, aber solange 
zumindest eine definierte softwareschnittstelle vorhanden ist, kann man 
das ja auch machen :-)... vieles ist ja so wild, das es kein anfang und 
kein ende hat...

gruß,
w.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

whitenoise wrote:
> @Johann und Oliver,
>
> schade das wenn man das schlüsselwort const verwendet dies nicht auch
> einfluss auf den ursprung der variablen nimmt sondern in diesem fall nur
> die variable vor dem zugriff schützt.

Das ist auch Sinn und Zweck von const. Wie wolltest Du mit const 
unterscheiden, ob ein Wert ins FLASH oder ins EEPROM lokatiert werden 
soll? Schon daran sieht man, daß const die falsche Baustelle dafür ist.
Kompexere µC haben evtl noch mehr Möglichkeiten, konstante Daten 
abzulegen.

> const heißt ja nun das die werte nicht verändert werden können, sollen
> müssen.

Nein, das heisst es nicht. Es gibt ja auch schöne Konstrukte wie
1
const volatile ...
womit mancher Zeitgenosse gerne ausdrückt, daß man den Wert nicht 
verändern darf, er sich aber ändern kann -- zB ein read-only SFR.

Und schliesslich: In einem Komposit können einzelne Komponenten const 
sein und andere nicht... wie wolltest Du so eine Struct/Union 
lokatieren?


>>>Leider gibt es in GCC 4 immer noch nicht die nötige Infrastruktur, um
>>>Harvard-Architekturen besser im Backend unterstützen zu können. Die
>>>Umsetzung von pgm_read* etc ist über Attribute implementiert, was aber
>>>die Aufgabe nicht adäquat löst. An der Stelle wären nämlich eigene
>>>Qualifier nötig (ähnlich wie const, volatile, unsigned, signed)...
>
> ...was mich etwas wundert ist, warum der compiler davon nichts weiß,
> dies ist dann auch der grund warum die werte aus dem flash vorm main ins
> ram kopiert werden, obwohl sie als const deklariert sind, demnach also
> eh nicht verändert werden, theoretisch müsste beim einsatz von const ja
> NUR eben dieser spezielle befehl verwendet werden um den zugriff ins
> flash an dieser stelle zu sichern, denn letztendlich werden die zellen
> ja sicher adressen haben, da dies aber alles viel komplizierter sein
> muss, wird es dann wohl so gemacht (ich meine, wer kopiert schon gern
> konstante werte ins ram) - schade.

Zunächst würde die Einführung neuer Qualifier eine Erweiterung des 
C-Standards entsprechen, was nicht so prickeln ist. huge, far, near etc 
sind Beispiele dafür. Mit der momentanen Strategie bleibt der Compiler 
hier "sauber", und lastet es dem Anwender auf, der über inline Asm und 
Attribute den Code patchen muss.

Der eigentliche Grund ist aber, daß GCC intern nur einen einzigen 
Pointer-Mode kennt, der per Makro definiert wird. Intern bräuchte man 
aber mehrer Pointer-Modi, die dann bei der asm-Ausgabe entsprechend 
umgesetzt werden könnten und vom Parser korrekt initialisiert würden. 
Problem ist der Mittelteil, wo zig Transformationen und Optimierungen 
geschehen. Die tausenden von Stellen im GCC dahingehend zu erweitern und 
zu testen ist ne echte Strafe und ein eigenes, auf mindestens 1 MJahr 
anzulegendes Projekt. Und wenn man damit fertig wäre, müsste man das in 
die dann aktuelle GCC-Version nachführen.

> @oli
> klar muss ich diese funktion dann immer neu implementieren, aber solange
> zumindest eine definierte softwareschnittstelle vorhanden ist, kann man
> das ja auch machen :-)... vieles ist ja so wild, das es kein anfang und
> kein ende hat...

Is ja nur ein *p++, falls man nicht den pathologischen Fall hat, durch 
einen nicht-char-Zeiger auf chars zuzugreifen. Dann muss eben noch der 
Cast dazu.

Johann

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.