Forum: Compiler & IDEs vorhandenes struct im RAM auf Spiflash oder SDCard speichern


von avus (Gast)


Lesenswert?

Moin,

manchmal oder sogar öfter tauchen hier Fragen auf, wie man ein einmal 
mehr oder weniger sorgfältig durchdachtes struct am besten nichtflüchtig 
verewigt.
Da kommen dann Antworten, daß man das struct eher als union formulieren 
sollte. Dann hat man aber andere Restriktionen.
Ich wollte es nicht glauben: Da hat man eine vermutlich lineare 
Anordnung von Bytes im RAM, und dann soll man die Bytes womöglich 
entweder ganz zu Fuß byteweise oder noch etwas intelligenter per union 
Stück für Stück durch die Gegend schaufeln? Obwohl alle beteiligten 
Speicher auch linear arbeiten?
Mir ist es nach harter Arbeit endlich gelungen, das zu lösen, und zwar 
mit meinem neuen Superzeiger ;))
Erfahrene C-Zeiger-Leute bitte weghören, die verstehen womöglich sogar, 
was dieses Konstrukt genau macht g

Es ging bei meinem Problem darum, ein TFT-Panel mit der Eigenschaft 
'Display' und der anderen Eigenschaft 'Touchpanel' miteinander zu 
verheiraten. Wenn man mehrere Displays hat, möchte man nicht immer neu 
kalibrieren.
So könnte eine ganz, ganz einfache Struktur, die die beiden miteinander 
verbindet, aussehen:
1
//TYPENDEKLARATION TFT Panel
2
//Die Struktur wird im SPI-Flash (auf dem Panel vorhanden) gespeichert
3
typedef struct
4
{
5
  char name[32];      //Name des Panels, frei verfügbar
6
  uint8_t type;      //0=SSD1963 (480x272,4.3"),1=SSD1963 (800x480,5"),2=SSD1963 (800x480,7"),3=ILI9328 (320x240,2.4"),4=ILI9325 (320x240,2.4"),5=ILI9341 (320x240,2.4")
7
  uint8_t pllfac;      //SSD1963 PLL-Faktor 0x23 für 10MHz, 0x1D für 12MHz etc.
8
  uint16_t width;      //Pixel X (z.B. 480)
9
  uint16_t height;    //Pixel Y (z.B. 272)
10
  uint16_t gamma;      //gamma values
11
  uint32_t thresup;    //Touchpanel resistance upper threshold
12
  uint32_t thresdo;    //Touchpanel resistance lower threshold
13
  uint32_t xoff;      //Touchpanel calibration value x offset
14
  uint32_t yoff;      //Touchpanel calibration value y offset
15
  uint32_t xfac;      //Touchpanel calibration value x factor
16
  uint32_t yfac;      //Touchpanel calibration value y factor
17
} PANEL_t;
18
19
PANEL_t panel;


Sinn: In main liest man den Spiflash aus und braucht nicht immer neu zu 
kalibrieren. Oder wenn ein solcher nicht vorhanden ist, eine SDcard, auf 
der z.B. der Druckpunkt des Panels abgespeichert ist (threshold up/do). 
Der benötigte Druck (bzw. umgekehrt - der Widerstand) ist bei einem 
2.4"- oder einem 7"-Display schon ziemlich unterschiedlich.
Das sind im obigen Beispiel also 64 Bytes in der Struktur "panel", die 
jetzt irgendwohin gespeichert werden sollen. Wie kriegt man also die 
RAM-Daten einfach ins Flash oder auf SD?

Wenn man einen Pointer definiert, der auf einen uint8_t-Typ zeigen soll, 
geht der Rest wie von selbst (*).
1
uint8_t *ptr = (uint8_t *)&panel;
Speichern ins Flash geht so:
1
SpiFlashUnlock();
2
SpiFlashSectorerase(0);
3
SpiFlashWriteByteBlock(0,ptr,sizeof(panel));
Dabei ist 'SpiFlashWriteByteBlock' die Standardroutine für SPIflash, die 
als erstes Argument die Adresse im Flash erwartet, als zweites den 
ersten Eintrag im Struct (= erstes Byte, was übertragen werden soll), 
und als dritten die Größe des Structs. Diese Routinen findet man überall 
im Netz, aber die Hin- und Herspeicherei zwischen struct, Flash und SD 
eher seltener.
Lesen geht mit dem gleichen Zeiger so:
1
uint8_t *ptr = (uint8_t *)&panel;
2
SpiFlashReadByteBlock(0,ptr,sizeof(panel));
Und zack, kann man auf die einzelnen Elemente der Struktur wieder 
zugreifen.

Auf SDCard funktioniert es genauso:
1
uint8_t *ptr = (uint8_t *)&panel;
Schreiben und Lesen eines "Device-Deskriptors" anhand von Elm-Chan-FATFS 
(devFlash und devSDCard sind Flags, die vorher schon getestet wurden und 
das Vorhandensein des jeweiligen Speichers prüfen):
1
// ########################
2
// Device-Deskriptor lesen, a) von Flash oder falls nicht vorhanden, von SDCard
3
// ########################
4
uint8_t ReadDeviceDeskriptor(void)
5
{
6
  uint8_t err = 0;
7
  UINT bytesread = 0;
8
  uint8_t *ptr = (uint8_t *)&panel;
9
10
  if (devflag & devFlash)
11
  {
12
    SpiFlashReadByteBlock(0,ptr,sizeof(panel));
13
  }else{
14
    err = 1;
15
    if (devflag & devSDCard)
16
    {
17
      if (!f_opendir(&dir, "system"))
18
      {
19
        if (!f_open(&fil,"system/stm32sys.cfg",FA_OPEN_EXISTING | FA_READ))
20
        {
21
          if (!f_read(&fil,ptr,sizeof(panel),&bytesread))
22
          {
23
            if (bytesread == sizeof(panel)) err = 0;
24
          }else{
25
            err = 1; //Read err
26
          }
27
          f_close(&fil);
28
        }else{
29
          err = 2; //Error, no "stm32sys.cfg" found
30
        }
31
        f_closedir(&dir);
32
      }else{
33
        err = 3; //Error, no "system" folder found
34
      }
35
    }else{
36
      err = 4; //Error, no SD Card available
37
    }
38
  }
39
  return err;
40
}
41
42
// ########################
43
// Device-Deskriptor schreiben, a) auf Flash oder falls nicht vorhanden, auf SDCard
44
// ########################
45
uint8_t WriteDeviceDeskriptor(void)
46
{
47
  uint8_t err = 0;
48
  UINT byteswritten = 0;
49
  uint8_t *ptr = (uint8_t *)&panel;
50
51
  if (devflag & devFlash)
52
  {
53
    SpiFlashUnlock();
54
    SpiFlashSectorerase(0);
55
    SpiFlashWriteByteBlock(0,ptr,sizeof(panel));
56
  }else{
57
    if (devflag & devSDCard)
58
    {
59
      if (!f_opendir(&dir, "system"))
60
      {
61
        if (!f_open(&fil,"system/stm32sys.cfg",FA_CREATE_ALWAYS | FA_WRITE))
62
        {
63
          if (!f_write(&fil,ptr,sizeof(panel),&byteswritten))
64
          {
65
            if (byteswritten == sizeof(panel)) err = 0;
66
          }else{
67
            err = 1; //Read err
68
          }
69
          f_close(&fil);
70
        }else{
71
          err = 2; //Error, could not open "stm32sys.cfg"
72
        }
73
        f_closedir(&dir);
74
      }else{
75
        err = 3; //Error, no "system" folder found
76
      }
77
    }else{
78
      err = 4; //Error, no SD Card available
79
    }
80
  }
81
  return err;
82
}


Trotzdem habe ich noch eine Frage, auch wenn das hier nicht erlaubt ist:

Das funktioniert zwar so, aber ist das für 'C'-Verhältnisse auch korrekt 
gemacht, also der Zeiger korrekt definiert? Irgendwelche Fallstricke zu 
befürchten?
Bin mit Assembler großgeworden, weiß also, wie man in der Machine 
indirekt indiziert adressiert, aber die 'C'-Sprache nervt mit ihrer 
Zeigescheisse unglaublich. Das versteht doch kein normaler Mensch.
Ich hab das halt nur solange ausprobiert, bis mir der blöde GCC-Compiler 
keine Warnings wegen inkompatiblen Zeigern oder noch Schlimmerem um die 
Ohren gehauen hat.

: Verschoben durch User
von Markus M. (Firma: EleLa - www.elela.de) (mmvisual)


Lesenswert?

Die Zeiger hast du richtig definiert. Genau so geht das in C.

von avus (Gast)


Lesenswert?

Hallo Markus, danke für die Rückmeldung.

Hätte schwören können, daß da wieder was faul ist ;)
Aber mit Deiner Aussage motiviert es mich eher, nochmal genau darüber 
nachzudenken, wie C das handhabt.
Denn was man will, weiß man ja. Irgendwo hängt ein Stückchen Speicher 
rum, und man will doch nur erreichen, daß man anderen, freundlichen 
Zeitgenossen (Modulen) mitteilt, wo dieser Speicher liegt.
Mir fehlt nicht mehr viel, und ich bekomme das beim STM32F4 in den 
Griff, anders als beim AVR. Da macht man halt mit PROGMEM und anderem 
rum.

And now for something completely different:
Angesichts der Tatsache, daß ziemlich viele TFT-Panels heutzutage mit 
SPI-Flash und SD-Kartenslot ausgestattet sind - ok, zumindest für die 
Arduino-Gemeinde - finde ich die Verschiebung ins GCC-Forum aufgrund 
eines, nebenbei bemerkt nicht besonders sachdienlichen, statements eines 
anderen Users, dessen "Beitrag" zudem gelöscht wurde, ziemlich daneben.

Tip: Im mikrocontroller.net mal nach "struct" googeln. Und dann sich 
wundern, in welcher Rubrik die Beiträge meist stehen.

Was für ein Schwachsinn, auf Wunsch eines(!) im Zweifel zweifelhaften 
Users den Beitrag zu verschieben.

von Peter D. (peda)


Lesenswert?

avus schrieb:
> Wenn man einen Pointer definiert, der auf einen uint8_t-Typ zeigen soll,
> geht der Rest wie von selbst (*).
1
uint8_t *ptr = (uint8_t *)&panel;

Damit machst Du genau das, was alle anderen mit einer Union machen. Die 
Union spart einem dieses ständige Casten ein und erhöht damit die 
Sicherheit.
Ein Cast schaltet ja immer viele Fehlertests aus.
Ein Cast sagt dem Compiler: Ich weiß es besser als Du, also sei still.

von Peter D. (peda)


Lesenswert?

avus schrieb:
> aber die 'C'-Sprache nervt mit ihrer
> Zeigescheisse unglaublich. Das versteht doch kein normaler Mensch.

Doch, man braucht nur ne Weile.
Im Prinzip sind Zeiger nur Adressen. Das muß man verinnerlichen.

Im Gegensatz zu Assembler kann man aber dem Compiler ziemlich genau 
sagen, von was ein Zeiger die Adresse ist (Funktion, Wert, Array, 
Struct, Structmember usw.).
Man muß also nicht umständlich alle Offsets selber ausrechnen, sondern 
überläßt das bequem und fehlerfrei dem Compiler.

Insbesondere bei IO-Registern sind Structs sehr nützlich und vor allem 
fehlersicher. Der Compiler meckert einen sofort an, wenn man ein Bit in 
einem falschen Register setzen will oder es garnicht existiert.
Wenn man dagegen nur schreibt:
1
*Adresse = Wert;
kann der Compiler nicht überprüfen, ob das zulässig ist, d.h. diese 
IO-Ressource in dem gewünschten Target existiert.

Leider wurden die IO-Structs bei den standard AVR noch nicht definiert, 
sondern erst bei den XMEGA und den ARM.

: Bearbeitet durch User
von J. -. (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Damit machst Du genau das, was alle anderen mit einer Union machen. Die
> Union spart einem dieses ständige Casten ein und erhöht damit die
> Sicherheit.
Ich habe ein struct gewählt, um möglichst frei in der Handhabung zu sein 
(32 char am Anfang etc.). Das Ding ist nur 'zufällig' 64Byte breit.

In der Union könnte man natürlich auch auf die chars herunterdividieren, 
aber mein Problem war doch: Wie speicher ich das Ding einfach komplett 
weg, ohne mir noch über die einzelnen Bytes Gedanken zu machen.

Zu Deiner zweiten Antwort:
> Im Gegensatz zu Assembler kann man aber dem Compiler ziemlich genau sagen, von 
> was ein Zeiger die Adresse ist (Funktion, Wert, Array, Struct, Structmember 
usw.).
> Man muß also nicht umständlich alle Offsets selber ausrechnen, sondern
> überläßt das bequem und fehlerfrei dem Compiler.
Absolut ein Vorteil, den ich noch nicht wirklich zu schätzen 
weiß/schätzen kann. Aber genau diese Zielrichtung ist sinnvoll, danke 
Dir ;)

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.