La documentación de Arduino dice que es posible mantener constantes como cadenas o lo que no quiera cambiar durante el tiempo de ejecución en la memoria del programa.
Todas las constantes están inicialmente en la memoria del programa. ¿Dónde más estarían cuando el poder está apagado?
Creo que está incrustado en algún lugar del segmento de código, que debe ser bastante posible dentro de una arquitectura von-Neumann.
En realidad es la arquitectura de Harvard .
¿Por qué demonios tengo que copiar el maldito contenido a la RAM antes de acceder?
Usted no De hecho, hay una instrucción de hardware (LPM - Load Program Memory) que mueve los datos directamente de la memoria del programa a un registro.
Tengo un ejemplo de esta técnica en la salida Arduino Uno al monitor VGA . En ese código hay una fuente de mapa de bits almacenada en la memoria del programa. Se lee de eso sobre la marcha y se copia a la salida de esta manera:
// blit pixel data to screen
while (i--)
UDR0 = pgm_read_byte (linePtr + (* messagePtr++));
Un desmontaje de esas líneas muestra (en parte):
f1a: e4 91 lpm r30, Z+
f1c: e0 93 c6 00 sts 0x00C6, r30
Puede ver que un byte de memoria de programa se copió en R30 y luego se almacenó inmediatamente en el registro USART UDR0. No hay RAM involucrada.
Sin embargo, hay una complejidad. Para cadenas normales, el compilador espera encontrar datos en RAM, no en PROGMEM. Son espacios de direcciones diferentes y, por lo tanto, 0x200 en RAM es algo diferente de 0x200 en PROGMEM. Por lo tanto, el compilador se toma la molestia de copiar constantes (como cadenas) en la RAM al inicio del programa, por lo que no tiene que preocuparse por saber la diferencia más adelante.
¿Cómo se maneja el código (32 kB) con solo 2 kB de RAM?
Buena pregunta. No se saldrá con la suya al tener más de 2 KB de cadenas constantes, porque no habrá espacio para copiarlas todas.
Es por eso que las personas que escriben cosas como menús y otras cosas con palabras, toman medidas adicionales para dar a las cadenas el atributo PROGMEM, que deshabilita su copia en la RAM.
Pero estoy desconcertado por esas instrucciones de solo leer e imprimir datos de la memoria del programa:
Si agrega el atributo PROGMEM, debe tomar medidas para que el compilador sepa que estas cadenas están en un espacio de direcciones diferente. Hacer una copia completa (temporal) es unidireccional. O simplemente imprima directamente desde PROGMEM, un byte a la vez. Un ejemplo de eso es:
// 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
Si pasa esta función un puntero a una cadena en PROGMEM, realiza la "lectura especial" (pgm_read_byte) para extraer los datos de PROGMEM en lugar de RAM, y la imprime. Tenga en cuenta que esto toma un ciclo de reloj adicional, por byte.
Y aún más interesante: ¿qué sucede con las constantes literales como en esta expresión que a = 5*(10+7)
5, 10 y 7 realmente se copian en la RAM antes de cargarlas en los registros? No puedo creer eso.
No, porque no tienen que serlo. Eso se compilaría en una instrucción "cargar literal en registro". Esa instrucción ya está en PROGMEM, por lo que ahora se trata el literal. No es necesario copiarlo en la RAM y luego volver a leerlo.
Tengo una larga descripción de estas cosas en la página Poner datos constantes en la memoria del programa (PROGMEM) . Eso tiene un código de ejemplo para configurar cadenas y matrices de cadenas, razonablemente fácil.
También menciona la macro F (), que es una manera fácil de imprimir simplemente desde PROGMEM:
Serial.println (F("Hello, world"));
Un poco de complejidad del preprocesador permite que se compile en una función auxiliar que extrae los bytes en la cadena de PROGMEM un byte a la vez. No se requiere el uso intermedio de RAM.
Es bastante fácil usar esa técnica para otras cosas que no sean Serie (por ejemplo, su LCD) derivando la impresión LCD de la clase Print.
Como ejemplo, en una de las bibliotecas LCD que escribí, hice exactamente eso:
class I2C_graphical_LCD_display : public Print
{
...
size_t write(uint8_t c);
};
El punto clave aquí es derivar de Imprimir y anular la función "escribir". Ahora su función anulada hace lo que sea necesario para generar un carácter. Como se deriva de Print, ahora puede usar la macro F (). p.ej.
lcd.println (F("Hello, world"));
string_table
matriz. Esa matriz podría ser de 20 KB y nunca cabría en la memoria (ni siquiera temporalmente). Sin embargo, puede cargar solo un índice utilizando el método anterior.