I'm programming a industrial plc and I have to manipulate bits for a profi-bus communication with a VFD. I get a 2byte status and have to send 2byte commands. For this operations I have to set bits to get the VFD operating. For example:
Byte n+1 Byte n
PLC --> --------------------- --------------- --> VFD
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
---------+--------- | | | | -+- | | +- 0: Reglersperre / Freigabe
| | | | | | | +--- 1: Freigabe / Schnellstopp
| | | | | | +----- 2: Freigabe / Halt
| | | | | +-------- 3..4: reserviert = 0
| | | | +------------5: Parametersatz-Umschaltung
| | | +------------- 6: Reset
| | +--------------- 7: reserviert = 0
| |
| +----------------- 8: Lüften der Bremse ohne Antreibsfreigabe
+---------------------------- 9..15: reserviert = 0
So I have to set bit 0 to set the VFD in operation mode. Then I need to set bit 2 to start the drive.
Now I found a question where bit-maipulation is described and I figured out that this solution should work, but I don't really understand it.
Can someone please explain why this works or doesn't work?
uint16_t change_bit(uint16_t command, unsigned int bit_nr, unsigned int val) {
/* command = 2byte command; bit_nr = bit to manipulate;
val = value bit should get (1;0) */
command ^= (-val ^ command) & (1U << bit_nr);
return command;
}
That seems to work, but it's very surprising and not so clear. Some one could it "too clever". A more clear way could be:
uint16_t change_bit(uint16_t command, unsigned int bit, bool value)
{
const uint16_t mask = 1u << bit;
if(value)
return command | mask;
else
return command & ~mask;
}
This has a jump in it (the if
), but a clever compiler might optimize that out. If it's not very performance-critical code, it's often better with clarity.
Note that using unsigned types is generally a good idea when doing bit-level manipulation.
This is indeed a clever trick that modifies a bit without branching. Here's an explanation that assumes you understand how bitwise operators work.
Let's start by rearranging the expression
(-val ^ command) & (1 << bit_nr)
First, let's swap -val
and command
(command ^ -val) & (1 << bit_nr)
Then, apply the distributive law
(command & (1 << bit_nr)) ^ (-val & (1 << bit_nr))
Now, realize that (assuming two's complement) if val
is 0, -val
is 0 (no bits set), and if val
is 1, -val
is -1 (all bits set)
(command & (1 << bit_nr)) ^ (val ? (1 << bit_nr) : 0)
So the assignment can be rewritten as an if-statement
if (val)
command ^= (command & (1 << bit_nr)) ^ (1 << bit_nr);
else
command ^= command & (1 << bit_nr);
If val
is 1, the bit at position bit_nr
is XORed with its negated value which always sets the bit. If val
is 0, the bit is XORed with itself which always clears the bit. All other bits a XORed with 0 which leaves them unchanged.
Here's a more readable branchless version that trades a bitwise operation for a shift:
uint16_t change_bit(uint16_t command, unsigned int bit_nr, unsigned int val) {
// Always clear the bit.
command &= ~(1u << bit_nr);
// Set the bit if val == 1.
command |= val << bit_nr;
return command;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With