Editorial Solution

The standard SPI protocol is designed for single-master and multiple-slave communication.

In a multi-master SPI setup, there can be more than one master, but special precautions must be taken for bus arbitration to ensure that only one master communicates at a time.

To handle bus arbitration and avoid data transfer conflicts in a multi-master SPI setup, here's how we can manage the SPI bus.

Key Steps for Bus Arbitration in Multi-Master SPI

  1. Disabling the SPI Bus After a Master Completes its Work
    • After a master finishes its communication, it must release control of the SPI bus so that the other master can access it.
    • To disable the master from the SPI bus, set all SPI pins (MOSI, MISO, SCK, and CS) to input and state LOW. This puts the pins in high-impedance mode, effectively disconnecting them from the bus.
    • An external pull-up resistor should be connected to ensure the CS pin doesn't float and make the line HIGH.
       
  2. Re-enabling the SPI Bus When a Master Needs to Interface
    • The code waits until the chip select pin (CS_PIN) goes HIGH, indicating that the peripheral device is ready for communication.
    • Before a master can send or receive data, it must re-enable the SPI interface by setting the necessary pins as output.
    • MOSISCK, and CS pins must be set as outputs for the master to communicate with the slave.
    • The CS pin for the slave should be pulled low to select the slave for communication.

By properly managing when and how masters disable and enable the SPI bus, conflicts between masters can be avoided, allowing safe communication in a multi-master SPI setup.

Hardware Connections

Let's go through the hardware connections step by step,

  • Ensure Proper Power Supply: Connect 5V and GND from the Arduino to the SD card module to power it correctly.
  • Pull-up Resistors for Buttons: The buttons are connected with internal pull-ups by setting the button pins as INPUT_PULLUP in the Arduino code, ensuring they register as LOW when pressed and HIGH when not pressed.
  • SPI Communication: The MOSI, MISO, SCK, and CS lines are shared between two Arduino masters and the SD card. Each master can use these lines to communicate with the SD card, depending on who is active and controlling the bus.

By following these steps, we will have a setup where two Arduino boards (masters) can communicate with an SD card module (slave) using SPI, with proper handling of bus arbitration.

Circuit connection

 

Firmware

As we have Master 1 and Master 2, so we have to develop two different codes.And below points, we have to take care of the codes. 

  • Debouncing Logic: Ensures stable button press detection.
  • SD Card Persistence: Stores the counter value even after the Arduino is powered off.
  • SPI Arbitration: Ensures the SPI bus is correctly managed by enabling/disabling SPI communication as needed.
  • Modular Code: Separate functions for SPI handling, SD card operations, and button debouncing make the code reusable and maintainable.

Code(Master 1)

#include <SPI.h>
#include <SD.h>

#define CS_PIN 10           
#define SWITCH_INC 3        
#define SWITCH_RST 4        
#define DEBOUNCE_DELAY 50   

uint8_t counter = 0;                                
unsigned long lastDebounceTime[2] = {0, 0};         // Last debounce time for each button
uint8_t lastButtonState[2] = {HIGH, HIGH};          // Last recorded state of each button
uint8_t buttonState[2] = {HIGH, HIGH};              // Current state of each button
uint8_t switchPins[2] = {SWITCH_INC, SWITCH_RST};   // Array to store button pins
const char* FILE_NAME = "count.txt";                // File name for storing the counter on the SD card

void setup() {
    Serial.begin(115200);  
    for (uint8_t i = 0; i < 2; i++) {
        pinMode(switchPins[i], INPUT_PULLUP);
    }

    counter = readCounterFromSD();                // Read the initial counter value from the SD card
    Serial.print("Initial counter value: ");
    Serial.println(counter);
}

void loop() {
    // Check if the increment button is pressed
    if (isButtonPressed(0)) {
        Serial.println("\nIncrement button pressed.");
        counter++;
        writeCounterToSD(counter);  // Save updated counter to SD card
        Serial.print("Counter Incremented to ");
        Serial.println(counter);
    }

    // Check if the reset button is pressed
    if (isButtonPressed(1)) {
        Serial.println("\nReset button pressed.");
        counter = 0;
        writeCounterToSD(counter);  // Save reset counter to SD card
        Serial.println("Counter reset to 0.");
    }
}

// Reads the counter value from the SD card
uint8_t readCounterFromSD() {
    uint8_t storedCounter = 0;
    initSDCard();

    // Check if the file exists on the SD card
    if (SD.exists(FILE_NAME)) {
        File file = SD.open(FILE_NAME, FILE_READ);
        if (file) {
            storedCounter = file.parseInt();  // Parse the counter value from the file
            file.close();
            Serial.println("Counter read from SD card.");
        } else {
            Serial.println("Error: Failed to read file.");
        }
    } else {
        Serial.println("No counter file found. Starting with counter = 0.");
    }

    disableSPI();
    return storedCounter;
}

// Writes the counter value to the SD card
void writeCounterToSD(uint8_t count) {
    initSDCard();  

    // Remove the existing file if it exists
    if (SD.exists(FILE_NAME)) {
        SD.remove(FILE_NAME);
        Serial.println("Old counter file deleted.");
    }

    // Create and write the new counter value to the file
    File file = SD.open(FILE_NAME, FILE_WRITE);
    if (file) {
        file.print(count);
        file.close();
        Serial.println("Counter updated on SD card.");
    } else {
        Serial.println("Error: Failed to write to SD card.");
    }

    disableSPI();
}

// Initializes the SD card
void initSDCard() {
    enableSPI();  // Enable SPI communication

    Serial.println("SD card initializing");

    while(!SD.begin(CS_PIN)){
      Serial.print(".");
      delay(1000);
    }

    Serial.println("SD card initialized.");
}

// Enables SPI communication
void enableSPI() {
    Serial.println("Waiting for free the SPI bus. ");
    while (!digitalRead(CS_PIN))  // Wait until the chip select pin is LOW
    {
      Serial.print(".");
      delay(1000);
    }
    SPI.begin();
    pinMode(CS_PIN, OUTPUT);
    pinMode(11, OUTPUT);  // MOSI pin
    pinMode(12, INPUT);   // MISO pin
    pinMode(13, OUTPUT);  // SCK pin
    digitalWrite(CS_PIN, HIGH);
    Serial.println("SPI Enabled.");
}

// Disables SPI communication by setting SPI pins to input mode and driving them low. 
// This makes the pins high impedance, effectively disconnecting the SPI interface.
void disableSPI() {
    SPI.end();
    pinMode(11, INPUT);  // MOSI pin
    pinMode(12, INPUT);  // MISO pin
    pinMode(13, INPUT);  // SCK pin
    pinMode(CS_PIN, INPUT);  
    digitalWrite(11, LOW);
    digitalWrite(12, LOW);
    digitalWrite(13, LOW);
    digitalWrite(CS_PIN, LOW);
    Serial.println("SPI Disabled.");
}

// Checks if a button is pressed with debouncing
bool isButtonPressed(uint8_t switchIndex) {
    uint8_t pin = switchPins[switchIndex];
    int reading = digitalRead(pin);

    // Check if the button state has changed
    if (reading != lastButtonState[switchIndex]) {
        lastDebounceTime[switchIndex] = millis();
    }
    lastButtonState[switchIndex] = reading;

    // Validate the button press after debounce delay
    if ((millis() - lastDebounceTime[switchIndex]) > DEBOUNCE_DELAY) {
        if (reading != buttonState[switchIndex]) {
            buttonState[switchIndex] = reading;
            if (buttonState[switchIndex] == LOW) {
                return true;  
            }
        }
    }
    return false;
}

 

Code Explanation (Master 1)

Setup

  • Serial Communication: Starts at 115200 baud rate for debugging/logging.
  • Button Pins: Configured with INPUT_PULLUP to detect button presses.
  • Counter Initialization: Reads the existing counter value from the SD card or starts at 0 if no file exists.

Loop

  1. Increment Button:
    • Checks if pressed using isButtonPressed(0).
    • Increments the counter and writes it to the SD card using writeCounterToSD().
  2. Reset Button:
    • Checks if pressed using isButtonPressed(1).
    • Resets the counter to 0 and updates the SD card.

SPI Management

  • enableSPI():
    • Waits for the SPI bus to be free (CS_PIN HIGH).
    • Configures SPI pins (MOSI, MISO, SCK) and starts SPI communication.
    • Drives CS_PIN HIGH to indicate communication readiness.
  • disableSPI():
    • Stops SPI communication using SPI.end().
    • Configures SPI pins as INPUT and drives them LOW to make them high-impedance, effectively disconnecting the master.

SD Card Operations

  • initSDCard():
    • Initializes the SD card and retries until successful, indicated by SD.begin(CS_PIN).
  • readCounterFromSD():
    • Checks for count.txt on the SD card.
    • Reads the counter value from the file, or defaults to 0 if the file doesn't exist.
  • writeCounterToSD():
    • Removes the existing file (if any).
    • Writes the updated counter value to the file count.txt.

Button Debouncing

  • isButtonPressed():
    • Reads the button state and detects changes.
    • Verifies state stability using DEBOUNCE_DELAY (50ms).
    • Returns true when a valid button press is detected.

Code (Master 2)

#include <SPI.h>
#include <SD.h>

#define CS_PIN 10            
#define SWITCH_PIN 3         
#define DEBOUNCE_DELAY 50    

uint8_t counter = 0;                  
unsigned long lastDebounceTime = 0;   
uint8_t lastButtonState = HIGH;       
uint8_t buttonState = HIGH;        

const char* FILE_NAME = "count.txt";  // File name used to store the counter on the SD card

void setup() {
    Serial.begin(115200);
    pinMode(SWITCH_PIN, INPUT_PULLUP);

    counter = readCounterFromSD();    // Read the counter value from the SD card at startup
    Serial.print("Counter value: ");
    Serial.println(counter);
}

void loop() {
    // Check if the button is pressed 
    if (isButtonPressed()) {
        Serial.println("\nRead button pressed.");
        uint8_t count = readCounterFromSD();  // Fetch the counter value from the SD card
        Serial.print("Stored count is: ");
        Serial.println(count);
    }
}

// Reads the counter value from the SD card
uint8_t readCounterFromSD() {
    uint8_t storedCounter = 0;
    initSDCard();

    // Check if the file exists on the SD card
    if (SD.exists(FILE_NAME)) {
        File file = SD.open(FILE_NAME, FILE_READ);  // Open the file for reading
        if (file) {
            storedCounter = file.parseInt();  // Read the counter value as an integer
            file.close();
            Serial.println("Counter read from SD card.");
        } else {
            Serial.println("Error: Failed to read the file.");
        }
    } else {
        Serial.println("No counter file found. Starting with counter = 0.");
    }

    disableSPI();  
    return storedCounter;
}

// Initializes the SD card and sets up the SPI interface
void initSDCard() {
    enableSPI();  // Enable SPI communication

    Serial.println("SD card initializing");

    while(!SD.begin(CS_PIN)){
      Serial.print(".");
      delay(1000);
    }

    Serial.println("SD card initialized.");
}

// Enables SPI communication
void enableSPI() {
    Serial.println("Waiting for free the SPI bus. ");
    while (!digitalRead(CS_PIN))  // Wait until the chip select pin is LOW
    {
      Serial.print(".");
      delay(1000);
    }
    SPI.begin();
    pinMode(CS_PIN, OUTPUT);
    pinMode(11, OUTPUT);  // MOSI pin
    pinMode(12, INPUT);   // MISO pin
    pinMode(13, OUTPUT);  // SCK pin
    digitalWrite(CS_PIN, HIGH);
    Serial.println("SPI Enabled.");
}

// Disables SPI communication by setting SPI pins to input mode and driving them low. 
// This makes the pins high impedance, effectively disconnecting the SPI interface.
void disableSPI() {
    SPI.end();
    pinMode(11, INPUT);  // MOSI pin
    pinMode(12, INPUT);  // MISO pin
    pinMode(13, INPUT);  // SCK pin
    pinMode(CS_PIN, INPUT);

    digitalWrite(11, LOW);
    digitalWrite(12, LOW);
    digitalWrite(13, LOW);
    digitalWrite(CS_PIN, LOW);
    Serial.println("SPI Disabled.");
}

// Checks if the button is pressed with debounce logic
bool isButtonPressed() {
    int reading = digitalRead(SWITCH_PIN);  // Read the current button state

    // Detect button state change
    if (reading != lastButtonState) {
        lastDebounceTime = millis();  // Update debounce timer
    }
    lastButtonState = reading;

    // Validate button press after debounce delay
    if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {
        if (reading != buttonState) {
            buttonState = reading;

            // Return true if the button is pressed
            if (buttonState == LOW) {
                return true;
            }
        }
    }

    return false;  // Return false if no valid press is detected
}

 

Code Explanation (Master 2)

Loop

  • Button Check: If the button is pressed (isButtonPressed()), it reads and prints the counter value from the SD card.

SPI Management

  • enableSPI(): Waits for the SPI bus to be free, configures SPI pins, and starts SPI communication.
  • disableSPI():
    • Stops SPI communication using SPI.end().
    • Configures SPI pins as INPUT and drives them LOW to make them high-impedance, effectively disconnecting the master.

SD Card Operations

  • initSDCard(): Initializes the SD card and retries until successful.
  • readCounterFromSD(): Reads the counter value from count.txt or initializes it to 0 if the file doesn't exist.

Button Debouncing

isButtonPressed(): Debounces the button with a delay of 50ms and detects valid presses.

Output

 

Master 1 Serial Monitor Output

 

 

Master 2 Serial Monitor Output

 

 

Video

 

Submit Your Solution