Strings mit AVR - aber richtig

OP #7326784
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?

Gast #7326791
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.

#7326801
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

Gast #7326849
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
}
OP #7326864
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

Gast #7326935
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.

OP #7326954
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?

Gast #7326983
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.

Gast #7328008
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.

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

OP #7328462
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.

Gast #7328471
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.

#7328582
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

Persönliche Seite #7328602
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.

Persönliche Seite #7328738
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.

#7328845
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?

Persönliche Seite #7330103
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

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren