AVR & PIC Microcontrollers

AVR (Arduino's ATmega) and PIC are the most popular 8-bit MCU families. Learn their Harvard RISC architectures, register-based I/O (DDR/PORT/PIN, TRIS/LAT), timers with prescalers and CTC/PWM modes, UART baud calculation (UBRR), ADC, fuses/configuration bits, and bare-metal C programming.

Arduino made the ATmega328P famous; PIC dominates many industrial and automotive low-cost designs. Both run efficient single-cycle (AVR) or 4-clock (PIC) instructions with rich peripherals, programmed in C with direct register access.

An analogy

Registers are the MCU's control panel: setting a bit in DDRB is like flipping the switch that makes a pin an output.

GPIO registers

AVR: DDRx sets direction (1 = output), PORTx writes outputs (or enables pull-ups on inputs), PINx reads inputs. PIC: TRISx (1 = input), LATx writes, PORTx reads. Read-modify-write on PIC ports should use LAT.

Timers and PWM

Prescalers divide the clock (1, 8, 64, 256, 1024). Modes: normal (overflow), CTC (clear on compare), fast PWM and phase-correct PWM. Output compare pins generate hardware PWM for LEDs, motors and servos.

Analog and communication

10-bit ADCs with multiplexed inputs, analog comparators, USART, SPI and I²C (TWI on AVR, MSSP on PIC). Baud-rate error should be < 2 %.

Configuration and tools

Fuses (AVR) and configuration bits (PIC) set clock source, brown-out, watchdog and code protection — wrong settings can 'brick' a chip. Tools: avr-gcc/Microchip Studio, MPLAB X with XC8, ISP/UPDI/ICSP programmers and debuggers.

Formulas

Worked example: 1 Hz CTC timer

Given: 16 MHz, prescaler 256

Find: OCR1A.

Answer: 62 499 (fits in 16 bits)

Worked example: UART at 9600

Given: 16 MHz

Find: UBRR and error.

Answer: UBRR 103, 0.16 % error ✓

Blink with Timer1 CTC interrupt and UART (ATmega328P, avr-gcc)

#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 16000000UL

ISR(TIMER1_COMPA_vect) {          /* fires every 1 s */
    PORTB ^= (1 << PB5);          /* toggle LED on D13 */
}

static void uart_init(void) {
    UBRR0 = 103;                  /* 16 MHz / (16*9600) - 1 = 103 */
    UCSR0B = (1 << TXEN0);        /* enable transmitter */
    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);   /* 8N1 */
}

int main(void) {
    DDRB |= (1 << PB5);                        /* PB5 output */
    TCCR1B = (1 << WGM12) | (1 << CS12);       /* CTC, prescaler 256 */
    OCR1A = 62499;                             /* 16e6/256/1 Hz - 1 */
    TIMSK1 = (1 << OCIE1A);                    /* enable compare interrupt */
    uart_init();
    sei();                                     /* global interrupts on */
    for (;;) { }
}

Bit manipulation with |=, &=~ and ^= sets, clears and toggles register bits. CTC mode resets the counter at OCR1A, giving an exact period without software reloads.

Common mistakes

Applications

Key takeaways

Going further