Contents

Peripheral Interfacing Basics

1. GPIO (General Purpose Input/Output)

1.1 What is it?

  • Digital pins configurable as input or output.
  • Used to interface with LEDs, buttons, relays, sensors, etc.
  • Can be basic (binary) or advanced (with alternate functions: PWM, ADC, etc.).

1.2 Why we need it

  • Direct control of and communication with external hardware.
  • Foundation of all microcontroller (MCU) hardware interfacing.

1.3 Hardware Details

  • Each pin connected to MCU’s internal bus via multiplexers and latches.
  • Input: Reads external voltage (logic HIGH/LOW), supports pull-up/pull-down resistors.
  • Output: Drives voltage (sourcing/sinking current).
  • Advanced: Interrupt-on-change, open-drain, Schmitt trigger input, analog capabilities.
  • Electrical characteristics: max current, voltage levels (e.g., 3.3V, 5V tolerant), ESD protection.

1.4 Software/Embedded Use

  • Register-based or HAL library APIs to set direction, state, and read value.
  • Edge/level-triggered interrupts (button debounce, wake-on-event).
  • Bit-banging for low-speed serial protocols.

1.5 Use Cases

  • Toggle LEDs, read switches, drive buzzers, multiplex displays.
  • Control relays, interface with low-pin-count devices.
  • Bit-banged protocols: 1-Wire, custom serial.

1.6 Advanced Usage

  • PWM for motor control or dimming LEDs.
  • Analog input (ADC) or output (DAC) multiplexed on some GPIOs.
  • Port expanders (e.g., MCP23017 over I2C/SPI) for more GPIOs.

1.7 Example: Toggling an LED (STM32 HAL, portable C, register-level)

// Toggle an LED connected to PA5 every 500ms
#include "stm32f4xx.h"

void GPIO_init() {
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;  // Enable GPIOA clock
    GPIOA->MODER |= (1 << (5 * 2));       // Set PA5 as output (01)
}

int main(void) {
    GPIO_init();
    while (1) {
        GPIOA->ODR ^= (1 << 5);           // Toggle PA5
        for (volatile int i = 0; i < 1000000; ++i); // crude delay
    }
    return 0;
}

Explanation:

  • Enables GPIOA, configures PA5 as output, toggles it in a loop—LED blinks.

1.8 Example: Reading a Button

// Reads a button on PC13, lights LED on PA5 if pressed
void Button_init() {
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN;   // Enable GPIOC clock
    GPIOC->MODER &= ~(3 << (13 * 2));      // Set PC13 as input (00)
}

int main(void) {
    GPIO_init();
    Button_init();
    while (1) {
        if (!(GPIOC->IDR & (1 << 13)))     // Active low button
            GPIOA->ODR |= (1 << 5);        // LED ON
        else
            GPIOA->ODR &= ~(1 << 5);       // LED OFF
    }
}

2. UART (Universal Asynchronous Receiver/Transmitter)

2.1 What is it?

  • Asynchronous serial communication interface: full duplex (TX/RX).
  • Transfers data in frames (start bit, 5-9 data bits, optional parity, stop bit).
  • No shared clock line—uses start/stop bits and agreed baud rate.

2.2 Why we need it

  • Simple, low-cost, widely supported point-to-point communication.
  • Used for debug consoles, sensor/MCU communication, GPS, Bluetooth modules.

2.3 Hardware Details

  • Two lines: TX (transmit), RX (receive), optional RTS/CTS for flow control.

  • Baud rates: standard (9600, 115200 bps), can be custom.

  • Internal shift registers, baud rate generator, status registers (parity error, framing error, buffer status).

  • Electrical standards:

    • TTL UART (0-3.3V or 0-5V logic).
    • RS-232 (±12V signaling for PC serial ports).
    • RS-485 (differential signaling for long-distance, multi-drop).

2.4 Software/Embedded Use

  • Blocking/polling, interrupt-driven, or DMA-based drivers.
  • Circular buffers for efficient RX/TX.
  • Bootloaders, debug logs, wireless (HC-05/ESP8266), or PC terminal communication.
  • Protocol stacks on top: SLIP, Modbus, custom packets.

2.5 Use Cases

  • Debug console (printf over UART).
  • Firmware upgrade over serial (XMODEM, YMODEM).
  • Communication with GSM/Bluetooth/WiFi modules.
  • GNSS (GPS) receivers, RFID readers.

2.6 Advanced Usage

  • Multi-processor communication using address frames.
  • Software UART (“bit-banged”) for MCUs with limited hardware UARTs.
  • Parity/error checking and flow control for reliability.

2.7 Example: Sending Text via UART (STM32 HAL)

#include "stm32f4xx.h"
void UART2_init() {
    RCC->APB1ENR |= RCC_APB1ENR_USART2EN;      // Enable USART2
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;       // Enable GPIOA
    GPIOA->MODER |= (2 << (2*2)) | (2 << (3*2)); // PA2/PA3 alt func
    GPIOA->AFR[0] |= (7 << (2*4)) | (7 << (3*4));// AF7 (USART2)

    USART2->BRR = 0x8B;   // 115200 baud @ 16MHz
    USART2->CR1 |= USART_CR1_TE | USART_CR1_UE; // Enable TX, UART
}

void UART2_send(char c) {
    while (!(USART2->SR & USART_SR_TXE)); // Wait for TX buffer empty
    USART2->DR = c;
}

void UART2_sendstr(const char* str) {
    while (*str) UART2_send(*str++);
}

int main() {
    UART2_init();
    while(1) {
        UART2_sendstr("Hello UART!\r\n");
        for (volatile int i = 0; i < 1000000; ++i);
    }
}

Explanation:

  • Sets up UART2 (PA2/PA3), sends a string repeatedly.

2.8 Example: Receiving via UART (Polling)

char UART2_recv() {
    while (!(USART2->SR & USART_SR_RXNE)); // Wait for RX not empty
    return USART2->DR;
}

3. SPI (Serial Peripheral Interface)

3.1 What is it?

  • Synchronous serial bus for high-speed, short-distance communication.
  • Master-slave topology; single master, multiple slaves.
  • Four main signals:
    • SCLK (clock), MOSI (master out, slave in), MISO (master in, slave out), SS (slave select, active low).

3.2 Why we need it

  • Fast, reliable, low-latency communication with sensors, flash memory, displays, ADC/DACs.
  • Full-duplex, much faster than UART/I2C.

3.3 Hardware Details

  • Shift registers move data on clock edges (CPOL/CPHA—clock polarity/phase).

  • Speeds: MHz range (typical 1-50 MHz, some up to 100+ MHz).

  • Each slave needs separate SS/CS line.

  • No defined protocol—device-specific commands and frames.

  • Electrical: Supports multiple voltage levels, can interface via level shifters.

  • Daisy-chaining possible but rare.

3.4 Software/Embedded Use

  • Register-based or HAL library SPI drivers.
  • Configure data mode (clock polarity/phase), bit order (MSB/LSB first), speed.
  • Blocking/interrupt/DMA modes for data transfer.
  • Drivers implement higher-level protocols (e.g., SD card, display drivers).

3.5 Use Cases

  • Interfacing with Flash memory (W25Q, AT45DB), LCD/OLED screens, touch controllers.
  • Reading sensors: ADCs, accelerometers, gyros.
  • Communicating with wireless modules, SD cards.
  • Daisy-chained devices: shift registers (74HC595), LEDs (WS2812 with custom timing).

3.6 Advanced Usage

  • Multi-master/multi-slave via careful bus arbitration (not standard).
  • Use of SPI expanders/multiplexers for many peripherals.
  • Protocol converters: SPI-to-UART, SPI-to-I2C bridges.

3.7 Example: SPI Master Transmit/Receive (STM32, register-level)

void SPI1_init() {
    RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    // PA5=SCK, PA6=MISO, PA7=MOSI
    GPIOA->MODER |= (2<<(5*2)) | (2<<(6*2)) | (2<<(7*2)); // AF mode
    GPIOA->AFR[0] |= (5<<(5*4)) | (5<<(6*4)) | (5<<(7*4)); // AF5 (SPI1)

    SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_BR_0 | SPI_CR1_SSI | SPI_CR1_SSM; // Master, baud rate, sw NSS
    SPI1->CR1 |= SPI_CR1_SPE; // Enable SPI
}

uint8_t SPI1_transfer(uint8_t data) {
    while (!(SPI1->SR & SPI_SR_TXE));
    *(volatile uint8_t*)&SPI1->DR = data;
    while (!(SPI1->SR & SPI_SR_RXNE));
    return *(volatile uint8_t*)&SPI1->DR;
}

int main() {
    SPI1_init();
    uint8_t received = SPI1_transfer(0xAB); // Send 0xAB, read response
    // Use 'received'...
}

Explanation:

  • Initializes SPI1 as master, sends a byte, receives response from slave.

4. I2C (Inter-Integrated Circuit)

4.1 What is it?

  • Synchronous, half-duplex, multi-master, multi-slave serial bus.
  • Two bidirectional lines: SDA (data), SCL (clock), pulled up to Vcc.
  • Each device has a 7/10-bit unique address.

4.2 Why we need it

  • Easy two-wire communication for connecting many devices.
  • Ideal for sensors, RTCs, EEPROMs, configuration ICs.

4.3 Hardware Details

  • Open-drain drivers, require pull-up resistors on both lines (typically 4.7k–10kΩ).

  • Bus speed: Standard (100 kHz), Fast (400 kHz), Fast+ (1 MHz), High-speed (3.4 MHz).

  • Protocol: Start, address+R/W, ACK/NACK, data, stop.

  • Arbitration: Multi-master support (bus can be shared without collisions).

  • Electrical: Can use level shifters for voltage translation, limited by bus capacitance (max ~400pF).

4.4 Software/Embedded Use

  • HAL/LL drivers or bit-banged software I2C.
  • Functions: start/stop, send/receive byte, check ACK, error handling.
  • Interrupt-based or DMA for efficiency.
  • Implement higher-level drivers: EEPROM, temperature/humidity sensors, RTCs.

4.5 Use Cases

  • Connecting multiple sensors (e.g., MPU6050, BME280) to one MCU.
  • External I2C EEPROM/Flash, port expanders.
  • Display modules (OLED, LCD), configuration ICs (PMICs, clock generators).

4.6 Advanced Usage

  • Bus recovery (in case of hung slaves).
  • Clock stretching (slower devices hold clock low).
  • SMBus (System Management Bus) is a stricter I2C superset used in PCs.

4.7 Example: Write/Read to I2C EEPROM (Bit-banging, portable C)

#define SCL_H() (GPIOB->ODR |= (1<<6))
#define SCL_L() (GPIOB->ODR &= ~(1<<6))
#define SDA_H() (GPIOB->ODR |= (1<<7))
#define SDA_L() (GPIOB->ODR &= ~(1<<7))
#define SDA_IN() (GPIOB->MODER &= ~(3<<(7*2))) // Input mode
#define SDA_OUT() (GPIOB->MODER |= (1<<(7*2))) // Output mode

void I2C_start() {
    SDA_OUT(); SDA_H(); SCL_H();
    SDA_L();   // Start condition
    SCL_L();
}
void I2C_stop() {
    SDA_OUT(); SCL_L(); SDA_L();
    SCL_H(); SDA_H(); // Stop condition
}
void I2C_writebit(int bit) {
    SDA_OUT();
    if (bit) SDA_H(); else SDA_L();
    SCL_H(); SCL_L();
}
int I2C_readbit() {
    int bit;
    SDA_IN(); SCL_H(); bit = (GPIOB->IDR & (1<<7)) ? 1 : 0; SCL_L();
    return bit;
}
// ... Similar for sending a byte, reading ACK, etc.

void I2C_sendbyte(uint8_t data) {
    for (int i=0; i<8; ++i)
        I2C_writebit((data & 0x80) != 0), data <<= 1;
    // Handle ACK...
}

// To write 0x55 to EEPROM address 0x50:
I2C_start();
I2C_sendbyte(0xA0); // 0x50<<1 | Write=0
I2C_sendbyte(0x00); // Address MSB
I2C_sendbyte(0x10); // Address LSB
I2C_sendbyte(0x55); // Data
I2C_stop();

Explanation:

  • Implements I2C protocol via bit-banging GPIO, sends data byte to EEPROM.

4.8 Example: Reading a Register from I2C Sensor (HAL version, pseudo-C)

uint8_t buf;
HAL_I2C_Mem_Read(&hi2c1, 0xD0, 0x75, I2C_MEMADD_SIZE_8BIT, &buf, 1, 100);

Explanation:

  • Reads register 0x75 from device at address 0x68 (0xD0 for write), stores result in buf.

5. Summary Table

InterfaceWiresTopologySpeedData Dir.ProtocolUse CasesProsCons
GPIO1Point-to-pointFastIn/OutNoneLEDs, buttons, relaysSimple, flexibleNo protocol, no data rate
UART2Point-to-point300bps-1MbpsDuplexFramed (async)Debug, modulesUbiquitous, simpleNo addressing, short range
SPI3-4+Master/slave1-100+ MHzDuplexDevice-specificSensors, Flash, LCDsFast, simple HWMore wires, per device CS
I2C2Multi-master/slave100k-3.4MHzHalf-duplexAddressedSensors, EEPROM, RTCsMulti-device, 2 wiresSlower, bus capacitance lim.
InterfaceInit complexitySpeedHardware pinsMCU code patternTypical Driver
GPIOLowFastAnySet/Clear, Poll, ISRHAL/LL or register
UARTMedMed2FIFO, ISR, DMAHAL/LL or reg
SPIMedHigh4+Blocking/ISR/DMAHAL/LL or reg
I2CMedMed2Bit-bang/ISR/DMAHAL/LL or bit-bang

6. System Integration and Security

Hardware

  • Level shifters: interface 5V/3.3V devices.
  • Bus resistors: I2C pull-ups, SPI terminations.
  • ESD protection, shielding for noisy environments.

Software

  • RTOS drivers: task-safe peripheral access.
  • Device trees or HAL layers for abstraction.
  • Bus arbitration and error recovery for robustness.

Security

  • Firmware validation for bootloaders over UART/I2C.
  • Encryption for sensitive data transmission (custom, or protocol-layer).

7. Summary

  • Use register-level access for max efficiency in critical code; HAL/LL for portability.
  • Always debounce GPIO inputs in software or use MCU hardware debounce, if available.
  • For UART, buffer incoming/outgoing data; use interrupt or DMA for high-speed.
  • For SPI, always check slave device datasheet for timing/CPOL/CPHA.
  • For I2C, ensure proper pull-ups; handle errors, especially with multiple devices.