Maybe this would work, but it is not standard C:
void setBitHigh( bit *b ){
b = 1;
}
setBitHigh( & RB3 );
If not, then there is no direct way to do what you want. You could of course return the value from the function and then assign it:
RB3 = newBitValue();
or you could pass the function both the byte address and the offset, and leave it to the function to do the dirty work:
void setBitHigh( unsigned char *a, bit_offset b ){
*a |= ( 1 << b );
}
setBitHigh( & PORTB, 3 );
Structures are unsuitable for writing register maps, because a struct can have padding bytes added anywhere inside it, for alignment purposes. This depends on the compiler - you have to ensure that no padding is used (for example through a static assert).
Furthermore, it is easy to make mistakes when writing large structures that are supposed to reflect a register map, you have to get every single byte right or you'll break everything. This makes the struct vulnerable during maintenance.
I would strongly suggest you to write register maps through macros, such as:
#define PERIPH_A_DID0 (*(volatile uint32_t*)0x400FE000u))
#define PERIPH_A_DID1 (*(volatile uint32_t*)0x400FE004u))
This also has the advantage of being 100% portable to any C compiler.
Alternatively, you could do something more intricate such as this:
typedef volatile uint8_t* SCI_port;
#ifndef SCI0
#define SCI0 (&SCI0BDH)
#define SCI1 (&SCI1BDH)
#endif
#define SCIBDH(x) (*((SCI_port)x + 0x00)) /* SCI Baud Rate Register High */
#define SCIBDL(x) (*((SCI_port)x + 0x01)) /* SCI Baud Rate Register Low */
#define SCICR1(x) (*((SCI_port)x + 0x02)) /* SCI Control Register1 */
#define SCICR2(x) (*((SCI_port)x + 0x03)) /* SCI Control Register 2 */
#define SCISR1(x) (*((SCI_port)x + 0x04)) /* SCI Status Register 1 */
#define SCISR2(x) (*((SCI_port)x + 0x05)) /* SCI Status Register 2 */
#define SCIDRH(x) (*((SCI_port)x + 0x06)) /* SCI Data Register High */
#define SCIDRL(x) (*((SCI_port)x + 0x07)) /* SCI Data Register Low */
Then when writing your driver, you can do like this:
void sci_init (SCI_port port, ...)
{
SCICR1(port) = THIS | THAT;
SCICR2(port) = SOMETHING_ELSE;
...
}
This is very useful when you have many identical peripherals on the MCU, with the same registers, but only want one code to control them.
Best Answer
In the general case, where you have memory mapped registers, you should always avoid bitfield structs. This is because those are very poorly specified by the C standard. Fundamental things that you may assume are specified, are not. This includes bit order, byte order, padding & alignment.
In addition,
uint8_t
among other types, is not even an allowed type in bitfields! You can only useint
types, though ifint
counts as signed or unsigned is not specified either...To sum it up, bitfields are horribly unreliable and you are left at the mercy of the compiler implementation (if any) when you use them.
What you should do instead, is to always use address and bit mask macros, together with the bit-wise operators. Those are 100% well-defined and portable.