Microcontroller courses in electrical and computer engineering frequently teach embedded systems using the classic Microchip ATmega32. In contrast, hobbyists, makers, and introductory robotics workshops almost universally start with the ubiquitous Arduino Uno, which is powered by the ATmega328P.
Students moving between these two chips often encounter frustrating compiler errors and hardware mismatches: Why does PORTA fail to compile on Arduino Uno? Why does a Timer0 register assignment from an ATmega32 textbook cause a compilation error on an ATmega328P?
This article provides a technical comparison of the ATmega32 and ATmega328P architectures. We compare their memory layouts, GPIO mappings, timer register structures, and interrupt vector tables, and provide tested register-level C examples that you can simulate directly in your browser.
---
1. Hardware and Package Overview
While both microcontrollers are based on the 8-bit AVR enhanced RISC architecture and execute most single-cycle instructions identically, their physical pinouts and silicon peripherals differ significantly.
| Specification | ATmega32 | ATmega328P (Arduino Uno) |
|---|---|---|
| Package Types | 40-pin PDIP, 44-lead TQFP/MLF | 28-pin PDIP, 32-lead TQFP/QFN |
| Flash Memory | 32 KB | 32 KB (0.5 KB typically used by bootloader) |
| SRAM | 2048 Bytes (2 KB) | 2048 Bytes (2 KB) |
| EEPROM | 1024 Bytes (1 KB) | 1024 Bytes (1 KB) |
| Total GPIO Lines | 32 dedicated I/O lines | 23 usable I/O lines |
| Full 8-bit Ports | 4 ports (PORTA, PORTB, PORTC, PORTD) | 2 full ports (PORTB, PORTD), 1 partial (PORTC: 7 pins) |
| ADC Channels | 8 channels (dedicated on PORTA, 10-bit) | 6 channels on DIP / 8 on TQFP (multiplexed on PORTC) |
| External Interrupts | 3 dedicated: INT0 (PD2), INT1 (PD3), INT2 (PB2) | 2 dedicated: INT0 (PD2), INT1 (PD3) + 3 Pin Change (PCINT) |
| Operating Voltage | 4.5V to 5.5V (standard) / 2.7V to 5.5V (ATmega32L) | 1.8V to 5.5V (ATmega328P) |
The PORTA Difference
The most noticeable hardware distinction for students is the port arrangement. The 40-pin ATmega32 offers four complete 8-bit bidirectional ports: PORTA, PORTB, PORTC, and PORTD. Port A is dedicated to the 10-bit Analog-to-Digital Converter (ADC0 through ADC7).
The 28-pin ATmega328P does not have a PORTA. To fit into a smaller package, its ADC channels (A0 to A5 on Arduino Uno) are multiplexed onto PORTC pins (PC0 to PC5). Code written for an ATmega32 that accesses DDRA or PORTA will fail to compile when targeting an ATmega328P.
---
2. Timer Register Architecture: TCCR0 vs TCCR0A/TCCR0B
Both chips feature an 8-bit Timer/Counter0 with compare match and PWM capabilities. However, Atmel redesigned the timer control registers on newer AVR devices like the ATmega328P.
ATmega32 Timer0: The Single `TCCR0` Register
On the ATmega32, all Timer0 modes, output compare behaviors, and prescalers are configured within a single 8-bit register: TCCR0.
ATmega32 TCCR0: [ FOC0 | WGM00 | COM01 | COM00 | WGM01 | CS02 | CS01 | CS00 ]
- WGM01:WGM00: Waveform Generation Mode (Normal, Phase Correct PWM, CTC, Fast PWM).
- COM01:COM00: Compare Output Mode (determines OC0 / PB3 pin behavior).
- CS02:CS00: Clock Select prescaler bits (No clock, 1, 8, 64, 256, 1024, or external clock).
ATmega328P Timer0: The Split `TCCR0A` and `TCCR0B` Registers
On the ATmega328P, Timer0 control is split across two registers to support additional compare channels (OC0A on PD6 and OC0B on PD5) and extended waveform modes:
ATmega328P TCCR0A: [ COM0A1 | COM0A0 | COM0B1 | COM0B0 | - | - | WGM01 | WGM00 ]
ATmega328P TCCR0B: [ FOC0A | FOC0B | - | - | WGM02 | CS02 | CS01 | CS00 ]
- Mode bits are split:
WGM01andWGM00reside inTCCR0A, whileWGM02resides inTCCR0B. - Prescaler bits (
CS02:CS00) reside exclusively inTCCR0B.
Attempting to assign TCCR0 = 0x05; on an Arduino Uno yields an error: 'TCCR0' was not declared in this scope.
---
3. Interrupt Vector Differences
Interrupt vector tables define the memory jump addresses executed when hardware events trigger. Because vector addresses are fixed in flash memory, compiling an ISR for the wrong target leads to misdirected execution.
External Interrupts Comparison
- ATmega32: Provides
INT0(vector 2, flash address0x0002),INT1(vector 3, address0x0004), andINT2(vector 4, address0x0006).INT2is an edge-triggered asynchronous interrupt on pin PB2. - ATmega328P: Provides
INT0(vector 2, address0x0002) andINT1(vector 3, address0x0004). Instead of anINT2, it provides three pin-change interrupt vectors (PCINT0_vect,PCINT1_vect,PCINT2_vect) that monitor any pin on ports B, C, and D.
Because the vector order differs, timer and ADC vectors on the ATmega32 do not occupy the same memory slots as on the ATmega328P. The compiler toolchain (avr-gcc) maps vector names (like TIMER0_OVF_vect) to their respective target addresses, which is why compiling with the exact -mmcu= flag is essential.
---
4. Practical Code Comparison
To demonstrate bare-metal programming without Arduino framework overhead (digitalWrite or delay), here are two practical examples tested with AVR-GCC.
Example 1: GPIO Output Toggling (PB0)
Both chips feature PORTB with identical bit orientations for pin PB0.
ATmega32 Implementation:
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void) {
// Configure PB0 as output
DDRB |= (1 << PB0);
while (1) {
// Toggle PB0
PORTB ^= (1 << PB0);
_delay_ms(250);
}
return 0;
}
This exact code compiles and runs identically on both ATmega32 and ATmega328P because DDRB and PORTB share standard definitions.
---
Example 2: Timer1 CTC Interrupt (Periodic Heartbeat & Frequency Math)
Timer1 is a 16-bit timer shared across AVR microcontrollers that supports Clear Timer on Compare Match (CTC) mode. Configuring Timer1 provides a rigorous comparison of interrupt masks and clock math.
#### The Frequency Calculation
Given a system clock frequency of $F_{CPU} = 16\text{ MHz}$ and a prescaler $N = 8$, the timer increments at $2\text{ MHz}$ (one tick every $0.5\ \mu\text{s}$). For a target compare-match interrupt period of $T_{int} = 1\text{ ms}$ (an interrupt rate of $1\text{ kHz}$):
$$OCR1A = \frac{F_{CPU}}{N \times f_{int}} - 1 = \frac{16{,}000{,}000}{8 \times 1000} - 1 = 1999$$
Key Distinction: Toggling an output pin (
PORTB ^= (1 << PB0)) inside each 1 ms compare interrupt generates a square wave with a period of $T_{wave} = 2 \times 1\text{ ms} = 2\text{ ms}$, which corresponds to a 500 Hz square wave, not 1 kHz. To generate a 1 kHz square wave via pin toggling, the interrupt must trigger every 0.5 ms ($OCR1A = 999$).
ATmega32 Implementation:
#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
ISR(TIMER1_COMPA_vect) {
// Toggles every 1 ms -> 500 Hz square wave output
PORTB ^= (1 << PB0);
}
int main(void) {
DDRB |= (1 << PB0);
// Normal port operation, CTC mode (WGM12=1 in TCCR1B)
TCCR1A = 0x00;
TCCR1B = (1 << WGM12) | (1 << CS11); // Prescaler = 8
// Set Compare Match value for 1 ms interval
OCR1A = 1999;
// Enable Timer1 Compare Match A Interrupt
// Note: ATmega32 uses shared TIMSK register (bit OCIE1A)
TIMSK |= (1 << OCIE1A);
// Enable Global Interrupts
sei();
while (1) {
// Main loop remains free
}
return 0;
}
ATmega328P (Arduino Uno) Implementation:
#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
ISR(TIMER1_COMPA_vect) {
// Toggles every 1 ms -> 500 Hz square wave output
PORTB ^= (1 << PB0);
}
int main(void) {
DDRB |= (1 << PB0);
// Normal port operation, CTC mode (WGM12=1 in TCCR1B)
TCCR1A = 0x00;
TCCR1B = (1 << WGM12) | (1 << CS11); // Prescaler = 8
// Set Compare Match value for 1 ms interval
OCR1A = 1999;
// Enable Timer1 Compare Match A Interrupt
// Note: ATmega328P uses dedicated TIMSK1 register (bit OCIE1A)
TIMSK1 |= (1 << OCIE1A);
// Enable Global Interrupts
sei();
while (1) {
// Main loop remains free
}
return 0;
}
Notice the architectural distinction: the ATmega32 routes all timer interrupt enable bits through a single consolidated TIMSK register, whereas the ATmega328P separates them into dedicated registers per peripheral (TIMSK0, TIMSK1, TIMSK2).
---
5. Simulating ATmega32 vs Arduino Uno in the Browser
For academic lab assignments and remote coursework, simulating bare-metal AVR code has traditionally required desktop software suites like Labcenter Proteus or Microchip Studio.
CircuPilot provides a browser-based workflow with verified toolchain integration:
1. Real Toolchain Compilation: Code is compiled server-side with standard avr-gcc via arduino-cli / MightyCore using the exact MCU target flags (-mmcu=atmega32 or -mmcu=atmega328p).
2. Instruction-Level Emulation: The resulting hex binary executes in an instruction-accurate AVR emulator (avr8js), updating I/O registers and peripheral states clock cycle by clock cycle.
3. Hardware Peripheral Status: Digital I/O ports (PORTA through PORTD), ADC channels, external interrupts (INT0/INT1), and 16-bit Timer1 CTC operations are fully supported. Because legacy AVR Timer0/Timer2 use unified single control registers (TCCR0), Timer0/Timer2 emulation is omitted in favor of Timer1 to guarantee timing fidelity.
4. Interactive Visual Pins: Connect virtual LEDs, potentiometers, 16x2 LCDs, or oscilloscopes directly to the emulated pins to observe execution in real time.
For tutorials on using the sibling 16 KB chip, explore our guide on ATmega16 Simulator Online: Register-Level AVR in a Browser.
---
Summary
- Use ATmega32 when studying microprocessor course syllabi that require four full 8-bit ports (PORTA through PORTD), 40-pin DIP breadboarding, and unified timer registers like
TCCR0. - Use Arduino Uno (ATmega328P) when building compact projects with modern peripheral split registers (
TCCR0A/TCCR0B), pin change interrupts, and broad hobbyist library support. - In both cases, bare-metal C with register manipulation provides precise timing and small code footprint compared to high-level framework wrappers.
Frequently asked questions
What is the main architectural difference between ATmega32 and ATmega328P?
ATmega32 is a 40-pin DIP legacy microcontroller with 32 GPIO lines across four full 8-bit ports (PORTA to PORTD), while the ATmega328P used on Arduino Uno is a 28-pin chip with 23 usable GPIO lines, lacking PORTA and grouping analog inputs onto PORTC.
How do Timer0 control registers differ between ATmega32 and ATmega328P?
ATmega32 configures Timer0 through a single 8-bit register (TCCR0) controlling mode, compare behavior, and clock prescaling. ATmega328P splits Timer0 control across two separate registers: TCCR0A (waveform generation and compare output modes) and TCCR0B (clock prescaler selection and upper waveform bits).
Why do interrupt vector tables differ between the two chips?
ATmega32 includes three external interrupts (INT0, INT1, and INT2 on PB2) with distinct vector locations, whereas ATmega328P provides INT0 and INT1 plus Pin Change Interrupts (PCINT). The vector shift changes absolute flash jump addresses in assembly and C startup code.
Can I simulate bare-metal AVR-GCC C code for ATmega32 without installing desktop software?
Yes. CircuPilot compiles standard AVR-GCC C code in the cloud and executes the resulting machine code in a browser-based AVR emulator, providing live pin states and waveform visualization without requiring Proteus or Atmel Studio.