Electronic – Absolute address of a function in Microchip XC16

compilermicrochipmicrocontrollerpic

Device: dsPIC33FJ128GP802

I have some *.s files as follows

.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

I have declared the same in a *.h

extern void D1(void);

Now I am passing the D1 to a table read function

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

My problem is that, if the address of D1 is above 0X8000, the routine is not correct. I tried large and small code models, but the result is the same.
I think this is due to the 16 bit limitation of the pointers
Is there any method to access the absolute address of D1 directly from the code. May be something like built in function or macros.

Best Answer

The data you're describing (full 24-bit use of program memory to store data) cannot be defined and initialized in C, and cannot read directly via C; the only way to access it is by encapsulating in a C-callable assembly function or an intrinsic.

There are really two questions here:

  1. how to play nicely with the compiler, assembler, and linker, so that when you define your 24-bit data in an assembly file as relocatable data with a symbolic name D1, rather than unnamed data at a fixed address, the compiler can see this variable to determine its address

  2. how to access the data

The 2nd question (how to access the data) is answered for 33EP parts in DS70613C and should be answered for 33FJ parts in DS70204C (but the examples in the 33FJ manual only use the low 16 bits). Here's an example code snippet from the 33EP reference manual that works for 33EP parts + should for 33FJ (I don't have a 33FJ device easily available):

(note: code uses int, whereas it would be better to use uint16_t and #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)
        ;
}

You'll note that the builtin functions __builtin_tblrdl() and __builtin_tblrdh() are used to read the low and high 16-bit words of data from a program memory location, and __builtin_tblpage() and __builtin_tbloffset() can be used to extract the page and offset of the address. In this particular example, the highWord array is always 0, and the lowWord array matches the prog_data defined and initialized in C.

Please note no pointers are used here! Although it is possible to use normal variables that are tagged with const, so that they are located by the linker in read-only program space, and so that you can read the memory using standard C pointer techniques, with the compiler automatically managing the paging registers for you, you can only store 16-bit data. You need to access the TBLRDL and TBLRDH builtin functions to get all 24 bits of data.

As for how to play nicely with the compiler/linker/etc, you have to fool the compiler and tell it it's only seeing 16-bit data. Here's an example that worked to get at the variable D1 declared elsewhere:

#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);

This does correctly read 24-bit values and stores them in the bottom 24 bits of a uint32_t. The extern D1 variable declared in C is a dummy variable that is only used to get at the starting address by taking advantage of the way the compiler/assembler/linker work together. The builtin functions handle the rest of the work.

What I don't know is how to automatically obtain the size of the data, since it's defined + initialized in assembly.