EEVblog Electronics Community Forum

Electronics => Microcontrollers => Topic started by: TrickyNekro on March 09, 2018, 06:11:04 pm

Title: Writing to PIC registers the AVR way? Using the XC8
Post by: TrickyNekro on March 09, 2018, 06:11:04 pm
Hello ladies and gents,

So the question I have is a bit more complex than it might seem.
The usual "nice" way to write to an AVR register bitwise is this:

Register |= (1<<Register_bit); // To Set the bit
Register &= ~(1<<Register_bit); // To Clean the bit

That makes for a really nice code when you try to set or clear multiple bits are you can see what´s going on.
If I remember correctly the Register_bit(s) are also defined as positions for AVRs.

In PICs not so much though. You have two definitions, one I can understand the other I do have a problem with understanding.
For example:
extern volatile __bit                   STRA                @ (((unsigned) &PSTR1CON)*8 ) + 0;
#define                                 STRA_bit            BANKMASK(PSTR1CON), 0

I get that the STRA "definition" is the absolute memory position of this bit. But I really don´t get the STRA_bit definition with the comma separated value.
I suppose you can not really use something like this with the XC8 compiler:

PSTR1CON |= (1<<STRA/ *or STRA_bit for the matter */ );

Am I wrong or am I missing something, I really do want to learn!

Thanks in advance, Lefteris
Title: Re: Writing to PIC registers the AVR way? Using the XC8
Post by: Buriedcode on March 09, 2018, 06:44:58 pm
I use the macros you posted at the beginning in all my XC8 projects.

Although it is trickier because you have different definitions for writing whole registers and single bits (with the suffix "bits" to signify a structure for that register), but ultimately you can just use the macros.

I'm not sure where you got those two lines from, but I'm guessing its either from the example projects or generated files.  Like others have commented here, I find the generated code to be bloated, but it does what its meant to do - quick and dirty testing, to be rewritten later on.

For you to use the "PSTR1CON |= (1<<STRA/ *or STRA_bit for the matter */  ):" part, then STRA or STRA_bit would have to be defined as a single digit indicating the bit in the register.  I don't think microchip has these in the include files (rather they are structures). 

Ultimately, there is multiple ways of doing this, and it all depends on how much you want to rely on the built in libraries (we all want to).
Title: Re: Writing to PIC registers the AVR way? Using the XC8
Post by: TrickyNekro on March 09, 2018, 06:56:20 pm
Yeah,

You can of course use the PSTR1CON.STRA_bit = 1; for example
but it gets messy, plus you can not access multiple bits at a time forcing you into more CPU cycles, unless the compiler does something.

Now that microchip and atmel merged let´s hope they invest some time to port GCC for the 8 bit micros.
That´s something to expect the next years (plus a combined programming degubbing tool... ahem ahem...)

Cheers,
Lefteris
Title: Re: Writing to PIC registers the AVR way? Using the XC8
Post by: Buriedcode on March 09, 2018, 07:28:12 pm
Well that would be


PSTR1CONbits.STRA_bit = 1;


Also, you can set/clear multiple bits in a register, or rather set multiple bits, or clear them:

Code: [Select]
PSTR1CON |= (1<<bit1) | (1<<bit2) | (1<<bit3);

PSTR1CON &= ~((1<<bit1) | (1<<bit2) | (1<<bit3)); // note the extra parenthesis

But this would also require definitions of bit1, bit2, bit3 etc..

Admittedly very few people just write a value to the whole register as this is unhelpful when reading code.  And yes, setting the bits one by one explicitly isn't particularly efficient - but often registers only have to be set once at start up. Doing it one by one also means you can see exactly how each register is configured.

I'm unsure why you wish to port macros when you can just copy/paste them into your header file.  I wasn't aware the above macros were included in GCC I've always just copied them over from a template:

Code: [Select]
// Bit macro definitions
#define BIT(x) (1 << (x))
#define SETBITS(x,y) ((x) |= (y))
#define CLEARBITS(x,y) ((x) &= (~(y)))
#define SETBIT(x,y) SETBITS((x), (BIT((y))))
#define CLEARBIT(x,y) CLEARBITS((x), (BIT((y))))
#define BITSET(x,y) ((x) & (BIT(y)))
#define BITCLEAR(x,y) !BITSET((x), (y))

But you're right in that it would be helpful, although may not be compatible with the structure/unions that microchip uses (as in, adds confusion) - although I could be wrong here... I haven't written much code for PIC s in a while and mostly just use the MCC to sort out register settings!

It would be nice if they made the free compilers less bloaty, the MCC poo, replaced the PICkit 3 with something less frustrating (PICkit 4 is out apparently) but we shall see :)
Title: Re: Writing to PIC registers the AVR way? Using the XC8
Post by: TrickyNekro on March 10, 2018, 01:31:36 am
Quote from: Buriedcode on Yesterday at 23:28:12 (https://www.eevblog.com/forum/index.php?topic=105639.msg1448236#msg1448236)

Also, you can set/clear multiple bits in a register, or rather set multiple bits, or clear them:
Code:
[Select] (https://www.eevblog.com/forum/javascript:void(0);)PSTR1CON |= (1<<bit1) | (1<<bit2) | (1<<bit3);

PSTR1CON &= ~((1<<bit1) | (1<<bit2) | (1<<bit3)); // note the extra parenthesis

Well yeah and the names of bit1, bit2, etc? That´s my point you see... In AVR GCC you have this, with the PIC not so much.
Quote from: Buriedcode on Yesterday at 23:28:12 (https://www.eevblog.com/forum/index.php?topic=105639.msg1448236#msg1448236)
And yes, setting the bits one by one explicitly isn't particularly efficient - but often registers only have to be set once at start up. Doing it one by one also means you can see exactly how each register is configured.
Well sometimes you can write to the complete register and it will do its thing. There was one time though, I can´t remember with which peripheral it was, using AVR, that the bits in the same register had
to be configured one at a time. And that was not clear at all in the datasheet, if even mentioned, but it´s been some time I got to see my older code to tell exactly where the problem was.
Quote from: Buriedcode on Yesterday at 23:28:12 (https://www.eevblog.com/forum/index.php?topic=105639.msg1448236#msg1448236)
It would be nice if they made the free compilers less bloaty

Oh that´s the funny part really... XC8 is not based on an open source compiler but rather the old Hi-Tech C compiler.
And yet the AVR compiler manages to be cleaner on a lot of stuff, which is based on GCC.


BTW just for the laughs, as much as I praise the AVR GCC, the PIC peripherals although convoluted are very flexible. TWI on AVRs gave me f*****g cancer.
I was trying to code a real time system and had to use pointers to structures and I can´t even remember what else all cause they couldn´t include a transmission ready bit on the HW level.
Cancer, simply cancer and these are not things you would thing of on the designing phase. But I digress! Now I get the cancer from the PWM set up in PICs.
You spend more time designing how your functions want to be rather than implementing them... pff...


PS: Important Important! I´ve been doing some digging in the compiler files, so of course there is a "rather cancerous" way to do it the AVR way, of course few must know of this.
For my example it would be:Code: [Select] (https://www.eevblog.com/forum/javascript:void(0);)PSTR1CON |= (1<<_PSTR1CON_STRA_POSN) | (1<<_PSTR1CON_STRB_POSN) | (1<<_PSTR1CON_STRC_POSN);

Cancer but it is exactly the same as in AVRs and this is well hidden! I had to look in the microcontroller header file, that´s a 46k line header file, although repetitive you can miss what you are looking for!

Cheers,
Lefteris
Title: Re: Writing to PIC registers the AVR way? Using the XC8
Post by: Ian.M on March 10, 2018, 01:57:24 am
XC8 does provide suitable #defines for the bit positions etc. for that style of register bit manipulation across all the 8 bit PIC ranges, but it is NOT at all well documented.   I haven't looked in the latest manual to see if they are mentioned but I believe you still have to dig in the headers.
 
e.g. here's the pic16f84.h OPTION register definitions:
Code: [Select]
// Register: OPTION_REG
extern volatile unsigned char           OPTION_REG          @ 0x081;
#ifndef _LIB_BUILD
asm("OPTION_REG equ 081h");
#endif
// bitfield definitions
typedef union {
    struct {
        unsigned PS                     :3;
        unsigned PSA                    :1;
        unsigned T0SE                   :1;
        unsigned T0CS                   :1;
        unsigned INTEDG                 :1;
        unsigned nRBPU                  :1;
    };
    struct {
        unsigned PS0                    :1;
        unsigned PS1                    :1;
        unsigned PS2                    :1;
    };
} OPTION_REGbits_t;
extern volatile OPTION_REGbits_t OPTION_REGbits @ 0x081;
// bitfield macros
#define _OPTION_REG_PS_POSN                                 0x0
#define _OPTION_REG_PS_POSITION                             0x0
#define _OPTION_REG_PS_SIZE                                 0x3
#define _OPTION_REG_PS_LENGTH                               0x3
#define _OPTION_REG_PS_MASK                                 0x7
#define _OPTION_REG_PSA_POSN                                0x3
#define _OPTION_REG_PSA_POSITION                            0x3
#define _OPTION_REG_PSA_SIZE                                0x1
#define _OPTION_REG_PSA_LENGTH                              0x1
#define _OPTION_REG_PSA_MASK                                0x8
#define _OPTION_REG_T0SE_POSN                               0x4
#define _OPTION_REG_T0SE_POSITION                           0x4
#define _OPTION_REG_T0SE_SIZE                               0x1
#define _OPTION_REG_T0SE_LENGTH                             0x1
#define _OPTION_REG_T0SE_MASK                               0x10
#define _OPTION_REG_T0CS_POSN                               0x5
#define _OPTION_REG_T0CS_POSITION                           0x5
#define _OPTION_REG_T0CS_SIZE                               0x1
#define _OPTION_REG_T0CS_LENGTH                             0x1
#define _OPTION_REG_T0CS_MASK                               0x20
#define _OPTION_REG_INTEDG_POSN                             0x6
#define _OPTION_REG_INTEDG_POSITION                         0x6
#define _OPTION_REG_INTEDG_SIZE                             0x1
#define _OPTION_REG_INTEDG_LENGTH                           0x1
#define _OPTION_REG_INTEDG_MASK                             0x40
#define _OPTION_REG_nRBPU_POSN                              0x7
#define _OPTION_REG_nRBPU_POSITION                          0x7
#define _OPTION_REG_nRBPU_SIZE                              0x1
#define _OPTION_REG_nRBPU_LENGTH                            0x1
#define _OPTION_REG_nRBPU_MASK                              0x80
#define _OPTION_REG_PS0_POSN                                0x0
#define _OPTION_REG_PS0_POSITION                            0x0
#define _OPTION_REG_PS0_SIZE                                0x1
#define _OPTION_REG_PS0_LENGTH                              0x1
#define _OPTION_REG_PS0_MASK                                0x1
#define _OPTION_REG_PS1_POSN                                0x1
#define _OPTION_REG_PS1_POSITION                            0x1
#define _OPTION_REG_PS1_SIZE                                0x1
#define _OPTION_REG_PS1_LENGTH                              0x1
#define _OPTION_REG_PS1_MASK                                0x2
#define _OPTION_REG_PS2_POSN                                0x2
#define _OPTION_REG_PS2_POSITION                            0x2
#define _OPTION_REG_PS2_SIZE                                0x1
#define _OPTION_REG_PS2_LENGTH                              0x1
#define _OPTION_REG_PS2_MASK                                0x4

Note the presence of OPTION_REG_bits.PS which allows the whole prescaler ratio to be set or read in a single assignment.   Other SFRs that have meaningful multi-bit fields smaller than the SFR size have similar multi-bit fields defined - see your PIC's device specific header for details.

The #defines you need are the _SFR_BIT_POSN, which gives the position of the bit (or of bit 0 of a multi-bit field), _SFR_BIT_SIZE, which gives the width of the field, and _SFR_BIT_MASK, which gives a '1's mask for it.   ..._POSITION and ..._LENGTH are verbose aliases for ..._POSN and ..._SIZE, so ignore them!

The ..._MASK ones are most useful for 'AVR style' SFR bit manipulation.  e.g your latest code in your P.S. becomes:
Code: [Select]
PSTR1CON |= _PSTR1CON_STRA_MASK | _PSTR1CON_STRB_MASK | _PSTR1CON_STRC_MASK;which is far less fugly.  Obviously to clear those bits you'd simply AND with the complement of the combined masks:
Code: [Select]
PSTR1CON &= ~(_PSTR1CON_STRA_MASK | _PSTR1CON_STRB_MASK | _PSTR1CON_STRC_MASK);
Also see the thread bitset bitclear C @microchip.com (http://www.microchip.com/forums/FindPost/518913), for assigning values to multiple bits simultaniously without causing any glitches (important on ports or if one of the bits in the SFR triggers a peripheral action) using XOR. 
 
There are also the legacy bit variables, of the same type as you originally found, and ASPIC assembler named bit support.  For clarity, I've pruned out SFR bits unrelated to OPTION_REG:
Code: [Select]
/*
 * Bit Definitions
 *  */
#define _DEPRECATED __attribute__((__deprecated__))
#ifndef BANKMASK
#define BANKMASK(addr) ((addr)&07Fh)
#endif

extern volatile __bit                   INTEDG              @ (((unsigned) &OPTION_REG)*8) + 6;
#define                                 INTEDG_bit          BANKMASK(OPTION_REG), 6

extern volatile __bit                   PS0                 @ (((unsigned) &OPTION_REG)*8) + 0;
#define                                 PS0_bit             BANKMASK(OPTION_REG), 0
extern volatile __bit                   PS1                 @ (((unsigned) &OPTION_REG)*8) + 1;
#define                                 PS1_bit             BANKMASK(OPTION_REG), 1
extern volatile __bit                   PS2                 @ (((unsigned) &OPTION_REG)*8) + 2;
#define                                 PS2_bit             BANKMASK(OPTION_REG), 2
extern volatile __bit                   PSA                 @ (((unsigned) &OPTION_REG)*8) + 3;
#define                                 PSA_bit             BANKMASK(OPTION_REG), 3

extern volatile __bit                   T0CS                @ (((unsigned) &OPTION_REG)*8) + 5;
#define                                 T0CS_bit            BANKMASK(OPTION_REG), 5

extern volatile __bit                   T0SE                @ (((unsigned) &OPTION_REG)*8) + 4;
#define                                 T0SE_bit            BANKMASK(OPTION_REG), 4

extern volatile __bit                   nRBPU               @ (((unsigned) &OPTION_REG)*8) + 7;
#define                                 nRBPU_bit           BANKMASK(OPTION_REG), 7

It should be noted that the legacy SFR single bit variables are *NOT* implemented on many new devices and should be avoided if you want your code to be reasonably future-proof.  Even when they are implemented, some may be missing due to bit name conflicts.   The xxxx_bit defines are provided for inline assembler programming only, as they are virtually useless in C.   
Title: Re: Writing to PIC registers the AVR way? Using the XC8
Post by: Buriedcode on March 10, 2018, 03:45:36 am
It should be noted that the legacy SFR single bit variables are *NOT* implemented on many new devices and should be avoided if you want your code to be reasonably future-proof.  Even when they are implemented, some may be missing due to bit name conflicts.   The xxxx_bit defines are provided for inline assembler programming only, as they are virtually useless in C.

I have noticed these occasionally in source code from projects published on the web, often a mix of just using the single-bit variable, along with the "xxxxbits.xxx" structure, and even the aforementioned macros (our over-used example PSTR1CON |= (1<<bit1) | (1<<bit2) | (1<<bit3);) .... all in the same file.

That has bugged/irritated me no-end, and I'm not exactly a neat programmer.  My guess is, many things are just copied/pasted from project to project, which is fine (I do it far too often) but can bite you in the ass later on when it points out something is undefined. I'll also have to admit I was rather confused when I peeked at the include files for XC8, with so many definitions for the same register - but I guess that can account for different preferences.

I've also used MikroC a fair bit, and that seems to make much more sense - GCC style - with the downsides that it isn't free, and that you end up using their libraries - which are very handy, work fine but..there is no readable source.  I could whine about XC8, but frankly it does the job.  Somewhat verbosely, but I'll get used to it - eventually.

To the OP, microchips own application libraries come with a boat-load of example projects, that all seem to be very consistent with conventions. These would be a good place to see what the preferred (as in, fully supported across all devices) macros are.