Arduino · 26 marzo 2013 1

La memoria SRAM dei microcontrollori

Come detto in un precedente articolo, l’architettura di un microcontrollore è di tipo Harvard, dove la SRAM (Static Random Access Memory) che contiene i dati del programma è separata dalla memoria Flash che contiene il programma stesso. Ma non tutti sanno che l’interno di questa RAM (ometto la “S”) è condiviso da più aree dati, alcune delle quali poco note. Vediamole insieme…

Quello che vedete qui sotto è un grafico che riproduce la RAM di un ATmega328P mentre è in esecuzione il programma registrato nella sua memoria Flash:

SRAM memory usage

La prima considerazione da fare riguarda l’indirizzo di partenza. Noterete che è $100, esadecimale per 256. Questo perché le prime 255 locazioni sono utilizzate dal microcontrollore stesso per mappare in memoria i suoi registri interni (come quelli dei timer, quelli relativi alle porte di I/O ecc..). A partire dall’indirizzo $100 c’è la memoria disponibile per il programma dell’utente: nel caso dell’ATmega328P, che ha 2048 byte di SRAM disponibile, la memoria inizia a $100, ossia 256, e termina a $8FF, ossia 2303 (2303-256=2047).

La RAM è organizzata in diverse aree, che hanno un nome proprio assegnato dal compilatore avr-gcc:

  • all’inizio è posta l’area denominata .data, che contiene le variabili statiche dichiarate nel programma.
  • A seguire troviamo l’area .bss, dove risiedono le variabili statiche non inizializzate nel programma.
  • L’area successiva è l’heap: l’heap è una struttura dati particolare che serve a gestire l’allocazione dinamica della memoria. Tutto quello che creiamo con malloc() finisce nell’heap, da cui è tolto poi con free().
  • L’ultima area è lo stack, collocato nella parte alta della RAM, che serve a contenere non solo i punti di ritorno per i salti alle subroutine del codice utente ma anche le variabili create localmente nonché i registri di sistema durante le ISR (tutte le volte che viene sollevato un interrupt viene chiamata la corrispondente routine di servizio ma, prima di questo, la CPU salva nello stack lo stato corrente dei registri per poter riprendere dallo stesso punto l’esecuzione del codice interrotto quando la ISR è terminata).

La somma dalle aree .data e .bss da come risultato la memoria occupata staticamente dal vostro programma, ed è il risultato che restituisce il programma avr-size. L’heap e lo stack, invece, sono strutture create durante l’esecuzione del programma e non possono essere predette per cui la memoria libera effettiva deve essere calcolata a runtime (per entrambi i casi, fai riferimento al mio precedente articolo).

L’heap non può sovrascrivere la memoria destinata alle variabili statiche perché l’area che lo contiene si espande verso la zona alta della memoria mentre le aree .data e .bss sono poste prima dell’heap stesso. Lo stack, invece, si espande verso la zona bassa: il rischio che si corre è la collisione delle due aree che porta l’heap a sovrascrivere i dati dello stack e viceversa, perché la CPU del microcontrollore non esegue nessun controllo sul verificarsi di questo fatto. I risultati sono catastrofici perché l’accavallamento dell’heap e dello stack porta al malfunzionamento immediato del programma ma anche al blocco improvviso del microcontrollore.

Durante la stesura di un programma è perciò di fondamentale importanza ricordarsi che stiamo lavorando con microcontrollori dalle ridotte capacità di memoria (sull’Arduino UNO abbiamo 2 kB di RAM quando i nostri computer hanno tagli di memoria che si misurano in GB!!) ed usare tutti gli accorgimenti che possiamo per ridurre il consumo inutile di RAM:

  • la seriale sull’Arduino viene gestita tramite 2 buffer (uno per la ricezione ed uno per la trasmissione) di 64 byte l’uno, quindi abbiamo già un consumo di 128 byte con il semplice utilizzo della seriale. Quindi usate la seriale solo quando serve oppure per debug, e toglietela se il vostro programma non la usa.
  • Usate i tipi di variabili più adatti per gestire i vostri dati. Ad esempio è un errore utilizzare un int per memorizzare il valore di un pin dato che esso non avrà mai un valore numerico elevato. Utilizzate un tipo byte: questo occupa 1 byte di RAM, mentre l’int ne occupa 2.
  • Qualunque variabile che viene utilizzata in un programma deve essere, prima di venir utilizzata, copiata in RAM! Questo è per via dell’architettura Harvard, che separa la memoria Flash dove risiede il codice dalla memoria SRAM dove “vivono” i dati. Questo vale anche per le stringhe: se usate molte stringhe, ad esempio lavorando con un display LCD oppure interagendo con la seriale, il rischio di saturare la memoria è molto elevato. Fortunatamente con l’IDE 1.0 è stata introdotta la funzione F() nel core dell’Arduino che permette di leggere le stringhe direttamente dalla Flash (questa è un’operazione possibile a runtime) senza doverle prima ricopiare in RAM. Questa funzione, però, si può utilizzare soltanto per testi predefiniti e non per stringhe composte a runtime e solo nelle funzioni di Arduino Serial.print/ln e Lcd.print/ln (la prima per la scrittura su seriale, la seconda per la scrittura su un display LCD con la libreria integrata LiquidCrystal). Quindi questo uso va bene:
    Serial.println(F("Salve")); //questo uso è consentito
    mentre questo uso non va bene:
    String temp = "Ciao";
    Serial.println(F(temp)); //questo uso NON è consentito
  • Ricordatevi che la Flash è organizzata in word, ossia una coppia di byte. Fateci caso: quando compilate uno sketch, non otterrete mai la dimensione del vostro sketch con un valore dispari ma sempre pari. Questo perché nonostante l’MCU sia ad 8 bit la memoria è invece a 16 bit. Quindi se il vostro sketch occupa 1353 byte, in Flash ne occuperà 1354. Questo vale anche per le stringhe che, come detto, sono memorizzate all’interno dello sketch (quindi in Flash) ma copiate in RAM durante l’esecuzione del programma (a meno che non utilizziate la funzione F() appena menzionata). Perciò quando la vostra stringa viene trasportata in RAM, anche qui occuperà lo stesso quantitativo di memoria che occupava in Flash: “CIAO” occuperà 4 byte, tanti quanti la stringa “AIO” nonostante questa sia lunga 3 byte.

Allegato trovate uno sketch che serve ad avere informazioni sull’utilizzo della RAM in tempo reale: la memoria libera calcolata è data dall’area compresa fra l’inizio dell’HEAP e l’inizio dello STACK. Sono poi stampate informazioni sull’indirizzo di inizio e fine di ogni area di cui abbiamo parlato.

Per approfondire: link 1.

Know Your Sram
Know Your Sram
know_your_sram.ino
Version: 1.0
2.2 KiB
2339 Downloads
Dettagli...