简体   繁体   中英

C macro to set multiple bits

I am working on a microcontroller in C. Part of this involves changes bit values in registers. I have come up with a few macros to make things easier:

#define BIT_SET(addr, shift) (addr | (1 << shift))
#define BIT_RESET(addr, shift) (addr & ~(1 << shift))
#define BIT_GET(addr, shift) (addr & (1 << shift))
...
reg1 = BIT_SET(reg1, 3); //set bit 3
...

However, now I want to make a macro which will change several bits at a time. For example, may want to change the first 3 bits of 10101010 to 110 , resulting in the number 11001010 .

Can I do this in C? Or am I approaching this the wrong way?

My experience has been having bit masks for multiple bits is much easier. So the individual bits are identified with a particular define whose name indicates the function of the bit as in

#define ENABLE_T1  0x0001
#define ENABLE_T2  0x0002

and then these are used with the bitwise operators to turn bits on and off.

unsigned short usReg1 = GetRegister(REG1);

// disable T1 and enable T2

usReg1 &= ~ ENABLE_T1;
usReg1 |= ENABLE_T2;

or

// enable both T1 and T2
usReg1 |= (ENABLE_T1 | ENABLE_T2);

or

// disable both T1 and T2
usReg1 &= ~(ENABLE_T1 | ENABLE_T2);

or to test bits

// is T1 turned on
if (usReg1 & ENABLE_T1) {  

// is either T1 or T2 turned on
if (usReg1 & (ENABLE_T1 | ENABLE_T2)) {  

// is T1 turned on and T2 turned off
if ((usReg1 & (ENABLE_T1 | ENABLE_T2)) == ENABLE_T1) {  

// is either T1 or T2 turned on but not both
if ((usReg1 & (ENABLE_T1 | ENABLE_T2)) && ((usReg1 & (ENABLE_T1 | ENABLE_T2)) != (ENABLE_T1 | ENABLE_T2))) {

A Note on Macros

By the way you need to make sure that you use parenthesis in macro definitions to isolate the arguments as much as possible for safety. Without using parenthesis to explicitly force a macro argument to be evaluated as a single expression, you will be at the mercy of the operator precedence rules and those may not always be what you want (see https://en.cppreference.com/w/c/language/operator_precedence ). So

#define BIT_SET(addr, shift) (addr | (1 << shift))

really should be written as

#define BIT_SET(addr, shift) ((addr) | (1 << (shift)))

For your example, since the bitwise shift operators are rather low on the precedence chart, it will probably be fine in most cases but with other macros, depending on operator precedence can lead to defects as you never know how conservative someone will be when using a macro.

You could introduce a fullwidth mask which defines the bits you want to set. Eg for setting the first 3 bits use 0xe0, which is in binary 11100000.

Along with that mask, provide the value to write also in full width.
Eg 0xc0 for the desired binary 110 in the first three bits.

Then a little bit-operatino magic is needed to only write the desired bit positions.

#define MULTIBIT_SET(addr, mask, value) (((addr)&((0xff)^(mask)))|((value)&(mask)))

Here ((0xff)^(mask)) inverts the mask, so that anding with this deletes the existing bits from the value.
((value)&(mask)) is the value but with only set bits in the desired positions, ie this prevents undesired setting of bits elsewhere.

In total, oring the two parts, gives you the desired result.

There is a design choice here, which I want to mention:
Call it paranoia. If somebody tells me "I want to set only the first three bits, to this value which has bits elsewhere", then I prefer to take it safe this way, ie removing the "elsewhere bits".
Yes, I am assuming that setting bits without intention is more of a risk than not setting them, which is a matter of taste.

You can always do it in C. :-)

If you're using a C99-compliant compiler, and given that it's 2019, I assume you are, you can use variadic macros to make it work, and more specifically, P99 .

I had an example where I was extracting the relevant components of P99, but it was becoming messy, because this is some serious use of the C preprocessor as a Turing-complete language.

This also abuses parentheses; in particular, EXTRACT_VALUE x expands to EXTRACT_VALUE (a, b) because we pass a list of tuples.

#include "p99.h"

#define EXTRACT_SHIFT(shift, value) (shift)
#define EXTRACT_VALUE(shift, value) (value)
#define MERGE(NAME, I, A, B) (A), (B)

#define SET_OR_CLEAR(target, x, i) \
    ((EXTRACT_VALUE x) ? \
        ((target) |= (1 << EXTRACT_SHIFT x)) : \
        ((target) &= ~(1 << EXTRACT_SHIFT x)))
#define SET_AND_CLEAR(target, pairs...) \
    P99_FOR(target, P99_NARG(pairs), MERGE, SET_OR_CLEAR, pairs)

// ...
reg1 = BIT_SET(reg3, 3); //set bit 3                                         
// ...
SET_AND_CLEAR(reg3, (7, 1), (6, 1), (5, 0)); // set bits 6 and 7, clear bit 5

You may need to compile with -std=c99 . Note that I used the variadic word pairs instead of __VA_ARGS__ , to try to make clear that we want 2-tuples as arguments.

Note that this doesn't mask out any of the input register that isn't being set or cleared.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM