Compilación de código para ejecutar desde RAM externa

13

Estoy considerando diseños para un sistema de juego minimalista basado en un PIC18F85J5. Parte de mi diseño es que los juegos se pueden cargar desde una tarjeta SD sin reprogramar el chip o flashear la memoria del programa. Elegí ese chip porque tiene una interfaz de memoria externa que me permitirá ejecutar código desde una SRAM externa.

La idea básica es que la memoria interna del programa contendrá una interfaz para explorar la tarjeta SD, y una vez que el usuario seleccione un programa, copiará un archivo hexadecimal de la tarjeta SD al ram externo, y luego saltará la ejecución al espacio ram externo .

La memoria interna del programa también tendrá varias bibliotecas para gráficos, entrada de controlador y otras diversas utilidades.

Estoy bastante seguro de que sé cómo hacer que las partes internas del firmware funcionen bien. El problema es crear programas para ejecutar desde la RAM externa. No se siente lo mismo que apuntar a una imagen normal, y debe conocer las funciones de la biblioteca que están disponibles en la memoria interna, pero no volver a compilarlas, solo vincularlas. También debe comenzar a usar direcciones justo después de los 32k de flash interno, no en cero. ¿Hay una buena manera de compilar un programa usando este tipo de restricciones?

Estoy usando el IDE de MPLab, pero no estoy muy familiarizado con él, o cómo hacer este tipo de personalización.

captncraig
fuente
Una de las mejores preguntas que he visto aquí en mucho tiempo ... Estoy ansioso por escuchar las ideas y respuestas.
Jon L
Algunos enfoques alternativos interesantes se discuten en electronics.stackexchange.com/questions/5386/…
Toby Jaffey
Lo vi, pero en su mayoría recomiendan escribir en flash, lo que me gustaría evitar. El diseño de mi hardware se parecerá a la figura 6 en la nota de aplicación en la respuesta aceptada.
captncraig

Respuestas:

8

Tienes dos problemas separados:

  1. Creación del código para el rango de direcciones RAM externas.

    Esto es realmente muy fácil. Todo lo que tiene que hacer es crear un archivo de enlace que contenga solo los rangos de direcciones que desea que ocupe el código. Tenga en cuenta que no solo necesita reservar un rango de direcciones de memoria de programa particular para estas aplicaciones externas, sino también algo de espacio RAM. Este espacio RAM, como las direcciones de memoria del programa, necesita ser reparado y conocido. Simplemente haga que esos rangos de direcciones fijas y conocidas estén disponibles para su uso en el archivo vinculador. No olvide hacer que NO estén disponibles en el archivo de enlace del código base.

    Después de que el código base carga una nueva aplicación en la memoria externa, debe saber cómo ejecutarla. Lo más fácil es probablemente que la ejecución comience en la primera ubicación de RAM externa. Esto significa que su código necesitará una sección de CÓDIGO en esa dirección de inicio absoluta. Esto contiene un GOTO a la etiqueta correcta en el resto del código, que será reubicable.

  2. Vinculación de aplicaciones a rutinas de biblioteca en el código base.

    No hay una forma simple e inmediata de hacer esto con las herramientas existentes de Microchip, pero tampoco es tan malo.

    Un problema mucho mayor es cómo desea lidiar con los cambios del código base. La estrategia simplista es construir su código base, ejecutar un programa sobre el archivo de mapa resultante para recolectar direcciones globales, luego hacer que escriba un archivo de importación con declaraciones EQU para todos los símbolos definidos globalmente. Este archivo de importación se incluiría en todo el código de la aplicación. No hay nada que vincular, ya que el código fuente de la aplicación contiene esencialmente las referencias de direcciones fijas a los puntos de entrada del código base.

    Es fácil de hacer y funcionará, pero considere lo que sucede cuando cambia el código base. Incluso una pequeña corrección de errores podría hacer que todas las direcciones se muevan, y luego todo el código de la aplicación existente no sería bueno y tendría que ser reconstruido. Si nunca planea proporcionar actualizaciones de código base sin actualizar todas las aplicaciones, tal vez pueda salirse con la suya, pero creo que es una mala idea.

    Una mejor manera es tener un área de interfaz definida en una dirección conocida fija elegida en el código base. Habría un GOTO por cada subrutina que el código de la aplicación pueda llamar. Estos GOTO se colocarían en direcciones conocidas fijas, y las aplicaciones externas solo llamarían a esas ubicaciones, que luego saltarían a donde la subrutina realmente terminara en esa compilación del código base. Esto cuesta 2 palabras de memoria de programa por subrutina exportada y dos ciclos adicionales en tiempo de ejecución, pero creo que vale la pena.

    Para hacer esto correctamente, necesita automatizar el proceso de generación de los GOTO y el archivo de exportación resultante que las aplicaciones externas importarán para obtener las direcciones de la subrutina (en realidad, el redirector GOTO). Es posible que pueda hacer algo con un uso inteligente de las macros MPASM, pero si estuviera haciendo esto definitivamente usaría mi preprocesador, ya que puede escribir en un archivo externo en el momento del preprocesamiento. Puede escribir una macro de preprocesador para que cada redirector pueda definirse mediante una sola línea de código fuente. La macro hace todo lo desagradable bajo el capó, que es generar el GOTO, la referencia externa a la rutina de destino real, y agrega la línea apropiada al archivo de exportación con la dirección constante conocida de esa rutina, todo con los nombres apropiados. Quizás la macro solo crea un montón de variables de preprocesador con nombres regulares (como una matriz expandible en tiempo de ejecución), y luego el archivo de exportación se escribe una vez después de todas las llamadas de macro. Una de las muchas cosas que mi preprocesador puede hacer que las macros MPASM no pueden hacer es manipular cadenas para construir nuevos nombres de símbolos a partir de otros nombres.

    Mi preprocesador y otras cosas relacionadas están disponibles de forma gratuita en www.embedinc.com/pic/dload.htm .

Olin Lathrop
fuente
Realmente me gusta la idea de una mesa de salto fija. Podría comenzar desde el final de la memoria flash y tener ubicaciones fijas para cada llamada al sistema. Incluso podría salirme con un archivo de encabezado mantenido manualmente con las direcciones de todas las subrutinas si no puedo entender todo ese vudú del preprocesador que usted describe.
captncraig
5

Opción 1: idiomas interpretados

Esto no responde directamente a la pregunta (que es una excelente pregunta, por cierto, y espero aprender de una respuesta que lo aborde directamente), pero es muy común cuando se realizan proyectos que pueden cargar programas externos para escribir los programas externos en Un lenguaje interpretado. Si los recursos son limitados (que estarán en este procesador, ¿ha pensado usar un PIC32 o un procesador ARM pequeño para esto?), Es común restringir el idioma a un subconjunto de la especificación completa. Aún más abajo en la cadena hay lenguajes específicos de dominio que solo hacen algunas cosas.

Por ejemplo, el proyecto elua es un ejemplo de un lenguaje interpretado de bajos recursos (64 kB RAM). Puede reducir esto a 32k de RAM si elimina algunas funciones (Nota: no funcionará en su procesador actual, que es una arquitectura de 8 bits. El uso de RAM externa probablemente será demasiado lento para los gráficos). Proporciona un lenguaje rápido y flexible en el que los nuevos usuarios podrían programar fácilmente juegos si proporciona una API mínima. Hay mucha documentación disponible para el idioma en línea. Hay otros idiomas (como Forth y Basic) que podría usar de manera similar, pero creo que Lua es la mejor opción en este momento.

De manera similar, podría crear su propio lenguaje específico de dominio. Tendría que proporcionar una API más completa y documentación externa, pero si los juegos fueran todos similares, esto no sería demasiado difícil.

En cualquier caso, el PIC18 probablemente no sea el procesador que usaría para algo que involucra programación / scripting y gráficos personalizados. Puede que esté familiarizado con esta clase de procesadores, pero sugeriría que este sería un buen momento para usar algo con un controlador de pantalla y más memoria.

Opción 2: solo reprograme todo

Sin embargo, si ya está planeando programar todos los juegos usted mismo en C, no se moleste en cargar solo la lógica del juego desde la tarjeta SD. Tiene solo 32kB de Flash para reprogramar, y podría obtener fácilmente una tarjeta microSD de 4 GB para esto. (Nota: las tarjetas más grandes a menudo son SDHC, que es más difícil de interactuar). Suponiendo que use cada último byte de sus 32 kB, eso deja espacio en la tarjeta SD para 131,072 copias de su firmware con cualquier lógica de juego que necesite.

Hay muchas aplicaciones para escribir cargadores de arranque para PIC, como AN851 . Tendría que diseñar su gestor de arranque para ocupar una región específica de memoria (probablemente la parte superior de la región de memoria, debería especificar esto en el vinculador) y especificar que los proyectos de firmware completos no llegan a esta región. La nota de aplicación explica esto con más detalle. Simplemente reemplace "Sección de inicio del PIC18F452" con "Sección de inicio que especifique en el vinculador" y todo tendrá sentido.

Luego, su gestor de arranque solo necesita permitir al usuario seleccionar un programa para ejecutar desde la tarjeta SD y copiar todo. Una IU podría ser que el usuario tiene que mantener presionado un botón para ingresar al modo de selección. Por lo general, el gestor de arranque simplemente verificaría el estado de este botón al reiniciar y, si no se mantiene presionado, iniciará el juego. Si se mantiene presionado, deberá permitir al usuario elegir un archivo en la tarjeta SD, copiar el programa y continuar iniciando el juego [nuevo].

Esta es mi recomendación actual.

Opción 3: Magia profunda que implica almacenar solo parte del archivo hexadecimal

El problema con su mecanismo previsto es que el procesador no maneja las API y las llamadas a funciones, sino que trata con números : direcciones a las que el puntero de instrucciones puede saltar y esperar que haya un código que ejecute una llamada a funciones de acuerdo con una especificación API. Si intenta compilar solo una parte del programa, el enlazador no sabrá qué hacer cuando llame check_button_status()o toggle_led(). Puede saber que esas funciones existen en el archivo hexadecimal en el procesador, pero necesita saber con precisión en qué dirección residen.

El vinculador ya divide su código en varias secciones; teóricamente podrías dividir esto en secciones adicionales con algunos -sectiony #pragmaencantamientos. Nunca he hecho esto, y no sé cómo. Hasta que los dos métodos anteriores me fallen (o alguien publique una respuesta increíble aquí), probablemente no aprenderé este mecanismo, por lo que no puedo enseñárselo.

Kevin Vermeer
fuente
Mi objeción al número 2 es que la memoria flash tiene un número limitado de ciclos de borrado en la vida útil del chip. No quiero usar un mcu más robusto porque voy por el minimalismo de 8 bits.
captncraig
@CMP: tiene al menos 10,000 ciclos de borrado. Si alguien juega un juego diferente todos los días, durará hasta el año 2039. Es casi seguro que Flash durará más que el resto del circuito. No creo que deba preocuparse por esto a menos que vaya a una sala de juegos que se juegue docenas de veces al día.
Kevin Vermeer
1
En segundo lugar, el minimalismo de 8 bits puede parecer genial, pero apesta a la programación. Es mucho más fácil obtener un microcontrolador resistente y hacer que se vea retro que verse obligado a hacerlo retroceder porque está superando los límites de su procesador.
Kevin Vermeer
1
Ambos puntos muy justos. Incluso si va por un recuento bajo de piezas, un pic32 no es tan diferente en costo o componentes externos que esto, y si tiene 512K de flash interno, incluso gana.
captncraig
Parece que una solución práctica sería usar un pic32 y escribir un gestor de arranque para actualizar desde la tarjeta SD. Me resultaría difícil reutilizar las funciones que necesitarían tanto el gestor de arranque como el código de usuario, pero siempre que lo mantenga en el flash de arranque de 12k debería proporcionar al código de usuario el chip completo, y pueden incluir las bibliotecas que quieran en la fuente nivel.
captncraig