Search code examples
c++arduinobit-manipulationavr

Bitwise Operations using shift registers


I am getting started learning more about AVR ATMEGA programming.
Reading an article about the inner workings of Arduinos, I am learning how the shiftOut method is structured. I am kind of familiar with bitwise operations so far, but I have an expression I do not understand yet:

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) {
            PORTD |= !!(val & (HIGH << i)); 
        } else {
            PORTD |= !!(val & (HIGH << (7 - i)));           
        }   
        PORTB |= (HIGH << clockPin);
        PORTB ^= (HIGH << clockPin);
    }
}

The line PORTD |= !!(val & (HIGH << i)); is not 100% clear to me. I understand that I set the i-th bit High on PORTD but what does the !! mean and val&(HIGH <<i))

I know this might sound basic, but can you please help me out?


Solution

  • Just try it:

    #include <stdio.h>
    #define HIGH 0
    int main ( void )
    {
    
        unsigned char i;
        unsigned char PORTD;
        unsigned char val;
    
        PORTD=0;
    
        for(val=3;val;val<<=1)
        {
            printf("0x%02X\n",val);
            for(i=0;i<8;i++)
            {
                PORTD |= !!(val & (HIGH << i));
                printf("PORTD 0x%02X\n",PORTD);
            }
        }
        return(0);
    }        
    

    I dont know what your definition of HIGH is but since they are walking it 8 time across a byte i assume it is one bit, it is perhaps allowing for positive or negative logic on the port, so could be a 0 or 1 I am thinking.

    I get all 0x01 if HIGH is 1 and all 0x00 if HIGH is 0. so it doesnt seem to be doing anyting. When you have && for example that is a boolean expression not a bitwise logical, so perhaps this is a typo?

    the val&(HIGH<<i) though is pretty simple, again you should have posted what HIGH was defined as, lets assume it is a 1 since that makes the most sense relative to the rest of the code. There is a loop i that goes from 0 to 7. so that means that you are anding (bitwise) with 1<<0 and then 1<<1 and then 1<<2 and so on. 1<<0 is just 1 right? so val&(1<<0) is the same as val&0x01. 1<<1 = 0x02 so val&0x02. this code is one bit at a time isolating the individual bits in val. you understand that if for example val is 0x07 then 0x07&0x02 = 0x02? you line each bit up 0x07 = 0b00000111, 0x02 = 0b00000010

     00000111
    &00000010
    =========
     00000010
    

    you look at this vertically isolate one column at a time and use the AND truth table which basically says the result is 0 unless both operands are a 1, there is one column where both operands are a 1 so the result for that column is a 1, the rest of the columns one or the other operand or both is a zero so the result is zero for that column.

     00000111
    &00000100
    =========
     00000100
    
     00000111
    &000010000
    =========
     00000000
    

    Increment i two more times and evaluate against a val of 0x07 and you see what is happening at least with the val&(HIGH<<i) assuming that HIGH is a 1, if HIGH is a 0 then you will always get zeros come out of this code.

    If you are wanting to and with a walking one why dont you just do val&HIGH, so again this doesnt make sense unless that peripheral or what is on the other end of that port desires this one bit at a time thing.

    The double bang (!!) looks like a logical operation to me and has no business here IMO.

    A single bang is a logical operation

    #include <stdio.h>
    
    #define HIGH 1
    
    int main ( void )
    {
    
        unsigned char i;
        unsigned char PORTD;
        unsigned char val;
    
        PORTD=0;
    
        val=3;
        {
            printf("0x%02X\n",val);
            for(i=0;i<8;i++)
            {
                PORTD |= !(val & (HIGH << i));
                printf("PORTD 0x%02X\n",PORTD);
            }
        }
        return(0);
    }
    

    The hope here would be that we know the compiler will generate 0x00 for a false, but what does it generate for a true? 0x01, 0xFF, the C language probably has a definition. So the above code is genrating a bitstream based on our value

    PORTD 0x00
    PORTD 0x00
    PORTD 0x01
    PORTD 0x01
    PORTD 0x01
    PORTD 0x01
    PORTD 0x01
    PORTD 0x01
    

    In the lsbit position, the the loop tickles a clock.

    My personal preference would not be to play games with the C language spec, but be explicit to what you want to do:

    #include <stdio.h>
    #define HIGH 1
    int main ( void )
    {
    
        unsigned char i;
        unsigned char PORTD;
        unsigned char val;
    
        PORTD=0;
    
        val=3;
        {
            printf("0x%02X\n",val);
            for(i=0;i<8;i++)
            {
                PORTD |= (~(val>>i))&1;
                printf("PORTD 0x%02X\n",PORTD);
            }
        }
        return(0);
    }        
    
    PORTD 0x00
    PORTD 0x00
    PORTD 0x01
    PORTD 0x01
    PORTD 0x01
    PORTD 0x01
    PORTD 0x01
    PORTD 0x01
    

    using all bitwise operations, no boolean true/false operations.

    Wondering if they were trying to do two boolean nots in a row to shift the not-zero bit to the zero bit position or something...instead for lsbit first or 7-i for msbit first.

    #include <stdio.h>
    #define HIGH 1
    int main ( void )
    {
    
        unsigned char i;
        unsigned char PORTD;
        unsigned char val;
    
        PORTD=0;
    
        val=3;
        {
            printf("0x%02X\n",val);
            for(i=0;i<8;i++)
            {
                PORTD |= (val>>i)&1;
                printf("PORTD 0x%02X\n",PORTD);
            }
        }
        return(0);
    }