7

I were examining shiftOut() function code in wiring_shift.c and I didn't quite understand what is going in digitalWrite function. I see !!(val & (1 << i)) is taking the bit value from val but how exactly it works?

The whole function is below.

void shiftOut(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder, uint8_t val)
{
    uint8_t i;

    for (i = 0; i < 8; i++)  {
        if (bitOrder == LSBFIRST)
            digitalWrite(dataPin, !!(val & (1 << i)));
        else    
            digitalWrite(dataPin, !!(val & (1 << (7 - i))));

        digitalWrite(clockPin, HIGH);
        digitalWrite(clockPin, LOW);        
    }
}
wizofwor
  • 298
  • 2
  • 5
  • 13
  • !!(val & (1 << i)) is the most complex part of this code. If you do understand this, then what is the part you do not understand? – Edgar Bonet Jun 01 '15 at 08:56
  • @edgar-bonet Actually this was the question. I can see it somehow calculates the bit value, but I didn't understand how it do this. – wizofwor Jun 01 '15 at 09:01
  • You do understand the behaviour of the shiftOut function? I mean, you do understand that it'll shift out a value (in binary form). And will give a clock pulse along with it. – aaa Jun 01 '15 at 10:29

1 Answers1

9

I'll assume bitOrder == LSBFIRST.

  • i is the bit number, i.e. the “index” of the next bit to write
  • 1 is 00000001 in binary
  • << is the shift left operator. It returns its first argument shifted left by as many positions as indicated by the second argument
  • 1<<i is binary 00000001 shifted left by i positions, i.e. something like 0...010...0, where the single 1 is in the i-th position counting from the right (rightmost being position 0)
  • & is the “bitwise and operator”, where any_bit & 0 is zero and any_bit & 1 is any_bit
  • val & (1 << i) is 0...0(i-th bit of val)0...0 in binary, where the i-th bit of val is in the i-th position of the result
  • !! is a double negation: it converts zero to zero and any non-zero value to one
  • !!(val & (1 << i)) is either 0 or 1, and is exactly the i-th bit of val
Edgar Bonet
  • 43,033
  • 4
  • 38
  • 76
  • let me summarize what I understand. Let assume val = '10010111';
    `for i=2`
      `!!(val & (1 << i))` = `!!('10010111' & '00000100')` = `!!('00000100')` = `1`
    
    

    If i is = 3 !!(val & (1 << i)) = !!('10010111' & '00001000') = !!('00000000') = 0

    – wizofwor Jun 01 '15 at 10:43
  • This is correct! – Edgar Bonet Jun 01 '15 at 10:48
  • And this means if I give 16bit or longer data to shiftOut, it will send least significant 8 bits and ignore the rest. – wizofwor Jun 01 '15 at 10:52
  • 1
    shiftOut() takes uint8_t data. If you call it with a 16-bit argument, the compiler will implicitly remove the 8 most significant bits before the actual call to shiftOut(). – Edgar Bonet Jun 01 '15 at 11:01
  • Thanks for the explanations above. This has been invaluable helping with understanding and re-using the code elsewhere. I see that double negation converts zero to zero and any non-zero value to one; I can't see what else it could be. Without double negation, wouldn't the output be the same anyway? What is it I am missing? – Steve McDonald Sep 30 '17 at 23:45
  • 1
    @SteveMcDonald: Yes, the output would be the same without the double negation, because digitalWrite() interprets any non-zero value (not just 1) as meaning HIGH. Apparently, the author of shiftOut() did not want to rely on this behavior, and instead wanted to always call digitalWrite() with either 0 (i.e. LOW) or 1 (HIGH). – Edgar Bonet Oct 01 '17 at 12:34