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
- Timer CTC frequency:
OCR = f_clk / (prescaler × f_target) − 1 - Fast PWM frequency (8-bit):
f_PWM = f_clk / (prescaler × 256) - AVR UART:
UBRR = f_clk / (16 × baud) − 1 - PIC instruction cycle:
T_cy = 4 / f_osc
Worked example: 1 Hz CTC timer
Given: 16 MHz, prescaler 256
Find: OCR1A.
- OCR1A = 16 × 10⁶/(256 × 1) − 1 = 62 499
Answer: 62 499 (fits in 16 bits)
Worked example: UART at 9600
Given: 16 MHz
Find: UBRR and error.
- UBRR = 16 × 10⁶/(16 × 9600) − 1 = 103.17 → 103
- Actual = 16 × 10⁶/(16 × 104) = 9615 → +0.16 %
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
- Wrong: Setting TRIS = 1 expecting output on PIC. — Right: PIC TRIS 0 = output (opposite of AVR DDR).
- Wrong: Ignoring baud-rate error. — Right: Keep error < 2 % or choose a better crystal.
Applications
- Arduino projects: Sensors, robots, prototypes.
- Appliances: PIC-based controllers in white goods.
- Motor control: PWM and ADC feedback.
Key takeaways
- DDR/PORT/PIN vs TRIS/LAT/PORT.
- OCR = f/(N·f_t) − 1.
- UBRR = f/(16·baud) − 1.
- Configure fuses carefully.
Going further
- Low-power modes: Sleep with watchdog wake-up.
- Bootloaders: Arduino-style serial programming.
- Peripheral pin select: Remappable PIC pins.