La documentazione di Arduino dice che è possibile mantenere costanti come stringhe o qualsiasi altra cosa che non voglio cambiare durante il runtime nella memoria del programma.
Tutte le costanti sono inizialmente nella memoria del programma. In quale altro luogo si troverebbero quando l'alimentazione è spenta?
Penso che sia incorporato da qualche parte nel segmento del codice, che deve essere abbastanza possibile all'interno di un'architettura von-Neumann.
In realtà è l'architettura di Harvard .
Perché mai devo copiare il dannato contenuto su RAM prima di accedervi?
Non In effetti esiste un'istruzione hardware (LPM - Load Program Memory) che sposta i dati direttamente dalla memoria del programma in un registro.
Ho un esempio di questa tecnica nell'output di Arduino Uno sul monitor VGA . In quel codice c'è un font bitmap memorizzato nella memoria del programma. Viene letto al volo e copiato nell'output in questo modo:
// blit pixel data to screen
while (i--)
UDR0 = pgm_read_byte (linePtr + (* messagePtr++));
Uno smontaggio di quelle linee mostra (in parte):
f1a: e4 91 lpm r30, Z+
f1c: e0 93 c6 00 sts 0x00C6, r30
Si può vedere che un byte di memoria del programma è stato copiato in R30 e quindi immediatamente archiviato nel registro USART UDR0. Nessuna RAM coinvolta.
Tuttavia c'è una complessità. Per le stringhe normali, il compilatore prevede di trovare dati nella RAM e non in PROGMEM. Sono spazi di indirizzi diversi, quindi 0x200 in RAM è qualcosa di diverso da 0x200 in PROGMEM. Quindi il compilatore si preoccupa di copiare le costanti (come le stringhe) nella RAM all'avvio del programma, quindi non deve preoccuparsi di conoscere la differenza in seguito.
Come viene gestito il codice (32 kB) quindi con solo 2 kB di RAM?
Buona domanda. Non avrai problemi con più di 2 KB di stringhe costanti, perché non ci sarà spazio per copiarle tutte.
Questo è il motivo per cui le persone che scrivono cose come menu e altre cose complesse, prendono ulteriori misure per dare alle stringhe l'attributo PROGMEM, che disabilita la loro copia nella RAM.
Ma sono sconcertato da quelle istruzioni per leggere e stampare i dati dalla memoria del programma:
Se aggiungi l'attributo PROGMEM devi prendere delle misure per far sapere al compilatore che queste stringhe si trovano in uno spazio di indirizzi diverso. Fare una copia completa (temporanea) è un modo. O semplicemente stampare direttamente da PROGMEM, un byte alla volta. Un esempio è:
// Print a string from Program Memory directly to save RAM
void printProgStr (const char * str)
{
char c;
if (!str)
return;
while ((c = pgm_read_byte(str++)))
Serial.print (c);
} // end of printProgStr
Se si passa a questa funzione un puntatore a una stringa in PROGMEM, esegue la "lettura speciale" (pgm_read_byte) per estrarre i dati da PROGMEM anziché dalla RAM e li stampa. Si noti che ciò richiede un ciclo di clock aggiuntivo, per byte.
E ancora più interessante: cosa succede alle costanti letterali come in questa espressione a = 5*(10+7)
sono 5, 10 e 7 realmente copiate nella RAM prima di caricarle nei registri? Non ci posso credere.
No, perché non devono esserlo. Ciò si comporterebbe in un'istruzione "carica letterale nel registro". Quell'istruzione è già in PROGMEM, quindi il letterale è ora trattato. Non è necessario copiarlo su RAM e poi rileggerlo.
Ho una lunga descrizione di queste cose sulla pagina Mettere dati costanti nella memoria del programma (PROGMEM) . Questo ha un codice di esempio per impostare stringhe e matrici di stringhe, abbastanza facilmente.
Menziona anche la macro F () che è un modo semplice per stampare semplicemente da PROGMEM:
Serial.println (F("Hello, world"));
Un po 'di complessità del preprocessore consente di compilare in una funzione di supporto che estrae i byte nella stringa da PROGMEM un byte alla volta. Non è richiesto alcun uso intermedio di RAM.
È abbastanza facile usare quella tecnica per cose diverse da Serial (es. Il tuo LCD) derivando la stampa LCD dalla classe Print.
Ad esempio, in una delle librerie LCD che ho scritto, ho fatto esattamente questo:
class I2C_graphical_LCD_display : public Print
{
...
size_t write(uint8_t c);
};
Il punto chiave qui è derivare da Print e sovrascrivere la funzione "write". Ora la tua funzione sovrascritta fa tutto il necessario per produrre un personaggio. Poiché è derivato da Stampa, ora puoi utilizzare la macro F (). per esempio.
lcd.println (F("Hello, world"));
string_table
dall'array. Quell'array potrebbe essere 20 KB e non starebbe mai nella memoria (nemmeno temporaneamente). Puoi comunque caricare solo un indice usando il metodo sopra.