I found this very confusing when I started to program my
ATtiny13. I read a bunch of different sites/blogs/
datasheets to get a feel for it (and am still slightly confused
tbh). So, I thought I'd give a brief overview of the basics in a way that makes sense to me. I'm not going to cover input here, just output; but it's a good start.
Here's the key concepts:
- AVR uCs are 8-bit devices.
- Every AVR has at least some pins which can act as either Input or Output (I/O) devices.
- The voltage on the pins can be sensed or controlled via software.
- Because of 1. physical pins are logically grouped in sets of 8 as I/O ports. For my ATtiny13 there is only one port (port B) as there are only 8 pins.
- Each I/O port has 3 registers associated with it: DDRx, PINx and PORTx (more on this later), where x represents the port (in my case there's only BBRB, PINB and PORTB available).
- I/O pins exists as registers inside the processor. It's the software controlled contents of these registers that controls the state and operation of the I/O ports and pins.
- The registers are 8-bits in size. Each bit in the register determines the operation of the corresponding number pin (0-7).
DDRxThis register the direction (input/output) of the pins on port x. Again, for the
ATtiny13 there is only a port B so we only have
DDRB available but on other
AVRs there can be many ports (e.g. the
atmega168 has 3 ports B, C and D controlling 23 programmable I/O pins) .
A '0' bit makes that port pin act as input.
A '1' bit makes that port pin act as output.
PORTxThis register contains the output state of the pins on port x.
A '0' bit is considered LOW (~0V)
A '1' bit is considered HIGH (~
Vcc)
PINxThis register contains the input state of the pins on port x.
A '0' bit indicates that the port pin is LOW (~0V).
A '1' bit indicates that the port pin is HIGH (~
Vcc).
So let's take a quick look at the simple code I posted in "
Starting with the AVR uC":
1: #include
2: #include
3:
4: //LED is wired into pin 7 (PB2)
5: #define LED PB2
6:
7: int main(void){
8: //set data direction register for pin 7 to output
9: DDRB |= _BV(DDB2);
10:
11: //infinite loop
12: while (1) {
13: //turn on the LED
14: PORTB |= _BV(LED);
15: //wait for 1/4 of a second
16: _delay_ms(250);
17: //turn off the LED
18: PORTB &= ~_BV(LED);
19: //wait for 1/4 second
20: _delay_ms(250);
21: }
22: }
5: define LED as PB2. This is just a convenience to allow us to refer to pin 7 as the variable LED. I have the LED wired into pin 7 and this pin is called PB2 according to my
datasheet (this can be different for different
uCs). PB2 is actually another variable which has been defined thanks to the
avr/
io.h import statement. I could have used the number 7 instead but I decided to use the name of the pin from the
datasheet.
9: This sets bit 7 of
DDRB to '1' without affecting any of the other bits in the register. This is the data direction register (
DDR), setting a pin in this register to a '1' makes that pin act as an output. I think I should have used the LED variable (defined earlier) rather than
DDR2, just for clarity.
Ok, next time.
14: Here we manipulate the bits in the
PORTB register. This statement sets bit 7 to '1' without affecting any of the other bits in the register. Assigning a '1' to bit 7 of
PORTB sets pin 7 to HIGH (~
Vcc). This allows current to flow through our LED, lighting it up (or burning it out if you've not added a resistor in serial with it).
18: This statement sets bit 7 of the
PORTB register to '0' without affecting any of the other bits. Pin 7 is now LOW, no current flows through the LED (so we've turned it off).
Now, here's a little nugget of info that didn't click until my trip to
Noisebridge and a chat with Mitch Altman: I/O pins, by their very nature, are ambivalent towards the direction of current flow. Let me say that again as it's something I didn't realise to begin with and it has an impact on programming
LEDs with the
AVR.
I/O pins don't care which way current flows through them. So, if you have an LED attached to an I/O port (via a resistor) then it can be orientated in either direction (i.e. connected to ground or
Vcc, it doesn't matter), the only difference will be whether or not a '1' or a '0' on the corresponding
PORTB pin switches it on or off. If the LED is connected to ground, then a '1' in the
PORTB register will turn it on; on the other hand, if it's connected to
Vcc, then a '0' will switch the LED on.
I know I laboured that last point, but it was non-obvious to me (as a beginner) so I wanted to share. It also meant that those common cathode
RGB LEDs I bought aren't as useless as I thought - I was under the wrong impression that I'd only be able to program them using 3 transistors attached to the
AVR as switches, because I thought you could only turn on
LEDs [with an
AVR] by placing a '1' in the
PORTB register...
doh!