Today I’d like to go into bit manipulation, ehm, a bit…
You need bit manipulation when you’re dealing with the individual bits in a byte, such as on the I/O ports of an ATmega, for example.
First the easy approach – use these predefined macros from the Arduino library:
- bit(N) returns an integer with the N’th bit set to 1
- bitRead(X,N) – returns the N-th bit of X as 0 or 1
- bitWrite(X,N,B) – sets Nth bit of X to B (0 or 1)
- bitSet(X,N) – sets the Nth bit of X to 1
- bitClear(X,N) – sets the Nth bit of X to 0
This is why you might see code such as the following:
This means: “set the Watchdog Interrupt Enable to 1 in the Watchdog Timer Control Register”. The WDTCSR and WDIE terms are predefined constants. WDIE is 6, for example.
Note that some of these routines can be written in terms of the others, i.e.
- bitSet(X,N) is the same as bitWrite(X,N,1)
- bitClear(X,N) is the same as bitWrite(X,N,0)
But what does it all mean?
Well, let’s dive in. First make sure that you are comfortable with “bit shifting”. The expression “bit(3)” is the same as “1 << 3”, which in turn is the same as doubling the value 1 three times, i.e. the value eight. So “bit(0)” is 1 doubled zero times (i.e. 1) and “bit(7)” is 1 doubled 7 times, i.e. 128. Bytes have 8 bits numbered 0 to 7, so all you need for (byte-sized) hardware registers is to remember that bits 0..7 map to (specific!) integers with values 1 to 128.
Setting a bit, is like OR-ing the bit with the rest of the value. The following statements are all identical:
WDTCSR = WDTCSR | bit(WDIE); WDTCSR = WDTCSR | (1 << WDIE); WDTCSR = WDTCSR | (1 << 6); WDTCSR = WDTCSR | 0b1000000; WDTCSR = WDTCSR | 0x40; WDTCSR = WDTCSR | 64;
This, in turn, can be abbreviated in C as:
WDTCSR |= bit(WDIE); WDTCSR |= (1 << WDIE); WDTCSR |= (1 << 6); etc...
Or you could write:
It’s all the same. So OR-ing is about setting bits (to 1).
Likewise, AND-ing is about not clearing bits (to 0). Whoa, that’s confusing. This expression returns a value which is what X was, but only for bit N:
X & bit(N);
So this will change X to a value with all bits except bit N set to zero:
X = X & bit(N);
To put it differently: X will lose its original bits, except bit N, which will be left alone. All the bits are set to zero, except bit N.
Usually, you want the opposite, setting only bit N to zero. That too is accomplished with AND-ing, but you have to flip all the 0’s to 1 and all the 1’s to 0 first. Hang in there, it’s a slightly longer story. This sets bit N to zero:
X = X & ~ bit(N);
Let’s examine what’s going on here. First, “bit(N)” is a value with only the Nth bit set. Now, “~ bit(N)” is a value with all the bits flipped around (“~” is called the complement operator in C), so that’s a value with all but the Nth bit set. Everything is 1, but bit N is 0.
Now we can tackle the expression “X & ~ bit(N)”. Since AND-ing is about “not clearing bits”, that means that the result of this expression is all the bits of X unchanged where “~ bit(N)” was one, which is almost everywhere. The only bit that differs is bit N – it is zero in “~ bit(N)”, therefore that particular bit will “not not clear …” (a double negation!): it will be cleared (to 0) in the result.
Finally, we replace X by that result. So X will change in precisely one bit: bit N. That bit will be cleared to zero, the rest is not affected. In short: we’ve cleared bit N.
Well, that’s why the bit/bitSet/etc macro definitions were introduced. These expressions are all identical:
X = X & ~ bit(N); X = X & ~ (1 << N); if (X & bit(N)) X = X - bit(N); bitClear(X, N);
That last one is clearest by far, because it conveys the actual operation with a well-chosen name: clear bit N, leave the rest alone.
So why would anyone ever choose to use anything but the bit/bitRead/etc routines?
Many reasons. Habit, perhaps. Coming from another environment which doesn’t have these macros. Being so used to this bit-manipulation that the use of words doesn’t really look any clearer. Whatever…
But another more important reason is that you can’t do everything with these bit/bitSet/bitClear routines. Sometimes you just have to go to the raw code. Such as when you need to set multiple bits at once, or flip bits. That’s why the ATmega datasheet has examples like these:
WDTCSR |= (1<<WDCE) | (1<<WDE);
By now, you should be able to decode such a statement. It’s the same as:
WDTCSR |= bit(WDCE) | bit(WDE);
In other words:
WDTCSR = WDTCSR | bit(WDCE) | bit(WDE);
Which in turn is almost the same as these two statements together:
WDTCSR |= bit(WDCE); WDTCSR |= bit(WDE);
That in turn, can be written as these two lines:
bitSet(WDTCSR, WDCE); bitSet(WDTCSR, WDE);
Much clearer, right? Except… it’s not 100% identical!
The problem is a hardware issue: timing. The above two statements will set both bits, but not at the same time! For hardware register settings, that difference can be important. There is a fraction of a microsecond between the WDCE bit being set and the WDE bit being set. Unfortunately, in some cases that causes real problems – your code won’t work as expected.
Tomorrow, I’ll continue on this topic, but it’ll be a bit more fun, because there will be LEDs involved!
(Please ignore the cable on the left, I snatched the above picture from this post)
Update – see this excellent Wikipedia article for more details about bitwise operations.