Yeah, this sort of thing was the MOST confusing part of getting into microcontroller programming, so lets break it down (sorry in advance that I’m probably re-explaining some stuff you already know).
DDRC stands for Data Direction Register C, it’s basically an eight bit number that controls the state of the pins of PortC. Technically PortC only has six pins on the ATMega168, PC0-PC5 (PC6 is the reset pin), but it’s an eight-bit microcontroller, so registers really can’t help but have eight bits.
By default, the data direction register bits are all set to zero, (in binary 0b00000000) making all the pins inputs. Lets say I want to set pin PC5 to be an output. I should the fifth bit of DDRC to 1 (keeping in mind that the rightmost bit is the zero’th bit). I could do something like:
But first off, that’s a little cumbersome to write out. You’re right about the << being a left bit-shifting operator, so I could write:
Where 1 in binary is 0b00000001, and shifted five bit-places to the left is 0b00100000, the number I want.
To help you keep your sanity, and make code more portable from device to device, the <avr/io.h> library contains a big list of register bit definitions that let you use the individual bit names. It’s really helpful for registers that don’t have logically numbered bits like the DDR ones. Basically PC5=5, so (1<<PC5) will be evaluated the same way as (1<<5). It’s not really different, but it’s a good bookkeeping practice.
Now, what if I want to set PC5 as an output, but I don’t know the state of, or want to mess with the other bits of DDRC (like they’re set by another part of the program). Unfortunately, the command DDRC=(1<<PC5) sets every other bit of DDRC to zero:
Whatever those other ? bits were, I’ve set them to zero now. The general way to deal with this is to use the bitwise or operator ‘|’ for example: 0b11100000|0b11000011=0b11100011. If either bit of the two numbers is 1, the resulting bit is set to one. Conveniently:
By those question marks, I mean that whatever the state of the other bits of DDRC were, they remain unchanged, and whatever the state of PC5 was, it is now set to 1. You can also use the | operator to set multiple bits to 1 at once:
Pretty neat! But that’s only good for setting bits to 1, what if I want to take an output pin and change it back to an input pin, again without affecting the other bits of the DDR. The trick is to use the bitwise and operator ‘&’, and the bitwise not operator ‘~’.
The & operator outputs a 1 when both of two corresponding input bits is 1, for example: 0b11000000&0b10111111=0b10000000.
The ~ operator flips the bits of a number, so ~0b11110000=0b00001111. This should not be confused with the logical not operator ‘!’, which sets all nonzero numbers to 0, and zero to 1.
SO, what happens when you run DDRC&=~(1<<PC5)? Well:
So, whether PC5 was an output or an input, you’ve now set it to be an input for sure, without affecting the input/output state of the other pins in Port C! ROCK ON!