Editorial Solution

Data Transmission Details

I2C Communication

  • The maximum achievable speed for I2C is 400 Kbps.
  • No additional delay is required for I2C communication.

SPI Communication

  • Arduino UNO can transmit data via SPI at a maximum speed of 8 MHz (Half of the clock speed of 16 MHz i.e. 16/2 = 8 MHz).
  • However, due to the processing of Interrupt Service Routines (ISRs) and library overhead, a 10-microsecond delay is required after transmitting each byte.
  • For this task, we will measure only the time consumed during data transmission, excluding the delay.

Why 10 microseconds delay

This delay is intentionally added to allow the slave to process the data between transfers. Without this delay, if the master sends data too quickly, the slave might not have enough time to process it before the next byte is transmitted. As a result, the data received count will be below 100 KB each time.

Calculations

Time consumed by 1 cycle = 1/16M = 62.5 ns.

ISR Execution

The actual processing inside the ISR (just incrementing SPIcount)  and SPI libraries overheads take a few cycles. Considering 100 cycles consumed.

Time consumed by 100 cycles = 62.5 ns * 100 = 6.25μs.

Practically, after introducing a 4μs delay, 100KB of data transmission begins to work reliably, but occasional data loss is observed. To ensure more consistent results, a delay of 10μs is added, which provides reliable transmission almost every time.

Connections

For I2C

  • Connect the SDA (A4 on Arduino UNO) and SCL (A5 on Arduino UNO) lines between both Arduinos.

For SPI

  • Connect the following lines between the Master and Slave Arduinos:
    • SS (Slave Select) → Pin 10
    • MOSI (Master Out Slave In)→ Pin 11
    • MISO (Master In Slave Out)→ Pin 12
    • SCK (Clock)→ Pin 13

Additional Note

  • Ensure both Arduinos share a common ground (GND) for proper communication.

Connection Diagram

 

Code (Master)

#include <Wire.h>  // I2C library
#include <SPI.h>   // SPI library


const char message[] = "I2C (Inter-Integrated Circuit) & SPI (Serial Peripheral Interface ) are two common communication protocols used to connect devices like sensors and microcontrollers. I2C uses two lines: SDA (data) and SCL (clock), allowing multiple devices to communicate through unique addresses. It supports multi-master and multi-slave configurations but operates at a slower speed, typically 100kHz to 400 kHz, though it can reach higher speeds in fast modes. I2C is simple and requires fewer connections, but its clock stretching and overhead make it less efficient for high-speed data transfers. SPI, on the other hand, uses four lines: MOSI (Master Out Slave In), MISO (Master In Slave Out), SCK (clock), and SS (Slave Select). SPI supports higher speeds (up to tens of MHz), making it better suited for fast data transfers. It's simpler in protocol, with no need for addressing, but it requires more wires and separate SS lines. While I2C is ideal for many devices, SPI excels in fast, reliable communication.";


uint16_t msgCount = 0;
uint8_t cnt32 = 0;  // this variable tracks count of 32-byte I2C buffer
uint32_t I2C_time_consumed = 0, SPI_time_consumed = 0;
uint32_t startI2C, endI2C, startSPI, endSPI;
uint8_t error = 0;

void setup() {
 // I2C setup
 Wire.begin();
 Wire.setClock(400000);  // Set I2C speed to 400 kHz (Fast mode)

 // SPI setup
 SPI.begin();
 SPI.setClockDivider(SPI_CLOCK_DIV4);  // Sets SPI clock at 4 MHz (16/4)
 pinMode(SS, OUTPUT);                  // Set Slave Select pin as output
 digitalWrite(SS, HIGH);               // Keep Slave Select HIGH initially

 // Serial communication
 Serial.begin(115200);
 delay(1000);  // delay for slave to initialize
 Serial.println("Sending data...");
}

void loop() {
 // I2C data transmission
 for (uint8_t i = 0; i < 100; i++) {
   Wire.beginTransmission(9);  // Transmit to I2C device #9
   while (msgCount < 1000) {
     if (cnt32 == 32) {
       startI2C = micros();
       error = Wire.endTransmission();  // this function returns 0, if I2C data is transmitted successfully.
       endI2C = micros();

       if (error) {
         Serial.print("Error in I2C communication: ");
         Serial.println(error);
       } else {
         I2C_time_consumed += endI2C - startI2C;
       }

       Wire.beginTransmission(9);  // Start new transmission
       cnt32 = 0;
     }
     Wire.write(message[msgCount]);
     msgCount++;
     cnt32++;
   }
   Wire.endTransmission();  // End transmission after 1KB
   msgCount = 0;
   cnt32 = 0;
 }

 // Print I2C time consumed
 Serial.print("I2C data transfer time consumed: ");
 Serial.print(I2C_time_consumed);
 Serial.println(" µs");

 // SPI data transmission
 digitalWrite(SS, LOW);  // Start SPI communication with slave
 for (uint8_t i = 0; i < 100; i++) {
   for (uint16_t j = 0; j < 1000; j++) {
     startSPI = micros();
     SPI.transfer(message[j]);
     endSPI = micros();
     SPI_time_consumed += endSPI - startSPI;
     delayMicroseconds(10);  //delay consumed by slave for processing data
   }
 }
 digitalWrite(SS, HIGH);  // End SPI communication

 // Print SPI time consumed
 Serial.print("SPI data transfer time consumed: ");
 Serial.print(SPI_time_consumed);
 Serial.println(" µs");

 // End loop
 while (1);
}

 

Code explanation

  1. Setup (I2C and SPI Initialization) 
    • The Wire.begin() function starts the I2C communication on the Arduino, and Wire.setClock(400000) sets the speed of the I2C bus to 400 kHz (fast mode).
    • The SPI.begin() function initializes the SPI communication, and SPI.setClockDivider(SPI_CLOCK_DIV4) sets the SPI speed to 4 MHz (the 16MHz clock is divided by 4). 
  2. I2C Data Transmission
    • The code sends a 1 KB block of data (message[]) over I2C 100 times (totaling 100 KB).
    • In I2C master mode, the Wire.write() function stores data in the I2C buffer, which has a capacity of 32 bytes. Once the buffer data is ready, the Wire.endTransmission() function transmits the stored data. We are measuring the time consumed by the Wire.endTransmission() function during this transmission process.
    • We are measuring the time taken using micros(). The time consumed by I2C transmission is accumulated in the I2C_time_consumed variable.
  3. SPI Data Transmission
    • Similarly, the code sends the same 1 KB block of data over SPI 100 times (totaling 100 KB).
    • The transmission time is measured for each byte using micros() and accumulated in the SPIduration variable.
    • A 10 µs delay is added to simulate overhead processing for SPI.
    • In SPI communication, the clock speed was set to 4 MHz, which proved to be stable and reliable. Although the maximum available clock speed is 8 MHz, data loss occurred when operating at this higher speed, with some data bytes missing. Therefore, 4 MHz is considered the optimal speed, along with a 10 µs delay, to ensure reliable data transmission.

 

Code (slave)

#include <Wire.h>
#include <SPI.h>

int LED = 13;
int x = 0;
uint32_t I2Ccount = 0, SPIcount = 0, prevSPIcount = 1000000, prevI2Ccount = 1000000;

void setup() {
 // Set up LED as output
 pinMode(LED, OUTPUT);

 // I2C configuration
 Wire.begin(9);  // Start I2C as Slave on address 9
 Wire.onReceive(receiveEvent);  // Attach a function to trigger when data is received over I2C
  // SPI configuration
 pinMode(MISO, OUTPUT);  // Set MISO as OUTPUT (to send data to Master IN)
 SPCR |= _BV(SPE);  // Enable SPI in Slave Mode
 SPI.attachInterrupt();  // Enable interrupt on SPI communication

 // Serial communication for output
 Serial.begin(115200);
}

// I2C receive event handler
void receiveEvent(int bytes) {
 while (Wire.available()) {
   x = Wire.read();  // Read one character from I2C
   I2Ccount++;  // Increment I2C byte count
 }
}

// SPI interrupt service routine (ISR) for received data
ISR(SPI_STC_vect) {
 SPIcount++;  // Increment SPI byte count
}

void loop() {
  // Check and print I2C data count
 if (I2Ccount >= 100000) {  // Check if received I2C data is 100 KB or more
   if (prevI2Ccount != I2Ccount) {  // Only print if the value has changed
     Serial.print("I2C data received: ");
     Serial.print((float)I2Ccount / 1000.00, 3);
     Serial.println(" KB");
     prevI2Ccount = I2Ccount;  // Update previous I2C count
   }
 }
  // Check and print SPI data count
 if (SPIcount >= 100000) {  // Check if received SPI data is 100 KB or more
   if (prevSPIcount != SPIcount) {  // Only print if the value has changed
     Serial.print("SPI data received: ");
     Serial.print((float)SPIcount / 1000.00, 3);
     Serial.println(" KB");
     prevSPIcount = SPIcount;  // Update previous SPI count
   }
 }
 delay(1000);  // Delay for readability
}

 

Code explanation

  1. I2C Setup
    • The Arduino UNO is set up as an I2C slave with address 9 using Wire.begin(9).
    • When data is received over I2C, the receiveEvent() function is triggered. This function reads the data byte-by-byte using Wire.read() and increments the I2Ccount variable to track how many bytes have been received.
  2. SPI Setup
    • The Arduino is set as an SPI slave. It enables SPI with SPCR |= _BV(SPE) and sets MISO as an output.
    • When SPI data is received, the SPI interrupt SPI_STC_vect is triggered. This interrupt service routine (ISR) increments the SPIcount variable to track how many bytes have been received.
  3. Loop
    • The loop() continuously checks if either I2C or SPI data has reached 100 KB. If it has, it prints the total amount of data received (in kilobytes) and updates the previous count to avoid redundant prints.

The setup is not working!

When you do the setup as shown in the connection diagram & upload the code on both Arduino’s. 

  • Master Arduino’s serial monitor → print time consumed to send.
  • Slave Arduino’s serial monitor → Print nothing.

This happens because the master may send data even if the slave isn't ready. To fix this, follow these steps.

  • Make the connections.
  • Open the Serial Monitors on both the master and slave (115200 baud rate).
  • Clear both Serial Monitors.
  • Press the reset button on the slave.
  • Press the reset button on the master.

Output


The setup photo

 

Serial Monitor Screen-Shot

 

Output Video

 

 

 

Submit Your Solution