XC8 - how can I pass bits to and from a function - Page 1
Forum section: electronics / general

author: Delta
author_rank: Super Contributor
timestamp: November 09, 2015, 02:40:10 pm
content: I am currently reading and debouncing a pushbutton input inside my ISR (a 1ms timer tick).Here is my code - it works well, although I'm sure it's not the most elegant or efficient method.SW1_PIN is #defined to a port pinpb1 and pb1_edge are global bit variablesdebounce_timer is a gloabl unsigned charCode:// read switch inputsif (SW1_PIN == 0) // if pin pulled down by button press{debounce_timer++;if (debounce_timer == DEBOUNCE_TIME){if (!pb1) // if this is the the transit..{pb1_edge = 1 ; // then set the edge bit -it will be cleared elsewhere}pb1=1;debounce_timer--;}}else{debounce_timer = 0 ;pb1=0 ;}Now I would like to wrap this up in a function, so I can call it from the ISR multiple times when I add additional pushbuttons to the project. I'd like to be able to be able to call it something like:Code:// read and debounce button 1debounce(*pointer_to_pb1_bit *pointer_to_pb1_edge_bit *pointer_to_pb1_debounce_timer)However I think XC8 does not allow pointers to bits? Is there a way to do this?I know I could just copy/paste the code and change it for each button, but I'm trying to learn good practices...

author: Delta
author_rank: Super Contributor
timestamp: November 09, 2015, 04:24:36 pm
content: Hmmmmm,http://www.xargs.com/pic/c-faq.html#pinarraysays that you can't use pointers to bit variables, so is there no way to tell a function which bits you want it to modify?

author: c4757p
author_rank: Super Contributor
timestamp: November 09, 2015, 04:29:40 pm
content: No, there isn't. How would a pointer to a bit variable even work?You can, of course, do things like passing in the number of the bit or a bit-mask. For instance instead of trying to pass in a "pointer" to variable.bit, pass &variable and (1<<bit).

author: Delta
author_rank: Super Contributor
timestamp: November 09, 2015, 04:31:57 pm
content: Quote from: c4757p on November 09, 2015, 04:29:40 pmErm, the same as a pointer to any other variable?Surely it just needs to tell the function the address of the bit?

author: c4757p
author_rank: Super Contributor
timestamp: November 09, 2015, 04:41:07 pm
content: Bits don'thaveaddresses. At least, not ones that can be passed to instructions.

author: Delta
author_rank: Super Contributor
timestamp: November 09, 2015, 04:56:37 pm
content: Quote from: c4757p on November 09, 2015, 04:41:07 pmNow I am confused! (I am a complete C beginner though!)unsigned char x ; // this will have an address in the PIC's RAM, 0x0A1 for examplebit y ; // this must also have an address in RAM, shurely? 0xA2.7 for example?Am I not grasping something basic here?

author: c4757p
author_rank: Super Contributor
timestamp: November 09, 2015, 05:01:01 pm
content: No, not necessarily. Variables often don't have any address in RAM unless they need to - unless you take the address of a variable and give it to something, the compiler's likely to just stuff the data in a register and leave it at that.If the compiler needs to allocate space for a "bit" variable, it'll allocate a whole byte anyway and use only one bit, because most platforms are not physically capable of addressing bits. A pointer to that "bit" variable will be a pointer to the byte the compiler used to store it.Relevant:Data structure alignmentIf the compiler you're using (I don't remember, for xc8) allows you to have "bit" variables that reference a single bit within a register, that's a special feature they have added, and will be quite limited - essentially, if the actual bit the variable is supposed to reference is not compile-time constant, it won't work. So a bit as a global in a header is fine, the compiler can see what that is, but a bit as a function parameter is not because you could pass anything into there.

author: Delta
author_rank: Super Contributor
timestamp: November 09, 2015, 05:17:11 pm
content: OK, cheers for explaining that.So to read 2 switches, I've just copy/pasted and made a new function. So in my "functions.c" file I have the following:Code:#include <xc.h>#include "defines.h"extern unsigned long int tick_1ms ;extern bit pb1,pb1_edge,rolloverflag1 ;extern unsigned char debounce_timer1 ;extern bit pb2, pb2_edge ;extern unsigned char debounce_timer2 ;void read_pb1(void){if (SW1_PIN == 0) // if pin pulled down by button press{debounce_timer1++;if (debounce_timer1 == DEBOUNCE_TIME){if (!pb1) // if this is the the transit..{pb1_edge = 1 ; // then set the edge bit -it will be cleared elsewhere}pb1=1;debounce_timer1--;}}else{debounce_timer1 = 0 ;pb1=0 ;}}void read_pb2(void){if (SW2_PIN == 0) // if pin pulled down by button press{debounce_timer2++;if (debounce_timer2 == DEBOUNCE_TIME){if (!pb2) // if this is the the transit..{pb2_edge = 1 ; // then set the edge bit -it will be cleared elsewhere}pb2=1;debounce_timer2--;}}else{debounce_timer2 = 0 ;pb2=0 ;}}Then in my timer ISR I call them:Code:// read switch inputsread_pb1() ;read_pb2() ;This obviously works fine, and I can of course keep doing this as I add switches, but is there not an elegant way were I can have a single "read_pb" function and call it with arguments to determine which switch to process?

author: c4757p
author_rank: Super Contributor
timestamp: November 09, 2015, 05:22:33 pm
content: Yeah, your read_pb function should read the whole port, and then AND that with (1<<pin_number) to get the individual bit.Code:void read_pb(uint8_t bitmask){bool sw_pin = PORTB & bitmask;....}read_pb1()becomesread_pb(1 << 1), andread_pb2()becomesread_pb(1 << 2).(Note: I don't know if the namePORTBis correct, I haven't done PIC in ages. Should be whatever name gets you the inputs from that port.)

author: Delta
author_rank: Super Contributor
timestamp: November 09, 2015, 05:28:22 pm
content: But how would the function "know" which flags (ie pb1_edge or pb2_edge) to set?

author: macboy
author_rank: Super Contributor
timestamp: November 09, 2015, 05:28:40 pm
content: Another way to reuse source code is by using a macro. It looks similar to calling a function but works differently behind the scenes. One important difference is no restrictions on what you can 'pass into' it because you are basically passing identifiers (like variable names) for use at compile time, not pointers for use at run time. Each use of the macro results in another instance of the code being created by the compiler, but you have only one copy to maintain in your source file. This results in bigger code size but less stack/memory usage and faster execution than using a function.

author: Delta
author_rank: Super Contributor
timestamp: November 09, 2015, 05:31:13 pm
content: Quote from: macboy on November 09, 2015, 05:28:40 pmThat sounds ideal! Heading to the XC8 user manual right now.... Standby for more stupid questions!

author: c4757p
author_rank: Super Contributor
timestamp: November 09, 2015, 05:33:14 pm
content: Quote from: Delta on November 09, 2015, 05:28:22 pmEither you can also pass it a pointer to the flag, or you can arrange the flags in a bitfield the same way as they are in the port (it can then just OR them in, or AND them out).

author: Delta
author_rank: Super Contributor
timestamp: November 09, 2015, 05:56:41 pm
content: Hmmm, the XC8 manual only talks about assembler macros, could anyone point me to some info about how I can "macro-ise" my read_pb() function please?PS. Thanks very much for all the help so far.

author: macboy
author_rank: Super Contributor
timestamp: November 09, 2015, 08:43:28 pm
content: Quote from: Delta on November 09, 2015, 05:56:41 pmA C macro is a special kind of #define. In pic.h you may find this:Code:#define __delay_us(x) _delay((unsigned long)((x)*(_XTAL_FREQ/4000000.0)))Note the parameterxWhen the compiler sees this in the source code:Code:__delay_us(450);...it replaces it with this:Code:_delay((unsigned long)((450)*(_XTAL_FREQ/4000000.0)))...and the pre-processor will also calculate the math (450)*(_XTAL_FREQ/4000000.0) into a constant before actual compile.Assuming that XC8 can use "concatenation" in the way the gcc does it, then your two functions:Code:void read_pb1(void){if (SW1_PIN == 0) // if pin pulled down by button press{debounce_timer1++;if (debounce_timer1 == DEBOUNCE_TIME){if (!pb1) // if this is the the transit..{pb1_edge = 1 ; // then set the edge bit -it will be cleared elsewhere}pb1=1;debounce_timer1--;}}else{debounce_timer1 = 0 ;pb1=0 ;}}void read_pb2(void){if (SW2_PIN == 0) // if pin pulled down by button press{debounce_timer2++;if (debounce_timer2 == DEBOUNCE_TIME){if (!pb2) // if this is the the transit..{pb2_edge = 1 ; // then set the edge bit -it will be cleared elsewhere}pb2=1;debounce_timer2--;}}else{debounce_timer2 = 0 ;pb2=0 ;}}Could become:Code:#define read_pb(x) { \if (SW ## x ## _PIN == 0) // if pin pulled down by button press \{\debounce_timer ## x++;\if (debounce_timer ## x == DEBOUNCE_TIME)\{\if (!pb ## x) // if this is the the transit..\{\pb ## x ## _edge = 1 ; // then set the edge bit -it will be cleared elsewhere\}\pb ## x=1;\debounce_timer ## x--;\}\\}\else\{\debounce_timer ## x = 0 ;\pb ## x=0 ;\}\}The backslashes are necessary since the #define ends at end-of-line otherwise.You might want to tell the compiler to keep intermediate files to check what code it is concocting from the macro.

author: Delta
author_rank: Super Contributor
timestamp: November 09, 2015, 11:10:28 pm
content: That's brilliant info, cheers Macboy! I think I can just about follow what's going on there...However...I have shamelessly copy/pasted your code into my project. And the compiler doesn't complain about the #define, but it doesn't like it when I "call" the macro.I get:Code:isr.c:45: error: (195) expression syntaxmake: *** [build/default/production/isr.p1] Error 1make: *** [.build-conf] Error 2make: *** [.build-impl] Error 2isr.c:45: error: (300) unexpected end of file(908) exit status = 1Line 45 is the last line of the ISR.c file, containing the last "}"Code:#include <xc.h>#include "defines.h"// Already declared in main.c, but I think I have to do this here...?extern unsigned long int tick_1ms ;extern bit rolloverflag1 ;extern bit pb1,pb1_edge ;extern unsigned char debounce_timer1 ;extern unsigned char debounce_timer2 ;void interrupt high_priority isr_high_priority (void){return; // nothing using high priority yins yet...}void interrupt low_priority isr_low_priority (void){if (TMR2IF) // if timer 2 has hit the period register value{TMR2IF=0; // CLEAR THE FLAG!!!!tick_1ms++; // increase tickerif (!tick_1ms) // if we're at zero{rolloverflag1 = 0; // clear the rollover flag}// read switch inputs// read_pb1() ;// read_pb2() ;read_pb(1) ;} // end of timer 2 ISRreturn;} // end of low priority ISR ## THIS IS LINE 45 ##The editor also shows a red"!" on line 45. If I comment out theCode:read_pb(1) ;all is well.I can't see that any braces have been missed or added, so once again I'm rather confused.

author: Delta
author_rank: Super Contributor
timestamp: November 10, 2015, 05:31:47 pm
content: Once again I have successfully blundered my way through and got it working!I also now know about .pre files - very handy for me to see what this these crazy-ass compilers do behind the scenes.First I had to remove the outermost braces around Macboy's code, then it compiled OK, but didn't work.Looking at the .pre file, I could see that is was only placing the first "if" statement and subsequent bit assignment. But why?I then noticed that the last line of the macro that was successfully expanded had a comment in it. A comment using the "//" method. So of course the pre-processor (correct term?) treated EVERYTHING after that as a comment, as the lines were concatenated using "\".I redid the comments using "/* blah blah /*" and it worked a treat!I've also optimised my debouncing code, so the #define:Code:#define read_pb(x) \if (SW ## x ## _PIN == 0) /* IF pin pulled low */ \{\if (!pb ## x) /* IF the button is NOT already set... */ \{\if (debounce_timer ## x == DEBOUNCE_TIME)\{\pb ## x ## _edge = 1 ; /* set the edge bit */ \pb ## x = 1 ; /* set the pb bit */ \}\debounce_timer ## x ++ ; /* if we reach here, the pin is pulled low, but the timer hasn't reached yet */ \}\\}\else\{\debounce_timer ## x = 0 ;\pb ## x=0 ;\}\now beautifully expands to:Code:if (PORTAbits.RA7 == 0) { if (!pb1) { if (debounce_timer1 == 250) { pb1_edge = 1 ; pb1 = 1 ; } debounce_timer1 ++ ; } } else { debounce_timer1 = 0 ; pb1=0 ; } ;if (PORTAbits.RA6 == 0) { if (!pb2) { if (debounce_timer2 == 250) { pb2_edge = 1 ; pb2 = 1 ; } debounce_timer2 ++ ; } } else { debounce_timer2 = 0 ; pb2=0 ; } ;This seems a very powerful and useful feature, so thanks again Macboy for explaining it to me.

author: DmitryL
author_rank: Regular Contributor
timestamp: November 10, 2015, 05:39:22 pm
content: LOLGood luck with debugging your code 3 month later.If someone can write horrible macros, this doesn't mean that is a right thing to do.

author: Ian.M
author_rank: Super Contributor
timestamp: November 10, 2015, 06:00:48 pm
content: Gack!!!Ditch all that and read Jack Ganssle' article:A Guide to Debouncing. When you get to page 2, pay particular attention to the section near the bottom titled 'Handling Multiple Inputs' which shows how to debounce a whole port's worth of inputs (or even more) in parallel in a computationally efficient fashion. C source is even provided.

author: Delta
author_rank: Super Contributor
timestamp: November 10, 2015, 06:10:44 pm
content: Quote from: DmitryL on November 10, 2015, 05:39:22 pmGlad I could amuse use.So what's so bad about it? The code is visible in the #define statement, and I know it works.Is using macros like that considered bad practice then?

author: Delta
author_rank: Super Contributor
timestamp: November 10, 2015, 06:14:28 pm
content: Quote from: Ian.M on November 10, 2015, 06:00:48 pmThanks for the Gack!I will read through the info you provided, but at the moment I how more interested in how to use a C compiler and the IDE, rather than how to write the best code. If that makes sense...

author: Ian.M
author_rank: Super Contributor
timestamp: November 10, 2015, 06:19:53 pm
content: Its still broken, try passing it a named (#defined) port letter or bit number to see how.Hint: if using ## you *MUST* use two layers of macros to avoid the preprocessor NOT expanding #defined formal parameters.

author: Delta
author_rank: Super Contributor
timestamp: November 10, 2015, 06:27:51 pm
content: Quote from: Ian.M on November 10, 2015, 06:19:53 pmSorry, I'm not following you mate.I pass it a number referring to what switch to read, and the physical input pin for that switch is #defined in a header file. Do you mean it won't work if I try to pass it PORTAbits.RA0 for example? I can't do that anyway because I can't then pass it which flag bits to set.Ideally I did want to be able to doQuotebut it's already been explained to me that that is not possible.

author: Ian.M
author_rank: Super Contributor
timestamp: November 10, 2015, 06:45:09 pm
content: Before I figured out how to create 'virtual' ports (to avoid the dreadedRMW effect), I've done macro stuff like setting up a 'friendly' name for a pin:#define SW1 A,6Then later on:MAKE_INPUT(SW1);x=READ_PIN(SW1);It breaks if you try to use:x=READ_PIN(A,6);because of how the formal parameters get expanded. You have to call the underlying macro READ__PIN(port,bit) to get it to use pins without friendly names.In your case, if you pass in a #defined friendly name for the input number rather than a raw number, it breaks. I see you are putting the abstraction one level deeper by #defining SWnPIN constants, e.g:#define SW1PIN PORTAbits.RAso the ## token pasting doesn't break immediately, but that's a really *FUGLY* way of doing it, as ideally if you are going to mess with macros for pin handling, you want to be able to translate the same friendly name into the correct reference for the PORT bit, LAT bit (if user accessible), TRIS bit and even the ANSELx bit on newer PICS with one ANSEL register per analog capable port.

author: Delta
author_rank: Super Contributor
timestamp: November 10, 2015, 06:56:15 pm
content: OK, I with you now. But for what I'm trying to achieve, passing the raw pin name is no use anyway, as I can't pass my flags.So, my method (from Macboy's suggestion) does work, but is fugly. Understood.So what is the elegant way to be able to reuse a block of code to read multiple switches?

author: Delta
author_rank: Super Contributor
timestamp: November 10, 2015, 07:06:35 pm
content: Just to add, I am fully aware that the code I am writing is going to be shite, however at this stage into my foray with C, I'm deliberately writing code that I can easily follow and understand. I don't mean to sound rude or react badly to criticism, but I'm just trying to keep it simple for now.

author: Ian.M
author_rank: Super Contributor
timestamp: November 10, 2015, 07:49:08 pm
content: Quote from: Delta on November 10, 2015, 07:06:35 pmNo problem. Exploring simple code and eventually more complex code and the far corners of the ANSI C language and specific compiler implementation is how you learn. If you try to jump straight to the top of the learning curve, you'll be likely to end up as a cargo cult coder!However, I'd do something like this:Caution: Air code warning!I've just thrown this together on the fly so I can freely disclose it. It should compile OK as part of a full program, unless I've made a typo.Code:// From Jack Ganssle's Example 3 debouncing algorithm// See page 2 of: http://www.ganssle.com/debouncing.htm// with actual debouncing extracted to main program for efficiency#define MAX_CHECKS 10 // # checks before a switch is debounceduint8_t Debounced_State=0,Old_Debounced_State; // Current and previous debounced// state of the switches.volatile uint8_t State; // Array that maintains bounce status// Service routine called by a timer interruptvoid DebounceSwitch(){static uint8_t Index; // Persistent pointer into StateState[Index++]=~PORTA; // All switches active low on Port A.// Leave out ~ for active highif(Index>=MAX_CHECKS) Index=0;}// Routine to debounce all keysvoid DebounceSwitches(){uint8_t i,j;Old_Debounced_State=Debounced_State;j=0xff;for(i=0; i<MAX_CHECKS;i++)j=j & State;Debounced_State= j;}// Macro to read Switch N where N is the bit number. Returns TRUE if pressed.#define ReadSwitch(n) (!!(Debounced_State & 1<<n))// Macro to read if Switch N has just been pressed. Returns True if newly pressed.#define ReadNewSwitch(n) (!!((Debounced_State^Old_Debounced_State) & Debounced_State & 1<<n))//Usage:#if 0 //Don't compile sample usage, its only a snippet!DebounceSwitches(); 'freshen' the debounceed state, call this ONCE in your current polling loop.if(ReadSwitch(0)) // used as a shift key so want pressed state, not newly pressed state{if(ReadNewSwitch(1)) { /* Shifted Key1 routine */ }if(ReadNewSwitch(2)) { /* Shifted Key2 routine */ }} else {if(ReadNewSwitch(1)) { /* Key1 routine */ }if(ReadNewSwitch(2)) { /* Key2 routine */ }}#endifIt can easily be extended for 16 switches and it isn't too tricky to extend it to scan and debounce a 16 key 4x4 matrix.--I'm no C guru. If I ever look like one its because I'm standing on the shoulder of giants.