I2C Communication
SPI Communication
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.
For I2C
For SPI
Additional Note
#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);
}
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).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). message[]
) over I2C 100 times (totaling 100 KB).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.micros().
The time consumed by I2C transmission is accumulated in the I2C_time_consumed
variable.micros()
and accumulated in the SPIduration
variable.#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
}
Wire.begin(9).
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.SPCR |= _BV(SPE)
and sets MISO
as an output.SPI_STC_vect
is triggered. This interrupt service routine (ISR) increments the SPIcount
variable to track how many bytes have been received.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.
This happens because the master may send data even if the slave isn't ready. To fix this, follow these steps.