0
\$\begingroup\$

I'm currently working on a small home alarm system that runs on the PIC16F628A microcontroller. It's not the first time I came to a project with the idea of using my own custom created registers that work exactly like the hardware registers, in the way that I can modify the whole register in one line or change the individual "parameters" of the register (ex: PORTAbits.RA0).

In the Microchip Developer Help there is a section called "Bit fields", under Bits, Bools, and Bit-fields wich talks about what seems like the closest implementation of a custom register, but it's not exactly what I'm looking for. In the example, you always have to write the name of the union, followed by a dot, and then, one of the bits or the whole register. It is nice, but I'm looking to not have to use the dot always, just only when I need to write the individual bits (and also, using the "bits" postfix).

I've also decided to take a look at one of the definition of the registers, just to see how could I find a way around:

// Register: TRISA
#define TRISA TRISA
extern volatile unsigned char           TRISA               __at(0x085);
#ifndef _LIB_BUILD
asm("TRISA equ 085h");
#endif
// bitfield definitions
typedef union {
    struct {
        unsigned TRISA0                 :1;
        unsigned TRISA1                 :1;
        unsigned TRISA2                 :1;
        unsigned TRISA3                 :1;
        unsigned TRISA4                 :1;
        unsigned TRISA5                 :1;
        unsigned TRISA6                 :1;
        unsigned TRISA7                 :1;
    };
} TRISAbits_t;
extern volatile TRISAbits_t TRISAbits __at(0x085);
// bitfield macros
...

In this definition, the advantage is that the memory address is known, so two variables can share the same memory address, but keep their different names. Is there any way I can achieve the same thing as hardware registers, but stored in the data memory?

\$\endgroup\$
5
  • 1
    \$\begingroup\$ two variables can share the same memory address, but keep their different names ... do you realize that they also share the content? \$\endgroup\$
    – jsotola
    Jul 14, 2023 at 2:32
  • \$\begingroup\$ Be aware that your micro has single-cycle instructions (BSF, BCF) to implement bit access on registers but not on memory. That means that an interrupt can happen halfway through a bit update on your "custom register", which you don't have to worry about when accessing bits of hardware registers. \$\endgroup\$
    – The Photon
    Jul 14, 2023 at 14:35
  • \$\begingroup\$ @ThePhoton does that mean that if an interrupt occurs at mid-update on one of my custom registers, data could potentially not be saved? or i'll just have "unexpected behaviour" if my interrupts use those custom registers? \$\endgroup\$
    – fpp
    Jul 14, 2023 at 18:12
  • 1
    \$\begingroup\$ The main risk would be if the interrupt handler also writes the "registers". \$\endgroup\$
    – The Photon
    Jul 15, 2023 at 5:39
  • \$\begingroup\$ Oh, ok. Thanks for the advice. I'll try to avoid writing operations of my registers on interrupts \$\endgroup\$
    – fpp
    Jul 15, 2023 at 13:43

1 Answer 1

1
\$\begingroup\$

With the magic of pointers, macros, structs & unions!

#include <stdint.h>

typedef struct
{
  unsigned bit0 : 1;
  unsigned bit1 : 1;
  unsigned bit2 : 1;
  unsigned bit3 : 1;
  unsigned bit4 : 1;
  unsigned bit5 : 1;
  unsigned bit6 : 1;
  unsigned bit7 : 1;
} reg_bits_t;                                   // typedef a struct containing 8 individual
                                                // single-bit bit-field members

uint8_t myRegister;                             // define an unsigned 8-bit variable
                                                // at some unspecified memory location
                                                // (decided by the compiler/linker)
                                                // doesn't matter where it is

reg_bits_t * const pMyReg = (reg_bits_t *)&myRegister; // define a constant pointer 
                                                // to a reg_bits_t union and initialize
                                                // that pointer to the memory address of the
                                                // previously defined myRegister variable

#define myRegBits (*pMyReg)                     // define a macro to dereference the pMyReg
                                                // union pointer (not strictly necessary)

void SomeFunction(void)
{
  myRegister = 0x12;    // as you would expect, myRegister now contains 0x12

  myRegBits.bit3 = 1;   // we set bit3, in the same memory space as myRegister
                        // so myRegister now contains 0x1a

  if (myRegBits.bit4 == 1)  // since myRegister == 0x12, it's bit4 is set (0x10)
  {
    // do something
  }

  // etc ...
}

The macro #define myRegBits (*pMyReg) is not strictly necessary.
You could just as easily access the bits using the -> operator on pMyReg directly:
pMyReg->bit6 = 1;

You could also define a macro which creates both the variable and pointer together with one line of code:

#define DEFINE_REGISTER(_regName) \
  uint8_t _regName;               \
  reg_bits_t * const _regName##Bits = (reg_bits_t *)&_regName;

DEFINE_REGISTER(myReg1);
DEFINE_REGISTER(myReg2);
DEFINE_REGISTER(myReg3);

but you then only have the option of using the -> operator to access the individual bits (unless you also define additional macros for each register):

myReg1 = 0x66;
myReg1Bits->bit4 = 1;

myReg2 = 0x99;
myReg2Bits->bit6 = 1;

All of this also works just as well on wider variables, just use a uint16_t or uint32_t and add extra bits to the reg_bits_t bitfield struct.
You're also free to use the built-in types like char, short, etc. instead of uint8_t, uint16_t, etc. I just prefer the <stdint.h> types because the size and signedness is explicitly defined in the name.

Another option would be to use a union to "wrap" a bit-field struct and a simple variable together, and then define a macro to "hide" the "unioniness":

#include <stdint.h>

typedef union
{
  uint8_t regVal;
  struct
  {
    unsigned bit0 : 1;
    unsigned bit1 : 1;
    unsigned bit2 : 1;
    unsigned bit3 : 1;
    unsigned bit4 : 1;
    unsigned bit5 : 1;
    unsigned bit6 : 1;
    unsigned bit7 : 1;
  } regBits;
} my_reg_t;

my_reg_t myRegUnion;
#define myRegVal    myRegUnion.regVal    // define a macro to access the regVal
                                         // in myRegUnion
#define myRegBits   myRegUnion.regBits   // define a macro to access the regBits
                                         // bitfield in myRegUnion

void SomeFunction(void)
{
  myRegVal = 0x34;
  myRegBits.bit1 = 1;                   // register now contains 0x35
}

or a slightly more advanced version which doesn't require you to define separate macros for each 'register' you use, using the same union typedef as above:

my_reg_t myReg1;
my_reg_t myReg2;
my_reg_t myReg3;
#define myRegVal(_x)   _x.regVal
#define myRegBits(_x)  _x.regBits

void SomeFunction(void)
{
  myRegVal(myReg1) = 0x45;
  myRegBits(myReg1).bit7 = 1;

  myRegVal(myReg2) = 0x88;
  myRegBits(myReg2).bit4 = 1;

  // etc
}

Depending on the underlying architecture of the MCU you do this on, some of these single-bit operations might be more or less 'code efficient' than others.
Many PICs for example have assembler instructions dedicated to manipulating single bits, and also may have "bit-banded" blocks in their memory maps where you're given different "windows" into the same memory where one "window" lets you read & write whole bytes while another "window" lets you read & write individual bits - both actually accessing the same physical underlying memory location.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for the great explanation. I decided to use the first option with union, but I do have some questions. Should I use the volatile type to prevent the compiler doing unexpected things? and also, Imagine I decide to have a lot of registers that are uint8_t, and the only thing that differs from one to another is the individual bit names. Should I create as many typedef union as I need? or should I instead create as many #define as different bit names I have? see an example here \$\endgroup\$
    – fpp
    Jul 14, 2023 at 19:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.