7

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;
}
Community
  • 1
  • 1
Gerald Zehetner
  • 596
  • 4
  • 21
  • Are you trying to just set bit 0 & bit 2 to 1? – dudeman Sep 05 '16 at 07:50
  • -val sets all bits as 1 when val==1 and all bits as 0 when val==0. – Paul Stelian Sep 05 '16 at 07:50
  • -val^command negates he command when val is 1 – Paul Stelian Sep 05 '16 at 07:51
  • 1
    This is just a example, I have to manipulate many different commands, so I want to write a funtion, which i can universally use. – Gerald Zehetner Sep 05 '16 at 07:51
  • The and selects that particular bit. It will be identical to the command bit when you want to set to 0 or opposite when you want to set as 1. Thus it successfully sets the bit – Paul Stelian Sep 05 '16 at 07:52
  • This command thus generally works, although it was not written for being easily readable by humans. – Paul Stelian Sep 05 '16 at 07:52
  • 1
    It is not clear what "command" is supposed to be. Either you wish to set a bit at a specific position to 1 or 0, or you wish to set several bits according to some pattern/command. Your function seems to attempt both at once, which doesn't make sense to me. – Lundin Sep 05 '16 at 07:54
  • A more easily understandable way (although with worse performance) is to manually select the bit, check if it needs to be toggled and, if so, do toggle it. – Paul Stelian Sep 05 '16 at 07:54
  • @Lun the function correctly works for one bit. – Paul Stelian Sep 05 '16 at 07:54
  • I just want to set a bit at a time. And I don't wan't to check what value it had before, I just want to set it to a defined value. – Gerald Zehetner Sep 05 '16 at 07:57
  • 1
    Then it would appear you are merely looking to do something like this? `uint16_t change_bit (uint16_t value, bool set, size_t bit_n) { if(set) return value | (1u << bit_n); else return value &= ~(1u << bit_n); }`. – Lundin Sep 05 '16 at 08:02
  • This is confusing: what is 1u? And I belive C has no bool datatype and I can't use stdbool.h in my environment. But i believe i could change that to int. – Gerald Zehetner Sep 05 '16 at 08:09
  • OK, i found out about 1u [here](http://stackoverflow.com/questions/4192440/is-there-any-difference-between-1u-and-1-in-c). But why is this version better then the example in my question? – Gerald Zehetner Sep 05 '16 at 08:16
  • C got a bool type 17 years ago... The `1u` is there because you shouldn't do bitwise operations on signed integer types. For example `1u << (8*sizeof(int)-1)` is fine, but `1 << (8*sizeof(int)-1)` will invoke undefined behavior, crash & burn. Since you might want to set the MSB of your data, you can't use `1` signed literals. – Lundin Sep 05 '16 at 08:23
  • OK. Thanks, I was aware of the unsigned/signed problem, but C99 standard hasn't arrived everywhere :( – Gerald Zehetner Sep 05 '16 at 08:26
  • You can swap the bool for `unsigned int`, though the advantage of `bool` is that it should only contain 1 or 0. Your original code would imply that "val" could be anything, which isn't true. Also the `-val` in the original code implies this; `~val` would have been more correct and more portable. – Lundin Sep 05 '16 at 08:28

2 Answers2

4

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.

unwind
  • 391,730
  • 64
  • 469
  • 606
  • As I'm on a real-time system it might get critical, but in my case I have enought time left. The uint_16 mask contains just 0s and with the << operation you move a 1 to the desired position, right? If I want to set to 1 you are or'ing the mask, which means i get a one at this position no matter what it was before. If I want to set to 0 you are adding the inverted mask? The result is that all bits get added a 1 just the selected bit gets added a 0, which means that all other bits stay the value they had and the desired bit is set to 0. Did I get that right? – Gerald Zehetner Sep 05 '16 at 08:58
  • @gerald-zehetner You could remove the branch by doing: return (command & ~mask) | mask; – mwk Sep 05 '16 at 09:00
  • @GeraldZehetner Yes, except it's "and"; not "add". – unwind Sep 05 '16 at 09:43
  • @mwk No, that would always set the bit, which isn't the goal (it should only be set if `value` is `true`), else it should be cleared. Hence the `if`. :) – unwind Sep 05 '16 at 09:43
  • Yes, I forgot that. I meant: return (command & ~(1u << bit)) | (((uint16_t) value) << bit); – mwk Sep 05 '16 at 09:55
2

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;
}
nwellnhof
  • 32,319
  • 7
  • 89
  • 113