Tengo algunas dificultades para entender la gestión de la memoria.
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. Creo que está incrustado en algún lugar del segmento de código, que debe ser bastante posible dentro de una arquitectura von-Neumann. Quiero hacer uso de eso para hacer posible mi menú de IU en una pantalla LCD.
Pero estoy desconcertado por esas instrucciones de solo leer e imprimir datos de la memoria del programa:
strcpy_P(buffer, (char*)pgm_read_word(&(string_table[i]))); // Necessary casts and dereferencing, just copy.
Serial.println( buffer );
¿Por qué demonios tengo que copiar el maldito contenido a la RAM antes de acceder? Y si esto es cierto, ¿qué sucede con todo el código entonces? ¿También se carga en la RAM antes de la ejecución? ¿Cómo se maneja el código (32 kB) con solo 2 kB de RAM? ¿Dónde están esos pequeños duendes que llevan disquetes?
Y aún más interesante: lo que sucede con las constantes literales como en esta expresión:
a = 5*(10+7)
¿Se copian realmente 5, 10 y 7 en la RAM antes de cargarlos en los registros? No puedo creer eso.
fuente
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.Respuestas:
AVR es una familia de arquitectura Harvard modificada , por lo que el código se almacena solo en flash, mientras que los datos existen principalmente en la RAM cuando se manipulan.
Con eso en mente, abordemos sus preguntas.
No necesita hacerlo per se, pero por defecto el código asume que los datos están en RAM a menos que el código se modifique para buscarlo específicamente en flash (como con
strcpy_P()
).No Arquitectura de Harvard. Vea la página de Wikipedia para los detalles completos.
El preámbulo generado por el compilador copia los datos que deberían ser modificables / modificados en SRAM antes de ejecutar el programa real.
No sé. Pero si los ves, entonces no hay nada que pueda hacer para ayudar.
Nah El compilador evalúa la expresión en tiempo de compilación. Todo lo que suceda depende de las líneas de código que lo rodean.
fuente
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};
¿Cómo traigo estos datos a flash y luego a la función SPI.transfer (), que toma un uint8_t por llamada?Así es como se
Print::print
imprime desde la memoria del programa en la biblioteca Arduino:__FlashStringHelper*
es una clase vacía que permite funciones sobrecargadas como imprimir para diferenciar un puntero para programar la memoria de una a la memoria normal, ya que ambos son vistosconst char*
por el compilador (consulte /programming/16597437/arduino-f- qué-lo-hace-realmente-hace )Por lo tanto, podría sobrecargar la
print
función de su pantalla LCD para que tome un__FlashStringHelper*
argumento, lo llamemosLCD::print
y luego uselcd.print(F("this is a string in progmem"));' to call it.
F () `es una macro que garantiza que la cadena esté en la memoria del programa.Para predefinir la cadena (para ser compatible con la impresión Arduino incorporada) he usado:
Creo que una alternativa sería algo como
lo que evitaría el
__FlashStringHelper
reparto.fuente
Todas las constantes están inicialmente en la memoria del programa. ¿Dónde más estarían cuando el poder está apagado?
En realidad es la arquitectura de Harvard .
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:
Un desmontaje de esas líneas muestra (en parte):
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.
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.
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:
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.
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:
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:
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.
fuente