Dirección absoluta de una función en Microchip XC16

Dispositivo: dsPIC33FJ128GP802

Tengo algunos archivos *.s de la siguiente manera

.global _D1
.section .speex, code
_D1:
.pword 0x66C821,  0x1B0090,  0xD96C36,  0x9B60B0,  0xDD4E36,  0xBF4E53
.pword 0xD1098B,  0x719BD9,  0x873989,  0x003B69,  0x279035,  0xED4244
.pword 0xE1403C,  0x54D439,  0x826550,  0xC59627,  0xDD0432,  0x88FA29

He declarado lo mismo en un *.h

extern void D1(void);

Ahora estoy pasando el D1 a una función de lectura de tabla

nowPlaying.file1 = (unsigned long) D1;
function(nowPlaying.file1);

Mi problema es que, si la dirección de D1 está por encima de 0X8000, la rutina no es correcta. Probé modelos de código grandes y pequeños, pero el resultado es el mismo. Creo que esto se debe a la limitación de 16 bits de los punteros. ¿Hay algún método para acceder a la dirección absoluta de D1 directamente desde el código? Puede ser algo así como funciones integradas o macros.

Nunca he usado la serie dsPIC, pero ¿hay alguna razón por la que no pueda usar una matriz C const en lugar del ensamblador? Creo que hay opciones para colocar eso en ubicaciones de código específicas (si es necesario por alguna otra razón). Solo pensando que tal vez alguna optimización del compilador acorta los punteros porque no espera estar haciendo referencia a datos en las ubicaciones de memoria más altas.
Sí, el manual del compilador dice que todos los punteros, incluidos los punteros de función, son de 16 bits. Estoy buscando algún otro método para acceder a la dirección de memoria. Si usamos la matriz const, no podemos usar el byte superior de una palabra de 3 bytes en dsPIC. Otra razón es que el archivo de ensamblaje es generado por un software de computadora proporcionado por un microchip para comprimir datos de voz.
Pregunta relacionada: electronics.stackexchange.com/questions/56058/… (aunque esto es para PIC, dsPIC probablemente funcione igual)
¿Se D1supone que los datos representan una función o una matriz de datos?
¿Por qué crees que necesitas otra forma de acceder a la dirección de memoria? ¿Qué tiene de malo el proporcionado en el idioma? es decir, D1?
@EJP Por favor, encuentre el segundo comentario de esta pregunta. En realidad, el compilador solo admite punteros de longitud 16.
@Saneesh Entonces responde mis preguntas. ¿Es este código o son datos? Si es un código, el sistema no lo admite más allá del espacio de direcciones de 16 bits, por lo que lo que intenta hacer es imposible sin importar cómo lo exprese. Si se trata de datos, dígalo e intente abordarlos como const short D1[].
La pregunta está fuera de tema: pertenece a stackOverflow.com. Implica la sintaxis del compilador, la semántica de declaración de tipos, etc., todo lo cual requiere la atención de los escritores de compiladores o los abogados del lenguaje C. Observo que se ha eliminado toda la discusión aquí y en meta que afirma lo contrario.
@EJP Una vez que el diseño integrado SE despegue, sería el foro ideal para este tipo de preguntas. Además, no creo que "toda" la discusión re: incrustada haya sido eliminada. Votado negativo, tal vez.
@Madmanguruman Se eliminó de aquí y de meta. Si hay algún otro lugar del que se ha hablado, nunca lo encontré. No veo por qué SO no es 'ideal' o simplemente el más apropiado de todos los lugares que se han mencionado, y como la única persona que realmente respondió la pregunta en cualquiera de esos lugares, me siento considerablemente más derecho que la mayoría a decirlo.
@EJP Revisé meta.electronics.stackexchange.com/questions/2622/… y su hilo de respuesta todavía está allí. Ha sido votado negativo, pero no eliminado.
¿Sería apropiado cambiar el título de esta pregunta a "Cómo paso la dirección absoluta de datos en flash a una función en Microchip XC16"?

Respuestas (3)

Los datos que está describiendo (uso completo de 24 bits de la memoria del programa para almacenar datos) no se pueden definir e inicializar en C, y no se pueden leer directamente a través de C; la única forma de acceder a él es encapsulando en una función de ensamblaje invocable por C o intrínseca.

Realmente hay dos preguntas aquí:

  1. cómo jugar bien con el compilador, el ensamblador y el enlazador, de modo que cuando defina sus datos de 24 bits en un archivo de ensamblaje como datos reubicables con un nombre simbólico D1, en lugar de datos sin nombre en una dirección fija, el compilador puede ver esta variable para determinar su dirección

  2. como acceder a los datos

La segunda pregunta (cómo acceder a los datos) se responde para piezas 33EP en DS70613C y debería responderse para piezas 33FJ en DS70204C (pero los ejemplos en el manual 33FJ solo usan los 16 bits bajos). Aquí hay un fragmento de código de ejemplo del manual de referencia 33EP que funciona para piezas 33EP + debería para 33FJ (no tengo un dispositivo 33FJ fácilmente disponible):

(nota: el código usa int, mientras que sería mejor usar uint16_ty #include <stdint.h>)

int prog_data[10] __attribute__((space(prog))) =
  {0x0000, 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x8888, 0x9999};

unsigned int lowWord[10], highWord[10];
unsigned int tableOffset, loopCount;

int main(void){
    TBLPAG = __builtin_tblpage (prog_data);
    tableOffset = __builtin_tbloffset (prog_data);
    /* Read all 10 constants into the lowWord and highWord arrays */
    for (loopCount = 0; loopCount < 10; loopCount ++)
    {
        lowWord[loopCount] = __builtin_tblrdl (tableOffset);
        highWord[loopCount] = __builtin_tblrdh (tableOffset);
        tableOffset +=2;
    }
    while(1)
        ;
}

Notará que las funciones integradas __builtin_tblrdl()y __builtin_tblrdh()se usan para leer las palabras de datos de 16 bits alto y bajo desde una ubicación de memoria de programa, y __builtin_tblpage() and __builtin_tbloffset()​​se pueden usar para extraer la página y el desplazamiento de la dirección. En este ejemplo particular, la matriz highWord siempre es 0 y la matriz lowWord coincide con prog_data definido e inicializado en C.

Tenga en cuenta que aquí no se utilizan punteros. Aunque es posible usar variables normales que están etiquetadas con const, para que el enlazador las ubique en el espacio del programa de solo lectura, y para que pueda leer la memoria usando técnicas de puntero C estándar, con el compilador administrando automáticamente los registros de paginación para usted, solo puede almacenar datos de 16 bits. Debe acceder a las funciones integradas TBLRDL y TBLRDH para obtener los 24 bits de datos.

En cuanto a cómo jugar bien con el compilador/enlazador/etc., debe engañar al compilador y decirle que solo ve datos de 16 bits. Aquí hay un ejemplo que funcionó para llegar a la variable D1 declarada en otro lugar:

#define D1_SIZE 18
extern uint16_t __attribute__((space(prog))) D1[D1_SIZE];

#define READ_DATA(dst, v, len) readData(dst, __builtin_tblpage(v), __builtin_tbloffset(v), len)
void readData(uint32_t *pdst, uint16_t page, uint16_t offset, uint16_t len)
{
    TBLPAG = page;
    while (len-- > 0)
    {
        uint16_t lo = __builtin_tblrdl (offset);
        uint16_t hi = __builtin_tblrdh (offset);
        *pdst++ = (((uint32_t)(hi)) << 16) | ((uint32_t)(lo));
        offset += 2;
    }
}

...

uint32_t d1copy[D1_SIZE];
READ_DATA(d1copy, D1, D1_SIZE);

Esto lee correctamente los valores de 24 bits y los almacena en los 24 bits inferiores de un uint32_t. La variable externa D1 declarada en C es una variable ficticia que solo se usa para llegar a la dirección de inicio aprovechando la forma en que el compilador/ensamblador/enlazador trabajan juntos. Las funciones integradas manejan el resto del trabajo.

Lo que no sé es cómo obtener automáticamente el tamaño de los datos, ya que está definido + inicializado en ensamblaje.

No lo convierta a unsigned long and back. Pidiendo problemas. Básicamente le estás mintiendo al compilador. La declaración correcta para nowPlaying.file1 es

struct
{
    // other stuff ...
    void (*D1)(void);
    // other stuff ...
} nowPlaying;

Y de manera similar para la función():

extern void function(void (*file)(void));

y eliminar todos los encasillamientos.

O, si @PeterJ sugiere, son datos, debe declararse como extern short D1[] en ambos lugares: y realmente no necesita el ensamblador; podrías haberlo declarado todo en C como const short D1[] = {...}; El compilador debe ponerlo en el segmento de código, ya que es constante.

A menos que esté leyendo algo mal, D1 no es un puntero a una función, son datos que se almacenan en el espacio del código.
@PeterJ Entonces no debería declararse como external void D1 (void), debería definirse como extern short D1[]. Nada de esto me convence de que la pregunta no pertenezca a SO.
Lo que el OP está hablando no se puede expresar en C en absoluto, debe ser encapsulado por una función de ensamblaje invocable por C o un intrínseco.
@JasonS No hay evidencia de eso en la pregunta, y el OP no ha podido aclarar.
Sí, si está familiarizado con las arquitecturas PIC33F / PIC33E.
@JasonS Exactamente como dije: no hay evidencia en la pregunta en sí.
....? OP mencionó que su dispositivo era un dsPIC33FJ128GP802, que proporciona pistas de contexto aquí. No es una pregunta general de C; de lo contrario, definitivamente sería apropiado para SO.

Parece que la respuesta simple es escribir la subrutina en ensamblador. Si mal no recuerdo, C30 no accede a la memoria del programa como datos usando punteros de 24 bits. En el mejor de los casos, puede acceder a la memoria del programa a través de la ventana PSV, pero solo puede ver los 16 bits bajos de cada palabra de memoria del programa de 24 bits.

Si sería muy simple escribir una rutina de ensamblador que se pueda llamar desde C30 que devuelva los 24 bits de datos dada una dirección de memoria de programa de 24 bits. Sin embargo, ¿son sus datos una colección de valores de 24 bits o realmente una lista de bytes que están empaquetados 3 por palabra? Si es lo último, entonces es aún más fácil. Escriba una rutina de ensamblador que le proporcione una vista de direcciones de bytes de la memoria del programa. La dirección aún necesitaría ser de 24 bits, pero los valores de datos ahora solo son de 8 bits.

O simplemente escriba toda la rutina en ensamblador. Si está haciendo este tipo de golpes de bytes de bajo nivel y empaquetado de memoria, probablemente sea más fácil. En ensamblador puedes hacer lo que quieras de la forma en que la máquina quiere hacerlo. En C, debe averiguar qué encantamientos murmurar al compilador para que escriba el código de la máquina por usted. A veces es más fácil hacerlo usted mismo directamente. La arquitectura dsPIC es particularmente fácil de escribir código ensamblador, definitivamente más fácil que un PIC 16.

TBLRDL y TBLRDH son la clave aquí, ya sea que use ensamblaje o las __builtin_tblrdX()funciones. Estoy de acuerdo contigo + disfruté de tu frase "En C tienes que descubrir qué encantamientos murmurar al compilador". Sin embargo, irónicamente, si realmente está tratando de exprimir el máximo rendimiento, a veces las __builtin()funciones son mejores, porque el compilador puede optimizar la forma en que genera el código, mientras que tiene que tratar las funciones de ensamblaje codificadas a mano como cajas negras que no puede alterar. .