After yesterday’s post about setting and clearing bits, let’s explore reversing bits, i.e. changing them from 0 to 1 and back. And let’s do it by blinking an LED attached to DIO of port 1 – i.e. Arduino digital pin 4:
The “if (onOff = 0)” etc is the logic that toggles onOff between 0 and 1 on each pass through the loop:
if (onOff == 0) onOff = 1; else onOff = 0;
But there are lots of ways to do the same thing, coded differently:
if (onOff == 0) onOff = 1; else onOff = 0;
if (onOff == 0) onOff = bit(0); else onOff = 0;
if (onOff == 0) bitSet(onOff, 0); else bitClear(onOff, 0);
onOff = onOff ? 0 : 1;
onOff = (~ onOff) & 1;
onOff = (onOff + 1) & 1;
onOff = ! onOff;
onOff = 1 - onOff;
onOff = onOff ^ 1;
onOff ^= 1;
See if you can figure out all of these.
Take your pick. Those last two use C’s XOR operator. I tend to prefer shorter source code, so I’d use that last notation (note that the resulting compiled code is not necessarily shorter than the other examples).
Now suppose you have a byte value “X” and you want to flip the 4th bit in it, while not changing anything else. That’s a bit more work. It could be done like this, for example:
if (bitRead(X, 4) == 0) bitSet(X, 4); else bitClear(X, 4);
Or like either of these:
X = X ^ bit(4);
X ^= bit(4);
This shows clearly that the “^” XOR operator does exactly what we need: flip bits.
Back to blinking an actual LED, as done with the above sketch. Here’s a little mind bender – another sketch, doing the same using raw ports and the XOR operator:
The first example was doing things “the Arduino way”, using pinMode() and digitalWrite(). It compiles to 890 bytes of code. This second example goes straight to the hardware and uses 554 bytes of code:
- Arduino digital pin 4 is bit 4 on the “D port” of an ATmega
- “DDRD” is the “Data Direction Register”, where we set up pin 4 as an output
- “PORTD” is the out “Port Register”, which controls the actual output signal
You can see the XOR in action in that last example. It takes all the output bits of port D (Arduino pins 0 .. 7), and flips just a single bit, i.e. bit 4.
Just for kicks, I’ll show you one more way to blink the LED:
This uses a relatively little-known feature of the hardware, which actually has “bit flipping” built-in. The “PIND” register is normally used for input, i.e. for reading the state of a pin as an input signal. But you can also write to that register. When you do, it will be used to flip output pins, but only for the bits which were set to 1. It’s essentially a built-in XOR.
That last example uses 550 bytes of code, most of which is overhead from the Arduino run-time library (setting up the milliseconds timer, etc). So what’s in a measly 4 bytes, right? Wrong. There is a minute, but sometimes important difference: the other approaches all had to read the register value first, flip the bit, and then write the value back. This last version only writes a (constant) value to a register. With interrupts, that can be very important: this last version can’t ever go wrong, it will always flip the requested bit. The other version could have an interrupt occur between the read and the write. It’s a known issue for the Arduino Mega. It can lead to code which runs for a week, and then fails mysteriously. Bugs like these are fiendishly hard to properly diagnose.
Bit-flipping can be quite useful for physical computing. Not only does it let you easily toggle specific bits, and change the state of some output pins, it can also be a way to clear a bit. Let’s say you need to generate a (very) quick pulse. Here are four ways to accomplish the same thing:
bitSet(PORTD, 4); bitClear(PORTD, 4);
PORTD |= bit(4); PORTD ^= bit(4);
PORTD |= bit(4); PIND = bit(4);
PIND = bit(4); PIND = bit(4);
That second one based on XOR works, because bit 4 is known to be one, so setting it to zero is always the same as flipping it. That’s also why the third PORTD/PIND example works, with PIND doing the XOR in hardware. Lastly, the fourth approach will only work if bit 4 was initially zero. It’s the fastest one, and does not suffer from the interrupt race condition mentioned above.
Ok, that’s enough flippin’ for one day!
Tomorrow, I’m going to go into, ehm… “fractional bits” (haha!) ;)
Update – see comment below on why “bitSet(PORTD, 4); bitClear(PORTD, 4);” are also interrupt-safe (mostly – but not on every pin of an Arduino Mega!).
The ‘bitSet(PORTD, 4); bitClear(PORTD, 4);’ example does a read/modify/write but in one instruction and two cycles (so it should work OK with interrupts).
Funny that the datasheet mentions this XORing feature of PINx but still lists all the bits in PINx as read-only. ;] And remember that this feature works only on the newer devices (ATmega88 and co., not on the old ATtiny2313 or ATmega8).
Ah, good catch.