The ATmega328P is a low-power 8-bit microcontroller built on the AVR enhanced RISC architecture, delivering approximately 1 MIPS per MHz of clock throughput — a design goal the datasheet states explicitly as letting you optimize power consumption versus processing speed. It carries 32 KB of in-system-programmable Flash with read-while-write support, 1 KB of EEPROM, 2 KB of SRAM, 23 GPIO lines, three timer/counters with compare modes, USART plus a byte-oriented 2-wire (I²C-like) interface and an SPI port, a 6-channel 10-bit ADC (8 channels in TQFP/QFN packages), and a watchdog timer with its own internal oscillator. Five power-saving modes — Idle, Power-down, Power-save, ADC Noise Reduction, and Standby — target battery-powered, wake-on-event, and precision-analog applications, and automotive-rated variants exist at 16 MHz, 2.7–5.5 V, and −40 °C to +125 °C. In short: it's for low-power 8-bit embedded control and sensing, from general-purpose boards up to automotive-grade environments.
Now the questions engineers actually ask.
What are the ATmega328P's memory sizes and key specifications?
The headline numbers, straight from the datasheet's feature enumeration:
| Resource | Size |
|---|---|
| In-system-programmable Flash (read-while-write) | 32 KB |
| EEPROM | 1 KB |
| SRAM | 2 KB |
| GPIO | 23 lines |
| ADC | 6-channel, 10-bit (8 channels in TQFP/QFN) |
| Timers | Three timer/counters with compare modes |
| Serial | USART, 2-wire (TWI/I²C-like), SPI |
| Temperature (automotive grade) | −40 °C to +125 °C at 2.7–5.5 V, 16 MHz |
So the answer to "atmega328p memory size" is 32 KB Flash / 1 KB EEPROM / 2 KB SRAM — enough for field-updatable firmware via bootloader, non-volatile parameter storage, and modest runtime state. The 2 KB SRAM is the number that most often bites people porting from larger parts, so budget buffer sizes early.
If your question was "atmega328p size" in the physical sense: the ADC channel count is package-dependent (see the integration section below), and the part comes in PDIP, TQFP, and QFN/MLF packages — pin the PCB for TQFP/QFN if you need all eight analog inputs.
How do I integrate the ATmega328P into a design? (Wiring, references, and pins)
Which ADC reference should I use, and what does AREF need?
The ADC reference-selection table gives you the two references you'll use in practice: REFS1:0 = 0,1 selects AVCC with an external capacitor at the AREF pin, and REFS1:0 = 1,1 selects the internal 1.1 V reference, also with an external capacitor at AREF. Both modes require that external capacitor — the datasheet's table states it for each mode. Don't hang a low-impedance load on AREF in AVCC-referenced mode: any external resistance to ground will shunt the reference and fight the internal reference path. The specific capacitance value isn't stated in the segments covered here; a 100 nF is the common choice in practice, but verify against the full datasheet before committing.
What source impedance can drive the ADC pin?
A normal single-ended conversion takes 13 ADC clock cycles total, of which 1.5 cycles are the sample-and-hold aperture. That short aperture means the sampling switch charges an internal sample capacitor through your source impedance — high impedance means the cap doesn't settle before the switch opens, and your reading reads low. Keep the driving source impedance low and put a small capacitor (100 nF is typical practice) right at the ADC pin, close to the MCU.
Does the I²C bus need external pull-ups?
Yes, in practice. PC4/PC5 (SDA/SCL) do have internal pull-ups, but they're controlled by the TWEN bit and the PORTxn × PUD logic in the port-pin override tables — the pull-up only engages under specific register conditions, and its value is weak. The reliable rule: use external pull-ups (4.7 kΩ is the common value) rather than the internal ones. External resistors give you defined, symmetric open-drain edges and don't depend on TWEN state. The TWI bit-rate generator itself offers prescaler values of 1, 4, 16, and 64 in the bit-rate path, so you have wide latitude on bus clock.
What about the PDIP package and unused pins?
Two integration gotchas:
- ADC7:6 exist only in the TQFP and QFN/MLF packages. The PDIP gives you ADC0–ADC5 only. If your BOM says PDIP and your schematic uses eight ADC channels, you'll discover the missing channels at bring-up.
- Don't leave unused pins floating. All I/O pins support the PUD (pull-up disable) bit in MCUCR; for unused pins, either enable the internal pull-up in software (DDRxn=0, PORTxn=1, PUD=0) or drive them as outputs low. Floating inputs can float through the input threshold region and draw unnecessary current. For unused ADC channels, set the per-channel digital-input-disable bits ADC0D–ADC5D in DIDR0 — this disables the digital input buffer on analog pins and avoids needless input current.
What do XTAL and RESET pins need?
PB7/PB6 are the crystal pins (XTAL2/XTAL1) with user-selectable pull-up/driver override behavior, and PC6 is RESET with the RSTDISBL fuse controlling whether it acts as reset or general I/O. Treat PC6 as sacred in production designs — see the fuse section below for why.
Known limitation: the specific crystal load-capacitor values, RESET pull-up value, decoupling capacitor values, and DC-characteristic limits (VIL/VIH, per-pin current, leakage) could not be verified against the datasheet's spec tables for this write-up. The commonly used values (22 pF crystal caps, 10 kΩ reset pull-up, 100 nF decoupling) are engineering practice, not verified numbers here — pull them from the full datasheet's DC Characteristics, Absolute Maximum Ratings, and oscillator chapters before finalizing a PCB.
Which fuses and ATmega328P-specific settings will trip me up?
This is the single biggest trap on this exact part:
- CKDIV8 is programmed by default, with the factory clock source being the calibrated internal RC oscillator (default CKSEL3:0 = 0,0,1,0). A brand-new ATmega328P therefore boots at 1 MHz effective (8 MHz ÷ 8) even though the headline spec is 16 MHz. If your code assumes full speed, you're running 8× slower. Un-program CKDIV8 or account for the divide in your timing setup.
- RSTDISBL (high fuse byte, bit 7) defaults unprogrammed — leave it that way unless you truly need the extra I/O. Programming it converts PC6/RESET into general I/O, which kills re-entry into SPI programming, since ISP enters through the reset line. Recovery then requires high-voltage (12 V) parallel programming.
- BOOTSZ1:0 = 0,0 is programmed by default, sizing the boot section as the largest option. If you're not using a bootloader, that's Flash wasted for nothing — set the smallest boot size you need to reclaim it.
- SPIEN is programmed by default (SPI programming enabled); DWEN (debugWIRE) defaults unprogrammed. So out of the box you can ISP-program but not debugWIRE-debug.
- Brown-out detection: all three BODLEVEL fuses default unprogrammed, meaning BOD is effectively disabled. The BOD monitors VCC against a fuse-selected trigger level with hysteresis — reset fires immediately when VCC drops below V_BOT− and recovers only after the time-out delay once VCC crosses V_BOT+, keeping detection spike-free. If your rail is noisy or has long turn-on ramps, pick a BODLEVEL per the fuse-decoding table rather than leaving BOD off; slow-falling rails corrupt memory.
- Watchdog timeouts are VCC-dependent. The prescale table lists typical timeouts at VCC = 5.0 V, from 16 ms (WDP = 0000) up to 8.0 s (WDP = 1001). WDP settings 1010 through 1111 are reserved — do not use them. At 3.3 V the typical timeouts shift, so don't hang firmware watchdog margins off the 5 V numbers. With WDTON unprogrammed (default), the WDT is software-controlled; programming WDTON forces it always-on — worth considering for unsupervised/remote designs.
One more clock note: the internal RC oscillator is runtime-adjustable via OSCCAL (register 0x66, CAL7:0 factory calibration byte). If you run crystal-less, you can load the factory calibration value and fine-tune OSCCAL in software to compensate for supply and temperature drift.
How do I test and verify an ATmega328P design after implementation?
Work through these after first power-up:
- Confirm the actual clock rate first. Read back the fuse bytes via ISP and check CKDIV8 and CKSEL3:0. Then measure a known-duration toggle (timer pin or UART baud) to confirm the effective F_CPU matches what your code assumes. A 1 MHz-vs-8 MHz mismatch shows up as everything running 8× slow and garbled serial output.
- Verify fuse configuration as a set: SPIEN programmed (or you can't ISP-program next time), RSTDISBL unprogrammed, BOOTSZ sized for your actual bootloader needs, and a BODLEVEL selected if your rail can sag or ramp slowly.
- Exercise the ADC on a known reference. Apply a known voltage, convert with REFS1:0 = 0,1 (AVCC) and with 1,1 (internal 1.1 V), and sanity-check readings against a meter. Confirm the AREF capacitor is populated for whichever mode you use — it's required in both.
- Check ADC channels match your package: on PDIP, only ADC0–ADC5 exist; ADC7:6 are TQFP/QFN only. Read from each channel you intend to use and confirm non-garbage values.
- Test TWI with external pull-ups: verify SDA/SCL edges with a scope. If edges are slow or the bus is marginal, the internal pull-ups engaged instead of (or alongside) your external 4.7 kΩ resistors.
- Validate watchdog behavior: force a hang and confirm the reset occurs within the expected timeout for your WDP setting at your actual VCC — not the 5 V table value if you run 3.3 V.
- Verify sleep-mode behavior if you use them: Idle keeps SRAM, timers, USART, 2-wire, SPI, and interrupts running; Power-down freezes everything until interrupt or reset; Power-save keeps the asynchronous timer running; ADC Noise Reduction stops CPU and I/O except the async timer and ADC; Standby keeps the crystal running for fast wake. Confirm wake sources actually wake the part in each mode you use.
Why is my ATmega328P circuit misbehaving? (Troubleshooting)
Map symptoms to causes:
- Everything runs 8× slower than expected, UART garbage, delays too long. CKDIV8 is still programmed from the factory — the part is booting at 1 MHz from the internal 8 MHz RC. Un-program CKDIV8 or divide your baud/timer math accordingly.
- Can't program via ISP anymore. Check whether RSTDISBL got programmed — PC6 became an I/O and the reset line no longer enters programming mode. Recovery requires high-voltage (12 V) parallel programming. Prevention: never program RSTDISBL unless you absolutely need the pin.
- ADC readings are low or noisy. Three suspects: source impedance too high for the 1.5-cycle sample-and-hold aperture (add a capacitor at the pin, buffer the source); a load shunting AREF (remove any DC path from AREF to ground); or digital input buffers active on analog pins (set DIDR0 bits for unused channels, and use ADC Noise Reduction sleep mode to cut switching noise during conversions).
- I²C bus unreliable. You're relying on internal pull-ups that only engage when TWEN permits and are weak regardless. Fit external 4.7 kΩ pull-ups and re-check edges.
- Random resets or memory corruption on a sagging rail. BODLEVEL fuses are all unprogrammed by default, so brown-out detection is off. Select a BODLEVEL appropriate to your supply and re-test.
- Watchdog resets at unexpected intervals. The WDP timeout table's typical values are specified at 5.0 V; at 3.3 V the timeouts shift. Recalculate margins at your actual VCC, and make sure your WDP code isn't landing in the reserved range (WDP = 1010 through 1111).
- Mysterious current draw. Floating unused inputs, or digital input buffers left enabled on analog pins. Enable pull-ups or drive unused pins low, and set the DIDR0 disable bits for channels you're not using.
- Timing drift in crystal-less designs. The internal RC drifts with supply and temperature; use OSCCAL to trim at runtime, or add a crystal on XTAL1/XTAL2 if your application can't tolerate the drift.
Part page: ATmega328P.