Bit Banging: Implementing SPI and I2C in Software

Bit Banging: Implementing SPI and I2C in Software

Bit banging is a technique where software directly controls GPIO pins to emulate hardware communication protocols like SPI, I2C, UART, or custom interfaces. This approach bypasses dedicated hardware peripherals, enabling microcontrollers (or even general-purpose CPUs) to communicate with devices that require specific protocols, even if the hardware lacks native support. While flexible, bit banging requires precise timing management and can consume significant CPU resources. This article explains bit banging in detail, with concrete examples for SPI and I2C implementations.


What is Bit Banging?

Definition

Bit banging involves using software to manually toggle GPIO pins to generate protocol-specific signals (clock, data, etc.). Instead of relying on hardware modules to handle timing and signal generation, the CPU directly manipulates the pins according to the protocol's requirements.

Use Cases

  1. Legacy Systems: Supporting older protocols without dedicated hardware.
  2. Custom Protocols: Implementing non-standard communication schemes.
  3. Resource Constraints: Microcontrollers lacking hardware peripherals for SPI/I2C.
  4. Debugging/Prototyping: Testing hardware interactions during development.

Challenges

  • Timing Sensitivity: Delays must match protocol specifications (e.g., clock frequency).
  • CPU Overhead: The CPU is occupied during transmission, limiting multitasking.
  • Jitter: Software delays may introduce timing inconsistencies.


Example: Bit Banging for SPI

SPI Protocol Basics

SPI (Serial Peripheral Interface) uses four signals:

  • MOSI (Master Out Slave In): Data from master to slave.
  • MISO (Master In Slave Out): Data from slave to master.
  • SCK (Serial Clock): Clock generated by the master.
  • CS/SS (Chip Select): Slave selection line.

SPI operates in four modes, determined by clock polarity (CPOL) and phase (CPHA). For example:

  • Mode 0: CPOL=0 (clock idle low), CPHA=0 (data sampled on rising edge).
  • Mode 3: CPOL=1 (clock idle high), CPHA=1 (data sampled on falling edge).

Bit Banging Implementation

/*
 * Implementation of bit banging protocol uses following IOs to be compatible
 * with the hardware SPI interface
 *
 *   D7     D6     D5     D4     D3     D2     D1     D0
 *   MISO   IN2    MOSI   OUT2   SCK    CS1    CS0    CS0
 */
        

Here, a parallel data port (D0–D7) is repurposed for SPI signals:

  • D0, D1: Chip Select lines (CS0, CS1).
  • D2: SCK (clock).
  • D4: MOSI (master output).
  • D7: MISO (master input).
  • Other pins (D3, D5, D6) handle auxiliary I/O (e.g., control signals).

Code Example (SPI Mode 0)

// Define pin mappings based on the parallel port
#define CS0    (1 << 0)  // D0
#define CS1    (1 << 1)  // D1
#define SCK    (1 << 2)  // D2
#define MOSI   (1 << 4)  // D4
#define MISO   (1 << 7)  // D7

// Initialize GPIO
void spi_init() {
    set_output_pins(CS0 | CS1 | SCK | MOSI); // Configure as outputs
    set_input_pin(MISO);                      // MISO is input
    write_pins(CS0 | CS1 | SCK);              // Deselect slaves, clock idle low
}

// Send a byte via SPI
uint8_t spi_transfer(uint8_t data) {
    uint8_t received = 0;
    
    // Select slave (e.g., CS0)
    write_pins(~CS0); // Pull CS0 low
    
    for (int i = 7; i >= 0; i--) {
        // Set MOSI to the current bit
        if (data & (1 << i))
            set_pin(MOSI);
        else
            clear_pin(MOSI);
        
        // Toggle SCK (rising edge for Mode 0)
        set_pin(SCK);        // Rising edge (data sampled by slave)
        delay_ns(100);       // Hold clock high
        
        // Read MISO (if full-duplex)
        received |= (read_pin(MISO) ? 1 : 0) << i;
        
        clear_pin(SCK);      // Falling edge (data prepared by master)
        delay_ns(100);       // Hold clock low
    }
    
    // Deselect slave
    set_pin(CS0);
    return received;
}
        

Key Steps:

  1. GPIO Configuration: Set MOSI, SCK, and CS as outputs; MISO as input.
  2. Slave Selection: Pull CS low to activate the target device.
  3. Data Transfer:Shift out data bits on MOSI.Toggle SCK to signal data sampling.Read MISO simultaneously (if full-duplex).
  4. Timing Delays: Critical for meeting setup/hold times (e.g., delay_ns()).


Example: Bit Banging for I2C

I2C Protocol Basics

I2C (Inter-Integrated Circuit) uses two bidirectional lines:

  • SDA (Serial Data): Transmits data and addresses.
  • SCL (Serial Clock): Synchronizes communication.

Key I2C operations:

  • Start Condition: SDA pulled low while SCL is high.
  • Stop Condition: SDA released high while SCL is high.
  • Data Transfer: Data bits change while SCL is low; sampled while SCL is high.
  • ACK/NACK: Slave pulls SDA low after each byte to acknowledge receipt.

Bit Banging Implementation

I2C requires open-drain outputs, meaning the software can only pull lines low. The bus relies on external pull-up resistors to return lines to high.

Code Example

// Define I2C pins
#define SDA    (1 << 3)  // D3
#define SCL    (1 << 5)  // D5

void i2c_init() {
    set_open_drain(SDA | SCL); // Configure as open-drain
    release_bus();             // SDA and SCL high via pull-ups
}

// Generate start condition
void i2c_start() {
    set_pin(SDA);  // SDA high
    set_pin(SCL);  // SCL high
    delay_ns(500);
    clear_pin(SDA); // SDA pulled low while SCL is high
    delay_ns(500);
    clear_pin(SCL); // SCL low to prepare for data
}

// Generate stop condition
void i2c_stop() {
    clear_pin(SDA); // SDA low
    set_pin(SCL);   // SCL high
    delay_ns(500);
    set_pin(SDA);   // SDA released high while SCL is high
    delay_ns(500);
}

// Send a byte and check ACK
bool i2c_write(uint8_t data) {
    for (int i = 7; i >= 0; i--) {
        // Set SDA to current bit
        (data & (1 << i)) ? set_pin(SDA) : clear_pin(SDA);
        delay_ns(100);
        
        // Pulse SCL
        set_pin(SCL);
        delay_ns(500);
        clear_pin(SCL);
        delay_ns(100);
    }
    
    // Check ACK
    set_pin(SDA);            // Release SDA for slave ACK
    set_pin(SCL);
    delay_ns(500);
    bool ack = !read_pin(SDA); // ACK is low
    clear_pin(SCL);
    return ack;
}
        

Key Steps:

  1. Start/Stop Conditions: Generated by specific SDA/SCL transitions.
  2. Data Transfer:Bits are shifted out MSB-first.SCL is pulsed high after setting SDA.
  3. ACK Check: Slave pulls SDA low after receiving a byte.


Bit Banging Trade-offs

Advantages

  • Flexibility: Supports any protocol or custom timing.
  • Cost-Effective: No need for dedicated hardware.
  • Compatibility: Works with GPIO-only devices.

Disadvantages

  • CPU Intensive: Occupies CPU during transmission.
  • Timing Challenges: Delays must be precise and consistent.
  • Limited Speed: Typically slower than hardware implementations.


Conclusion

Bit banging is a powerful technique for implementing communication protocols in software. While it offers flexibility and hardware compatibility, developers must carefully manage timing and CPU usage. The examples above illustrate how SPI and I2C can be emulated using GPIO pins, enabling communication even on hardware without dedicated peripherals. By understanding these principles, engineers can adapt bit banging to meet diverse embedded system requirements.

I feel like bit banging should be an undergrad requirement. Understanding serialization, bit sampling, and deserialization is a major step toward engineering other serial interfaces.

Like
Reply
Teju Khubchandani

Senior Director, HPC @ Renesas | ex-Apple | ex-Google

4w

While we can mimic the protocol with GPIOs, I have always been curious how software handles I2C clock stretching?

Like
Reply

Most microcontrollers have hardware I2C. If you run out of hardware ports or you use the wrong pins you can use software I2C. Or you can add another microcontroller, or use an I2C multiplexer. For $0.50 you can get an ATtiny with hardware I2C up to 1MHz that can act as a subprocessor for a sensor.

SJ Kim

A software engineer at CB

1mo

It can be risky when the system is under a heavy load. The clock can be stretched, so some peripherals might not like it?

To view or add a comment, sign in

More articles by David Zhu

Insights from the community

Others also viewed

Explore topics