Cosa risiede nei diversi tipi di memoria di un microcontrollore?


25

Esistono diversi segmenti di memoria in cui vengono inseriti vari tipi di dati dal codice C dopo la compilazione. Vale a dire: .text, .data, .bss, stack e heap. Voglio solo sapere dove ciascuno di questi segmenti risiederebbe in una memoria del microcontrollore. Cioè, quali dati vanno in quale tipo di memoria, dati i tipi di memoria sono RAM, NVRAM, ROM, EEPROM, FLASH ecc.

Ho trovato le risposte a domande simili qui, ma non sono riusciti a spiegare quali sarebbero i contenuti di ciascuno dei diversi tipi di memoria.

Qualsiasi tipo di aiuto è molto apprezzato. Grazie in anticipo!


1
NVRAM, ROM, EEPROM e Flash sono praticamente nomi diversi per la stessa cosa: memoria non volatile.
Lundin,

Leggermente tangenziale alla domanda, ma il codice può (eccezionalmente) esistere in gran parte di questi, in particolare se si considerano gli usi di patch o calibrazione. A volte verrà spostato prima dell'esecuzione, a volte eseguito sul posto.
Sean Houlihane,

@SeanHoulihane L'OP sta chiedendo dei microcontrollori, che quasi sempre si eseguono da Flash (hai qualificato il tuo commento con eccezionale). Micro processori con MB di RAM esterna con Linux per esempio, sarebbe copiare i loro programmi nella RAM per eseguire loro, forse fuori una scheda SD in qualità di un volume montabile.
Tcrosley,

@tcrosley Ora ci sono microcontrollori con TCM e talvolta i microcontrollori fanno parte di un SoC più grande. Ho anche il sospetto che ci siano casi come i dispositivi eMMC in cui l'MCU si avvia da solo dalla RAM fuori dal suo archivio (basato sulla memoria di alcuni telefoni a muro di un paio di anni fa). Sono d'accordo, non è una risposta diretta, ma penso sia molto rilevante che le mappature tipiche non siano in alcun modo regole rigide.
Sean Houlihane,

1
non ci sono regole per connettere una cosa all'altra, certo che le cose di sola lettura come testo e rodata vorrebbero idealmente andare in flash, ma anche i dati. codice bootstrap). questi termini (.text, ecc.) non hanno nulla a che fare con i microcontrollori, è una cosa compilatore / toolchain che si applica a tutti i target di compilatori / toolchain. alla fine della giornata il programmatore decide dove vanno le cose e informa la toolchain tramite uno script linker di solito.
old_timer

Risposte:


38

.testo

Il segmento .text contiene il codice effettivo ed è programmato nella memoria Flash per i microcontrollori. Potrebbe esserci più di un segmento di testo quando vi sono più blocchi non contigui di memoria Flash; ad es. un vettore iniziale e un vettore di interruzione situato nella parte superiore della memoria e un codice che inizia da 0; o sezioni separate per un bootstrap e un programma principale.

.bss e .data

Esistono tre tipi di dati che possono essere allocati all'esterno di una funzione o procedura; il primo è dati non inizializzati (storicamente chiamati .bss, che include anche i dati 0 inizializzati), e il secondo è inizializzato (non-bss), o .data. Il nome "bss" deriva storicamente da "Block Started by Symbol", utilizzato in un assemblatore circa 60 anni fa. Entrambe queste aree sono situate nella RAM.

Durante la compilazione di un programma, le variabili verranno assegnate a una di queste due aree generali. Durante la fase di collegamento, tutti gli elementi di dati verranno raccolti insieme. Tutte le variabili che devono essere inizializzate avranno una parte della memoria del programma riservata per contenere i valori iniziali e, poco prima che venga chiamato main (), le variabili verranno inizializzate, in genere da un modulo chiamato crt0. La sezione bss è inizializzata su tutti gli zeri dallo stesso codice di avvio.

Con alcuni microcontrollori, ci sono istruzioni più brevi che consentono l'accesso alla prima pagina (prime 256 posizioni, a volte chiamate pagina 0) di RAM. Il compilatore per questi processori può riservare una parola chiave come neardesignare variabili da inserire lì. Allo stesso modo, ci sono anche microcontrollori che possono fare riferimento solo a determinate aree tramite un registro puntatore (che richiede istruzioni aggiuntive) e che tali variabili sono designate far. Infine, alcuni processori possono indirizzare una sezione di memoria bit per bit e il compilatore avrà un modo per specificarlo (come la parola chiave bit).

Quindi potrebbero esserci segmenti aggiuntivi come .nearbss e .neardata, ecc., Dove vengono raccolte queste variabili.

.rodata

Il terzo tipo di dati esterno a una funzione o procedura è simile alle variabili inizializzate, tranne per il fatto che è di sola lettura e non può essere modificato dal programma. Nel linguaggio C, queste variabili sono indicate con la constparola chiave. Di solito sono memorizzati come parte della memoria flash del programma. A volte vengono identificati come parte di un segmento .rodata (dati di sola lettura). Sui microcontrollori che utilizzano l' architettura Harvard , il compilatore deve utilizzare istruzioni speciali per accedere a queste variabili.

pila e mucchio

Lo stack e l'heap sono entrambi collocati nella RAM. A seconda dell'architettura del processore, lo stack può aumentare o diminuire. Se cresce, verrà posizionato nella parte inferiore della RAM. Se cresce, verrà posizionato alla fine della RAM. L'heap utilizzerà la RAM rimanente non allocata alle variabili e aumenterà la direzione opposta dello stack. La dimensione massima dello stack e dell'heap può in genere essere specificata come parametri del linker.

Le variabili posizionate nello stack sono tutte le variabili definite all'interno di una funzione o procedura senza la parola chiave static. Una volta venivano chiamate variabili automatiche ( autoparola chiave), ma quella parola chiave non è necessaria. Storicamente, autoesiste perché faceva parte del linguaggio B che precedeva C, e lì era necessario. Anche i parametri delle funzioni vengono inseriti nello stack.

Ecco un tipico layout per la RAM (supponendo che non ci sia una sezione speciale della pagina 0):

inserisci qui la descrizione dell'immagine

EEPROM, ROM e NVRAM

Prima che arrivasse la memoria Flash, EEPROM (memoria di sola lettura programmabile e cancellabile elettricamente) veniva utilizzata per memorizzare i dati di programma e const (segmenti .text e .rodata). Ora è disponibile solo una piccola quantità (ad es. Da 2 KB a 8 KB di byte) di EEPROM, se presente, e viene generalmente utilizzata per archiviare i dati di configurazione o altre piccole quantità di dati che devono essere conservati al momento dell'accensione. ciclo. Questi non vengono dichiarati come variabili nel programma, ma vengono scritti utilizzando registri speciali nel microcontrollore. EEPROM può anche essere implementato in un chip separato e accessibile tramite un bus SPI o I²C.

La ROM è sostanzialmente uguale a Flash, tranne per il fatto che è programmata in fabbrica (non programmabile dall'utente). È utilizzato solo per dispositivi di volume molto elevato.

La NVRAM (RAM non volatile) è un'alternativa alla EEPROM e viene solitamente implementata come un CI esterno. La RAM normale può essere considerata non volatile se è sottoposta a backup a batteria; in tal caso non sono necessari metodi di accesso speciali.

Sebbene i dati possano essere salvati su Flash, la memoria Flash ha un numero limitato di cicli di cancellazione / programma (da 1000 a 10.000), quindi non è proprio progettata per questo. Richiede anche la cancellazione di blocchi di memoria in una sola volta, quindi è scomodo aggiornare solo pochi byte. È inteso per codice e variabili di sola lettura.

EEPROM ha limiti molto più alti sui cicli di cancellazione / programma (da 100.000 a 1.000.000), quindi è molto meglio per questo scopo. Se sul microcontrollore è disponibile EEPROM ed è abbastanza grande, è dove si desidera salvare i dati non volatili. Tuttavia, dovrai anche cancellare prima i blocchi (in genere 4KB) prima di scrivere.

Se non c'è EEPROM o è troppo piccola, è necessario un chip esterno. Una EEPROM da 32 KB è solo 66 ¢ e può essere cancellata / scritta per 1.000.000 di volte. Una NVRAM con lo stesso numero di operazioni di cancellazione / programma è molto più costosa (x10) Le NVRAM sono in genere più veloci per la lettura rispetto alle EEPROM, ma più lente per la scrittura. Possono essere scritti su un byte alla volta o in blocchi.

Un'alternativa migliore a entrambi è la FRAM (RAM ferroelettrica), che ha cicli di scrittura sostanzialmente infiniti (100 trilioni) e nessun ritardo di scrittura. Ha circa lo stesso prezzo di NVRAM, circa $ 5 per 32 KB.


Era un'informazione davvero utile. Potresti fornire un riferimento alla tua spiegazione? Come libri di testo o riviste, nel caso volessi leggere di più su questo ..?
Soju T Varghese,

un'altra domanda, potresti per favore dare un'idea dei vantaggi o degli svantaggi di una memoria rispetto all'altra (EEPROM, NVRAM e FLASH)?
Soju T Varghese,

Bella risposta. Ne ho pubblicato uno complementare focalizzandomi più in dettaglio su ciò che va specificamente nel linguaggio C.
Lundin,

1
@SojuTVarghese Ho aggiornato la mia risposta e incluso anche alcune informazioni su FRAM.
Tcrosley,

@Lundin abbiamo usato gli stessi nomi di segmento (es. .Rodata) in modo che le risposte si completino perfettamente.
Tcrosley,

21

Sistema incorporato normale:

Segment     Memory   Contents

.data       RAM      Explicitly initialized variables with static storage duration
.bss        RAM      Zero-initialized variables with static storage duration
.stack      RAM      Local variables and function call parameters
.heap       RAM      Dynamically allocated variables (usually not used in embedded systems)
.rodata     ROM      const variables with static storage duration. String literals.
.text       ROM      The program. Integer constants. Initializer lists.

Inoltre, di solito ci sono segmenti flash separati per il codice di avvio e i vettori di interruzione.


Spiegazione:

Una variabile ha una durata di archiviazione statica se viene dichiarata come statico se risiede nell'ambito del file (a volte sciatamente chiamato "globale"). C ha una regola che afferma che tutte le variabili di durata della memoria statica che il programmatore non ha inizializzato esplicitamente devono essere inizializzate a zero.

Ogni variabile di durata della memoria statica inizializzata su zero, implicitamente o esplicitamente, finisce in .bss. Mentre quelli che sono esplicitamente inizializzati su un valore diverso da zero finiscono in .data.

Esempi:

static int a;                // .bss
static int b = 0;            // .bss      
int c;                       // .bss
static int d = 1;            // .data
int e = 1;                   // .data

void func (void)
{
  static int x;              // .bss
  static int y = 0;          // .bss
  static int z = 1;          // .data
  static int* ptr = NULL;    // .bss
}

Tenere presente che un'installazione non standard molto comune per i sistemi incorporati prevede un "avvio minimo", il che significa che il programma salterà tutte le inizializzazioni di oggetti con durata di archiviazione statica. Pertanto, potrebbe essere saggio non scrivere mai programmi che si basano sui valori di inizializzazione di tali variabili, ma invece li imposta in "runtime" prima che vengano utilizzati per la prima volta.

Esempi di altri segmenti:

const int a = 0;           // .rodata
const int b;               // .rodata (nonsense code but C allows it, unlike C++)
static const int c = 0;    // .rodata
static const int d = 1;    // .rodata

void func (int param)      // .stack
{
  int e;                   // .stack
  int f=0;                 // .stack
  int g=1;                 // .stack
  const int h=param;       // .stack
  static const int i=1;    // .rodata, static storage duration

  char* ptr;               // ptr goes to .stack
  ptr = malloc(1);         // pointed-at memory goes to .heap
}

Le variabili che possono andare in pila possono spesso finire nei registri della CPU durante l'ottimizzazione. Come regola generale, qualsiasi variabile che non ha il suo indirizzo preso può essere inserita in un registro CPU.

Si noti che i puntatori sono un po 'più intricati rispetto ad altre variabili, poiché consentono due diversi tipi di const, a seconda che i dati puntati debbano essere di sola lettura o se il puntatore stesso debba esserlo. È molto importante conoscere la differenza in modo che i tuoi puntatori non finiscano nella RAM per caso, quando volevi che fossero in flash.

int* j=0;                  // .bss
const int* k=0;            // .bss, non-const pointer to const data
int* const l=0;            // .rodata, const pointer to non-const data
const int* const m=0;      // .rodata, const pointer to const data

void (*fptr1)(void);       // .bss
void (*const fptr2)(void); // .rodata
void (const* fptr3)(void); // invalid, doesn't make sense since functions can't be modified

Nel caso di costanti intere, elenchi di inizializzatori, valori letterali di stringa, ecc., Possono finire in .text o .rodata a seconda del compilatore. Probabilmente, finiscono come:

#define n 0                // .text
int o = 5;                 // 5 goes to .text (part of the instruction)
int p[] = {1,2,3};         // {1,2,3} goes to .text
char q[] = "hello";        // "hello" goes to .rodata

Non capisco, nel tuo primo codice di esempio, perché "static int b = 0;" va in .bss e perché 'static int d = 1;' va in .data ..? Secondo la mia comprensione, entrambe sono variabili statiche che sono state inizializzate dal programmatore .. allora che cosa fa la differenza? @Lundin
Soju T Varghese,

2
@SojuTVarghese Perché i dati .bss sono inizializzati su 0 come blocco; valori specifici come d = 1 devono essere memorizzati in flash.
Tcrosley,

@SojuTVarghese Aggiunti alcuni chiarimenti.
Lundin,

@Lundin Inoltre, dal tuo ultimo codice di esempio, significa che tutti i valori inizializzati vanno in .text o .rodata e le loro rispettive variabili da sole vanno in .bss o .data? In tal caso, come vengono mappate le variabili e i relativi valori corrispondenti (ovvero, tra i segmenti .bss / .data e .text / .rodata)?
Soju T Varghese,

@SojuTVarghese No, in .datagenere ha un cosiddetto indirizzo di caricamento in flash, in cui sono memorizzati i valori iniziali e un cosiddetto indirizzo virtuale (non realmente virtuale in un microcontrollore) nella RAM, in cui la variabile viene archiviata durante l'esecuzione. Prima maindell'inizio i valori iniziali vengono copiati dall'indirizzo di caricamento all'indirizzo virtuale. Non è necessario memorizzare gli zeri, quindi .bssnon è necessario memorizzare i suoi valori iniziali. sourceware.org/binutils/docs/ld/…
starblue,

1

Mentre tutti i dati possono andare in qualsiasi memoria scelta dal programmatore, generalmente il sistema funziona meglio (ed è destinato a essere utilizzato) in cui il profilo di utilizzo dei dati è abbinato ai profili di lettura / scrittura della memoria.

Ad esempio il codice del programma è WFRM (scrivine pochi, leggi molti), e ce n'è molto. Questo si adatta perfettamente a FLASH. ROM OTOH è W una volta RM.

Stack e heap sono piccoli, con molte letture e scritture. Quello si adatterebbe meglio alla RAM.

La EEPROM non si adatterebbe bene a nessuno di questi usi, ma si adatta al profilo di piccole quantità di dati perisistenti all'accensione, quindi dati di inizializzazione specifici dell'utente e forse risultati di registrazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.