ATmega328P

The ATmega328P is an 8-bit AVR microcontroller (summary/datasheet). It's the "picoPower®" variant of the ATmega328, and the two are largely similar. It used to be made by Atmel, but Atmel was recently taken over by Microchip (the makers of the PIC line).

Other chips in the same family include the ATmega48, ATmega88, and ATmega168. They're basically the same chip, but with different amounts of flash ROM, RAM, and EEPROM space. The digits before the final 8 say how much flash ROM the chip has (in kB). The ATmega8 is a close cousin.

Pinout (28-pin PDIP)

Most pins serve multiple purposes, so I've broken them out by category. See the Arduino pin mapping (or below) for a comprehensive summary.

Power

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
              +-----------+
       RESET -|  1 \_/ 28 |-
             -|  2     27 |-
             -|  3     26 |-
             -|  4     25 |-
             -|  5     24 |-
             -|  6     23 |-
         VCC -|* 7     22@|- GND
         GND -|@ 8     21 |-
             -|  9     20*|- AVCC
             -| 10     19 |-
             -| 11     18 |-
             -| 12     17 |-
             -| 13     16 |-
             -| 14     15 |-
              +-----------+

Clock

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
              +-----------+
             -|  1 \_/ 28 |-
             -|  2     27 |-
             -|  3     26 |-
             -|  4     25 |-
             -|  5     24 |-
             -|  6     23 |-
             -|* 7     22@|-
             -|@ 8     21 |-
XTAL1, TOSC1 -|  9     20*|-
XTAL2, TOSC2 -| 10     19 |-
             -| 11     18 |-
             -| 12     17 |-
             -| 13     16 |-
        CLKO -| 14     15 |-
              +-----------+

Digital ports

There are 3 ports (B, C, D) with 8 pins each (not contiguous!), except there's no pin C7. Pin C6 is special, since it's shared with RESET; it can only be used when RSTDISBL is programmed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
              +-----------+
      C { C6 -|  1 \_/ 28 |- C5 \
        / D0 -|  2     27 |- C4 |
        | D1 -|  3     26 |- C3 |
      D | D2 -|  4     25 |- C2 | C
        | D3 -|  5     24 |- C1 |
        \ D4 -|  6     23 |- C0 /
             -|* 7     22@|-
             -|@ 8     21 |-
      B / B6 -|  9     20*|-
        \ B7 -| 10     19 |- B5 \
        / D5 -| 11     18 |- B4 |
      D | D6 -| 12     17 |- B3 | B
        \ D7 -| 13     16 |- B2 |
      B { B0 -| 14     15 |- B1 /
              +-----------+

Pin change interrupts

The 3 ports (B, C, D) correspond directly to the pin change mask registers (PCMSK0, PCMSK1, PCMSK2). Hence, there's no PCINT15.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
              +-----------+
 1 { PCINT14 -|  1 \_/ 28 |- PCINT13 \
   / PCINT16 -|  2     27 |- PCINT12 |
   | PCINT17 -|  3     26 |- PCINT11 |
 2 | PCINT18 -|  4     25 |- PCINT10 | 1
   | PCINT19 -|  5     24 |- PCINT9  |
   \ PCINT20 -|  6     23 |- PCINT8  /
             -|* 7     22@|-
             -|@ 8     21 |-
 0 /  PCINT6 -|  9     20*|-
   \  PCINT7 -| 10     19 |- PCINT5  \
   / PCINT21 -| 11     18 |- PCINT4  |
 2 | PCINT22 -| 12     17 |- PCINT3  | 0
   \ PCINT23 -| 13     16 |- PCINT2  |
 0 {  PCINT0 -| 14     15 |- PCINT1  /
              +-----------+

External interrupts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
              +-----------+
             -|  1 \_/ 28 |-
             -|  2     27 |-
             -|  3     26 |-
        INT0 -|  4     25 |-
        INT1 -|  5     24 |-
             -|  6     23 |-
             -|* 7     22@|-
             -|@ 8     21 |-
             -|  9     20*|-
             -| 10     19 |-
             -| 11     18 |-
             -| 12     17 |-
             -| 13     16 |-
             -| 14     15 |-
              +-----------+

Timers

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
              +-----------+
             -|  1 \_/ 28 |-
             -|  2     27 |-
             -|  3     26 |-
             -|  4     25 |-
        OC2B -|  5     24 |-
          T0 -|  6     23 |-
             -|* 7     22@|-
             -|@ 8     21 |-
             -|  9     20*|-
             -| 10     19 |-
    T1, OC0B -| 11     18 |-
        OC0A -| 12     17 |- OC2A
             -| 13     16 |- OC1B
        ICP1 -| 14     15 |- OC1A
              +-----------+

Analog

ADC6 and ADC7 are only present in the 32-pin packages.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
              +-----------+
             -|  1 \_/ 28 |- ADC5
             -|  2     27 |- ADC4
             -|  3     26 |- ADC3
             -|  4     25 |- ADC2
             -|  5     24 |- ADC1
             -|  6     23 |- ADC0
             -|* 7     22@|-
             -|@ 8     21 |- AREF
             -|  9     20*|-
             -| 10     19 |-
             -| 11     18 |-
        AIN0 -| 12     17 |-
        AIN1 -| 13     16 |-
             -| 14     15 |-
              +-----------+

USART, I2C, SPI

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
              +-----------+
             -|  1 \_/ 28 |- SCL0  \
      / RXD0 -|  2     27 |- SDA0  / I2C
      | TXD0 -|  3     26 |-
USART .      -|  4     25 |-
      .      -|  5     24 |-
      \ XCK0 -|  6     23 |-
             -|* 7     22@|-
             -|@ 8     21 |-
             -|  9     20*|-
             -| 10     19 |- SCK0  \
             -| 11     18 |- MISO0 | SPI
             -| 12     17 |- MOSI0 |
             -| 13     16 |- SS0   /
             -| 14     15 |-
              +-----------+

Minimal hookup

At the very least, VCC and GND must be supplied, with 1.8 V to 5.5 V between them. It's good practice to connect the two GND pins together, but it's not absolutely necessary (i.e. you should do it on a PCB, but your circuit will probably work without it when prototyping). The datasheet advises connecting AVCC to VCC when not using the ADC, but this is also not absolutely necessary.

Schematic showing the minimal hookup for an ATmega328P

If you're running from an internal oscillator, that's all you need to have the chip going. Keep in mind that the maximum clock speed is limited by the supply voltage.

Compiling

On Arch Linux, the avr-gcc, avr-libc, and avr-binutils packages are needed.

Here's a minimal blinking program:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <avr/io.h>
#include <avr/wdt.h>
#include <util/delay.h>

int main(void) {
    // Clear reset flags.
    MCUSR = 0;

    // Set pin D5 to output.
    DDRD |= _BV(5);

    for (;;) {
        // Toggle pin D5.
        PORTD ^= _BV(5);
        // Wait 500 ms.
        _delay_ms(500);
    }

    return 0;
}

Build an ELF using

1
avr-gcc -Os -mmcu=atmega328p -DF_CPU=1000000 -o main.bin main.c

This assumes that your clock is at 1 MHz, which is true for a brand new chip with original fuse settings. Otherwise, set F_CPU accordingly. It also optimizes for size, which is often what you want for a microcontroller. If you just want to compile an object file and not link it, use -c as usual.

You can take a look at the result with

1
avr-objdump -D main.bin

To make an Intel HEX file, use

1
avr-objcopy -j .text -j .data -O ihex main.bin main.hex

Programming

On Arch Linux, the avrdude package gives you avrdude.

You may want to automatically set the permissions of your programmer device to something permissible. For example, for a USBtinyISP-derived programmer like the SparkFun Pocket AVR Programmer, you could make the file /etc/udev/rules.d/98-usbtiny.rules with the contents

1
ATTRS{idVendor}=="1781", ATTRS{idProduct}=="0c9f", MODE="0666"

To see a list of programmers supported by avrdude, run

1
avrdude -c '?'

I'm assuming usbtiny in the following, because that's what I use.

To see information about the current device (and make sure everything is set up correctly), use

1
avrdude -c usbtiny -p atmega328p -vvv

It should output (among other things)

1
avrdude: Device signature = 0x1e950f (probably m328p)

To flash an Intel HEX file (in this case main.hex), use

1
avrdude -c usbtiny -p atmega328p -U flash:w:main.hex:i

This means "write the Intel HEX file main.hex to the flash ROM".

Fuses

Some aspects of the chip's operation (e.g. the clock source) can only be changed by setting "fuses". The fuse values can only be modified when the chip is being programmed. For whatever reason, the fuse logic is inverted, so programming a bit means setting it to 0, not 1!

The ATmega328P has 3 fuse bytes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Extended               High                 Low

                       +-------- RSTDISBL   +-------- CKDIV8
                       |+------- DWEN       |+------- CKOUT
                       ||+------ SPIEN      ||+------ SUT1   \
                       |||+----- WDTON      |||+----- SUT0   /
11111                  ||||+---- EESAVE     ||||+---- CKSEL3 \
|||||+--- BODLEVEL2 \  |||||+--- BOOTSZ1 \  |||||+--- CKSEL2 |
||||||+-- BODLEVEL1 |  ||||||+-- BOOTSZ0 /  ||||||+-- CKSEL1 |
|||||||+- BODLEVEL0 /  |||||||+- BOOTRST    |||||||+- CKSEL0 /
||||||||               ||||||||             ||||||||
76543210               76543210             76543210

The factory defaults are

1
2
3
E 0xff   H 0xd9   L 0x62
11111111 11011001 01100010
76543210 76543210 76543210

This results in the following settings:

Setting Bits Value
CKSEL 0010 Internal RC oscillator (8 MHz)
SUT 10 Maximum startup time
CKOUT 1 Pin 14 not used for clock output
CKDIV8 0 Clock divided by 8
BOOTRST 1 Application reset (no boot loader)
BOOTSZ 00 Maximum boot loader size
EESAVE 1 EEPROM erased on chip erase
WDTON 1 Watchdog forced reset disabled
SPIEN 0 Serial programming enabled
DWEN 1 debugWIRE disabled
RSTDISBL 1 Pin 1 used for reset
BODLEVEL 111 BOD disabled

To get the full 8 MHz from the internal oscillator, unset CKDIV8 by programming 0xe2 to the low byte:

1
avrdude -c usbtiny -p atmega328p -vvv -U lfuse:w:0xe2:m

The Engbedded AVR fuse calculator makes life a lot easier when changing fuses.

Target board

You need some way of connecting your programmer to your chip. The easiest way is to use a target board (make sure to get one with a ZIF socket!).

It's also straightforward to make a target board. If your programmer uses the standard AVR ISP header and cable, you can refer to the standard pinouts:

1
2
3
4
5
6
7
                           +------+
      +-----+         MOSI | 1  2 | VCC
 MISO | 1 2 | VCC          | 3  4 | GND
  SCK   3 4 | MOSI   RESET   5  6 | GND
RESET | 5 6 | GND      SCK | 7  8 | GND
      +-----+         MISO | 9 10 | GND
                           +------+

Here's how you might connect a header to the right spots beside the chip using jumper wires: Wiring for ATmega328P target board for ISP programming (front) Wiring for ATmega328P target board for ISP programming (back) It would be better to use a shrouded header, but I didn't have any.

Then just add a ZIF socket and some female headers: Completed ATmega328P target board for ISP programming (front) Completed ATmega328P target board for ISP programming (back) The red dots on the socket and ISP header indicate pin 1.

And here's how it looks all wired up and ready to go: ATmega328P target board for ISP programming with inserted chip and plugged in ISP connector

Fun fact: Just like a breadboard, this target board has enough parasitic capacitance that you can throw on a crystal without any load capacitors. As far as I can resolve with my scope, I get within 0.2% of the rated frequency with a 16 MHz crystal.

External clock

If you set CKSEL to 0000, the chip expects a ready-made clock signal on pin 9 (XTAL1). You can do basically whatever you want here, like a 555 timer at 70 kHz.

Even if the chip is trying to use an external crystal on XTAL1 and XTAL2 (CKSEL set to one of the many crystal oscillator settings), you can feed it a square wave on XTAL1 and it will still work! This is useful for rescue to switch back to the internal oscillator. Just note that programming has to happen no faster than 4 times slower than the clock speed, so you might need to do something like

1
avrdude ... -B 15kHz

Two-wire (I2C)

Pin 27 is SDA and pin 28 is SCL. While there is hardware support for I2C, you'll need a library to use it if you don't want to mess around with the registers directly.

For a master device, you can use Peter Fleury's I2C Master Interface library. It's GPLv3, and dead simple to use. If your slave devices don't have pull-ups, you'll need 4.7 kΩ on both SCL and SDA. The only files you need are i2cmaster.h and twimaster.c, which I'll assume are in i2cmaster/. Build with -Ii2cmaster and include i2cmaster/twimaster.o when linking. Set SCL_CLOCK to what you want to run the bus at, but be careful that your CPU clock speed allows for the setting.

Datasheet problems

The ATmega328/P - Complete Datasheet (Rev. B – 11/2016, dated "12/10/2016" and marked "Atmel-42735B-ATmega328/P_Datasheet_Complete-11/2016" on the bottom of each page) has some issues: