PROGMEM: devo copiare i dati da Flash a RAM per la lettura?


8

Ho delle difficoltà a capire la gestione della memoria.

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. Penso che sia incorporato da qualche parte nel segmento del codice, che deve essere abbastanza possibile all'interno di un'architettura von-Neumann. Voglio usarlo per rendere possibile il mio menu UI su un LCD.

Ma sono sconcertato da quelle istruzioni per leggere e stampare i dati dalla memoria del programma:

strcpy_P(buffer, (char*)pgm_read_word(&(string_table[i]))); // Necessary casts and dereferencing, just copy. 
    Serial.println( buffer );

Perché mai devo copiare il dannato contenuto su RAM prima di accedervi? E se questo è vero, cosa succede a tutto il codice allora? È anche caricato su RAM prima dell'esecuzione? Come viene gestito il codice (32 kB) quindi con solo 2 kB di RAM? Dove sono quei piccoli folletti che trasportano floppy disk?

E ancora più interessante: cosa succede alle costanti letterali come in questa espressione:

a = 5*(10+7)

5, 10 e 7 sono davvero copiati nella RAM prima di caricarli nei registri? Non ci posso credere.


Una variabile globale viene caricata in memoria e non viene mai rilasciata da essa. Il codice sopra riportato copia i dati in memoria solo quando necessario e li rilascia al termine. Si noti inoltre che il codice sopra legge solo un byte string_tabledall'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.
Gerben,

@Gerben: questo è un vero svantaggio per le variabili globali, non l'ho ancora preso in considerazione. Adesso ho mal di testa. E lo snippet di codice era solo un esempio della documentazione. Mi sono trattenuto per programmare sth. me stesso prima di avere chiarimenti sui concetti. Ma ho qualche intuizione ora. Grazie!
Ariser - ripristina Monica il

Ho trovato la documentazione un po 'confusa quando l'ho letta per la prima volta. Prova a guardare anche alcuni esempi di vita reale (come ad esempio una biblioteca).
Gerben,

Risposte:


10

AVR è una famiglia di architettura Harvard modificata , quindi il codice viene archiviato solo in Flash, mentre i dati esistono principalmente nella RAM quando vengono manipolati.

Con questo in mente, rispondiamo alle tue domande.

Perché mai devo copiare il dannato contenuto su RAM prima di accedervi?

Non è necessario di per sé, ma per impostazione predefinita il codice presuppone che i dati siano nella RAM a meno che il codice non venga modificato per cercarlo in modo specifico in Flash (come con strcpy_P()).

E se questo è vero, cosa succede a tutto il codice allora? È anche caricato su RAM prima dell'esecuzione?

No. Architettura di Harvard. Vedi la pagina di Wikipedia per i dettagli completi.

Come viene gestito il codice (32 kB) quindi con solo 2 kB di RAM?

Il preambolo generato dal compilatore copia i dati che dovrebbero essere modificabili / modificati in SRAM prima di eseguire il programma effettivo.

Dove sono quei piccoli folletti che trasportano floppy disk?

Boh. Ma se ti capita di vederli, non c'è niente che io possa fare per aiutare.

... 5, 10 e 7 sono davvero copiati nella RAM prima di caricarli nei registri?

Nah. Il compilatore valuta l'espressione al momento della compilazione. Qualsiasi altra cosa accada dipende dalle righe di codice che la circondano.


Ok, non sapevo che AVR fosse harvard. Ma ho familiarità con questo concetto. A parte i goblin, penso di sapere quando usare quelle funzioni di copia ora. Devo limitare l'uso di PROGMEM ai dati che raramente vengono utilizzati per salvare i cicli della CPU.
Ariser - ripristina Monica il

O modifica il tuo codice per usarlo direttamente da Flash.
Ignacio Vazquez-Abrams,

Ma come sarebbe questo codice? diciamo che ho diverse matrici di uint8_t che rappresentano stringhe che voglio mettere su un display LCD tramite SPI. const uint8_t test1[5]= { 0x54, 0x65, 0x73, 0x74, 0x31 }; const uint8_t bla[9]= { 0x62, 0x6c, 0x61, 0x62, 0x6c, 0x61, 0x62, 0x6c, 0x62 }; const uint8_t Menu[4]= { 0x3d, 0x65, 0x6e, 0x75};come posso portare questi dati in flash e successivamente nella funzione SPI.transfer (), che richiede un uint8_t per chiamata.
Ariser - ripristina Monica il


8

Ecco come si Print::printstampa dalla memoria del programma nella libreria Arduino:

size_t Print::print(const __FlashStringHelper *ifsh)
{
  const char PROGMEM *p = (const char PROGMEM *)ifsh;
  size_t n = 0;
  while (1) {
    unsigned char c = pgm_read_byte(p++);
    if (c == 0) break;
    n += write(c);
  }
  return n;
}

__FlashStringHelper*è una classe vuota che consente a funzioni sovraccaricate come print di differenziare un puntatore per programmare la memoria da una alla memoria normale, come entrambe sono viste come const char*dal compilatore (vedi /programming/16597437/arduino-f- cosa fa realmente )

Quindi potresti sovraccaricare la printfunzione per il tuo display LCD in modo che prenda un __FlashStringHelper*argomento, chiamiamolo LCD::printe quindi usi lcd.print(F("this is a string in progmem"));' to call it.F () `è una macro che assicura che la stringa sia nella memoria del programma.

Per predefinire la stringa (per essere compatibile con la stampa Arduino integrata) ho usato:

const char firmware_version_s[] PROGMEM = {"1.0.2"};
__FlashStringHelper* firmware_version = (__FlashStringHelper*) firmware_version_s;
...
Serial.println(firmware_version);

Penso che un'alternativa sarebbe qualcosa di simile

size_t LCD::print_from_flash(const char *pgms)
{
  const char PROGMEM *p = (const char PROGMEM *) pgms;
  size_t n = 0;
  while (1) {
    unsigned char c = pgm_read_byte(p++);
    if (c == 0) break;
    n += write(c);
  }
  return n;
}

che eviterebbe il __FlashStringHelpercast.


2

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"));
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.