8
\$\begingroup\$

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.

\$\endgroup\$
12
  • \$\begingroup\$ I've never used the dsPIC series, but any reason you can't use a C const array instead of the assembler? I think there's options to place that at specific code locations (if you need to for some other reason). Just thinking maybe some compiler optimization shortens the pointers because it's not expecting to be referencing data at the higher memory locations. \$\endgroup\$
    – PeterJ
    Feb 2 '13 at 6:33
  • \$\begingroup\$ Yes, the compiler manual says all pointers including function pointers are of 16 bit. I am looking for some other method to access the memory address. If we use const array, we cann't use the upper byte of a 3 byte word in dsPIC. Another reason is that, the assembly file is generated by a computer software provided by microchip to compress speech data. \$\endgroup\$ Feb 2 '13 at 6:41
  • \$\begingroup\$ Related question: electronics.stackexchange.com/questions/56058/… (although this is for PIC, dsPIC probably works the same) \$\endgroup\$
    – user17592
    Feb 2 '13 at 7:52
  • \$\begingroup\$ Is the data at D1 supposed to represent a function or an array of data? \$\endgroup\$
    – The Photon
    Feb 3 '13 at 6:56
  • 2
    \$\begingroup\$ @Saneesh So answer my questions. Is this code or is it data? If it's code, the system doesn't support it beyond the 16-bit address space, so what you're trying to do is impossible no matter how you express it. If it's data, please say so, and please try addressing it as const short D1[]. \$\endgroup\$
    – user207421
    Feb 6 '13 at 9:40
4
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

Don't convert it to unsigned long and back. Asking for trouble. You're basically lying to the compiler. The correct declaration for nowPlaying.file1 is

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

And similarly for function():

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

and remove all the typecasts.

Or, if @PeterJ suggests, it is data, it should be declared as extern short D1[] in both places: and you don't really need the assembler; you could have declared it all in C as const short D1[] = {...}; The compiler should put it into the code segment as it is const.

\$\endgroup\$
7
  • \$\begingroup\$ Unless I'm miss-reading something D1 isn't a pointer to a function, it's data being stored in code space. \$\endgroup\$
    – PeterJ
    Feb 2 '13 at 12:47
  • 1
    \$\begingroup\$ @PeterJ Then it shouldn't be declared as external void D1(void), it should be defined as extern short D1[]. None of this convinces me that the question doesn't belong on SO. \$\endgroup\$
    – user207421
    Feb 2 '13 at 23:47
  • \$\begingroup\$ What the OP is talking about cannot be expressed in C at all, it needs to be encapsulated by a C-callable assembly function or an intrinsic. \$\endgroup\$
    – Jason S
    Mar 4 '13 at 22:17
  • \$\begingroup\$ @JasonS There's no evidence of that in the question, and the OP has failed to clarify. \$\endgroup\$
    – user207421
    Mar 5 '13 at 21:19
  • \$\begingroup\$ Yes there is, if you're familiar with the PIC33F / PIC33E architectures. \$\endgroup\$
    – Jason S
    Mar 6 '13 at 2:58
1
\$\begingroup\$

It seems like the simple answer is to write the subroutine in assembler. If I remember right, C30 doesn't access program memory as data using 24 bit pointers. At best it can access program memory thru the PSV window, but then you can only see the low 16 bits of each 24 bit program memory word.

If would be very simple to write a assembler routine that is callable from C30 that returns the 24 bits of data given a 24 bit program memory address. However, is your data a collection of 24 bit values or really a list of bytes that happen to be packed 3 per word? If the latter, then it's even easier. Write a assembler routine that gives you a byte-addresses view of program memory. The address would still need to be 24 bits, but the data values now only 8 bits.

Or just write the whole routine in assembler. If you're doing this kind of low level byte banging and memory packing, it is probably easier. In assembler you can just do what you want in the way the machine wants to do it. In C you have to figure out what incantations to mutter to the compiler to have it write the machine code for you. Sometimes it just easier to do it yourself directly. The dsPIC architecture is particularly easy to write assembly code for, definitely easier than a PIC 16.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ TBLRDL and TBLRDH are the key here, whether you use assembly or the __builtin_tblrdX() functions. I agree with you + got a kick out of your phrasing "In C you have to figure out what incantations to mutter to the compiler". Ironically, though, if you're really trying to squeeze maximum performance, sometimes the __builtin() functions are better, because the compiler can optimize the way it generates code, whereas it has to treat hand-coded assembly functions as black boxes it can't alter. \$\endgroup\$
    – Jason S
    Mar 6 '13 at 3:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.