HomeAbout UsContact Us

Software UART using GPIO and Timer in Embedded C

By Jithin Tom
Published in Embedded C/C++
September 24, 2026
4 min read

Table Of Contents

01
ASCII Art Diagram: Software UART Timing
02
Introduction
03
Core Concepts
04
Implementation Strategy
05
Timing Calculations
06
Transmit Implementation
07
Receive Implementation
08
CPU Load Considerations
09
Practical Example: STM32 Implementation
10
Performance Optimization
11
Common Pitfalls and Solutions
12
When to Use Software UART
13
Conclusion
14
Related Reading
15
References
16
Frequently Asked Questions

ASCII Art Diagram: Software UART Timing

CPU Core → [Timer ISR] → GPIO Pin
↓ ↑ ↓
[Bit Counter] ← [Shift Register] → [Serial Out]
↓ ↑ ↓
[State Machine] ← [Bit Timer] → [Start/Stop Bits]
↓ ↑ ↓
[Receive Buffer] ← [Sample Points] → [Transmit Buffer]
Timer Interval = 1/(Baud Rate × Oversampling Factor)
Each ISR: Sample input, shift output, update counters

Introduction

When your embedded application needs more serial ports than the available hardware UARTs, or you’re working with a microcontroller that lacks sufficient UART peripherals, software UART (also known as bit-banging UART) provides a flexible solution. By using GPIO pins and timer interrupts, you can implement serial communication entirely in software.

This approach is particularly useful in scenarios like:

  • Prototyping with low-pin-count MCUs
  • Adding debug ports to existing designs
  • Implementing software-defined peripherals
  • Learning fundamental UART concepts

Core Concepts

Software UART relies on precise timing to serialize and deserialize data bits. The fundamental operations include:

  1. Transmission: Converting parallel data to serial bit stream
  2. Reconstruction: Converting serial bit stream back to parallel data
  3. Timing Control: Ensuring each bit lasts for the correct duration
  4. Frame Management: Handling start, data, parity, and stop bits

The key insight is that UART communication is synchronous at the bit level but asynchronous between devices - each device uses its own clock to sample/transmit bits at the agreed-upon baud rate.

Implementation Strategy

Hardware Requirements

  • One GPIO pin for TX (output)
  • One GPIO pin for RX (input)
  • One timer peripheral capable of generating periodic interrupts
  • Sufficient CPU cycles to handle ISR overhead

Software Components

  1. Timer ISR: Fires at regular intervals (baud rate × oversampling)
  2. State Machines: Separate TX and RX state machines
  3. Buffers: Storage for incoming/outgoing data
  4. Bit Counters: Track bit position within each frame
  5. Shift Registers: Convert between parallel and serial formats

Timing Calculations

The foundation of reliable software UART is accurate timing. Critical parameters include:

Baud Rate: Bits per second (e.g., 9600, 115200) Bit Time: 1 / Baud Rate (e.g., 104.167µs at 9600 baud) Oversampling Factor: Typically 8x, 16x, or 32x for noise immunity Timer Period: Bit Time / Oversampling Factor

For example, at 115200 baud with 16x oversampling:

  • Bit Time = 8.68µs
  • Timer Period = 0.5425µs (1.844 MHz timer frequency)

Transmit Implementation

The transmit process follows this sequence in each timer ISR:

1. If in start bit state: drive GPIO low, decrement bit counter
2. If in data bit state: output LSB of shift register, shift right, decrement counter
3. If in parity state: output calculated parity bit, decrement counter
4. If in stop bit state: drive GPIO high, decrement counter
5. If all bits transmitted: return to idle state

Key optimization techniques:

  • Pre-calculate bit masks to avoid division in ISR
  • Use lookup tables for parity calculation
  • Maintain separate shift registers for TX and RX
  • Implement double-buffering to prevent data corruption

Receive Implementation

Receiving requires sampling the GPIO pin at the optimal point within each bit period:

1. Detect falling edge (start bit)
2. Wait half a bit time to reach bit center
3. Sample GPIO pin every N timer ticks (where N = oversampling factor)
4. Use majority voting over multiple samples for noise immunity
5. Assemble received bits into bytes
6. Validate stop bit(s) and transfer data to receive buffer

The receive state machine needs to handle:

  • Start bit detection (transition from high to low)
  • Data bit sampling with noise filtering
  • Parity verification (if enabled)
  • Stop bit validation
  • Framing error detection

CPU Load Considerations

Software UART consumes CPU resources proportional to the baud rate and oversampling factor. The interrupt rate is:

Interrupts per second = Baud Rate × Oversampling Factor × 2 (TX + RX)

For example, full-duplex 115200 baud with 16x oversampling requires:

  • 115200 × 16 × 2 = 3,686,400 interrupts per second

This represents significant CPU load, especially on lower-frequency MCUs. Mitigation strategies include:

  • Using lower baud rates when high speed isn’t required
  • Implementing half-duplex mode to share the same ISR
  • Using DMA with timer to reduce CPU intervention
  • Offloading to a secondary core or coprocessor if available

Practical Example: STM32 Implementation

Here’s a simplified structure for STM32F4:

// Timer configuration (TIM2 for 1.844 MHz at 115200 baud, 16x oversampling)
void UART_Init(void) {
// Configure GPIO pins: PA2 (TX), PA3 (RX) as alternate function
// Configure TIM2: ARR = 0, PSC = (SystemCoreClock/2/1843200)-1
// Enable TIM2 interrupt
}
// Timer ISR - called every 0.5425µs
void TIM2_IRQHandler(void) {
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
// Handle TX state machine
UartTxIsr();
// Handle RX state machine
UartRxIsr();
}
}
// Transmit ISR (called every timer tick)
void UartTxIsr(void) {
static uint8_t tx_bit_counter = 0;
static uint16_t tx_shift_reg = 0;
switch (tx_state) {
case UART_TX_IDLE:
if (tx_buffer_has_data()) {
tx_shift_reg = tx_buffer_pop() | 0xFF00; // Add stop bit
tx_state = UART_TX_START;
tx_bit_counter = 0;
GPIO_WriteBit(UART_TX_PORT, UART_TX_PIN, Bit_RESET); // Start bit
}
break;
case UART_TX_START:
if (++tx_bit_counter >= OVERSAMPLING) {
tx_bit_counter = 0;
tx_state = UART_TX_DATA;
}
break;
case UART_TX_DATA:
if (tx_bit_counter >= OVERSAMPLING) {
tx_bit_counter = 0;
if (tx_shift_reg & 0x01) {
GPIO_WriteBit(UART_TX_PORT, UART_TX_PIN, Bit_SET);
} else {
GPIO_WriteBit(UART_TX_PORT, UART_TX_PIN, Bit_RESET);
}
tx_shift_reg >>= 1;
if (++tx_data_bit_count >= 8) {
tx_state = UART_TX_STOP;
}
}
break;
// ... handle stop bit state similarly
}
}

Performance Optimization

Several techniques can improve software UART performance and reliability:

1. Interrupt Prioritization

  • Assign highest priority to UART timer ISR
  • Prevents jitter from lower-priority interrupts
  • Critical for maintaining accurate bit timing

2. Efficient ISR Design

  • Minimize function calls in ISR
  • Use inline assembly for critical timing sections
  • Pre-compute values outside ISR when possible

3. Double Buffering

  • Separate ISR buffers from application buffers
  • Prevents data corruption during buffer swaps
  • Allows ISR to run uninterrupted

4. GPIO Optimization

  • Use ports with bit-banding or atomic set/clear registers
  • Avoid read-modify-write operations on GPIO
  • Configure pins for maximum slew rate

5. Adaptive Oversampling

  • Reduce oversampling factor at lower baud rates
  • Increase factor only when noise is detected
  • Dynamically adjust based on line quality

Common Pitfalls and Solutions

Pitfall 1: Timer Jitter Problem: Variable ISR latency causes bit timing errors Solution: Use highest interrupt priority, minimize ISR work, consider using DMA trigger

Pitfall 2: GPIO Delay Variations Problem: Different GPIO pins have different output delays Solution: Calibrate per-pin delays, use same port for TX/RX when possible

Pitfall 3: Noise on RX Line Problem: False start bit detection or bit errors Solution: Implement debouncing, use majority voting, add hardware RC filter

Pitfall 4: Buffer Overrun Problem: RX data overwritten before application reads it Solution: Implement buffer depth checking, use flow control (RTS/CTS), increase buffer size

Pitfall 5: Priority Inversion Problem: Low-priority task holds resource needed by UART ISR Solution: Use priority inheritance protocols, keep ISR non-blocking

When to Use Software UART

Choose software UART when:

  • Hardware UART peripherals are exhausted
  • Cost reduction is prioritized over power efficiency
  • Flexibility in pin assignment is required
  • Prototyping or educational purposes
  • Baud rates are moderate (< 1 Mbps)

Stick with hardware UART when:

  • Maximum throughput is required (> 1 Mbps)
  • Power efficiency is critical
  • Multiple high-speed serial ports needed
  • Complex features like DMA, LIN, or smartcard support required

Conclusion

Software UART using GPIO and timer interrupts provides a versatile solution for adding serial communication capabilities to embedded systems. While it consumes more CPU resources than hardware UART, proper implementation can achieve reliable communication at practical baud rates.

Key takeaways for successful implementation:

  1. Calculate precise timing based on baud rate and oversampling requirements
  2. Implement robust state machines with error detection
  3. Optimize ISR execution for minimal jitter
  4. Validate signal integrity with oscilloscope during development
  5. Consider hardware alternatives for high-throughput applications

With careful design and testing, software UART can be a dependable tool in your embedded development toolkit, especially when hardware resources are constrained.

References

  1. ARM, “Cortex-M Devices Generic User Guides”, ARM DUI 0552 (M3) / DUI 0553 (M4) / DUI 0646 (M7), Section “SysTick Timer”
  2. STMicroelectronics, “STM32F4xx Reference Manual”, RM0090, Section “General-purpose timers (TIM2/TIM3/TIM4/TIM5)”
  3. Joseph Yiu, “The Definitive Guide to ARM Cortex-M3 and Cortex-M4 Processors”, 3rd Ed., Chapter 9 “Timers”
  4. FreeRTOS Kernel Source, portable/GCC/ARM_CM3/port.cvPortSetupTimerInterrupt() configuration
  5. Maxim Integrated, “Understanding UART Baud Rate Error” Application Note
  6. Texas Instruments, “GPIO Speed and Delay Considerations” Application Report

Frequently Asked Questions

Why would I use software UART instead of hardware UART?

Software UART is useful when you run out of hardware UART peripherals, need additional serial ports beyond what the MCU provides, or want to save cost by using a cheaper MCU without multiple UARTs. It's also valuable for learning purposes and prototyping.

What timer resolution do I need for reliable software UART at common baud rates?

For reliable software UART, your timer should interrupt at least 8x the baud rate to sample each bit multiple times. For 115200 baud, you need ~921.6 kHz timer interrupts (1.085µs period). Most applications use 16x oversampling for better noise immunity.

How do I handle CPU load from software UART interrupts?

Software UART increases CPU load proportional to baud rate. At 115200 baud with 16x oversampling, you get ~1.84 million interrupts per second. Use lower baud rates when possible, implement efficient ISRs, or consider using DMA with timer for less CPU-intensive solutions.

Can software UART achieve the same reliability as hardware UART?

With proper timing calculations, interrupt prioritization, and efficient ISR implementation, software UART can achieve reliability comparable to hardware UART for baud rates up to 115200. Beyond that, jitter and interrupt latency become limiting factors.

What GPIO considerations affect software UART performance?

Use GPIO pins with fast toggle speeds and minimal output delay. Avoid pins shared with other high-frequency peripherals. Ensure your GPIO port allows bit-banding or atomic bit-set/clear operations to prevent read-modify-write issues in ISRs.

Tags

embedded-cuartgpiotimersoftware-uartstm32

Share


Previous Article
Fixing TFLite Micro Model Loading Failures on STM32H7
Jithin Tom

Jithin Tom

A Closer Look at C/C++, RTOS, and Embedded Systems

Related Posts

UART Overrun Errors in STM32: Fixing with DMA
UART Overrun Errors in STM32: Fixing with DMA
September 18, 2026
4 min
© 2026, All Rights Reserved.
Powered By Netlyft

Quick Links

Advertise with usAbout UsContact Us

Social Media