Forum: Mikrocontroller und Digitale Elektronik Externen I2C (F)RAM bequem verwalten


von Embedded Programmer (Gast)


Lesenswert?

Hallo,

in einem meiner aktuellen Projekte werden (kleine) Datenmengen 
regelmäßig aktualisiert und sollen halbwegs persistent gespeichert 
werden. Ich habe mich für FRAM entschlossen, welcher mittels I2C von 
meinem AVR (ATmega328p) angesprochen wird.

Teil meines Projekts ist eine I2C Bibliothek sowie eine FRAM Bibliothek, 
welche die Zugriffe etwas abstrahieren. Auf Daten im FRAM kann ich dann 
mit folgenden Funktionen zugreifen:
1
uint8_t fram_read_byte(uint16_t addr);
2
void fram_read_block(uint16_t addr, void* dst, size_t len);
3
4
void fram_write_byte(uint16_t addr, uint8_t val);
5
void fram_write_block(uint16_t addr, void* src, size_t len);

Die Adresse ist ja jeweils nur eine Zahl, die sich auf das entsprechende 
Byte im FRAM bezieht. Das funktioniert soweit auch super, nun habe ich 
aber eine eher konzeptuelle Frage. Wie lässt sich der Speicher nun 
bequem mittels C bzw. avr-libc heraus verwalten und darauf zugreifen?

Beispeiseweise möchte ich eine größere Struktur folgender Art im FRAM 
ablegen:
1
typedef struct {
2
3
  char name[64];
4
  uint16_t impulses;
5
  uint64_t counter;
6
7
} prefs_sub_t;
8
9
typedef struct {
10
11
  version_t version;
12
  size_t length;
13
  prefs_sub_t prefs[256];
14
15
} prefs_t;

Insgesamt ist eine konkrete Instanz diesen Typs zu groß, um sie im RAM 
vorzuhalten. Außerdem möchte ich mir den Overhead der korrekten 
"Synchronisation" beider Kopien ersparen. Daher möchte ich die Daten NUR 
(!) im FRAM vorhalten und mittels geeigneter Funktionen bequem auf diese 
zugreifen können.

Allerdings erscheint mir das relativ kompliziert und ich muss händisch 
die jeweiligen Offsets berechnen. Mal angenommen, ich möchte auf den 
Namen des 17. Elements zugreifen, sähe das ja in etwa so aus (vom 
Konzept her, ob es tatsächlich kompiliert weiß ich gar nicht):
1
char[32] str;
2
fram_read_block(0 + sizeof(version_t) + sizeof(length) + sizeof(prefs_sub_t) * 17, str, 64);

Das erscheint mir etwas unpraktisch und erfordert wohl viel Rechnerei. 
Vorallem erfordert es Anpassungen, sobald sich die Strukturen einmal 
ändern.

Schöner wäre es, wenn man z.B. ähnlich zum Zugriff auf dem EEPROM einen 
eigenen Pointer-Typ definieren könnte. Ist das denn (ohne tiefgreifende 
Eingriffe in avr-libc bzw. avr-gcc) möglich? Also ein eigenes Attribut 
z.B. FMEM zu definieren (analog zu EEMEM)?

Wie würdet ihr so etwas lösen? Gibt es hier brauchbare Projekte, bei 
denen man sich inspirieren lassen kann? Externen Speicher mittels I2C 
anzusprechen ist ja nicht ganz exotisch, auch wenn es meistens EEPROM 
ist ...

Vielen Dank!

von eagle user (Gast)


Lesenswert?

> ich muss händisch die jeweiligen Offsets berechnen

Es gibt neben sizeof auch offsetof(struct_t,member), damit wird es 
kürzer und unempfindlich(er) gegen Änderungen einer struct Definition.

Zweitens könnte man z.B. eine Funktion schreiben, die ein komplettes 
Element prefs_sub_t liefert. Die berechnet dann intern den Offset und 
ruft fram_read_block() auf.

von S. R. (svenska)


Lesenswert?

Da deine Struktur nicht in den RAM passt, kannst du immer nur je ein 
Element in der Struktur benutzen. Das ist ein eher schlechter Ansatz.

Du könntest eine Funktion schreiben, die einen Zeiger auf die Struktur 
zurückgibt, wobei das gesuchte Element (also Zeiger + Offset) auf einen 
internen Puffer zeigt, der von der Funktion mit dem passenden Element 
gefüllt wurde. Ein Makro sollte den Elementnamen dann in Offset+Größe 
wandeln können (mit offsetof/sizeof).

Bei jedem Aufruf der Funktion wird dann das zuletzt gelesene Element 
zurückgeschrieben und das neue Element gelesen. Beachte, dass der Zeiger 
selbst ungültig ist!

1
  struct whatever *mystruct;
2
3
  /* lies element1 */
4
  mystruct = update_cache(element1);
5
  mystruct->element1 = ...;
6
7
  /* schreibe element1, lies element2 */
8
  mystruct = update_cache(element2);
9
  mystruct->element2 = ...;
10
11
  /* schreibe element2, lies element1 */
12
  mystruct = update_cache(element1);

von Karol B. (johnpatcher)


Lesenswert?

Etwas ähnliches wurde bereits hier [1] diskutiert. Im Wesentlichen 
scheint es zwei Optionen zu geben:

- Adressenrechnerei, welche mittels geeigneter Makros etwas vereinfacht 
werden kann, wie z.B. hier [2] vorgeschlagen:
1
typedef struct {
2
  uint8_t  a;
3
  uint16_t b;
4
  uint32_t c;
5
} eeprom_t __attribute__(__packed__);
6
7
eeprom_t* eeprom = (eeprom_t*) 0;
8
9
uint32_t var = 50;
10
eeprom_write(&eeprom->c, &var, sizeof(eeprom->c));

- Eigener Adressbereich mit passendem Linkerskript, so wie es hier [3] 
beschrieben wird. Das sieht im einfachsten Fall in etwa so aus:
1
uint16_t FRAM_Word __attribute__((section(".fram")));
2
float FRAM_Coeffs[16] __attribute__((section(".fram")));
3
struct
4
{
5
  char name[20];
6
  float Coeff;
7
  uint16_t Backup;
8
} FRAM_Backup __attribute__((section(".fram")));
9
//...
10
//...
11
//...
12
char name[20];
13
for(uint8_t x = 0; x < 20; x++)
14
   name[x] = FRAM_read_byte(&FRAM_Backup.name[x]);
1
LDFLAGS += -Wl,--section-start=.fram=0x820000

Die zweite Option hat den Vorteil, dass willkürliche Variablen 
"abgelegt" und adressiert werden können ohne mit Adressen bzw. Offsets 
herum rechnen zu müssen. Allerdings hat dieser Ansatz auch Nachteile. 
Pointer sind bei avr-gcc wohl prinzip-bedingt 16 Bit breit, d.h. mehr 
als 65K können nicht addressiert werden. Abhilfe wären verschiedene 
Speicherbereiche, aber das macht es nicht einfacher ...

[1]: Beitrag "ARM-GCC: EEPROM abbilden?"
[2]: Beitrag "Re: ARM-GCC: EEPROM abbilden?"
[3]: 
http://www.avrfreaks.net/forum/how-get-linker-create-new-memory-section

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.