PIC18 Gestión de memoria

6

El tamaño de pila limitado de los PIC de presupuesto es un área problemática y he ajustado mi código para adaptarse a esta realidad. Actualmente adopto un paradigma aproximado de agrupar funciones estrechamente relacionadas en un módulo y declarar todas las variables estáticas globales en el módulo (para reducir la cantidad de variables almacenadas en el psecto automático y las cuestiones de mutabilidad solo son relevantes en los ISR, que explico). .) No hago esto porque es una buena práctica, pero la realidad es que tienes una cantidad limitada de espacio para asignar todas las funciones locales que existen en un proyecto completo.

En el mundo integrado de chips de 8/16 bits, ¿es este un método apropiado, siempre que esté seguro de tomar las precauciones necesarias? También hago cosas como asignar > 256 bytes de RAM para buffers de Ethernet y tengo que acceder a esa memoria a través de punteros para que pueda evitar la semántica de la banca de memoria. ¿Lo estoy haciendo mal? Mi aplicación funciona, pero estoy 100% abierta a sugerencias para mejorar.

    
pregunta Nate

3 respuestas

1

Tal vez me esté faltando algo aquí (los párrafos podrían ayudar) pero con las variables locales del compilador C18 dentro de una función se asignan generalmente en la pila de software, así que no tengo idea de lo que significa lo siguiente:

  

pero la realidad es que tienes un finito   cantidad de espacio para asignar todos los locales   vars de función que existen en un todo   proyecto

Al mover todas sus variables a variables globales dentro de un módulo que está requiriendo, hay espacio para todas ellas al mismo tiempo.

  

almacena y tiene que acceder a esa memoria   a través de punteros para que pueda evitar la   Semántica de la banca de memoria.

¿Qué compilador estás usando?

    
respondido por el Mark
1

Hay dos enfoques utilizados para la asignación de variables en los compiladores PIC. Algunos usan una pila de software indexada fuera de FSR2, mientras que otros simplemente usan variables superpuestas estáticamente. ambos planteamientos tienen ventajas y desventajas. El uso de variables superpuestas significa que no hay posibilidad de un desbordamiento de pila en tiempo de ejecución. También significa que uno puede tener aproximadamente 64-128 bytes de variables globales a las que se puede acceder desde cualquier instrucción sin tener que preocuparse por la banca. Desafortunadamente, impide la recursión, dificulta ciertas situaciones que involucran punteros de función y, a menudo, conduce a un código que está inflado con instrucciones movlb porque los compiladores a menudo no son muy buenos para organizar los bancos de manera eficiente.

La mejor disposición de las variables dependerá del tipo de compilador que estés usando. Desafortunadamente, el código optimizado para un compilador a menudo funciona mal en otro.

Por cierto, no tengo idea de por qué Microchip no puede hacer un chip que, por ejemplo, proporcione 16 bytes de direccionamiento indirecto fuera de FSR2 y ocho bytes cada uno fuera de FSR0 y FSR1, mientras que deja 64-96 bytes del "banco común" disponible para el almacenamiento del usuario, pero por la razón que sea, la mayoría de los PIC que he visto hacen uso de el área "común" y una propuesta de "todo o nada" a pesar del hecho de que pocas rutinas necesitarán más de 16 bytes del marco de pila local.

    
respondido por el supercat
1

Lo que describas no es una buena idea. Sin embargo, si lo tiene funcionando en un proyecto en particular y ha sido bien probado, entonces déjelo en paz.

Generalmente asigno variables en un PIC 18 de cuatro maneras diferentes:

  1. Globales. Estos son los pocos valores que deben ser visibles a nivel del sistema fuera de los módulos individuales. Otra forma de verlo es que estos son los valores utilizados para comunicarse entre los módulos. Si encuentra que una gran parte de su estado debe ser global, es posible que no haya particionado bien el sistema en módulos.

    Un ejemplo de estado global podría ser valores A / D filtrados finales. Las señales analógicas se leen más rápido de lo que se necesitan los resultados en el módulo AD. Esto aplica dos polos de filtrado de paso bajo a cada señal y también posiblemente alguna escala. El estado del filtro es privado para el módulo AD con solo los valores finales filtrados y escalados declarados como globales, ya que estos son todo lo que el resto del sistema necesita conocer.

    Normalmente uso el banco de acceso para el estado global. Esto debería ser una colección de variables individuales de varios módulos, por lo que generalmente se ajusta fácilmente. Si necesita exportar buffers enteros por alguna razón, entonces estos deben estar en su propia sección, y por supuesto, cualquier otro módulo que acceda a dichos buffers debe saber que no están en el banco de acceso.

  2. locales estáticos. Estos contienen los valores que deben ser persistentes, pero son privados para los módulos individuales. En el ejemplo anterior, los valores de filtro intermedio estarían en esta categoría. Deben permanecer entre las llamadas al módulo, o se utilizan para comunicarse entre las rutinas del módulo que se pueden llamar por separado.

    Por lo general, los pongo en la memoria almacenada, con todo el estado local de un módulo en el mismo banco. Defino la constante LBANK (banco local) en la parte superior del módulo para definir en qué banco estarán las variables locales de ese módulo. Esto se garantiza definiendo las secciones del vinculador en el archivo del vinculador .BANKn donde N es el número del banco. Luego, en el código, las variables se definen en los bancos nombrados para garantizar que el vinculador los coloque allí. Conocer el banco en el momento de la creación es útil para permitir una administración inteligente de los bancos.

  3. Dinámico. Mantengo una pila de datos y uso FSR2 como puntero de pila. Con la elección correcta del diseño de la pila, empujar y abrir bytes de RAM se puede hacer con instrucciones simples. Envuelvo estos en macros push y pop para que no tenga que pensar en la mecánica de la pila cada vez y hacer que el código sea más legible.

    C18 hace esto con lo que se denomina variables "automáticas" en C. Sin embargo, mientras que C18 parece generar un código confiable, su elección de manejo de memoria solo se puede llamar muerte cerebral en el mejor de los casos. Solo hay 3 FSRs, haciéndolos preciosos. C18 toma dos de estos para su propio uso, dejando la aplicación solo con FSR0. C18 tiene una pila de datos, pero increíblemente, el diseño de la pila se elige de modo que el empuje y el pop no sean instrucciones simples. También tiene un modelo de paso de argumentos de limpieza de llamadas, que consume memoria innecesariamente para la mayoría de las llamadas de subrutinas normales, pero eso es una digresión para otro día.

  4. Registros generales. Normalmente defino los primeros 16 bytes de memoria como globales llamados REG0-REG15. Dado que estos se encuentran en el banco de acceso, se pueden utilizar de forma muy similar a los registros generales de otras máquinas. Estos son los caballos de batalla del estado temporal. También los uso para argumentos de subrutinas y devuelvo valores la mayor parte del tiempo. Por ejemplo, la subrutina UART_PUT envía el byte en REG0 y la subrutina UART_GET devuelve el siguiente byte recibido en REG0. Las subrutinas generalmente las conservan, excepto aquellas en las que devuelven datos de manera explícita. Estos son los valores temporales que se usan internamente para las subrutinas. Para ayudar a las subrutinas a preservarlas, tengo macros para que enumere el conjunto de estos registros generales que una subrutina eliminará en un lugar en la definición de subrutina. Estos se insertan automáticamente en la pila de datos en la entrada de subrutina y se colocan justo antes de la devolución.

respondido por el Olin Lathrop

Lea otras preguntas en las etiquetas