Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C set a bit (bit manipulation)

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;
}
like image 440
Gerald Zehetner Avatar asked Sep 05 '16 07:09

Gerald Zehetner


2 Answers

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.

like image 171
unwind Avatar answered Oct 17 '22 02:10

unwind


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;
}
like image 29
nwellnhof Avatar answered Oct 17 '22 02:10

nwellnhof