Umbral de 32K para datos en ROM de microcontroladores AVR

¿Qué podría ser peor que las muletas? Solo muletas incompletamente documentadas.


imagen


Aquí hay una captura de pantalla del último entorno de desarrollo integrado oficial para microcontroladores AVR de 8 bits, Atmel Studio 7, el lenguaje de programación C. Como puede ver en la columna Valor, la variable my_array contiene el número 0x8089. En otras palabras, la matriz my_array se encuentra en la memoria que comienza en la dirección 0x8089.


Al mismo tiempo, la columna Tipo nos da información ligeramente diferente: my_array es una matriz de 4 elementos de tipo int16_t ubicados en ROM (esto se indica mediante la palabra prog, a diferencia de los datos para RAM), comenzando desde la dirección 0x18089. Detente, pero después de todo 0x8089! = 0x18089. ¿Cuál es la dirección real de la matriz?


Lenguaje C y arquitectura de Harvard


Los microcontroladores AVR de 8 bits fabricados anteriormente por Atmel, y ahora Microchip, son populares, en particular, debido al hecho de que son la base de Arduino, construido en la arquitectura de Harvard, es decir, el código y los datos se encuentran en diferentes espacios de direcciones. La documentación oficial contiene ejemplos de código en dos idiomas: ensamblador y C. Anteriormente, el fabricante ofrecía un entorno de desarrollo integrado gratuito que solo admite ensamblador. Pero, ¿qué pasa con aquellos a quienes les gustaría programar en C, o incluso en C ++? Hubo soluciones pagas, por ejemplo, IAR AVR y CodeVisionAVR. Personalmente, nunca lo usé, porque cuando comencé a programar AVR en 2008, ya había WinAVR gratuito con la capacidad de integrarse con AVR Studio 4, y simplemente está incluido en el Atmel Studio 7 actual.


El proyecto WinAVR se basa en el compilador GNU GCC, que fue desarrollado para la arquitectura von Neumann, lo que implica un espacio de direcciones único para código y datos. Al adaptar GCC a AVR, se aplicó la siguiente muleta: las direcciones 0 a 0x007fffff se asignan para el código (ROM, flash) y 0x00800100 para 0x0080ffff para datos (RAM, SRAM). Hubo todo tipo de otros trucos, por ejemplo, las direcciones de 0x00800000 a 0x008000ff representaron registros a los que se puede acceder con los mismos códigos de operación que RAM. En principio, si usted es un programador simple, como un arduino principiante, y no un hacker, mezclador y C / C ++ en el mismo firmware, no necesita saber todo esto.


Además del compilador real, WinAVR incluye varias bibliotecas (parte de la biblioteca estándar C y módulos específicos de AVR) en forma del proyecto AVR Libc. La última versión, 2.0.0, se lanzó hace casi tres años, y la documentación está disponible no solo en el sitio del proyecto en sí, sino también en el sitio del fabricante del microcontrolador. También hay traducciones no oficiales al ruso.


Datos en el espacio de direcciones del código


A veces, en un microcontrolador, necesita colocar no solo una gran cantidad de datos, sino muchos: simplemente no caben en la RAM. Además, estos datos son inmutables, conocidos en el momento del firmware. Por ejemplo, una imagen ráster, una melodía o algún tipo de tabla. Al mismo tiempo, el código a menudo ocupa solo una pequeña fracción de la ROM disponible. Entonces, ¿por qué no usar el espacio restante para los datos? Fácil! La documentación de avr-libc 2.0.0 cubre un capítulo completo de 5 datos en el espacio del programa. Si omite la parte sobre las líneas, entonces todo es extremadamente simple. Considera un ejemplo. Para RAM, escribimos así:


unsigned char array2d[2][3] = {...}; unsigned char element = array2d[i][j]; 

Y para ROM como esta:


 #include <avr/pgmspace.h> const unsigned char array2d[2][3] PROGMEM = {...}; unsigned char element = pgm_read_byte(&(array2d[i][j])); 

Es tan simple que esta tecnología se ha cubierto repetidamente incluso en RuNet.


Entonces, ¿cuál es el problema?


¿Recuerdas la afirmación de que 640 KB es suficiente para todos? ¿Recuerdas cómo cambiaste de la arquitectura de 16 bits a 32 bits y de 32 a 64 bits? ¿Cómo funcionó Windows 98 de manera inestable en más de 512 MB de RAM mientras estaba diseñado para 2 GB? ¿Alguna vez ha actualizado el BIOS para que la placa base funcione con discos duros de más de 8 GB? ¿Recuerdas los puentes en 80 GB de discos duros, recortando su volumen a 32 GB?


El primer problema me superó cuando intenté crear una matriz de al menos 32 KB en ROM. ¿Por qué en ROM y no en RAM? Porque en la actualidad, los AVR de 8 bits con más de 32 KB de RAM simplemente no existen. Y con más de 256 B - existen. Esta es probablemente la razón por la cual los creadores del compilador eligieron 16 b (2 B) para los punteros en RAM (y al mismo tiempo para el tipo int), que se puede encontrar en la lectura del párrafo Tipos de datos ubicado en el capítulo 11.14 ¿Qué registros utiliza el compilador C? Documentación de AVR Libc. Ah, y no íbamos a hackear, pero aquí están los registros ... Pero volvamos a la matriz. Resultó que no puede crear un objeto mayor que 32,767 B (2 ^ (16 - 1) - 1 B). No sé por qué fue necesario hacer que la longitud del objeto sea significativa, pero esto es un hecho: ningún objeto, incluso una matriz multidimensional, puede tener una longitud de 32.768 B o más. Un poco como una limitación en el espacio de direcciones de las aplicaciones de 32 bits (4 GB) en un sistema operativo de 64 bits, ¿no?


Que yo sepa, este problema no tiene solución. Si desea colocar un objeto con una longitud de 32,768 en la ROM, divídalo en objetos más pequeños.


Volvemos nuevamente al párrafo Tipos de datos: los punteros son de 16 bits. Aplicamos este conocimiento al capítulo 5 de Datos en el espacio del programa. No, la teoría es indispensable; se necesita práctica. Escribí un programa de prueba, lancé un depurador (desafortunadamente, software, no hardware) y vi que la función pgm_read_byte solo puede devolver datos cuyas direcciones caben en 16 bits (64 KB; gracias, no 15). Luego se produce un desbordamiento, la parte más antigua se descarta. Es lógico, dado que los punteros son de 16 bits. Pero surgen dos preguntas: por qué esto no está escrito en el capítulo 5 (una pregunta retórica, pero fue él quien me impulsó a escribir este artículo) y cómo superar el límite de ROM de 64 KB sin cambiar al ensamblador.


Afortunadamente, además del Capítulo 5, hay otra 25.18 pgmspace.h File Reference, de la que aprendemos que la familia de funciones pgm_read_* es solo una pgm_read_*_near para pgm_read_*_near , que acepta direcciones de 16 bits, y también hay pgm_read_*_far , y puede enviar Dirección de 32 bits Eureka!


Escribimos el código:


 unsigned char element = pgm_read_byte_far(&(array2d[i][j])); 

Se compila, pero no funciona como nos gustaría (si array2d se encuentra después de 32 KB). Por qué Sí, porque la operación & devuelve un número de 16 bits con signo. Es curioso que la familia pgm_read_*_near acepte direcciones de 16 bits sin firmar, es decir, es capaz de trabajar con 64 KB de datos, y la operación & solo es útil para 32 KB.


Sigamos adelante. ¿Qué tenemos en pgmspace.h además de pgm_read_* ? La función pgm_get_far_address(var) , que ya tiene media página de descripción, y reemplaza la operación & .


Probablemente correcto:


 unsigned char element = pgm_read_byte_far(pgm_get_far_address(array2d[i][j])); 

Error de compilación Leemos la descripción: 'var' debe resolverse en el momento de la vinculación como un símbolo existente, es decir, un nombre de variable de tipo simple, un nombre de matriz (no un elemento indexado de la matriz, si el índice es una constante, el compilador no se queja pero no puede obtener la dirección si la optimización está habilitada), un nombre de estructura o un nombre de campo de estructura, un identificador de función, un identificador definido de enlazador, ...


Ponemos otra muleta: pasamos de los índices de matriz a la aritmética del puntero:


 unsigned char element = pgm_read_byte_far(pgm_get_far_address(array2d) + i*3*sizeof(unsigned char) + j*sizeof(unsigned char)); 

Ahora todo funciona.


Conclusiones


Si escribe en C / C ++ para microcontroladores AVR de 8 bits utilizando el compilador GCC y almacena los datos en ROM, entonces:


  • con un tamaño de ROM de no más de 32 KB, no tendrá problemas leyendo solo los datos del Capítulo 5 en el Espacio del programa;
  • para ROM de más de 32 KB, debe usar la familia de funciones pgm_read_*_far , la función pgm_get_far_address lugar de & , la aritmética del puntero en lugar de los índices de matriz, y el tamaño de cualquier objeto no puede exceder 32.767 B.

Referencias


Source: https://habr.com/ru/post/es436904/


All Articles