• 0

# Fast C parity calculations on PIC16F

## Question

This is my attempt to convert the blazingly fast assembler routine for calculating parity into a C function.

The original comes from here: https://www.microchip.com/forums/m4762.aspx

Unfortunately Microchip have killed off the board where the original discussion took place at http://asp.microchip.com/webboard/wbpx.dll/~DevTools/read?21443,5

```#include <xc.h>

//returns 0x00 (even parity) or 0x80 (odd parity)
unsigned char parity(volatile unsigned char dat)    //("volatile" is required because no C code reads the parameter)
{
asm("swapf parity@dat,w");    //assume correct bank is still selected
asm("xorwf parity@dat,w");    //W has 8 bits reduced to 4 with same parity
asm("addlw 41h");   // bit 1 becomes B0^B1 and bit 7 becomes B6^B7
asm("iorlw 7Ch");   // for carry propagation from bit 1 to bit 7
asm("addlw 2");     // Done! the parity bit is bit 7 of W
asm("andlw 80h");   // set NZ if odd parity, and leave 00 or 80 in W
asm("return");
return 1;           //dummy instruction to defeat "no return value" error
}

void main(void) {
unsigned char idx=0;
while(1)
{
PORTA = parity(idx);
idx++;
}
}```

I'm not sure if there's a cleaner way to suppress the "no return value" error, without generating extra code.

## Recommended Posts

• 0

A suggested C macro is almost as good, but adds an extra temporary storage location for the first calculation

`#define PARITY(b)   ((((((b)^(((b)<<4)|((b)>>4)))+0x41)|0x7C)+2)&0x80)`

It also generates a "warning: (752) conversion to shorter data type" on the macro invocation.

##### Share on other sites
• 0

What happens when you do "return WREG" as the last line? I think in PRO mode on XC8 that may work and you can remove the return asm line. I can check tomorrow if this works, you can probably check it right now 🙂

##### Share on other sites
• 0
10 minutes ago, Orunmila said:

What happens when you do "return WREG" as the last line? I think in PRO mode on XC8 that may work and you can remove the return asm line. I can check tomorrow if this works, you can probably check it right now 🙂

That was what I did first, and was horrified by the code generated by XC8 2.0 in free mode.

That was my attempt to force it to do it efficiently.

XC8 v.1.34 in Pro  mode only has one pointless instruction:

```   309                           ;main.c: 41: return WREG;
310  07FE  0809               	movf	9,w	;volatile
311  07FF  0008               	return```

Std and Free mode have an extra pointless instruction:

```   320  07FD  0020               	movlb	0	; select bank0
321  07FE  0809               	movf	9,w	;volatile
322  07FF  0008               	return```

and XC8 2.0 in C90 Free mode (Opt 0) is a bit ordinary:

```  1218  07D4                     ;main.c: 41: return WREG;
1219                           	movlb 0	; select bank0
1220  07D4  0020               	movf	(9),w	;volatile
1221  07D5  0809               	goto	l9
1222  07D6  2FD7
1223                           l594:
1224  07D7                     	line	42
1225
1226                           l9:
1227  07D7                     	return```

C90 Free / Opt 1 is same as v1.x in free mode

```   541                           ;main.c: 41: return WREG;
542  07D9  0020               	movlb	0	; select bank0
543  07DA  0809               	movf	9,w	;volatile
544  07DB  0008               	return```

C99 mode generated the same code.

(And that macro generates horrible code if you are not in Pro mode...)

##### Share on other sites
• 0

Sadly, free mode is embarrassing and pro mode is expensive.  It has been for a long time.

##### Share on other sites
• 0

I have been playing for a while with coding functions in ASM which can be called from C to cater specifically to cases like these.

One way to get the code to be exactly what you need it to be like this case here is to add a .S file to your project and code it like this. This will be accessible as a C function of type 4217 (passes in 1 byte in W, returns 1 byte in W)

I made a small change there to what you had because you assumed the parameter was NOT passed in W, while with XC8 this is the normal way it will be done and if it is passed in W the code seems like it would fail. I just declared a local var and moved w into it as you assumed was happening during the call.

If you want to save that extra instruction and 1 byte of ram you can remove the parameter altogether, and code the ASM code to read it from the source symbol.

```#include <xc.inc>
GLOBAL _calcParity        ; make _calcParity globally accessible
SIGNAT _calcParity,4217   ; tell the linker how it should be called

GLOBAL    calcParity@tmp

PSECT cstackCOMMON,class=COMMON,delta=1,space=1
calcParity@tmp ds 1

; everything following will be placed into the mytext psect
PSECT mytext,global,class=CODE,delta=2

; our routine to calculate the parity
_calcParity:

; W is loaded by the calling function;
movwf calcParity@tmp
swapf calcParity@tmp,w   ; assume correct bank is still selected
xorwf calcParity@tmp,w   ; W has 8 bits reduced to 4 with same parity
addlw 41h                ; bit 1 becomes B0^B1 and bit 7 becomes B6^B7
iorlw 7Ch                ; for carry propagation from bit 1 to bit 7
addlw 2                  ; Done! the parity bit is bit 7 of W
andlw 80h                ; set NZ if odd parity, and leave 00 or 80 in W

; the result is already in the required location (W)so we can ; just return immediately
return```

You can then call that from C like so :

```#include <xc.h>
#include <stdint.h>

uint8_t calcParity(uint8_t dat);

void main(void) {
volatile uint8_t c = 0;

c = calcParity(0x01);
c = calcParity(0x11);
c = calcParity(0x03);
c = calcParity(0x13);
}```

Not sure if this helps you.

We have started a conversation with the compiler team to see what they think about optimizing out movf w,w since that never makes sense (although it is a trick used to clear status flags in some cases...). Lots of people still on Vacation until Monday, so we will have to be patient for their input.

##### Share on other sites
• 0
1 hour ago, Orunmila said:

One way to get the code to be exactly what you need it to be like this case here is to add a .S file to your project and code it like this. This will be accessible as a C function of type 4217 (passes in 1 byte in W, returns 1 byte in W)

I made a small change there to what you had because you assumed the parameter was NOT passed in W, while with XC8 this is the normal way it will be done and if it is passed in W the code seems like it would fail. I just declared a local var and moved w into it as you assumed was happening during the call.

Very nice.

You're right about the bug though. I think I got conned by C99 mode with no optimisation. Normally the parameter is just in W, so you do need to create a temporary variable and save it as you did.

Is your tmp variable essentially static? i.e. are other functions at the same level able to overlay that location?

Quote

If you want to save that extra instruction and 1 byte of ram you can remove the parameter altogether, and code the ASM code to read it from the source symbol.

Now THIS sounds interesting. How would you do that?

This function does not alter the scratch location, it just needs to be able to read it.

##### Share on other sites
• 0

I am still learning how the linker interprets things. This is using the compiled stack and the linker should have all the information it needs to re-use the location but I have no idea if it will.

EDIT: Actually the more I look at that the more I think it will not re-use the memory :(. It is just saying reserve 1 byte of common ram, not associated with the function call.

I have been playing with proxy functions, and I was hoping that once they fix inlining in the compiler that this would give a good path to making ASM functions with a proxy wrapper like this.

This way the C function gets all the bells and whistles that C provides in terms of stack management, and you can use SW or compiled stack if you please, or even just make this function reentrant, and it all still works. It gets really complicated when you inline the c function and use it more than once, but if you could coax the linker into inlining the ASM function since it is called only once ever, this would be a really neat solution.

As things stand now you end up with an extra call and return which is really 4 instuctions penalty, so if you are doing this for a speed optimization that sucks, if you are doing it to access some fancy ASM code then it works nicely.

```uint8_t asm_calcParity();

uint8_t calcParity(uint16_t c)
{
volatile uint16_t  tmp;  // This generates no code but reserves the temp stack space
tmp = c;                 // This simply does a MOVWF tmp

return asm_calcParity(); // And since this is returning what another function returned in W it should just generate a CALL and RETURN
}```

Then the ASM just becomes this:

```#include <xc.inc>
GLOBAL _asm_calcParity        ; make _calcParity globally accessible
SIGNAT _asm_calcParity,4217   ; tell the linker how it should be called

GLOBAL calcParity@tmp

; everything following will be placed into the mytext psect
PSECT mytext,global,class=CODE,delta=2

; our routine to calculate the parity
_asm_calcParity:

; W is loaded by the calling function;
swapf calcParity@tmp,w   ; assume correct bank is still selected
xorwf calcParity@tmp,w   ; W has 8 bits reduced to 4 with same parity
addlw 41h                ; bit 1 becomes B0^B1 and bit 7 becomes B6^B7
iorlw 7Ch                ; for carry propagation from bit 1 to bit 7
addlw 2                  ; Done! the parity bit is bit 7 of W
andlw 80h                ; set NZ if odd parity, and leave 00 or 80 in W

; the result is already in the required location (W)so we can ; just return immediately
return```

##### Share on other sites
• 0

I just had a naughty thought.

If you restrict usage to enhanced devices, presumably our routine is allowed to trash WREG, STATUS and BSR.

What if we just used BSR as our temporary scratch register? It's a core register, so doesn't need banking to access (obviously!), and is automatically preserved when interrupts are serviced. The remaining four instructions are all immediate, so not affected by BSR.

Edit: Scrub that. BSR is only 5 or 6 bits, never 8, so can't be used for our purpose which requires all 8 bits.

##### Share on other sites
• 0

Be careful, you have to tell the compiler if you are going to affect BSR specifically, it tracks the banks and will optimize to minimize bank switches but this can get you into real trouble!

What I like about the proxy/trampoline type approach I posted before is that it forces the banks to be correct because of the compiler managed local variables.

## Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

×   Pasted as rich text.   Paste as plain text instead

Only 75 emoji are allowed.

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×

• ### Popular Contributors

Nobody has received reputation this week.

• ### Similar Content

• By KM1
Hello,
I have run into a strange (for me) issue with the rtcounter module as provided by MCC and shown by the example program in the blog area of this site. I am using MPLabX and XC8, both up-to-date versions and I am trying the example on a pic18F47k40 xpress board. The issue is I set an output (RA4) in main after initializing the pic and then enter the while(1) loop where the rtcount_callNextCallback(); is called. The output now is turning on and off. The off duration is typically ~60u seconds, and on time can be varied with changes to the timer interrupt settings (I use a 1mS timer0 in 16 bit mode) and the number sent to the callback. Approximately every 3mS, the off time increases such that that cycle (only) is approx 50% duty cycle.
Turning the output on or off in the callback routine has no effect - I initially wanted to toggle on and off at a speed visible to the eye. Commenting out the callback in the main loop stops the output from going low.
I have checked errata and used the data sheet to confirm register settings, I tried moving the current-limited LED from RA4 to RA2, and I have studied the example program looking for obvious differences that may cause this behaviour.
I would appreciate thoughts and/or suggestions.
Keith

• By burkart
Hello All,
I am working on a project (in MPLAB v5.10) with a PIC18F27K40 (PIC18 library v1.77.0) and I'm using the MCC (v3.85.1) generated I2C drivers, I2CSIMPLE from the Foundation Services. I can read from and write successfully to devices on the I2C bus. The problem comes when I try to communicate with a device that's not on the bus, the micro goes into an endless loop waiting for i2c_status to not be busy. My knowledge of programming in C is about 6 on a scale of 10, and for programming for embedded purposes, about 5 out of 10. I would like to have it so that I can check if a specific device is present on the I2C bus, and also be able to recover from errors on the bus without it going into a loop indefinitely.

This I2C driver is pretty complex, and I am having difficulties wrapping my head around it. How would I make it so that the driver just returns an error or something I can check for status, rather than loop endlessly until the operation completes, which it never will?

I have not edited any of the MCC generated code. This includes leaving the IRQ enable line commented in the i2c_master.c file, so instead it polls instead of using an interrupt to check if the i2c operation has completed.
// uncomment the IRQ enable for an interrupt driven driver. // mssp1_enableIRQ(); Following is an example of how I am calling the i2c driver.
i2c_write1ByteRegister(address, 0x0D, 0x07); // GPPUB pull-ups on pins 0-2 I am attempting to initialize a port extender, MCP23018, specifically enabling some pull-up resistors. I would like to issue this command, and if the extender is not present, then the micro will perform some tasks differently. With the port extender present the write operation works as expected and everything is fine. Of course the problem is when the extender is NOT on the bus to acknowledge.
I have another question as well. This driver seems to operate a little slow. When watching the bus with a logic analyzer I noticed a rather long pause between bytes. I went looking through the i2c driver and in i2c1_driver.c I found the following code which I suspect is the cause.
inline void mssp1_waitForEvent(uint16_t *timeout) { // uint16_t to = (timeout!=NULL)?*timeout:100; // to <<= 8; if(PIR3bits.SSP1IF == 0) { while(1)// to--) { if(PIR3bits.SSP1IF) break; __delay_us(100); } } } What is the purpose of the 100 us delay in the while loop? Reducing or eliminating the delay results in reducing or removing the pause between byte transactions, but I don't know enough to know how else this edit will effect the driver. Also, what is the commented out code at the top of the function used for? Is this part of the infinite loop problem I mentioned above?

--
James Burkart
• By Orunmila
error: variable has incomplete type 'void'
If you are getting this error message trying to compile code that used to work or from some example code you found somewhere it is very likely because of the changes in the XC8 interrupt syntax introduced with V2.0 of XC8.
Specifically I am getting this today for my interrupt service routine definition. I used to use the age-old way of doing this in XC8 as follows:
// Old way to do this void interrupt myISR(void) { // Old way Interrupt code here } After the changes to the XC8 compiler which were mostly motivated to get better C standard compliance, particularly with C99, the syntax for that function should now use the commonly adopted concept of function declaration-specifier attributes, which traditionally start with 2 underscores and contains either a list of parameters in brackets or have empty brackets if no parameters are present.
// New and improved and C99 compliant way to specify an interrupt service routine in XC8 void __interrupt() myISR(void) { // New and improved interrupt code here } This syntax is now also consistent between XC8, XC16 and XC32
Please see this post for more information on how to either work around this or change to the new syntax.
https://www.microforum.cc/topic/5-i-used-to-use-to-locate-variables-but-since-xc8-20-this-is-no-longer-working/
• By Orunmila
I just downloaded XC32 V2.15, I was using V2.10 before. I find that some of my projects no longer compile. On my first check I noticed that the problems seem to occur when inline functions are used and the same header where the inline implementation is done is included in more than one compilation unit?
Has any of you seen similar issues?
I will investigate further and post here if I arrive at an answer.

UPDATE: Ok, I managed to make a small test project to replicate the problem. I am attaching it here.
TestInlineXC32_2.15.zip

Next I am going to test this on some other compilers to see what the deal is. I have confirmed that with that project when you switch it to V2.10 or older it all compiles just fine, but if you use V2.15 it failes to link with the following error:
"/Applications/microchip/xc32/v2.15/bin/xc32-gcc"   -mprocessor=32MZ2048EFM100  -o dist/default/production/TestInlineXC32_2.15.X.production.elf build/default/production/main.o build/default/production/otherFile.o          -DXPRJ_default=default  -legacy-libc    -Wl,--defsym=__MPLAB_BUILD=1,--no-code-in-dinit,--no-dinit-in-serial-mem,-Map="dist/default/production/TestInlineXC32_2.15.X.production.map",--memorysummary,dist/default/production/memoryfile.xml
nbproject/Makefile-default.mk:151: recipe for target 'dist/default/production/TestInlineXC32_2.15.X.production.hex' failed
make[2]: Leaving directory '/Users/ejacobus/MPLABXProjects/TestInlineXC32_2.15.X'
nbproject/Makefile-default.mk:90: recipe for target '.build-conf' failed
make[1]: Leaving directory '/Users/ejacobus/MPLABXProjects/TestInlineXC32_2.15.X'
nbproject/Makefile-impl.mk:39: recipe for target '.build-impl' failed
build/default/production/otherFile.o: In function `myInlineFunction':
/Applications/microchip/xc32/v2.15/bin/bin/gcc/pic32mx/4.8.3/../../../../bin/pic32m-ld: Link terminated due to previous error(s).
collect2: error: ld returned 255 exit status
make[2]: *** [dist/default/production/TestInlineXC32_2.15.X.production.hex] Error 255
make[1]: *** [.build-conf] Error 2
make: *** [.build-impl] Error 2
BUILD FAILED (exit value 2, total time: 680ms)

• By Orunmila
If you have purchased a "MPLAB(R) Xpress PIC18F47K40 Evaluation Board" from Microchip (part number DM182027) and you are running into difficulty because the board is behaving strangely it is most likely caused by a silicon errata on this device!
The relevant section of the Errata is shown at the end.
What is happening is that the compiler is using a TBLRD instruction somewhere and this instruction is not behaving as expected due to a silicon bug in REV A2 of the PIC18F47K40, causing the read to fail and the program to malfunction. Typically this happens as part of the C initialization code generated by the XC8 compiler, and since the compiler is optimizing, changing the code may cause the problem to temporarily disappear because you have few enough global variables that a table read is no longer the fastest way to initialize the memory segment for variables with static linkage.
The XC8 compiler can avoid generating the sequence which will cause the failure if you tell it in the linker settings to implement the workaround for this Errata. This is done by adding +NVMREG to the setting as follows. Note that this is under the section "XC8 Linker" and the Option Category "Additional Options".

This is the relevant section of the Errata.

• By Orunmila
I have a bunch of old projects using the @ method to hard locate variables at a specific location in data memory, but since I upgraded to XC8 2.0 none of these are working any more!
How do I do this with the latest compiler? I am using XC8 v2.xx.
I also saw this error at my interrupt service routine and I suspect that this is the same problem.
My interrupt code looks like this
void interrupt my_isr(void) { ... my code here } error: variable has incomplete type 'void'
×

• Search