Search Knowledge

© 2026 LIBREUNI PROJECT

C Programming Mastery / AVR Microcontroller Interfacing

AVR Register Programming: GPIO

Microcontroller Registers & MMIO

In embedded systems, we control hardware by writing to specific memory addresses. This is called Memory-Mapped I/O (MMIO). In the C language, compiler headers map these hardware addresses to register names (such as DDRB or PORTB).

The Three GPIO Port Registers

On AVR microcontrollers (like the ATmega2560), each physical port (Port A, Port B, etc.) is controlled by three 8-bit registers. Replacing x with the port letter (e.g., PORTA, DDRA, PINA):

  1. DDRx (Data Direction Register): Configures whether the pin is an Input or Output.
    • Set bit to 0: Input mode (Default).
    • Set bit to 1: Output mode.
  2. PORTx (Data Register): Sets the output state, or toggles internal pull-ups.
    • If pin is Output (DDRx = 1): Write 1 to set pin High (Vcc), or 0 to set pin Low (GND).
    • If pin is Input (DDRx = 0): Write 1 to enable the internal pull-up resistor, or 0 for floating/tri-state.
  3. PINx (Input Pins Address Register): Read-only register containing the physical voltage levels on the pins.
    • Reads 1 if the pin voltage is High, and 0 if the pin voltage is Low.

Active-Low Button with Pull-Up

Floating input pins are highly sensitive to electromagnetic noise, resulting in random reads. To prevent this, we connect input buttons in an active-low configuration:

  • Connect the button between the input pin and Ground (GND).
  • Enable the microcontroller’s internal pull-up resistor, which connects the pin internally to Vcc through a large resistor.

Button States

  • Button Released: The pull-up resistor holds the pin voltage at Vcc. Reading PINx returns 1.
  • Button Pressed: The switch closes, short-circuiting the pin directly to Ground (GND). The pin voltage drops to 0V. Reading PINx returns 0.

Example Driver Implementation

To write a driver for a button connected to Port A, Pin 1 (PA1):

#include <avr/io.h>
#include <stdbool.h>

void button_init(void) {
    // 1. Configure PA1 as Input (Clear DDR bit)
    DDRA &= ~(1 << PA1);
    
    // 2. Enable internal pull-up on PA1 (Set PORT bit)
    PORTA |= (1 << PA1);
}

bool button_is_pressed(void) {
    // Active-low: pressed state reads as 0 (LOW) in PINA
    return (0 == (PINA & (1 << PA1)));
}

Active-High vs. Active-Low LEDs

  • Active-High LED: Pin connects to the LED anode, LED cathode to GND. Write 1 (PORTx |=) to turn ON; write 0 to turn OFF.
  • Active-Low LED: Pin connects to the LED cathode, LED anode to Vcc. Write 0 (PORTx &= ~) to turn ON; write 1 to turn OFF.

Why must we enable the internal pull-up resistor when connecting a button between an input pin and ground?

Configure GPIO Input Pull-up

// Configure PB4 as an input pin with the internal pull-up enabled
DDRB  (1 << PB4); // Set to input
PORTB  (1 << PB4); // Enable pull-up

References & Further Reading

  • Microchip Technology. ATmega2560 8-bit AVR Microcontroller Datasheet. Section 13 (I/O Ports).
  • VIA University College: Embedded Software 1 (ESW1) Lecture Notes - Section 8 (AVR Register Programming).