Forum: Mikrocontroller und Digitale Elektronik STM32F4 GCC Linker section nur global?


von Reginald L. (Firma: HEGRO GmbH) (reggie)


Lesenswert?

Howdy!

Ich habe an meinem F4 einen SDRAM angeschlossen und möchte nun, der 
Übersichtlichkeit wegen, auf Arrays darin direkt über definierte 
Variablen zugreifen. Dazu sehe ich (Anfänger) momentan zwei 
Möglichkeiten:

1. Pointer auf eine Adresse im SDRam erstellen und (für mich) 
unangenehme Akrobatik betreiben.

2. Folgendermaßen:
Im Linkerscript:
1
SDRAM (RWX)       : ORIGIN = 0xD0000000, LENGTH = 8M
und
1
  .sdram (NOLOAD) :
2
  {
3
      . = ALIGN(4);
4
5
    *(.sdram)
6
    *(.sdram.*)
7
8
    . = ALIGN(4);
9
  } > SDRAM

Ein Define:
1
#define SDRAM (__attribute__((section(".sdram"))))

Und dann Variable initialisieren und damit spielen:
1
SDRAM char bufbuf[100] = { 0 };
2
for (volatile int i = 0; i < 100; i++)
3
{
4
    bufbuf[i] = i;
5
    if(bufbuf[i] != i)
6
        i = 42;
7
}

Das einzig doofe ist, dass "attribute section" anscheinend nur global 
benutzt werden darf, "because of the way linkers work".
Habt ihr einen Trick auf Lager?


Danke schonmal!
Grüße
Reggie

: Bearbeitet durch User
von Tassilo H. (tassilo_h)


Lesenswert?

Reginald L. schrieb:
>
> Das einzig doofe ist, dass "attribute section" anscheinend nur global
> benutzt werden darf, "because of the way linkers work".
> Habt ihr einen Trick auf Lager?
>

Naja, lokale Variablen sind per default automatisch und landen auf dem 
Stack. Den kannst du per Linkerscript und/oder startup-code hinlegen wo 
du willst, aber halt nur komplett, C kennt halt nur einen Stack.

Du kannst aber wohl innerhalb von Funktionen static-Variablen anlegen, 
fuer die geht dann auch ein "attribute section" (aber sie leben halt 
dauerhaft und belegen auch dauerhaft Speicher).
1
static SDRAM char localbuf[42];

von Manuel W. (multisync)


Lesenswert?

Warum legst du nicht einfach die ganze .data section über das linker 
script in deinen SDRAM?

*(.data*)

  .sdram (NOLOAD) :
  {
    . = ALIGN(4);
    *(.data*)
    . = ALIGN(4);
  } > SDRAM

von Reginald L. (Firma: HEGRO GmbH) (reggie)


Lesenswert?

Tassilo H. schrieb:
> Du kannst aber wohl innerhalb von Funktionen static-Variablen anlegen,
> fuer die geht dann auch ein "attribute section" (aber sie leben halt
> dauerhaft und belegen auch dauerhaft Speicher).
Das hilft mir eigentlich schon weiter, werde morgen gleich ausprobieren, 
danke!

Manuel W. schrieb:
> Warum legst du nicht einfach die ganze .data section über das linker
> script in deinen SDRAM?
Da dürfen in meinem Fall nur bestimmte Puffer rein. Andere Variablen und 
Puffer liegen im ccm und Backup.

von Nop (Gast)


Lesenswert?

Du kannst auch globale Variablen mit static anlegen. Die "leben" dann 
nur innerhalb der C-Datei, in denen sie deklariert werden. Also wenn 
function static nicht ausreicht.

Das mit den Pointern laß mal lieber, denn dann müßtest Du das gesamte 
SDRAM so behandeln, könntest also den C-Linker dort überhaupt nichts 
hinlinken lassen. Wahrscheinlich gerietest Du damit dann auch früher 
oder später in Probleme mit pointer aliasing.

Das mit den Sektions-Attributen per define mache ich auf Cortex M4 auch 
so, weil CCM und restliches SRAM nicht am Stück sind, ich also selber 
entscheiden muß, was wohin gehen soll.

Paß übrigens auf mit der Initialisierung. Wenn Du vorinitialisierte 
Arrays willst, dann gibt es zwei Optionen:

a) sie sollen mit 0 initialisiert sein. Das ist noch einfach, dazu mußt 
Du bloß Dein SDRAM ausnullen. Geht natürlich erst nach der 
System-Initialisierung, weil der FSMC dafür ja schon konfiguriert sein 
muß.

Das kannst Du auch gleich mit einem Speichertest verbinden, der 
besonders bei externem RAM anzuraten ist. Ist aber nicht ganz trivial, 
wenn Du das richtig haben willst.

b) sie sollen mit anderen Werten initialisiert sein. Das kannst Du am 
einfachsten in einer C-Routine machen.

Und bedenke, daß Zugriffe auf das SDRAM um einen Faktor von ungefähr 8 
langsamer sind als das interne SRAM oder CCM. Leg also keine Variablen 
ins SDRAM, die Du extrem oft brauchst.

von Reginald L. (Firma: HEGRO GmbH) (reggie)


Lesenswert?

Nop schrieb:
> Das mit den Pointern laß mal lieber, denn dann müßtest Du das gesamte
> SDRAM so behandeln, könntest also den C-Linker dort überhaupt nichts
> hinlinken lassen. Wahrscheinlich gerietest Du damit dann auch früher
> oder später in Probleme mit pointer aliasing.
Ja, so hatte ich das bisher. So macht das coden nicht wirklich Spaß.

Nop schrieb:
> Paß übrigens auf mit der Initialisierung.
Das habe ich gerade gemerkt. Mit der obigen "Initialisierung" wird wohl 
nur die Variable deklariert, initialisiert wird da laut Memory-Viewer 
nicht mit 0.

Nop schrieb:
> Das kannst Du auch gleich mit einem Speichertest verbinden, der
> besonders bei externem RAM anzuraten ist. Ist aber nicht ganz trivial,
> wenn Du das richtig haben willst.
Genau, das habe ich bisher auch so gemacht, erst den Speicher komplett 
mit i + 1 beschreiben und dann komplett mit i + 1 lesen. Wie man so 
einen RAM "normgerecht" prüft, bin ich gerade dabei mich einzulesen.

Nop schrieb:
> Und bedenke, daß Zugriffe auf das SDRAM um einen Faktor von ungefähr 8
> langsamer sind als das interne SRAM oder CCM. Leg also keine Variablen
> ins SDRAM, die Du extrem oft brauchst.
OK, also, dass der SDRAM langsamer als der SRAM ist, leuchtet mir ein. 
Erstens läuft er beim STM32 mit maximal HCLK / 2 und zweitens ist es bei 
mir auch noch ein 16bit Speicher.
Aber warum soll der CCM langsamer sein? In welchem Datasheet finde ich 
das entsprechende Kapitel? Habe mir gerade das Datasheet, Programming 
und Reference Manual durchgeschaut und nichts dazu gefunden. Ich konnte 
lediglich herauslesen, dass der SRAM mit 0 waitstates angesprochen wird.
Hast du auch einen Verweis zu den D- und I-Bussen in peto?


Danke!

EDIT:
Achja und sehe ich das richtig, dass es ansonsten keine Möglichkeit gibt 
Variablen benutzerfreundlicher auf den verschiedenen Memorys zu 
verteilen? Ich muss mit den oben erwähnten Einschränkungen leben?

: Bearbeitet durch User
von rmu (Gast)


Lesenswert?

Reginald L. schrieb:
> Achja und sehe ich das richtig, dass es ansonsten keine Möglichkeit gibt
> Variablen benutzerfreundlicher auf den verschiedenen Memorys zu
> verteilen? Ich muss mit den oben erwähnten Einschränkungen leben?

Für dynamisches Zeug gäbs das Konzept eines Zonen-Allokators. Da könnte 
man dann eine "schnelle" Zone dem internen SRAM zordnen, und eine 
"langsame" dem externen.

Ob das in Summe benutzerfreundlicher als das Präprozessor-Makro ist 
liegt im Auge des Benutzers :-).

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Du könntest deine malloc (_sbrk) Implementierung so bauen, dass sie 
Pointer auf den SDRAM zurückliefert. Dann kannst du malloc/new normal 
benutzen. Große lokale Variablen sind selbst auf dem PC gefährlich, 
daher lässt sich so doch recht "normal" programmieren.

von Reginald L. (Firma: HEGRO GmbH) (reggie)


Lesenswert?

Mhhh schade. Von dynamischen variable verabschiede ich mich gerade

von Tassilo H. (tassilo_h)


Lesenswert?

Reginald L. schrieb:
> Achja und sehe ich das richtig, dass es ansonsten keine Möglichkeit gibt
> Variablen benutzerfreundlicher auf den verschiedenen Memorys zu
> verteilen? Ich muss mit den oben erwähnten Einschränkungen leben?

Nein, du kannst auch Variablen in deiner SDRAM section initialisieren.
Im Linkerfile brauchst du zwei sections fuers SDRAM. Eine muss mit 
NOLOAD gekennzeichnet werden. Hierhin kommen alle Variablen, die 
nicht/mit 0 initialisiert werden sollen. Die andere ist dann fuer 
Variablen, die ungleich 0 initialisiert werden (d.h. wo der Linker einen 
Speicherbereich im ROM mit den Initialisierungsdaten anlegen soll).

Im Startup-code, sobald das SDRAM-Interface konfiguriert ist, musst Du 
dann entsprechend eine Routine hinzufuegen, die den NOLOAD-SDRAM-Bereich 
ausnullt, und eine zweite, die die Initialisierungsdaten in den zweiten 
SDRAM-Bereich kopiert.

Dann ist es fast so komfortabel wie mit Variablen im normalen RAM, nur 
mit dem Unterschied, daß der Linker die initialisierten und nicht 
initialiserten/genullten Variablen im SDRAM nicht automatisch auf die 
beiden Bereiche aufteilt, sondern daß man das selber mit passenden 
section attributes machen muß.

von Tassilo H. (tassilo_h)


Lesenswert?

Ach ja, hier der Auszug aus meinem Linkerfile, wo ich das fuers CCM 
gemacht habe. So aehnlich sollte das auch fuers SDRAM gehen (nur die 
Initialisierung muss vmtl. irgendwo in die Main, nachdem das SDRAM 
konfiguriert ist.
1
OUTPUT_FORMAT ("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
2
/* Internal Memory Map*/
3
MEMORY
4
{
5
/* Uncomment as needed depending on debug build or build with bootloader */
6
/*  rom (rx)  : ORIGIN = 0x08000000, LENGTH = 0x00078000*/
7
  rom (rx)  : ORIGIN = 0x08008000, LENGTH = 0x00078000
8
  ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00020000
9
  ccm (rwx) : ORIGIN = 0x10000000, LENGTH = 0x00010000
10
  bkpsram (rw) : ORIGIN = 0x40024000, LENGTH = 0x00001000
11
}
12
13
_eram = 0x20000000 + 0x00020000;
14
SECTIONS
15
{
16
  .text :
17
  {
18
  _usedromstart = .;
19
    KEEP(*(.isr_vector))
20
    *(.text*)
21
    *(.rodata*)
22
  } > rom 
23
  
24
  .ARM.exidx : 
25
  {
26
    __exidx_start = .;
27
    *(.ARM.exidx* .gnu.linkonce.armexidx.*)
28
    __exidx_end = .;
29
  } > rom
30
  
31
  __etext = .;
32
  /* _sidata is used in coide startup code */
33
  _sidata = __etext;
34
  .data : AT (__etext)
35
  {
36
    __data_start__ = .;
37
    /* _sdata is used in coide startup code */
38
    _sdata = __data_start__;
39
    
40
    *(vtable)
41
    *(.data*)
42
    . = ALIGN(4);
43
    /* All data end */
44
    __data_end__ = .;
45
    /* _edata is used in coide startup code */
46
    _edata = __data_end__;
47
  } > ram 
48
  /* _siccmdata is used in startup code to init vars in ccm */
49
  _siccmdata = __etext + SIZEOF (.data);
50
  .ccmdata : AT ( _siccmdata )
51
  {
52
    _sccmdata = .;
53
    *(.ccmdata*)
54
    . = ALIGN(4);
55
    /* All data end */
56
    _eccmdata = .;
57
  } > ccm
58
  _usedromend = _siccmdata + SIZEOF (.ccmdata);
59
  .bss :
60
  {
61
    . = ALIGN(4);
62
    __bss_start__ = .;
63
    _sbss = __bss_start__;
64
    *(.bss*)
65
    *(COMMON)
66
    . = ALIGN(4);
67
    __bss_end__ = .;
68
    _ebss = __bss_end__;
69
    __ram_end__ = ABSOLUTE(.);
70
  } > ram 
71
  .ccmbss (NOLOAD) :
72
  {
73
    . = ALIGN(4);
74
    _sccmbss = .;
75
    *(.ccmbss*)
76
    . = ALIGN(4);
77
    _eccmbss = .;
78
  } > ccm
79
  .bkpsram (NOLOAD) :
80
  {
81
    *(.bkpsram*)
82
  } > bkpsram
83
    
84
/*  .heap (COPY):
85
  {
86
    __end__ = .;
87
    _end = __end__;
88
    end = __end__;
89
    *(.heap*)
90
    __HeapLimit = .;
91
  } > ram 
92
  */
93
  /* .stack_dummy section doesn't contains any symbols. It is only
94
  * used for linker to calculate size of stack sections, and assign
95
  * values to stack symbols later */
96
  .co_stack (NOLOAD):
97
  {
98
    . = ALIGN(8);
99
    __StackBottom = ABSOLUTE(.);
100
    *(.co_stack .co_stack.*)
101
  } > ccm 
102
  
103
  /* Set stack top to end of ram , and stack limit move down by
104
  * size of stack_dummy section */
105
  __StackTop = ORIGIN(ccm ) + LENGTH(ccm );
106
  /*__StackLimit = __StackTop - SIZEOF(.co_stack);*/
107
  PROVIDE(__Stack = __StackTop);
108
  
109
  /* Check if data + heap + stack exceeds ram  limit */
110
  /*ASSERT(__StackLimit >= __HeapLimit, "region ram  overflowed with stack")*/
111
  ASSERT(__StackTop - __StackBottom >= __StackSize, "Not enough room for stack in CCM")
112
}

und der Startupcode dazu:
1
/*----------Symbols defined in linker script----------------------------------*/
2
extern unsigned long _sidata;    /*!< Start address for the initialization
3
                                      values of the .data section.            */
4
extern unsigned long _sdata;     /*!< Start address for the .data section     */
5
extern unsigned long _edata;     /*!< End address for the .data section       */
6
extern unsigned long _sbss;      /*!< Start address for the .bss section      */
7
extern unsigned long _ebss;      /*!< End address for the .bss section        */
8
extern void _eram;               /*!< End address for ram                     */
9
10
extern unsigned long _siccmdata;    /*!< Start address for the initialization
11
                                      values of the .data section.            */
12
extern unsigned long _sccmdata;     /*!< Start address for the .data section     */
13
extern unsigned long _eccmdata;     /*!< End address for the .data section       */
14
extern unsigned long _sccmbss;      /*!< Start address for the .bss section      */
15
extern unsigned long _eccmbss;      /*!< End address for the .bss section        */
16
17
18
19
/*----------Function prototypes-----------------------------------------------*/
20
extern int main(void);           /*!< The entry point for the application.    */
21
//extern void SystemInit(void);    /*!< Setup the microcontroller system(CMSIS) */
22
void Default_Reset_Handler(void);   /*!< Default reset handler                */
23
static void Default_Handler(void);  /*!< Default exception handler            */
24
25
extern unsigned int __Stack;
26
27
// vector table removed
28
//...
29
//
30
31
32
/**
33
  * @brief  This is the code that gets called when the processor first
34
  *         starts execution following a reset event. Only the absolutely
35
  *         necessary set is performed, after which the application
36
  *         supplied main() routine is called.
37
  * @param  None
38
  * @retval None
39
  */
40
void Default_Reset_Handler(void)
41
{
42
  /* Initialize data and bss */
43
  unsigned long *pulSrc, *pulDest;
44
45
  /* Copy the data segment initializers from flash to SRAM */
46
  pulSrc = &_sidata;
47
48
  for(pulDest = &_sdata; pulDest < &_edata; )
49
  {
50
    *(pulDest++) = *(pulSrc++);
51
  }
52
53
  /* Copy the .ccmdata segment initializers from flash to SRAM */
54
  pulSrc = &_siccmdata;
55
56
  for(pulDest = &_sccmdata; pulDest < &_eccmdata; )
57
  {
58
    *(pulDest++) = *(pulSrc++);
59
  }
60
  
61
  /* Zero fill the bss segment.  This is done with inline assembly since this
62
     will clear the value of pulDest if it is not kept in a register. */
63
  __asm("  ldr     r0, =_sbss\n"
64
        "  ldr     r1, =_ebss\n"
65
        "  mov     r2, #0\n"
66
        "  .thumb_func\n"
67
        "zero_loop:\n"
68
        "    cmp     r0, r1\n"
69
        "    it      lt\n"
70
        "    strlt   r2, [r0], #4\n"
71
        "    blt     zero_loop"::: "r0","r1","r2","cc","memory");
72
  /* Zero fill the .ccmbss segment.  */
73
  __asm("  ldr     r0, =_sccmbss\n"
74
        "  ldr     r1, =_eccmbss\n"
75
        "  mov     r2, #0\n"
76
        "  .thumb_func\n"
77
        "zero_loop2:\n"
78
        "    cmp     r0, r1\n"
79
        "    it      lt\n"
80
        "    strlt   r2, [r0], #4\n"
81
        "    blt     zero_loop2"::: "r0","r1","r2","cc","memory");
82
#ifdef __FPU_USED
83
  /* Enable FPU.*/ 
84
  __asm("  LDR.W R0, =0xE000ED88\n"
85
        "  LDR R1, [R0]\n"
86
        "  ORR R1, R1, #(0xF << 20)\n"
87
        "  STR R1, [R0]");
88
#endif  
89
90
  /* Call the application's entry point.*/
91
  main();
92
}

und schliesslich noch die Makros dazu
1
// defines to move global variables to core coupled memory
2
// use _CCM for uninitialized variables
3
#define _CCM  __attribute__((section (".ccmbss")))
4
// use _CCMDATA for initialized variables
5
#define _CCMDATA  __attribute__((section (".ccmdata")))
6
// use this for uninitialized variables in backup sram
7
#define _BKPSRAM  __attribute__((section (".bkpsram")))

Und als Beispiel:
1
static _CCM char c;
2
static _CCMDATA int[3] = {1,2,3};

von Reginald L. (Firma: HEGRO GmbH) (reggie)


Lesenswert?

Was ich nicht so recht blicke, warum der linker hier nur statics 
akzeptiert. Im Prinzip sind das doch alles irgendwelche Speicheradressen 
die der Linker sieht. Wenn ich bestimmte Variablen in bestimmte 
Speicherbereiche schieben möchte, ist mir klar, dass ich ihm das 
gesondert sagen muss, da er sonst den voreingestellten Speicherbereich 
nimmt und die Variablen dort anordnert.
Warum kann ich dem Linker nicht mitteilen: Nimm Variable x und y und 
platziere sie so wie du sie auch sonst platzieren würdest, mit der 
Ausnahme, dass du diesmal nicht zwischen 0x00 und 0xAA sondern zwischen 
0xAB und 0xFF platzierst.

Ich schaffe erst seit kurzem mit dem linker script, bin also noch grün 
hinter den Ohren :>

: Bearbeitet durch User
von Tassilo H. (tassilo_h)


Lesenswert?

Also, du kannst
* globale Variablen (also ausserhalb einer Funktion, die Angabe von 
static beeinflusst hier nur die Sichbarkeit in anderen Quellcodedateien) 
sowie
* static-Variablen innerhalb von Funktionen (die static-Angabe aendert 
hier nicht die Sichtbarkeit, sondern erzeugt eine Variable, die 
daherhaft existiert)
mit einem section-Attribut versehen, aber NICHT automatische Variablen 
innerhalb von Funktionen.

Das liegt daran, dass Speicher für die automatischen Variablen beim 
Funktionsaufruf auf dem Stack reserviert und beim Ruecksprung wieder 
freigegeben werden. Die Adresse wird also erst beim Funktionsaufruf 
festegelegt. Es kann die gleiche Variable auch mehrfach auf dem Stack 
geben, wenn sich die Funktion rekursiv selber aufruft - jeder Aufruf 
erzeugt einen neuen Satz der automatischen Variablen der Funktion (NICHt 
aber der static-Variablen, das sind dieselben fuer alle Aufrufe). Die 
Variablen belegen nur solange Speicher, solange die Funktion laueft.

Nun gibt es aber in C nur einen Stack (und die meisten Prozessoren haben 
auch nur einen Stackpointer). Wo der liegt, kannst Du aendern, 
allerdings waere das SDRAM ein schlechter Platz, da es langsamer ist als 
die anderen RAM-Bereiche. Insofern liegt bei automatischen Variablen 
ganz von selber fest, in welchem RAM-Bereich sie landen, naemlich dort 
wo der STack ist.

Leider ist es auch so, dass mit einem section-Attribut die Automatik, 
die entscheidet, ob eine Variable initialisiert ist oder nicht und 
danach die section aussucht (bss oder data) ausser Gefecht gesetzt wird, 
so dass man dafuer von Hand die richtige section setzen muss.

Wenn Du die Variablen im SDRAM, aber nicht global oder static haben 
willst, koenntest Du den Heap ins SDRAM legen und die Variablen dann 
dynamisch reservieren. Allerdings ist ein Heap oft auf einem 
embedded-System ueberfluessig; die größten Speicherbrocken, die man 
braucht, sind irgendwelche Buffer, die entweder sowieso dauerhaft 
existieren muessen oder bei denen man garantieren muss, dass genug 
Speicher dafuer vorhanden ist. Das laeuft darauf hinaus, dass man den 
SPeicher auch gleich dauerhaft in einer globalen oder static-Variablen 
reservieren kann.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Hier auch nochmal etwas Senf dazu wie man die Sections außerhalb des ROM 
initialisiert:
http://www.fritzler-avr.de/spaceage2/soft_start.php

von Reginald L. (Firma: HEGRO GmbH) (reggie)


Lesenswert?

Vielen Dank für die Ausführung und deine Codeschnipsel! Top 
Hilfestellung und Erläuterungen!

Tassilo H. schrieb:
> Die Adresse wird also erst beim Funktionsaufruf
> festegelegt.
Tassilo H. schrieb:
> Nun gibt es aber in C nur einen Stack (und die meisten Prozessoren haben
> auch nur einen Stackpointer).
Wenn ich das jetzt alles richtig verstanden habe 
(https://de.wikibooks.org/wiki/C%2B%2B-Programmierung/_Speicherverwaltung/_Stack_und_Heap), 
ist in unserem Fall der Stack im SRAM, wo wir ihn ja auch hin haben 
wollen, zwecks Performance. Der normale Programmablauf soll ja möglichst 
schnell sein.
Lokale ("automatische"?) Variablen benutzen den Stack. Natürlich kann 
ich lokale Variablen dann nur in der section initialisieren, wo der 
Stack liegt.

Hab ich das einigermaßen kapiert?

Laut diversen Forenbeiträgen, werden lokale Variablen immer auf dem 
Stack erstellt, weil das am "schnellsten" bzw. am "einfachsten" geht. 
Irgendwas mit push und pop :) Hast du da auch noch einen Wink, warum das 
so ist?

EDIT: Ach, und ich werde deinem Beispiel folgen, du hast absolut recht, 
im SDRAM (sollen!) ausschließlich ein paar größere Buffer liegen die 
sowieso dauerhaft existieren müssen.
Und mir ist aufgefallen, dass ich so ziemlich das wiederholt habe, was 
du geschrieben hast :)

: Bearbeitet durch User
von Gerd E. (robberknight)


Lesenswert?

Reginald L. schrieb:
> Laut diversen Forenbeiträgen, werden lokale Variablen immer auf dem
> Stack erstellt, weil das am "schnellsten" bzw. am "einfachsten" geht.
> Irgendwas mit push und pop :) Hast du da auch noch einen Wink, warum das
> so ist?

Denk an Rekursion. Wie willst Du die Variablen platzieren, daß sie trotz 
Rekursion weiterhin funktionieren? Das wäre ohne sie auf den Stack zu 
packen ziemlich aufwendig.

von Reginald L. (Firma: HEGRO GmbH) (reggie)


Lesenswert?

Gerd E. schrieb:
> Denk an Rekursion. Wie willst Du die Variablen platzieren, daß sie trotz
> Rekursion weiterhin funktionieren? Das wäre ohne sie auf den Stack zu
> packen ziemlich aufwendig.
Da setzt mein Gehirn grad aus. Das war zuviel :)

von Tassilo H. (tassilo_h)


Lesenswert?

Reginald L. schrieb:
> Laut diversen Forenbeiträgen, werden lokale Variablen immer auf dem
> Stack erstellt, weil das am "schnellsten" bzw. am "einfachsten" geht.
> Irgendwas mit push und pop :) Hast du da auch noch einen Wink, warum das
> so ist?

Naja, ein Stack (Stapelspeicher/Kellerspeicher in der dt. Informatik) 
ist gerade die passende Speicherstruktur. Mit jedem Funktionsaufruf legt 
man die neuen automatisch Variablen und die Ruecksprungadresse auf den 
Stapel. Der Ruecksprung aus den Funktionen erfogt zwangslaeufig immer in 
der ungekehrten Reihenfolge wie der Aufruf, also kann man immer die 
oberen Elemente wieder vom Stapel raeumen.
So bleibt die Speichernutzung gleichzeitig auch schoen ohne Loecher wie 
sie bei einem Heap entstehen koennten (bzw. bei exakt dieser Reihenfolge 
der Reservierung und Freigabe auch nicht entstehen, aber auf dem Heap 
braucht man extra Speicher um sich die freien Bloecke zu merken und ggf. 
zusammenzufassen, ist also mehr Aufwand).

Und fuer die Speicherung von Ruecksprungadressen hat sowieso (fast) 
jeder Prozessor Hardware-Support fuer einen Stack, also bietet sich der 
auch fuer die automatischen Variablen an. Je nach Prozessor gibt es auch 
Befehle, um direkt bequem auf den Speicher relativ zum Stackpointer 
zugreifen zu koennen.

von Reginald L. (Firma: HEGRO GmbH) (reggie)


Lesenswert?

Mw E. schrieb:
> Hier auch nochmal etwas Senf dazu wie man die Sections außerhalb des ROM
> initialisiert:
> http://www.fritzler-avr.de/spaceage2/soft_start.php
Top!

Ich bin bedient, vielen lieben Dank @all!

von Reginald L. (Firma: HEGRO GmbH) (reggie)


Lesenswert?

Soo, ich melde mich nochmal mit dem Ergebnis zurück, vor allem, da ich 
so eine Anleitung für den STM32 im Internet leider nicht finden konnte, 
vielleicht hilft es auch anderen Greenhorns wie mir :)

Nochmal kurz zusammengefasst:
Problemstellung: Anstatt mit Pointern auf den SDRAM zuzugreifen, möchte 
man möglichst einfach mit Arrays arbeiten, so wie man es sonst auch 
gewohnt ist:
1
// Macht keinen Spaß:
2
for (int i = 0; i < len; i++)
3
    (*(uint32_t*)(SDRAM_BUFFER + (i * 4))) = (uint32_t)(mybuffbuff[i]);
4
// Macht total saumäßigen Spaß:
5
for (int i = 0; i < len; i++)
6
    sdram_spass_buffer[i] = mybuffbuff[i]

Dazu erst das LinkerScript anpassen:
1
Memory:
2
SDRAM (RWX) : ORIGIN = 0xD0000000, LENGTH = 8M
1
Sections:
2
. = ALIGN(4);
3
_sisdraminit = .;
4
.sdraminit : AT( _sisdraminit )
5
{
6
    . = ALIGN(4);
7
    _ssdraminit = .;
8
    *(.sdraminit*)
9
    . = ALIGN(4);
10
    _esdraminit = .;
11
} > SDRAM
12
13
.sdram (NOLOAD) :
14
{
15
    . = ALIGN(4);
16
    *(.sdram)
17
    *(.sdram.*)
18
    . = ALIGN(4);
19
} > SDRAM

Für den nächsten Schritt muss erst der FMC für den SDRAM initialisiert 
werden. Das kann man am geschicktesten schon im Reset_Handler() in der 
StartUp-File machen, direkt nach dem SystemInit(). Im Prinzip muss dann 
nur noch kopiert werden, das sieht dann etwa so aus:
1
extern void *_sidata, *_sdata, *_edata;
2
extern void *_sbss, *_ebss;
3
extern void *_sisdraminit, *_ssdraminit, *_esdraminit;    // Das hier muss rein!
4
5
6
void __attribute__((naked, noreturn)) Reset_Handler()
7
{
8
    void **pSource, **pDest;
9
    for (pSource = &_sidata, pDest = &_sdata; pDest != &_edata; pSource++, pDest++)
10
        *pDest = *pSource;
11
12
    for (pDest = &_sbss; pDest != &_ebss; pDest++)
13
  *pDest = 0;
14
15
    SystemInit();
16
    
17
    SdramInit();    // Hier wird der FMC gestartet, SDRAM ist danach einsatzbereit
18
19
    pSource = &_sisdraminit;                             // Das hier muss auch rein,
20
    for (pDest = &_ssdraminit; pDest < &_esdraminit;)    // hier werden die initialisierten Variablen / Arrays
21
        *(pDest++) = *(pSource++);                       // ins SDRAM kopiert
22
23
    (void)main();
24
    for (;;);
25
}

Das wars jetzt eigentlich schon, man legt sich noch zwei defines an um 
Variablen zu definieren und initialisieren. Das kann so aussehen:
1
#define    DEFINE_IN_SDRAM            __attribute__((section(".sdram")))
2
#define    INITIALIZE_IN_SDRAM        __attribute__((section(".sdraminit")))

Nun kann der Spaß losgehen:
1
static DEFINE_IN_SDRAM char spass_buffer_definiert[20];
2
static INITIALIZE_IN_SDRAM char spass_buffer_initialisiert[100] = { "Meine Fresse, ist das ein Spaß!!!" };
3
4
void spass_funktion()
5
{
6
    spass_buffer_definiert[5] = 5;
7
}

Die Geschichte mit static und lokolen Variablen ist weiter oben erklärt. 
Hoffe es haben sich keine Fehler eingeschlichen.


PS: Jetzt habe ich auch ansatzweise eine Ahnung was da im Linker und 
StartupCode drin steht, wollte ich schon immer wissen, aber es hat sich 
nie ergeben. Nochmals vielen lieben Dank an alle! Bussi :> Meine 
Problemstellung wurde innerhalb kürzester Zeit gelöst.

: Bearbeitet durch User
von Nop (Gast)


Lesenswert?

Reginald L. schrieb:

> Das habe ich gerade gemerkt. Mit der obigen "Initialisierung" wird wohl
> nur die Variable deklariert, initialisiert wird da laut Memory-Viewer
> nicht mit 0.

Richtig, weil uninitialisierte Variablen in C ganz allgemein keinen 
definierten Inhalt haben. Noch schlimmer, der Compiler erkennt den 
lesenden Zugriff auf uninitialisierte Variablen als undefiniertes 
Verhalten (nach C-Standard) und kann dann Dein ganzes Programm 
wegoptimieren. Weil das Verhalten ja schließlich "irgendwas" sein darf.

Embedded wird im Startupcode der Speicher für die globalen 
uninitialisierten Variablen normalerweise genullt, aber das muß man halt 
auch selber machen, sonst tut es niemand.

> Genau, das habe ich bisher auch so gemacht, erst den Speicher komplett
> mit i + 1 beschreiben und dann komplett mit i + 1 lesen. Wie man so
> einen RAM "normgerecht" prüft, bin ich gerade dabei mich einzulesen.

Das ist ne Wissenschaft für sich. Aber der Ansatz, erstmal komplett zu 
beschreiben und dann komplett zu lesen, ist schon sehr gut. Also nicht 
dasselbe in jede Zelle.

Beim Lesen würde ich übrigens jede Zelle zweimal nacheinander lesen und 
das erste Ergebnis verwerfen. Wenn Du das in C machst, geht sowas mit 
einem Pointer auf volatile uint32_t. Sinn der Sache: "dirty RAM faults" 
erkennt man damit auch.

> Aber warum soll der CCM langsamer sein?

Mißverständnis, ich meinte: SDRAM ist langsamer als SRAM oder CCM, d.h. 
SDRAM ist langsamer als SRAM, und SDRAM ist auch langsamer als CCM. Weil 
SRAM und CCM gleich schnell sind. Die wesentlichen Unterschiede zwischen 
SRAM und CCM ist, daß CCM nicht mit DMA geht, und daß man keinen Code 
aus dem CCM ausführen kann. Letzteres, weil CCM nur über den D-Bus 
angebunden ist. Siehe Reference Manual DM00031020, chapter 2.1.

> Achja und sehe ich das richtig, dass es ansonsten keine Möglichkeit gibt
> Variablen benutzerfreundlicher auf den verschiedenen Memorys zu
> verteilen? Ich muss mit den oben erwähnten Einschränkungen leben?

Also ich finde das mit dem Linkerscript recht komfortabel. (; Klar, daß 
Du für den Stack nur einen Speicherbereich definieren kannst, liegt halt 
an C selber.

> Mhhh schade. Von dynamischen variable verabschiede ich mich gerade

Das ist ohnehin besser für embedded. Speicherfragmentierung und nicht 
deterministische Allokation führen auf Dauer zu Systemen, die erstens 
nicht mehr testbar sind und zweitens auch nicht robust funktionieren. 
Meine Erfahrung aus einem Projekt, wo sowas nach zwei Wochen Betrieb 
dann einfach nicht mehr ging.
1
void **pSource, **pDest;

Das sollte meiner Meinung nach "uint32_t *" als Datentyp sein. Mit 
void-Pointern kann man nicht rechnen. In C ist die Pointer-Arithmetik 
vom Datentyp abhängig.

Beispiel:
1
uint8_t *byte_ptr = 0;
2
uint32_t *word_ptr = 0;
3
4
byte_ptr++; /*zeigt jetzt auf Adresse 1*/
5
word_ptr++; /*zeigt jetzt auf adresse 4*/

Wenn man im C-Code also den Pointer um X erhöht, macht der Compiler 
daraus automatisch eine Addition um X*M, wobei M die Bytegröße des 
zugrundeliegenden Datentyps ist. Geht auch für große Daten wie structs. 
Void hat aber keinen Datentyp. Void-Pointer werden zum Übergeben an 
Routinen benutzt, aber zum Dereferenzieren müssen sie gecastet werden.

Der Grund, wieso der obige C-Code mit "void **" trotzdem funktioniert, 
ist der, daß man dann ja einen Pointer auf einen void-Pointer 
deklariert, und dessen Breite ist die verwendete Pointergröße. Auf STM32 
wird das ein 32bit-Pointer sein. Damit landet man dann auch wieder bei 
4-Byte-Schritten. Aber das ist keine gute Konstruktion, weil eigentlich 
uint32_t gewünscht wird.

von Nop (Gast)


Lesenswert?

Ach ja, wichtig noch zum Stackverständnis: C initialisiert lokale 
Vriablen (also die auf dem Stack) nie, aus Geschwindigkeitsgründen. 
Lesender Zugriff daraus ist wieder undefiniertes Verhalten, die ganze 
Routine kann wegoptimiert werden.

Da jede Routine auf dem Stack etwas macht und nach ihrem Ende den 
Bereich wieder freigibt, hängt der Inhalt, den die nächste Routine bei 
ihrem Start auf dem Stack vorfindet, davon ab, welche anderen Routinen 
vorher was mit dem Stack gemacht haben. Es ist also nicht vorhersagbar.

Auch Routinen, die keine lokalen Variablen anlegen, können den Stack 
verändern. Und zwar dann, wenn die Register nicht ausreichen für das, 
was die Routine tut, und der Compiler z.B. Zwischenergebnisse mal auf 
den Stack schiebt.

Das gilt nicht für lokale static-Variablen. Diese sind nicht auf dem 
Stack, sondern es sind von der Handhabung her globale Variablen, die 
aber nur in ihrer eigenen Routine angesprochen werden können. Stichwort 
Datenkapselung.

Aber ansonsten sieht das schon ganz gut aus, was Du da machst. (:

Mich würde mal interessieren, was ein Benchmark Deines SDRAM ergibt. 
Also einfach mal sowas:
1
volatile uint32_t test_array[10240];
2
...
3
for (loops=0; loops < N; loops++)
4
    for (i=0; i < 10240; i++)
5
        test_array[i] = i;

Das einmal für ein array im SRAM und SDRAM. N so anpassen, daß man gut 
meßbare Zeiten rauskriegt.

von Reginald L. (Firma: HEGRO GmbH) (reggie)


Lesenswert?

Nop schrieb:
> Das ist ne Wissenschaft für sich. Aber der Ansatz, erstmal komplett zu
> beschreiben und dann komplett zu lesen, ist schon sehr gut. Also nicht
> dasselbe in jede Zelle.
Ich habe mich da jetzt ein wenig eingelesen, und zumindest, wenn man 
einen frischen Ram zum ersten mal physisch angeschlossen hat, könnte man 
so vorgehen, wie auf folgender Seite beschrieben:
http://www.esacademy.com/en/library/technical-articles-and-documents/miscellaneous/software-based-memory-testing.html

Nop schrieb:
> Mißverständnis, ich meinte: SDRAM ist langsamer als SRAM oder CCM, d.h.
> SDRAM ist langsamer als SRAM, und SDRAM ist auch langsamer als CCM.
Ja, ich Trottel habe deinen Text nicht richtig gelesen.

Nop schrieb:
> Also ich finde das mit dem Linkerscript recht komfortabel. (; Klar, daß
> Du für den Stack nur einen Speicherbereich definieren kannst, liegt halt
> an C selber.
Ja, ich bin jetzt auch recht zufrieden damit. Wobei es halt schon Top 
wäre, wenn man in seiner IDE ein neues Projekt eröffnet, seinen SDRAM in 
die defines einträgt und dann mit
1
SDRAM lustiger_buffer[10] = { 0 };
2
SDRAM zweiter_lustiger_buffer[10];
3
4
void lustige_funktion()
5
{
6
    SDRAM spielverderber_variable = 42;
7
    lustiger_buffer[5] = 42;
8
    zweiter_lustiger_buffer[2] = 42;
9
}
ganz normal, wie mit allen anderen Variablen, arbeiten könnte, ohne sich 
um den Hintergrund zu kümmern. Ich verstehe natürlich, dass das so nicht 
funktioniert. Aber man darf doch Visionen haben :) "I have a dream"

Nop schrieb:
> Das sollte meiner Meinung nach "uint32_t *" als Datentyp sein. Mit
> void-Pointern kann man nicht rechnen. In C ist die Pointer-Arithmetik
> vom Datentyp abhängig.
Da habe ich mich an den in meinem Startupfile schon vorhandenen Pointern 
gehalten. "Never touch a running system"

Nop schrieb:
> Damit landet man dann auch wieder bei
> 4-Byte-Schritten. Aber das ist keine gute Konstruktion, weil eigentlich
> uint32_t gewünscht wird.
Ich benutze VisualGDB und das hat schon so einige Bugs. Habe auch öfter 
Probleme in Verbindung mit Intellisense. Wenn VisualStudio nicht die 
m.M.n. bequemste und ergonomischste Programmierumgebung wäre, hätte ich 
wohl auch schon von VisualGDB losgelassen. Und das für 80 €.

Nop schrieb:
> ich würde mal interessieren, was ein Benchmark Deines SDRAM ergibt.
Das habe ich schon mal ganz zu Anfang gemacht. Da hatte ich mehrere 
Tests angelegt gehabt. Leider hatte ich die dumme Angewohnheit, 
unbenötigte Codeschnipsel wieder zu löschen.
Im Prinzip habe ich Tests gemacht zu:
- for-Schleife mit 8bit, 16bit und 32bit schreiben und lesen
- DMA2D mit 8bit, 16bit und 32bit schreiben und lesen
Schau mal hier rein, da habe ich mal etwas dazu geschrieben:
Beitrag "STM32F4 SDRAM Geschwindigkeit"
Mit DMA2D waren da 170MB/s drin, gott, war ich begeistert was da alles 
geht. Diese krassen Datenmengen.
Wenn du dennoch einen Test möchtest, mach ich ihn dir :)

: Bearbeitet durch User
von Reginald L. (Firma: HEGRO GmbH) (reggie)


Lesenswert?

Jetzt stehe ich doch noch vor einem Problem:

Ich habe, unter anderem, Fonts auf einem SPI-Flash gespeichert. Diese 
möchte ich beim Programmstart in den SDRAM laden und über FontInfo font 
darauf zugreifen.

Die Font-Klasse sieht so aus:
1
  class CharacterInfo
2
  {
3
  public:
4
    uint8_t widthpixel;    // width, in bits (or pixels), of the character
5
    uint8_t unused;
6
    uint8_t datawidth;    // width, in bytes of data bitmap
7
    const uint8_t* data;  // offset of the character's bitmap, in bytes, into the FONT's data array
8
  };
9
10
  class FontInfo
11
  {
12
    // Describes a single font
13
  public:
14
    const char* fontName;    // Font name string
15
    char charHeight;      // height in pixels
16
    char startChar;        // the first character in the font (e.g. in charInfo and data)
17
    char endChar;        // the last character in the font
18
    char spacewidth;      // width, in pixels, of space character
19
    CharacterInfo* charInfo;  // pointer to array of char information
20
  };
Je nach Font, zeigt CharacterInfo auf unterschiedlich viele Arrays. Der 
Pointer data in CharacterInfo zeigt, je nach Character, auf einen 
verschieden großen Array.

Gehe ich richtig der Annahme, dass ich hier nur diese Möglichkeiten 
habe:
1. Die Daten wieder per Pointer in den SDRAM laden. Also:
Reginald L. schrieb:
> 1. Pointer auf eine Adresse im SDRam erstellen und (für mich)
> unangenehme Akrobatik betreiben.
-> Damit wäre die ganze Geschichte mit INIT_TO_SDRAM für die Katz.
2. Die unzähligen Arrays abzählen, bzw. abzählen lassen und dann die 
Arrays vom Compiler erstellen lassen. Kommt eigentlich nicht in Frage, 
da immer wieder neue "Objekte" (Fonts, Bitmaps, etc pp) hinzukommen bzw. 
ausgetauscht werden, das ganze soll also ziemlich dynamisch sein.
3. Im Linker die Größe des SDRAMs verkleinern, sodass ich einen Teil mit 
Pointern verwalte und der Rest weiterhin vom Linker verwaltet wird.

von Reginald L. (Firma: HEGRO GmbH) (reggie)


Angehängte Dateien:

Lesenswert?

Ich glaube ich habe noch eine Möglichkeit gefunden, die mich anmacht:
1. 2 verschieden Font-Klassen. Die eine const, da aus dem STM-Flash in 
den SPI-Flash programmiert wird. Die andere non-const, damit Pointer 
erstellt werden können. Über ein #define wird festgelegt ob gerade 
SPI-Flash programmiert werden soll oder nicht, demnach wechseln auch die 
Klassen. Im Prinzip hatte ich das so schon bisher. Oder gibt es eine 
Möglichkeit dem Compiler zu sagen, er soll alle uint8_t's aus einem 
bestimmten Objekt in den STM-Flash schmeißen?
2. Es wird ein SDRAM Buffer initialisiert, der genauso groß ist wie der 
SPI-Flash.
3. Ein wenig Akrobatik mit dem SDRAM Buffer. So wird eine eigene Klasse 
Pointer beinhalten, die auf die richtigen Stellen im SDRAM zeigen. Von 
dort aus werden später auch die jeweiligen Objekte ausgewählt.

Ich glaube mein Geschreibsel ist ziemlich verwirrend. Ich lade mal ein 
Font hoch, dann kann man sich das auch vorstellen. Und vllt hat ja 
jemand noch nen Tipp oder ne Idee. Oder ne ganz andere Vorgehensweise :>

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.