Forum: Mikrocontroller und Digitale Elektronik Strings mit AVR - aber richtig


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Luky S. (luky)


Lesenswert?

Ich bin gerade sehr verwirrt, weil es ja sehr einfach sein sollte, aber 
ich finde eine Menge widersprüchlicher Infos im Netz:

Ich möchte einfach nur ein paar konstante "Strings" und auch Wertearrays 
(uint16_t und uint32_t) im Flash und nicht im RAM ablegen und über die 
UART verschicken.

Was ist jetzt wirklich die "aktuellste" und sichere Variante?
PROGMEM ja oder ist das doch nur kosmetisch?

Was ist mit pgm_read_byte oder pgm_read_byte_near? Wann ist der 
Unterschied relevant?

Und wie wird das zu sendende Array im Flash korrekt an die Sendefunktion 
übergeben?

Gibt es irgendwelche Sideeffects beim abspeichern im Flash vs. im RAM?

von Thorsten M. (cortex_user)


Lesenswert?

Luky S. schrieb:
> Was ist jetzt wirklich die "aktuellste" und sichere Variante?
> PROGMEM ja oder ist das doch nur kosmetisch?

Ja, ist immer noch aktuell. Nur musst Du dann alle Funktionen so 
schreiben, dass sie damit umgehen können oder Dir die Daten vorher in 
ein Clipboard holen im RAM. Alternativ kann print überladen werden mit 
__FlashStringHelper*, dazu gibt es auch Beispielcodes, wie progmem.h 
verwendet wird.
1
#include <avr/pgmspace.h>
2
const static char PROGMEM text_A[]     = "Text A ist hier";
3
const static char PROGMEM text_B[]     = "Text B ist hier";

Steht im Flash, kann aber ohne weiteres nicht an Funktionen übergeben 
werden.

: Bearbeitet durch User
von 900ss D. (900ss)


Lesenswert?

Luky S. schrieb:
> eine Menge widersprüchlicher Infos im Netz

Die avrlibc ist sehr gut dokumentiert auf ihrer Homepage. Die 
Informationen dort kannst du als gültig ansehen.

Hier ist der Link:
https://www.nongnu.org/avr-libc/user-manual/

: Bearbeitet durch User
von Εrnst B. (ernst)


Lesenswert?

Thorsten M. schrieb:
> Ja, ist immer noch aktuell.

wie in "Funktioniert noch"

Luky S. schrieb:
> Was ist jetzt wirklich die "aktuellste" und sichere Variante?

das ist "__flash". Da kümmert sich der Compiler. Zumindest soweit 
möglich.
1
#include <stdint.h>
2
#include <avr/pgmspace.h>
3
4
extern __flash const uint8_t test_flash[3];
5
extern const uint8_t test_progmem[3] PROGMEM;
6
7
8
void access() {
9
    volatile uint8_t x;
10
    x=test_flash[2];
11
    x=pgm_read_byte(&test_progmem[2]);
12
}

Kompiliert (mit Optimierung) zu:
1
       ldi r30,lo8(test_flash+2)
2
        ldi r31,hi8(test_flash+2)
3
        lpm r24,Z
4
        std Y+1,r24
5
6
        ldi r30,lo8(test_progmem+2)
7
        ldi r31,hi8(test_progmem+2)
8
        lpm r30, Z
9
        std Y+1,r30

d.H. identischer ASM-Code, nur ein anderes Register (r30) zum 
Zwischenspeichern...

AVR-GCC-Tutorial: Flash mit flash und Embedded-C

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Εrnst B. schrieb:
> Luky S. schrieb:
>> Was ist jetzt wirklich die "aktuellste" und sichere Variante?
>
> das ist "__flash". Da kümmert sich der Compiler. Zumindest soweit
> möglich.

In C.

Für den Fall, daß da doch noch Arduino als Salamischeibe und damit C++ 
nachgereicht wird, geht es nur mit PROGMEM.

Oliver

von Peter D. (peda)


Lesenswert?

Ein Array aus Strings fester Länge geht.
Ein Array aus Pointern auf Strings geht nicht, bzw. nur mit extrem viel 
Schreibarbeit.

von EAF (Gast)


Lesenswert?

Oliver S. schrieb:
> Für den Fall, daß da doch noch Arduino als Salamischeibe und damit C++
> nachgereicht wird, geht es nur mit PROGMEM.

Wobei Arduino netter Weise das F() Macro mit bringt, und auch die 
"incomplete __FlashStringHelper Class".
Mit F() werden allerdings keine Duplikate erkannt.

Die Print Methoden sind passend dazu ausgestattet.

Hier mal im Beispiel:
1
#include <Streaming.h> // die Lib findest du selber ;-)
2
Print &cout = Serial; // cout Emulation für "Arme"
3
4
using FlashStr = __FlashStringHelper *;
5
6
7
const char roman[] PROGMEM {"Hier steht ein Roman im Flash!"};
8
9
void setup() 
10
{
11
  Serial.begin(9600);
12
  cout << F("Start: ") << F(__FILE__) << endl;
13
  cout << FlashStr(roman) << endl;
14
15
  // alternativ:
16
  Serial.print( F("Start: ")); Serial.println(F(__FILE__));
17
  Serial.println(FlashStr(roman));
18
  Serial.println((FlashStr)roman);
19
  Serial.println((__FlashStringHelper *)roman);
20
}
21
22
void loop() 
23
{
24
25
}

von sprachsensibler (Gast)


Lesenswert?

EAF schrieb:
> const char roman[]

... würde ein Mensch aus dem englischen Sprachraum empfinden
als "const char römisch", würde also einen römischen
Roman vermuten. Oder aber auch ein "Array von Römern" ....

SCNR

von Luky S. (luky)


Lesenswert?

Es geht tatsächlich um C auf einen ATMega328 ohne Arduino, sondern mit 
dem Microchip (ehemals Atmel) Studio.
Zunächst will ich mal Statusinfos ausgeben, also
const __flash char BUILDTIME[]  = {__DATE__ " " __TIME__};
und einen Hilfetext
const __flash char HELP[] = {"Für Hilfe siehe ....\r\n"};
über UART verschicken

von Georg M. (g_m)


Lesenswert?

Luky S. schrieb:
> im Flash und nicht im RAM ablegen

Und nicht im EEPROM?

von EAF (Gast)


Lesenswert?

Luky S. schrieb:
> Zunächst will ich mal Statusinfos ausgeben, also
> ...
> über UART verschicken

Das ist ja auch in C kein Drama!
Oder?

Luky S. schrieb:
> pgm_read_byte_near
Die Probleme fangen erst an, wenn der "near" Bereich verlassen wird.
Denn Pointer in AVR C sind nur 16Bit breit.

von Oliver S. (oliverso)


Lesenswert?

Luky S. schrieb:
> ATMega328

EAF schrieb:
> Die Probleme fangen erst an, wenn der "near" Bereich verlassen wird.

Das ist eher unwahrscheinlich.

Oliver

von Luky S. (luky)


Lesenswert?

Es sollte ja kein Drama sein, aber irgendwie erzählt jeder was 
anderes...

von EAF (Gast)


Lesenswert?

Luky S. schrieb:
> Es sollte ja kein Drama sein, aber irgendwie erzählt jeder was
> anderes...

Über 99% der Menschheit hat überhaupt keine Ahnung worüber wir her 
reden.
Lass sie erzählen ....

von sprachsensibler (Gast)


Lesenswert?

EAF schrieb:
> Die Probleme fangen erst an, wenn der "near" Bereich verlassen wird.

Nein, denn es gibt in <pgmspace.h> eine Sammlung von String-
Funktionen die das Arbeiten mit Strings im Flash ermöglichen,
und das auch für den Speicherbereich oberhalb der
16-Bit-Adressierung.

Beispielhaft eine Funktion die für Flash im Far-Bereich arbeitet:
1
char *strncpy_PF(char *dest, uint_farptr_t src, size_t len);

Mit diesen Funktionen lässt sich eigentlich alles im "Progmem"
und oberhalb von 16-Bit-Speicheradressierung erschlagen.

von Luky S. (luky)


Lesenswert?

Wenn der String
1
const __flash char BUILDTIME[]  = {__DATE__ " " __TIME__};
und die Ausgabefunktion
1
void send_byte_usart0(u8 c) {  
2
  while(!(UCSR2A&_BV(UDRE2))); //Uart not ready
3
  UDR2 = c; //send data
4
}
5
6
void print_flashstr(PGM_P str) {
7
u8 c;
8
  while((c=pgm_read_byte_near ((u16)str))) {
9
    send_byte_usart0(c); 
10
    str++;
11
  }
12
}
benutzt wird, sollte
1
print_flashstr(BUILDTIME);
beim ATMega328 und auch bei den größeren Varianten (z.B. ATMega2560) 
sicher funktionieren?

von sprachsensibler (Gast)


Lesenswert?

Luky S. schrieb:
> sollte print_flashstr(BUILDTIME);
> beim ATMega328 und auch bei den größeren Varianten (z.B. ATMega2560)
> sicher funktionieren?

Warum probierst du es nicht einfach aus? Hast du keinen
Controller dafür? Sollten wir diesen einfachen Test für dich
durchführen müssen?

Solange du im unteren 16-Bit Adressraum ("near") bleibst ist
das Verhalten für den ATMega2560 gleich. Ansonsten verwende
die vorher von mir bereits erwähnten far-Funktionen fürs Flash.

von Luky S. (luky)


Lesenswert?

Funktionieren tut es ja, aber ich bin daran interessiert, es gleich 
"richtig" zu machen und halt nicht nur irgendwie...

von Stefan F. (stefanus)


Lesenswert?

"richtig" wäre, wenn man sich an den C Standard halten könnte. Dann 
würde ein einfaches

const char s[]="Hello World!"

genügen. Bei ARM Controllern ist das so.

Doch leider kommt man nicht umhin, bei AVR eine Sonderlocke zu machen, 
weil für den Zugriff auf Flash andere Assembler-Befehle nötig sind als 
für Zugriff auf RAM, was in C eigentlich nicht vorgesehen ist. Im 
Artikel https://www.nongnu.org/avr-libc/user-manual/pgmspace.html und in 
https://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html 
ist detailliert beschrieben, welcher Workaround in der AVR C Bibliothek 
vorgesehen ist. Was im AVR Umfeld richtig ist, steht dort.

Auf die Seite wurde schon in der ersten Antwort hingewiesen. Ich 
wiederhole den Hinweis, weil es mir ein Rätsel ist, was es da großartig 
zu diskutieren gibt. Das Thema hätte nach der ersten Antwort bereits 
beendet sein sollen.

: Bearbeitet durch User
von Εrnst B. (ernst)


Lesenswert?

Stefan F. schrieb:
> weil es mir ein Rätsel ist, was es da großartig
> zu diskutieren gibt. Das Thema hätte nach der ersten Antwort bereits
> beendet sein sollen.

Weil:
>> Ab Version 4.7 unterstützt avr-gcc Adress-Spaces gemäß dem Embedded-C Dokument
>> ISO/IEC TR18037

damit gibt es schon zwei Methoden, um die AVR-Sonderlocke für 
Strings-im-Flash umzusetzen.
Und wo es mehrere Wege zum Ziel gibt, kann man über die Vor- und 
Nachteile jedes Weges diskutieren.

Und exakt das war auch in der Fragestellung des TE:

Luky S. schrieb:
> Was ist jetzt wirklich die "aktuellste" und sichere Variante?

und "PROGMEM" ist nunmal sicher nicht der "aktuellste" Weg. Vielleicht 
der am häufigsten gegangene.

: Bearbeitet durch User
von Luky S. (luky)


Lesenswert?

ernst hat es verstanden. Ich war bzw. bin immer noch verwirrt, welche 
die "beste" Möglichkeit ist, relativ viel Text RAM-sparend abzulegen und 
z.B. über die UART auszugeben (kann auch für eine Displayausgabe sein)
Und wie man das konkret sauber umsetzt.

von W.S. (Gast)


Lesenswert?

Stefan F. schrieb:
> Doch leider kommt man nicht umhin, bei AVR eine Sonderlocke zu machen,
> weil...

Tja, das ist keine 'Sonderlocke', sondern der ganz normale Unterschied 
zwischen Harvard (AVR) und v.Neumann (ARM, PC, andere). Eigentlich 
sollte das jeder begriffen haben, wenn er mit Programmieren anfängt. Es 
sind eben die bei Harvard getrennten Adreßräume zwischen Daten und 
Instruktionen. Da kann man nicht mit einem schlichten Zeiger überall 
hinzeigen. Und weil C eben sehr zeigerlastig ist, fällt das bei C eben 
besonders auf.

W.S.

von Oliver S. (oliverso)


Lesenswert?

Ab hier lesen:

https://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Flash_mit_PROGMEM_und_pgm_read

Und dann mach einfach, was du für richtig hältst. Das ist dann für dich 
"das Beste".

Oliver

von Wilhelm M. (wimalopaan)


Lesenswert?

EAF schrieb:
> Mit F() werden allerdings keine Duplikate erkannt.

Wäre ja auch zu einfach gewesen, wenn das Arduino-Framework irgendetwas 
richtig machen würde ;-)

von Sebastian W. (wangnick)


Lesenswert?

Luky S. schrieb:
> Wenn der Stringconst __flash char BUILDTIME[]  = {__DATE__ " "
> __TIME__};
> und die Ausgabefunktionvoid send_byte_usart0(u8 c) {
>   while(!(UCSR2A&_BV(UDRE2))); //Uart not ready
>   UDR2 = c; //send data
> }
> void print_flashstr(PGM_P str) {
> u8 c;
>   while((c=pgm_read_byte_near ((u16)str))) {
>     send_byte_usart0(c);
>     str++;
>   }
> }
> benutzt wird, sollteprint_flashstr(BUILDTIME);
> beim ATMega328 und auch bei den größeren Varianten (z.B. ATMega2560)
> sicher funktionieren?

Auf dem Atmega328: Ja. Auf den größeren Varianten (1284, 2560) mit >64KB 
Flash: Nein, nicht wenn BUILDTIME im oberen Flash-Bereich abgelegt ist. 
Dann ist pgm_read_byte_far und dann sind bei Pointern die Handstände mit 
dem Z-Register nötig, weil 16 Bit nicht mehr ausreichen.

Was da compilermäßig inzwischen Stand der Kunst ist würde mich auch 
interessieren.

LG, Sebastian

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Sebastian W. schrieb:
> Auf dem ATmega328: Ja. Auf den größeren Varianten (1284, 2560) mit >64KB
> Flash: Nein, nicht wenn BUILDTIME im oberen Flash-Bereich abgelegt ist.
> Dann ist pgm_read_byte_far und dann sind bei Pointern die Handstände mit
> dem Z-Register nötig, weil 16 Bit nicht mehr ausreichen.

For allem braucht man einen eigenen Datentyp und Makros aus 
avr/pgmspace.h, und überhaupt an die Adressen zu kommen und diese in 
einer Variable zu halten.

> Was da compilermäßig inzwischen Stand der Kunst ist würde mich auch
> interessieren.

Er gibt Address-Space __memx.  Pointer darauf sind 24-Bit Zeiger.  Beim 
Zugriff wird RAMPZ passend gesetzt und danach wieder hergestellt. 
Objektgröße ist aber wie für "normalen" Code auch auf 32767 Bytes 
begrenzt.  Und mit 24-Bit pointern zu hantieren ist natürlich 
aufwändiger als mit normalen Zeigern.  Dafür wird Leser über 
Segmentgrenzen hinweg unterstützt.

Daten werden in .progmemx.data abgelegt, was nach ausführbarem Code 
(.text) kommt, während normales progmem in .progmem.data vor 
ausführbaren Code lokatiert wird.

Und dann gibt's auch noch __flash1 etc., die auch in der obigen 
Wiki-Seite beschrieben werden, die aber eine Ergänzung zum Linkerscript 
erfordern für Sections .progmem1.data etc.

Dann gibt es nocht Devices wie ATmega4808, die Flash im RAM-Adressraum 
sichtbar machen.  Und dort wird .rodata dann ins Flash lokatiert und man 
braucht weder PROGMEM noch __flash.

Neuere AVR32D Devices haven nur einen Teil den Flashs im RAM Adressraum, 
z.b. nur das Ende des Flashs, was aver von avr-gcc momentan nicht 
unterstützt wird. Dazu bräuchte es ein eigenes Linkerscript also eine 
neue Emulaton (z.B. avrxmega8) für Binutils und Erweiterung im Compiler 
und in AVR-Libc.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> EAF schrieb:
>> Mit F() werden allerdings keine Duplikate erkannt.
>
> Wäre ja auch zu einfach gewesen, wenn das Arduino-Framework irgendetwas
> richtig machen würde ;-)

Das hat nix mit Arduino zu tun sondern ist eine Schwäche des Compilers.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Johann L. schrieb:
> Wilhelm M. schrieb:
>> EAF schrieb:
>>> Mit F() werden allerdings keine Duplikate erkannt.
>>
>> Wäre ja auch zu einfach gewesen, wenn das Arduino-Framework irgendetwas
>> richtig machen würde ;-)
>
> Das hat nix mit Arduino zu tun sondern ist eine Schwäche des Compilers.

Nein.
Man kann es mit Templates so machen, dass Duplikate nur einmal  werden.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Es gibt nun mal Schwächen im avr-gcc mit Duplikaterkennung, egal ob man 
da C oder C++ reinfüttert.

von Wilhelm M. (wimalopaan)


Lesenswert?

Johann L. schrieb:
> Es gibt nun mal Schwächen im avr-gcc mit Duplikaterkennung, egal ob man
> da C oder C++ reinfüttert.

Wie gesagt: mit C++ Templates kann man das elegant lösen, auch wenn es 
der avr-gcc selbst nicht kann.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

...evtl wurde auch nur vergessen, -fmerge-all-constants anzugeben.

Hier ein Beispiel mit
1
avr-gcc -Os foo.c -mmcu=atmega8 -o foo.elf -fmerge-all-constants -fno-ipa-icf-variables && (avr-nm foo.elf | grep  gut)

-fno-ipa-icf-variables wird benötigt wegen PR92606.
1
#include <string.h>
2
#include <avr/pgmspace.h>
3
4
const char gut[] = "alles gut.";
5
const char ngut[] = "nicht alles gut.";
6
7
const __flash char f_gut[] = "alles gut.";
8
const __flash char f_ngut[] = "nicht alles gut.";
9
10
PROGMEM const char p_gut[] = "alles gut.";
11
12
void get_str (char *str, char *nstr)
13
{
14
    strcpy (str, gut);
15
    strcpy (nstr, ngut);
16
}
17
18
void get_f_str (char *str, char *nstr, char* pstr)
19
{
20
    strcpy_P (str, (const char*) f_gut);
21
    strcpy_P (nstr, (const char*) f_ngut);
22
    strcpy_P (pstr, p_gut);
23
}
24
25
int main(){}

*Ausgabe von nm:*
1
0000002c T f_gut
2
00000026 T f_ngut
3
00800066 D gut
4
00800060 D ngut
5
0000002c T p_gut

* Wie erwartet werden trailing Strings gemergt. Zum Beispiel ist &gut = 
6 + &ngut, d.h. gut[] ist Teil von ngut[].

* Dito für Strings im Flash: &p_gut = &f_gut = 6 + &f_ngut.

PR92606 mischt Strings im Flash mit Strings im RAM, daher muss diese 
(Compiler-)Optimierung deaktiviert werden.

Normales String-Merging geschieht hingegen auf Linker-Ebene, indem 
passende Sections-Flags gesetzt werden: "S" für String und "M" für 
Mergeable.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Johann L. schrieb:
> PR92606 mischt Strings im Flash mit Strings im RAM, daher muss diese
> (Compiler-)Optimierung deaktiviert werden.

Du meinst den Bug 92606:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92606

Und -fno-ipa-icf-variables ist ein Work-around für diesen Bug. Dein 
Patch wurde ja (leider) nicht angenommen.

Welche Optimierungen fallen denn noch weg, wenn -fno-ipa-icf-variables 
gesetzt ist?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Wilhelm M. schrieb:
> Du meinst den Bug 92606:

Ja. PR steht für "Problem Report", und es gibt eine fortlaufende 
Numerierung.

https://gcc.gnu.org/PR92606

> Welche Optimierungen fallen denn noch weg, wenn -fno-ipa-icf-variables
> gesetzt ist?

Es fällt genau diese Optimierung weg:
1
  -fipa-icf-variables         Perform Identical Code Folding for variables.

Wobei "Code-Folding" eher ein "Merging" bedeutet.  Details in 
./gcc/ipa-icf.c, Schalter heißt flag_ipa_icf_variables:

https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=gcc/ipa-icf.cc

: Bearbeitet durch User

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.