Author Topic: Can I "claim" a WREG in my program to persist in every context? PIC24F & XC16  (Read 4828 times)

0 Members and 1 Guest are viewing this topic.

Offline aurmerTopic starter

  • Regular Contributor
  • *
  • Posts: 84
  • Country: us
  • Insert text here.
Programmer's Reference
PIC24F being used
Compiler's Reference


So I have a ridiculously small program to be run (probably don't need all my WREGs). It basically sits in a while(1){} until one of a few interrupts hit. Then I use a few bit-flags in each interrupt to decide where to go from there. These bit-wide flags are relevant in almost every code context.

My question is this. Is there a way to tell the compiler to ALWAYS leave WREG13 (arbitrary number choice) alone when pushing a context on the stack? Basically, I want to claim WREG13 so that the compiler will NEVER use it when it interprets my C code.

EDIT:

I am trying to save cycles in my interrupts. That is the purpose of this. It would be nice if I could just store this information in the same place that I can operate on it.

Alternatively, are there any other SFR's which can be ALU operands?
« Last Edit: September 29, 2016, 02:54:54 pm by zetharx »
If I just asked the wrong question, shame on me for asking before I was ready for help. Please be kind and direct me to a resource which will teach me the question I SHOULD be asking. Thank you.
 

Offline JPortici

  • Super Contributor
  • ***
  • Posts: 3452
  • Country: it
going by memory here..
ALU can operate on any register for some instructions like bit manipulation/test or applying an operand to a register...
b = a + b can be coded as
Code: [Select]
MOV  _a,W0
ADD _b
instead of loading both registers to the accumulators, performing the operation and moving them back to ram

in any case forget about W0 to W3.
W0 for obvious reasons
W0-W3 are used as shadow registers for fast context switching
they also hold the result of multiplication/division
W4-W11 are used by the DSP instructions
W14-W15 are used by stack/frame pointers

so that leaves out W12 and W13.
I should take a look at a couple of projects and look at the disassembly but i feel safe to say that they were never used. in the compiler's folder you should have a bunch of documents that will explain in detail the C/asm translation.
« Last Edit: September 29, 2016, 05:02:19 pm by JPortici »
 

Offline DJohn

  • Regular Contributor
  • *
  • Posts: 103
  • Country: gb
I have no experience with PIC, and haven't tried this.  But have a look at Appendix F of that compiler document.  It's possible to assign global variables to fixed registers, and the compiler won't use those registers for anything else.
Code: [Select]
register int *foo asm ("w8");
Then all you have to do is never use the variable 'foo' anywhere.

This will only affect code in compilation units that include this definition.  It won't prevent library functions from using your register.  If you've got source for the library you could recompile it.  If not, all I can think of is not using it at all.
 

Offline dannyf

  • Super Contributor
  • ***
  • Posts: 8221
  • Country: 00
Is there a register attribute?
================================
https://dannyelectronics.wordpress.com/
 

Offline aurmerTopic starter

  • Regular Contributor
  • *
  • Posts: 84
  • Country: us
  • Insert text here.
Thank you for your responses. DJohn, Appendix F says I can define a "global register variable" just like you mentioned.

Code: [Select]
register int *foo asm ("w8");
Assigning this (at least the document claims) permanently for the duration. This is great. I will give it a shot.

I am also a bit weary because this attribute is under the section called "Depreciated Features". Hopefully it is still supported.
If I just asked the wrong question, shame on me for asking before I was ready for help. Please be kind and direct me to a resource which will teach me the question I SHOULD be asking. Thank you.
 

Offline Howardlong

  • Super Contributor
  • ***
  • Posts: 5315
  • Country: gb
If your ISRs are simple then you can use the shadow register set, and set them at the same priority so they serialise and don't try to re-use the shadow register set.

This assumes that your ISRs are simple and don't use anything beyond W0-W3 (from memory that is the shadow register subset).
 

Offline aurmerTopic starter

  • Regular Contributor
  • *
  • Posts: 84
  • Country: us
  • Insert text here.
This is very strange.

Code: [Select]
register int *foo asm ("w13");

This effectively puts my information into W13 like I wanted, but I wanted to use the contents of W13 in every context of the program... including all the ISR's.

I am looking at the assembly generated by my compiler, and the compiler is pushing and popping W13.

I want to suppress all pushing and popping and utilization of W13. I want to tell the compiler that W13 is "taken" and "relevant" always and that it doesn't have permission to to even put it on the stack temporarily.

This "register" attribute is labeled as depreciated, but the user's manual doesn't specify what feature replaced it. Maybe the new feature can do what I want, but I haven't found anything that looks like it in Section 8.11 "Variable Attributes"

Does anyone have any ideas on how I can instruct the compiler to leave a working register alone no matter what?

Compiler User Guide


EDIT:

I think that I am one step closer, but it still doesn't work. I found the "-ffixed-reg" flag for the compiler which says that it will regard any working register as fixed and not use it in code, but even with this flag, my ISR's are pushing and popping that register which overwrites changes that I make to it during the ISR.
« Last Edit: October 01, 2016, 09:53:48 pm by zetharx »
If I just asked the wrong question, shame on me for asking before I was ready for help. Please be kind and direct me to a resource which will teach me the question I SHOULD be asking. Thank you.
 

Offline mikeselectricstuff

  • Super Contributor
  • ***
  • Posts: 13695
  • Country: gb
    • Mike's Electric Stuff
If the program is ridiculously small as you say, you could probably have written it in assembler in the time you've spend dicking around with compiler settings.

Maybe write it in C first, then take the output as your assembler source and tweak it as required.
Youtube channel:Taking wierd stuff apart. Very apart.
Mike's Electric Stuff: High voltage, vintage electronics etc.
Day Job: Mostly LEDs
 

Offline aurmerTopic starter

  • Regular Contributor
  • *
  • Posts: 84
  • Country: us
  • Insert text here.
For the matter of record and forum searches, I found a solution which works for me.

There is a function attribute "naked" which prevents all pushing and popping for that function.

Code: [Select]
void __attribute__((naked)) func();
Ideally I would like to find a way to prevent it for just specified registers, but hey, you can use "naked" and then manually push whatever you want at the beginning of your ISR.
If I just asked the wrong question, shame on me for asking before I was ready for help. Please be kind and direct me to a resource which will teach me the question I SHOULD be asking. Thank you.
 

Offline Howardlong

  • Super Contributor
  • ***
  • Posts: 5315
  • Country: gb
If the program is ridiculously small as you say, you could probably have written it in assembler in the time you've spend dicking around with compiler settings.

Maybe write it in C first, then take the output as your assembler source and tweak it as required.

I concur, what the OP is trying to do is sounding more and more like a hack, trying to bang a square peg into a round hole. I could certainly see maintenance being a nightmare.

Perhaps if the OP explains more about the context we can offer a different solution. At the moment it sounds like the OP wants to use a register as a global volatile variable, but we don't know why.
 

Offline hans

  • Super Contributor
  • ***
  • Posts: 1626
  • Country: nl
Quote
13.8    FUNCTION CALL CONVENTIONS
When calling a function:
•  Registers W0-W7 are caller saved. The calling function must  preserve these values before the function call if their value is required subsequently from the function call. The stack is a good place to preserve these values.
•  Registers W8-W14 are callee saved. The function being called must save any of these registers it will modify.
•  Registers W0-W4 are used for function return values.
•  Registers W0-W7 are used for argument transmission.
•  DBRPAG/PSVPAG should be preserved if the  -mconst-in-code (auto_psv)  memory model is being used.
Quote
13.8.3 Preserving Registers Across Function Calls

The compiler arranges for registers W8-W15 to be preserved across ordinary function calls. Registers W0-W7 are available as scratch registers. For interrupt functions, the compiler arranges for all necessary registers to be preserved, namely W0-W15 and RCOUNT.

So it seems reserving W8-W13 is your best bet (this part doesn't mention about stack and frame pointers), but then you need to tell XC16 that W13 is a "global" throughout the program. I suspect it's quite hard to tell GCC that, it's almost like defining a custom call convention.

Defining naked functions everywhere is nasty stuff. Not only do you need to push and pop all W0-W7 as a precaution (probably taking more CPU time), you also shouldn't forget to add "return" and "refie" statements  at the end of your code. This almost completely neglects the purpose of writing in C, namely it being a "portable assembler" and not having to worry about all of this stuff. I think "reserving" W13 is by definition not something that is that 'portable', and probably not supported at all.

I think you're best off writing this project in assembler.
 

Offline aurmerTopic starter

  • Regular Contributor
  • *
  • Posts: 84
  • Country: us
  • Insert text here.
Thanks for your comments and thoughts.

Here is some more detail, and also what I ended up doing in the end...

I have 72 LED's charlieplexed on 9 pins.
I have to light 3 LED's at a time (which just means that I need to time-share the bus for 3 different lights).
I have to include software-implemented PWM to the LED's so that they can be variable brightness (depending on what I read on an ambient light sensor).

So this is my math.

3 LEDs * 60Hz * 8 (PWM resolution) = 1440 ISR calls per second

So naturally, I would like to minimize the ISR time.

I have not really determined that I need to go this far with minimizing ISR instruction count, but hey, I am a student, and I want to learn some new things... like what would it take to go to the extreme?

I over-simplified the question to begin with because the answer to my original question also answers my reality.

So the options which I am exploring are these.
- Minimize ISR by making sure that the pointer to my LED-register-data is always in a working register
- I COULD control the LED brightness by automating a variable in-series resistance (I am exploring Voltage-Controlled Resistors through use of JFET). This solution would take the software-implemented PWM off my back.

If anyone made it to the end of this, I definitely would appreciate the "you missed this obvious solution" response. =)

EDIT:

In the end, I just have the one single timer interrupt service routine set to "naked". As long as that ISR is simple, only using w0 and w1, "naked function" and "register variable" is a viable solution. I am not using libraries, or any pre-compiled code. This is a standalone project. I would hardly consider this a hack because (from my limited exposure) reserving a working register throughout a program's operation would be considered a standard practice in assembly.

Though I understand I am just trying to tell my compiler, "Hey, my program will never get so complex that you need all W0-W13 at any time in order to maintain efficiency so give me W13 and never utilize it, push it, or pop it." I still have not found that directive/instruction. So I suppose my use of "naked" is a workaround for that.
« Last Edit: October 02, 2016, 03:20:08 pm by zetharx »
If I just asked the wrong question, shame on me for asking before I was ready for help. Please be kind and direct me to a resource which will teach me the question I SHOULD be asking. Thank you.
 

Offline hans

  • Super Contributor
  • ***
  • Posts: 1626
  • Country: nl
Is the PWM resolution 8 steps or 8-bits? Because 8-bits is 256 steps.

At 1440 isr/s optimizing 4 cycles from an ISR is not really worth much. 1440x4 cycles = 5760 cycles saved each second, and on a 16MIPS CPU that's 0.036%. Designing on such small margins that could break your application is very tedious (wont say it can't be engineered - look at the people that bitbang VGA ports from the 70MIPS PIC24s)

But I'd rather think saving "cycles" from an ISR is desired at like tens of thousands to hundreds of thousands ISR/s. Like want to save 1% CPU time = 160k cycles, save 4 cycles at a time = 40k isr/s. And as you can see, C is a wonderful language for writing application logic code, but at this 'assembler' and 'inside the CPU' level it becomes limited/tedious.

So if the PWM resolution is really 256 steps (46k isr/s), then sure this could make sense if you absolutely want that 1% CPU load back. But if your ISR code is like 100 cycles long, you're spending ~4.6 million instructions per second inside ISR code, which is much more significant.
 

Offline aurmerTopic starter

  • Regular Contributor
  • *
  • Posts: 84
  • Country: us
  • Insert text here.
My use of W13 as variable storage reduces every manipulation of that variable from

asm mov.w //move the variable into a WREG
asm inc  //operate on that variable
asm mov.w //move the new value back into its place in memory

down to just one

asm inc  //operate on the variable

I manipulate this variable a few times in the ISR, so that alone certainly saves more than 4 instructions when you then add the "naked" attribute.

This being said, thank you for bringing it into perspective of howmuch CPU bandwidth I am actually "saving". I understand that the magnitude that I am working on, while it seems to me like 1k interrupts per second is significant, it is actually not significant compared to the capability of the chip.
If I just asked the wrong question, shame on me for asking before I was ready for help. Please be kind and direct me to a resource which will teach me the question I SHOULD be asking. Thank you.
 

Offline Howardlong

  • Super Contributor
  • ***
  • Posts: 5315
  • Country: gb
As Hans suggests, 1440 interrupts/s is bugger all in the embedded world.

If, however, you need 256 rather than 8 levels then it's more like 46k interrupts/s which is a bit more demanding but still doable and not at all extreme assuming the ISR is simple.

Does your PIC24 have DMA? You might not need interrupts at all.

 

Offline aurmerTopic starter

  • Regular Contributor
  • *
  • Posts: 84
  • Country: us
  • Insert text here.
It does have DMA, but I am not familiar with how this could eliminate the need for interrupts?
If I just asked the wrong question, shame on me for asking before I was ready for help. Please be kind and direct me to a resource which will teach me the question I SHOULD be asking. Thank you.
 

Offline mikeselectricstuff

  • Super Contributor
  • ***
  • Posts: 13695
  • Country: gb
    • Mike's Electric Stuff
As you're effectively only lighting one LED at a time you don't need to use PWM - just turn each on for a set amount of time ( timer int with variable period) and add a gap as required for dimming.
So at, say 100hz, that's one int to turn each on and one to turn it off, so 600 ints per second. No need to be messing about with registers.


Youtube channel:Taking wierd stuff apart. Very apart.
Mike's Electric Stuff: High voltage, vintage electronics etc.
Day Job: Mostly LEDs
 

Offline aurmerTopic starter

  • Regular Contributor
  • *
  • Posts: 84
  • Country: us
  • Insert text here.
I see, so the T1Interrupt that I am using can simply be run at the frequency needed for persistence of vision for 3 LEDs "at once"... and each time it turns on a new LED, it also enables T2Interrupt to start ticking down to then turn that LED off.
Then, all I need to do to make sure I don't break my program is never allow the T2Interrupt to be slower than my T1Interrupt  :P
If I just asked the wrong question, shame on me for asking before I was ready for help. Please be kind and direct me to a resource which will teach me the question I SHOULD be asking. Thank you.
 

Offline Howardlong

  • Super Contributor
  • ***
  • Posts: 5315
  • Country: gb
So you can prepare up front a series of values to successively set to the TRIS and LAT registers into a couple of buffers, and set up two DMA channels, one for TRIS and one for LAT.

Instead of using a timer to trigger yout ISR, use the timer to act as the DMA event for both channels.

While the buffer is automatically being emptied into your LED array, you can prepare the next buffers. Once the DMA has finished, you can restart the DMA again but with the new buffers you've just prepared.

Then switch back to preparing the first DMA buffers, rinse and repeat.

I'll grant you that DMA is hardly a beginner's concept, but it can be very powerful.

Typically, rather than having two pairs of buffers on the PIC24 you'd have a pair of large ones and use the HALFIF and DONEIF bits with the channels set to repeated operation to switch which halves needs populating while the other halves are pumped out.

Oh, and listen to Mike, this stuff is his bread and butter.
 

Offline mikeselectricstuff

  • Super Contributor
  • ***
  • Posts: 13695
  • Country: gb
    • Mike's Electric Stuff
I see, so the T1Interrupt that I am using can simply be run at the frequency needed for persistence of vision for 3 LEDs "at once"... and each time it turns on a new LED, it also enables T2Interrupt to start ticking down to then turn that LED off.
Then, all I need to do to make sure I don't break my program is never allow the T2Interrupt to be slower than my T1Interrupt  :P
You only need one timer, alternately turning on & off (maybe use a state machine) just reload the PRx value each time, though if you already use T1 for other stuff, a second timer would be the way to do it.
« Last Edit: October 02, 2016, 07:06:18 pm by mikeselectricstuff »
Youtube channel:Taking wierd stuff apart. Very apart.
Mike's Electric Stuff: High voltage, vintage electronics etc.
Day Job: Mostly LEDs
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf