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
Interface | Wires | Topology | Speed | Data Dir. | Protocol | Use Cases | Pros | Cons |
---|---|---|---|---|---|---|---|---|
GPIO | 1 | Point-to-point | Fast | In/Out | None | LEDs, buttons, relays | Simple, flexible | No protocol, no data rate |
UART | 2 | Point-to-point | 300bps-1Mbps | Duplex | Framed (async) | Debug, modules | Ubiquitous, simple | No addressing, short range |
SPI | 3-4+ | Master/slave | 1-100+ MHz | Duplex | Device-specific | Sensors, Flash, LCDs | Fast, simple HW | More wires, per device CS |
I2C | 2 | Multi-master/slave | 100k-3.4MHz | Half-duplex | Addressed | Sensors, EEPROM, RTCs | Multi-device, 2 wires | Slower, bus capacitance lim. |
Interface | Init complexity | Speed | Hardware pins | MCU code pattern | Typical Driver |
---|---|---|---|---|---|
GPIO | Low | Fast | Any | Set/Clear, Poll, ISR | HAL/LL or register |
UART | Med | Med | 2 | FIFO, ISR, DMA | HAL/LL or reg |
SPI | Med | High | 4+ | Blocking/ISR/DMA | HAL/LL or reg |
I2C | Med | Med | 2 | Bit-bang/ISR/DMA | HAL/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.