Simulating I2C and SPI Signals on Arduino

3 min read

In this post, we explore how to manually simulate I2C and SPI signals using an Arduino and simple digital I/O operations — a method often called bit-banging. This can be useful for debugging, testing logic analyzers – my case -, or understanding how these communication protocols work under the hood.

Overview of I2C and SPI

I2C (Inter-Integrated Circuit)

I2C is a synchronous, multi-master, multi-slave, packet-switched, single-ended, serial communication bus. It typically uses:

  • SCL (Clock): Controls the timing of communication
  • SDA (Data): Transfers data bit-by-bit

I2C communication consists of start/stop conditions, address and data transmission, and acknowledgements.

SPI (Serial Peripheral Interface)

SPI is another synchronous communication protocol but faster and more flexible than I2C. It uses:

  • MOSI (Master Out, Slave In): Data from master to slave
  • MISO (Master In, Slave Out): Data from slave to master
  • SCLK: Clock line for synchronization
  • CS (Chip Select): Activates the specific slave

The Arduino Script: What It Does

The provided Arduino script simulates both I2C and SPI traffic by toggling GPIO pins manually. This is a great way to learn how these protocols operate at a low level.

Pin Configuration

FunctionPin
I2C SCLD0 (GPIO 0)
I2C SDAD1 (GPIO 1)
SPI MOSID2 (GPIO 2)
SPI MISOD3 (GPIO 3)
SPI SCLKD4 (GPIO 4)
SPI CSD5 (GPIO 5)

I2C Simulation

The script implements the I2C protocol manually:

  1. Sends a START condition by pulling SDA low while SCL is high.
  2. Transmits 8 bits of data, starting from the most significant bit (MSB).
  3. Sends a simulated ACK bit (we don’t read a real response in this case).
  4. Ends the transmission with a STOP condition.

SPI Simulation

For SPI, the script:

  1. Pulls CS low to select the slave.
  2. Sends 8 bits of data via MOSI, toggling SCLK with each bit.
  3. Pulls CS high to finish the transfer.

What This Is Useful For

  • Learning how serial protocols work.
  • Testing external analyzers or microcontrollers‘ input behavior.
  • Understanding timing and signaling for embedded development.
#include <Arduino.h>

// --- I2C Pins ---
#define I2C_SCL 0  // D0
#define I2C_SDA 1  // D1

// --- SPI Pins ---
#define SPI_MOSI 2 // D2
#define SPI_MISO 3 // D3 (optional in this simulation)
#define SPI_SCLK 4 // D4
#define SPI_CS   5 // D5

// I2C timing
#define I2C_FREQ 100000
#define I2C_DELAY_US (1000000 / I2C_FREQ) / 2

// SPI timing
#define SPI_DELAY_US 10

void i2c_start() {
  digitalWrite(I2C_SDA, HIGH);
  digitalWrite(I2C_SCL, HIGH);
  delayMicroseconds(I2C_DELAY_US);
  digitalWrite(I2C_SDA, LOW);
  delayMicroseconds(I2C_DELAY_US);
  digitalWrite(I2C_SCL, LOW);
}

void i2c_stop() {
  digitalWrite(I2C_SDA, LOW);
  digitalWrite(I2C_SCL, HIGH);
  delayMicroseconds(I2C_DELAY_US);
  digitalWrite(I2C_SDA, HIGH);
  delayMicroseconds(I2C_DELAY_US);
}

void i2c_write_byte(uint8_t byte) {
  for (int i = 0; i < 8; i++) {
    digitalWrite(I2C_SDA, (byte & 0x80) ? HIGH : LOW);
    delayMicroseconds(I2C_DELAY_US);
    digitalWrite(I2C_SCL, HIGH);
    delayMicroseconds(I2C_DELAY_US);
    digitalWrite(I2C_SCL, LOW);
    byte <<= 1;
  }

  // ACK bit (just simulate low)
  pinMode(I2C_SDA, INPUT); // Let the line float (simulate read)
  digitalWrite(I2C_SCL, HIGH);
  delayMicroseconds(I2C_DELAY_US);
  digitalWrite(I2C_SCL, LOW);
  pinMode(I2C_SDA, OUTPUT);
}

void spi_transfer_byte(uint8_t byte) {
  digitalWrite(SPI_CS, LOW);
  for (int i = 0; i < 8; i++) {
    digitalWrite(SPI_MOSI, (byte & 0x80) ? HIGH : LOW);
    delayMicroseconds(SPI_DELAY_US);
    digitalWrite(SPI_SCLK, HIGH);
    delayMicroseconds(SPI_DELAY_US);
    digitalWrite(SPI_SCLK, LOW);
    byte <<= 1;
  }
  digitalWrite(SPI_CS, HIGH);
}

void setup() {
  Serial.begin(115200);
  Serial.println("Simulating I2C and SPI signals...");

  // I2C
  pinMode(I2C_SCL, OUTPUT);
  pinMode(I2C_SDA, OUTPUT);
  digitalWrite(I2C_SCL, HIGH);
  digitalWrite(I2C_SDA, HIGH);

  // SPI
  pinMode(SPI_MOSI, OUTPUT);
  pinMode(SPI_MISO, INPUT); // not used
  pinMode(SPI_SCLK, OUTPUT);
  pinMode(SPI_CS, OUTPUT);
  digitalWrite(SPI_CS, HIGH);
  digitalWrite(SPI_SCLK, LOW);
}

void loop() {
  // --- Simulate I2C: Send one byte ---
  i2c_start();
  i2c_write_byte(0xA5); // example byte (0b10100101)
  i2c_stop();

  // --- Simulate SPI: Send one byte ---
  spi_transfer_byte(0x3C); // example byte (0b111100)

  delay(50); // Wait some time
}
Swift

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Seite verwendet Akismet, um Spam zu reduzieren. Erfahre, wie deine Kommentardaten verarbeitet werden..