How Does an Embedded System Handle Input and Output Operations?

How does an embedded system handle input and output operations?

The first time I wired up a button and an LED to a microcontroller, I remember being surprised at how much was happening underneath what looked like two lines of code. Reading a button press and lighting an LED seems trivial, but it opened my eyes to how embedded systems actually perceive and affect the physical world — through a layered set of electrical, digital, and software mechanisms collectively called input/output, or I/O.

In this article, I want to walk through exactly how I/O works in embedded systems, from raw electrical signals up through firmware logic, covering digital I/O, analog I/O, communication interfaces, and the different strategies (polling, interrupts, DMA) used to manage it all.

What “I/O” Really Means in Embedded Systems

Input/output refers to how an embedded system exchanges information with the physical world and other systems. Inputs bring information in — a button press, a temperature reading, an incoming data packet. Outputs send information or action out — lighting an LED, driving a motor, transmitting data over a network.

graph LR
    subgraph Inputs
    BTN[Button]
    SENSOR[Sensor - Analog/Digital]
    RXDATA[Incoming Serial Data]
    end

    subgraph "Embedded System"
    MCU[Microcontroller]
    end

    subgraph Outputs
    LED[LED]
    MOTOR[Motor]
    TXDATA[Outgoing Serial Data]
    end

    BTN --> MCU
    SENSOR --> MCU
    RXDATA --> MCU
    MCU --> LED
    MCU --> MOTOR
    MCU --> TXDATA

Digital I/O: The Simplest Form

Digital I/O deals with signals that are either HIGH (typically 3.3V or 5V) or LOW (0V) — representing binary 1 or 0. This is handled through GPIO (General Purpose Input/Output) pins.

Reading digital input (e.g., a button):

if (GPIOA->IDR & (1 << 0)) {   // Read pin PA0
    // Button pressed (assuming active-high wiring)
}

Writing digital output (e.g., an LED):

GPIOA->ODR |= (1 << 5);   // Set PA5 high, turning LED on

Underneath this simple code, the microcontroller is reading or writing a specific bit in a hardware register that’s electrically connected to a physical pin on the chip.

Analog I/O: Bridging the Physical and Digital Worlds

Most real-world signals — temperature, light, sound, pressure — are analog, meaning they vary continuously rather than being simply on or off. Microcontrollers are digital devices, so they need a way to convert between the two.

Analog-to-Digital Converter (ADC)

An ADC samples an analog voltage and converts it into a digital number representing that voltage.

uint16_t read_temperature_sensor(void) {
    ADC1->CR2 |= ADC_CR2_SWSTART;         // Start conversion
    while (!(ADC1->SR & ADC_SR_EOC));     // Wait for completion
    return ADC1->DR;                       // Read converted value
}

Digital-to-Analog Converter (DAC)

A DAC does the reverse, converting a digital value into an analog voltage — useful for generating audio signals or analog control voltages.

Pulse Width Modulation (PWM)

Many microcontrollers don’t have a true DAC on every pin, so they simulate analog-like output using PWM — rapidly switching a digital pin on and off, where the ratio of “on time” to “off time” (the duty cycle) determines the effective average voltage. This is commonly used to control motor speed or LED brightness.

graph TD
    A["PWM Signal - 25% Duty Cycle"] --> A1["_-_______-_______-______"]
    B["PWM Signal - 75% Duty Cycle"] --> B1["-----_---------_--------_"]

Communication-Based I/O

Beyond simple GPIO and analog I/O, embedded systems frequently exchange structured data with other devices using serial communication protocols.

ProtocolWiresSpeedTypical Use
UART2 (TX/RX)Low-MediumDebug console, simple point-to-point links
SPI4 (MOSI, MISO, SCK, CS)HighDisplays, Flash memory, fast sensors
I2C2 (SDA, SCL)Low-MediumMultiple low-speed sensors on shared bus
CAN2 (CAN-H, CAN-L)MediumAutomotive/industrial networks

Here’s a simple example reading a sensor over I2C:

uint8_t read_sensor_i2c(uint8_t device_addr, uint8_t reg_addr) {
    I2C1->CR1 |= I2C_CR1_START;
    while (!(I2C1->SR1 & I2C_SR1_SB));

    I2C1->DR = (device_addr << 1);          // Send device address (write)
    while (!(I2C1->SR1 & I2C_SR1_ADDR));
    (void)I2C1->SR2;

    I2C1->DR = reg_addr;                    // Send register address
    while (!(I2C1->SR1 & I2C_SR1_TXE));

    I2C1->CR1 |= I2C_CR1_START;             // Repeated start
    I2C1->DR = (device_addr << 1) | 1;      // Read mode
    while (!(I2C1->SR1 & I2C_SR1_ADDR));
    (void)I2C1->SR2;

    I2C1->CR1 &= ~I2C_CR1_ACK;
    I2C1->CR1 |= I2C_CR1_STOP;

    while (!(I2C1->SR1 & I2C_SR1_RXNE));
    return I2C1->DR;
}

Three Strategies for Handling I/O

Embedded systems generally use one of three approaches to manage I/O, each with different tradeoffs.

1. Polling

The processor repeatedly checks (polls) whether new input data is ready, in a loop. It’s simple to implement but wastes CPU cycles and can miss fast events if the loop is doing other work.

while (1) {
    if (UART1->SR & USART_SR_RXNE) {   // Is data ready?
        uint8_t data = UART1->DR;
        process(data);
    }
}

2. Interrupts

Instead of constantly checking, the processor configures the peripheral to trigger an interrupt when data is ready, letting the CPU do other work (or sleep) in the meantime.

void USART1_IRQHandler(void) {
    if (USART1->SR & USART_SR_RXNE) {
        uint8_t data = USART1->DR;
        process(data);
    }
}

3. DMA (Direct Memory Access)

For high-throughput I/O, DMA lets a peripheral transfer data directly to or from memory without involving the CPU at all for each byte, freeing the processor to do other work while large transfers happen in the background.

sequenceDiagram
    participant Periph as Peripheral (e.g. ADC/UART)
    participant DMA as DMA Controller
    participant MEM as Memory (RAM)
    participant CPU as CPU Core

    Periph->>DMA: Data ready
    DMA->>MEM: Transfer data directly
    DMA->>CPU: Interrupt only when transfer complete
    CPU->>CPU: Continue other work during transfer

Comparing the Three Approaches

graph TD
    A[I/O Handling Strategy] --> B[Polling<br/>Simple, wastes CPU cycles]
    A --> C[Interrupts<br/>Efficient, responsive]
    A --> D[DMA<br/>Best for high-throughput, minimal CPU load]
MethodCPU UsageResponsivenessComplexityBest For
PollingHigh (constant checking)Depends on loop timingLowSimple, non-critical tasks
InterruptsLow (idle until event)HighMediumTime-sensitive, sporadic events
DMAVery lowHighHigherBulk data transfer (audio, sensor streaming)

A Real Example: Complete Sense-and-Respond Loop

Let’s tie it together with a slightly more complete example — an interrupt-driven button that toggles an LED, alongside a periodic ADC read using DMA.

volatile uint16_t adc_buffer[10];

void EXTI0_IRQHandler(void) {           // Button interrupt
    if (EXTI->PR & (1 << 0)) {
        GPIOA->ODR ^= (1 << 5);         // Toggle LED
        EXTI->PR |= (1 << 0);           // Clear interrupt flag
    }
}

void DMA2_Stream0_IRQHandler(void) {    // ADC DMA complete interrupt
    if (DMA2->LISR & DMA_LISR_TCIF0) {
        process_adc_samples(adc_buffer, 10);
        DMA2->LIFCR |= DMA_LIFCR_CTCIF0;
    }
}

int main(void) {
    system_init();
    enable_button_interrupt();
    enable_adc_dma(adc_buffer, 10);

    while (1) {
        enter_low_power_sleep();  // CPU sleeps between events
    }
}

This design lets the microcontroller stay mostly asleep, waking only when the button is pressed or when a batch of ADC samples is ready — an efficient, responsive, and low-power way to handle both types of I/O.

Frequently Asked Questions

What’s the difference between GPIO and a communication interface like SPI? GPIO handles simple, individual digital signals — on/off states for a single pin. Communication interfaces like SPI, I2C, and UART use structured protocols to exchange multi-bit data reliably between devices, often over just a few shared wires.

Why not just use polling for everything? It seems simpler. Polling wastes processing power and battery life by constantly checking for events that may not have occurred yet, and it can miss brief or rapid events if the CPU is busy elsewhere. Interrupts and DMA let the system respond to events efficiently and immediately, without wasting cycles.

Can a single microcontroller pin function as both input and output? Yes. Most GPIO pins are configurable — firmware can set a pin’s mode (input, output, alternate function) at runtime, and even switch modes dynamically as needed.

Summary

Embedded systems handle input and output through a layered set of mechanisms: digital GPIO for simple on/off signals, analog-to-digital and digital-to-analog conversion for continuous real-world signals, PWM for analog-like control, and structured communication protocols like UART, SPI, and I2C for exchanging data with other devices. Underneath it all, the system chooses between polling, interrupts, and DMA to balance responsiveness, efficiency, and CPU usage. Mastering these I/O mechanisms is fundamental to embedded development, because virtually everything an embedded system does ultimately comes down to reading inputs and driving outputs correctly and efficiently.

References and Further Reading

  • ARM Cortex-M GPIO and Peripheral Documentation — https://developer.arm.com/documentation
  • STM32 Reference Manual (GPIO, ADC, DMA, I2C, SPI, UART) — https://www.st.com/en/microcontrollers-microprocessors/stm32-32-bit-arm-cortex-mcus.html
  • Espressif ESP32 Peripherals Guide — https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/
  • Arduino I/O Reference — https://docs.arduino.cc/language-reference/
  • Microchip AVR I/O Documentation — https://www.microchip.com/en-us/products/microcontrollers-and-microprocessors/8-bit-mcus/avr-mcus
Total
1
Shares

Leave a Reply

Previous Post
What is the importance of power management in embedded systems?

What Is the Importance of Power Management in Embedded Systems?

Next Post
What are the advantages of using an RTOS in an embedded system?

What Are the Advantages of Using an RTOS in an Embedded System?

Related Posts