#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.
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)
>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
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.
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.
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
...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.
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
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
progmemchar*foo1;// foo1 zeigt ins Flash und steht im RAM/GPR
2
char*progmemfoo2;// foo2 zeigt ins RAM und steht im FLASH
3
void*a=foo1;// lädt foo1 per LDS/LDD (oder MOV falls schon im GPR)
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.
>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
@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.
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
constvolatile...
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