Can I "claim" a WREG in my program to persist in every context? PIC24F & XC16  - Page 1
Forum section: electronics / general

author: aurmer
author_rank: Regular Contributor
timestamp: September 29, 2016, 02:48:51 pm
content: Programmer's ReferencePIC24F being usedCompiler's ReferenceSo 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?

author: JPortici
author_rank: Super Contributor
timestamp: September 29, 2016, 04:59:30 pm
content: 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 asCode:MOV _a,W0ADD _binstead of loading both registers to the accumulators, performing the operation and moving them back to ramin any case forget about W0 to W3.W0 for obvious reasonsW0-W3 are used as shadow registers for fast context switchingthey also hold the result of multiplication/divisionW4-W11 are used by the DSP instructionsW14-W15 are used by stack/frame pointersso 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.

author: DJohn
author_rank: Regular Contributor
timestamp: September 29, 2016, 06:41:27 pm
content: 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: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.

author: dannyf
author_rank: Super Contributor
timestamp: September 29, 2016, 07:18:10 pm
content: Is there a register attribute?

author: aurmer
author_rank: Regular Contributor
timestamp: September 29, 2016, 08:07:41 pm
content: Thank you for your responses. DJohn, Appendix F says I can define a "global register variable" just like you mentioned.Code: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.

author: Howardlong
author_rank: Super Contributor
timestamp: September 29, 2016, 08:29:09 pm
content: 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).

author: aurmer
author_rank: Regular Contributor
timestamp: October 01, 2016, 09:36:43 pm
content: This is very strange.Code: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 aloneno matter what?Compiler User GuideEDIT: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.

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: October 01, 2016, 10:00:58 pm
content: 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.

author: aurmer
author_rank: Regular Contributor
timestamp: October 01, 2016, 10:57:14 pm
content: 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:void __attribute__((naked)) func();Ideally I would like to find a way to prevent it forjust specified registers, but hey, you can use "naked" and then manually push whatever you want at the beginning of your ISR.

author: Howardlong
author_rank: Super Contributor
timestamp: October 02, 2016, 08:53:06 am
content: Quote from: mikeselectricstuff on October 01, 2016, 10:00:58 pmI 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.

author: hans
author_rank: Super Contributor
timestamp: October 02, 2016, 10:49:35 am
content: QuoteQuoteSo 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.

author: aurmer
author_rank: Regular Contributor
timestamp: October 02, 2016, 03:08:18 pm
content: 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 secondSo naturally, I would like to minimize the ISR time.I have not really determined that Ineedto 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.

author: hans
author_rank: Super Contributor
timestamp: October 02, 2016, 03:18:21 pm
content: 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.

author: aurmer
author_rank: Regular Contributor
timestamp: October 02, 2016, 03:26:21 pm
content: My use of W13 as variable storage reduces every manipulation of that variable fromasm mov.w //move the variable into a WREGasm inc //operate on that variableasm mov.w //move the new value back into its place in memorydown to just oneasm inc //operate on the variableI 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.

author: Howardlong
author_rank: Super Contributor
timestamp: October 02, 2016, 03:45:47 pm
content: 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.

author: aurmer
author_rank: Regular Contributor
timestamp: October 02, 2016, 03:49:43 pm
content: It does have DMA, but I am not familiar with how this could eliminate the need for interrupts?

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: October 02, 2016, 03:54:23 pm
content: 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.

author: aurmer
author_rank: Regular Contributor
timestamp: October 02, 2016, 04:05:13 pm
content: 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

author: Howardlong
author_rank: Super Contributor
timestamp: October 02, 2016, 04:08:02 pm
content: 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.

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: October 02, 2016, 04:26:24 pm
content: Quote from: zetharx on October 02, 2016, 04:05:13 pmYou 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.