Arduino SAMD21 SPI Slave On Sercom2

I'm trying to create an SPI slave device using the SAMD21G18A. I came up with the following code, but can't seem to get it to work right. I've tried many different approaches. The behavior is quite simple. It enters the "handleCSPin" but not the "SERCOM2_Handler". Any suggestions are appreciated. If i forgot to include some critical information, please let me know.

#include <Sercom.h>
#include "Arduino.h"
#include "variant.h"
#include "wiring_private.h" // pinPeripheral() function

//serial config
#define Serial SerialUSB
#define baudrate 2000000 //serial speed is set to maximum 2,000,000 baud. Serial is used for DEBUG only

#define INTERNAL_SPI_SERCOM     SERCOM2
#define INTERNAL_SPI_MOSI      4
#define INTERNAL_SPI_MISO      2
#define INTERNAL_SPI_SCK       3
#define INTERNAL_SPI_CS        8

#define START_BYTE 0xAA

// Define the mode explicitly
#define SERCOM_MODE_SPI_SLAVE   0x2

volatile uint8_t spiBuffer[3];
volatile bool newCommandAvailable = false;
volatile bool isTransactionStarted = false;

void setup() {
    // Setup pins for SERCOM functionality
    pinPeripheral(INTERNAL_SPI_MOSI, PIO_SERCOM_ALT);
    pinPeripheral(INTERNAL_SPI_MISO, PIO_SERCOM);
    pinPeripheral(INTERNAL_SPI_SCK, PIO_SERCOM_ALT);

    // Configure the SERCOM module for SPI slave operation
    INTERNAL_SPI_SERCOM->SPI.CTRLA.reg = SERCOM_MODE_SPI_SLAVE |
                                         SERCOM_SPI_CTRLA_DIPO(2) |
                                         SERCOM_SPI_CTRLA_DOPO(0);

    INTERNAL_SPI_SERCOM->SPI.CTRLB.reg = SERCOM_SPI_CTRLB_RXEN | 
                                         SERCOM_SPI_CTRLB_CHSIZE(0x0);   // 8-bit

    while(INTERNAL_SPI_SERCOM->SPI.SYNCBUSY.bit.CTRLB);

    // Enable interrupt on receive complete
    INTERNAL_SPI_SERCOM->SPI.INTENSET.reg = SERCOM_SPI_INTENSET_RXC;

    // Enable the SPI
    INTERNAL_SPI_SERCOM->SPI.CTRLA.bit.ENABLE = 1;
    while(INTERNAL_SPI_SERCOM->SPI.SYNCBUSY.bit.ENABLE);

    // Enable SERCOM interrupt
    NVIC_EnableIRQ(SERCOM2_IRQn);

    // Setup CS pin for interrupt
    pinMode(INTERNAL_SPI_CS, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(INTERNAL_SPI_CS), handleCSPin, FALLING);

    Serial.begin(baudrate);
}

void loop() {
    if (newCommandAvailable) {
        Serial.print("Position: "); Serial.println(spiBuffer[1]);
        Serial.print("Duration: "); Serial.println(spiBuffer[2]);
        newCommandAvailable = false;
    }
}

void handleCSPin() {
    isTransactionStarted = true; // indicate that a new SPI transaction has started
    Serial.println("triggered");
}

void SERCOM2_Handler() {

  Serial.println("in handler");

    // Only process data if we're in a valid transaction
    if (!isTransactionStarted) {
        return;
    }
    
    uint8_t data = INTERNAL_SPI_SERCOM->SPI.DATA.reg;   // Read data

    static int index = 0;
    spiBuffer[index++] = data;

    if (index == 3) {
        if (spiBuffer[0] == START_BYTE) {
            newCommandAvailable = true;
        }
        index = 0;  // Reset for the next set of bytes
        isTransactionStarted = false; // Reset the flag for the next transaction
    }
}

For reference here is the master code. The master is a arduino UNO.

#include <SPI.h>

#define START_BYTE 0xAA
#define CS_PIN 10

#define baudrate 2000000 //serial speed is set to maximum 2,000,000 baud. Serial is used for DEBUG only

//spi config
#define speedMaximum 10000
#define dataOrder MSBFIRST
#define dataMode SPI_MODE0

void setup() {
  Serial.begin(baudrate);

  // SPI Setup
  SPI.begin();
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(CS_PIN, HIGH);  // Ensure CS is deactivated
}

void sendSPICommand(uint8_t position, uint8_t duration) {

  SPI.beginTransaction(SPISettings(speedMaximum, dataOrder, dataMode));

  digitalWrite(CS_PIN, LOW);  // Activate CS
  
  // Transmit data
//  delay(500);
  SPI.transfer(START_BYTE);
//  delay(500);
  SPI.transfer(position);
//  delay(500);
  SPI.transfer(duration);
//  delay(500);

  digitalWrite(CS_PIN, HIGH);  // Deactivate CS

  SPI.endTransaction();
}

void loop() {
  sendSPICommand(100, 10);
  Serial.println("100");
  delay(2000);
  sendSPICommand(200, 10);
  Serial.println("200");
  delay(2000);

}

Don't you need a SS configured (via pinPeripheral) for slave mode? You only MISO, MOSI, and SCK...

Is this a board with Zero compatible pin assignments?

its a custom board, but the pin assignments are the same (i program it as an arduino zero). I do believe that is my issue -- i don't think i wired it using the correct Chip Select pin for Sercom2. There is no way to change it without a board rev :frowning: Honestly, didn't know that was a thing. I'm an electrical engineer, not a software engineer.

Still very interested in how i would do it "proper" next time, and what exactly my mistakes where.

In any case since all i wanted was basic non-critical one-way communication of two variables, i simply wrote my own communication library using morse code. Let it run for a few days, and don't think it messed up yet. In any case, maybe someone will find my morse code communication useful?

MASTER CODE

/*********************************************************************************************************************
Morse Code Communication To 3 Slaves. Recieves Two Values, NOTE 1-13 and DURATION 0-100
Duration of 0 means the Valve Toggle Sets The Duration
*********************************************************************************************************************/



/*********************************************************************************************************************
Pin Definitions & Variables
*********************************************************************************************************************/

#define CLOCK_PIN 81 //SD_SCK
#define DATA_PIN 82 //SD_MOSI For reference, SD_MISO is pin 80
#define SELECT_PIN_A 39
#define SELECT_PIN_B 18
#define SELECT_PIN_C 0


// Morse code for numbers 0-9. Only considering numbers for simplicity.
String morseCode[13] = {
  "-----",  // 0
  ".----",  // 1
  "..---",  // 2
  "...--",  // 3
  "....-",  // 4
  ".....",  // 5
  "-....",  // 6
  "--...",  // 7
  "---..",  // 8
  "----.",  // 9
  ".-.-.",  // 10
  ".--.-",  // 11
  ".---."   // 12
};

/*********************************************************************************************************************
Morse Code Setup
*********************************************************************************************************************/

void MorseSetup() {
  pinMode(CLOCK_PIN, OUTPUT);
  pinMode(DATA_PIN, OUTPUT);
  pinMode(SELECT_PIN_A, OUTPUT);
  pinMode(SELECT_PIN_B, OUTPUT);
  pinMode(SELECT_PIN_C, OUTPUT);
  digitalWrite(SELECT_PIN_A, HIGH); // deselect at the beginning
  digitalWrite(SELECT_PIN_B, HIGH); // deselect at the beginning
  digitalWrite(SELECT_PIN_C, HIGH); // deselect at the beginning
}

/*********************************************************************************************************************
Morse Code Internal Functions
*********************************************************************************************************************/

#define CommunicationSpeed 1 //how fast the clock pulses are. This is controlled my master only. Nothing to change on slave when adjusted here.

void sendMorse(int num) {
  if (num < 0 || num > 12) return; 
  
  String code = morseCode[num];
  Serial.print("Sending: ");
  Serial.println(code);  // print the Morse code being sent
  for (int i = 0; i < code.length(); i++) {
    if (code[i] == '.') {
      digitalWrite(DATA_PIN, HIGH);
    } else {
      digitalWrite(DATA_PIN, LOW);
    }
    pulseClock(); // send clock pulse with data set
  }
}

void pulseClock() {
  digitalWrite(CLOCK_PIN, LOW);
  delay(CommunicationSpeed); //how fast the clock pulses are
  digitalWrite(CLOCK_PIN, HIGH);
  delay(CommunicationSpeed); //how fast the clock pulses are
}

void sendData(int position, int duration, int selection) {
  selectSlave(selection);  // select the appropriate slave, Start Transmission

  sendMorse(position);
  sendMorse(duration / 10); // tens place
  sendMorse(duration % 10); // ones place
  
  deselectSlaves();  // deselect all slaves at the end of the communication, End Transmission
}

void selectSlave(int selection) {
  digitalWrite(SELECT_PIN_A, HIGH);
  digitalWrite(SELECT_PIN_B, HIGH);
  digitalWrite(SELECT_PIN_C, HIGH);
  
  switch(selection) {
    case 1: digitalWrite(SELECT_PIN_A, LOW); break;
    case 2: digitalWrite(SELECT_PIN_B, LOW); break;
    case 3: digitalWrite(SELECT_PIN_C, LOW); break;
  }
}

void deselectSlaves() {
  digitalWrite(SELECT_PIN_A, HIGH);
  digitalWrite(SELECT_PIN_B, HIGH);
  digitalWrite(SELECT_PIN_C, HIGH);
}

/*********************************************************************************************************************
Main Morse Code Function. Call This From Main Program. Sets the position and duration values for the selected PP.
Recieves Position, Duration, Selection. Selection is which of the 3 PP Modules are selected.
Returns 1 for Success, 0 for fail.
*********************************************************************************************************************/

#define POSITION_MIN 0
#define POSITION_MAX 12

#define DURATION_MIN 0
#define DURATION_MAX 12

int MorseCode(int position, int duration, int selection) {

  if(position < POSITION_MIN || position > POSITION_MAX || 
     duration < DURATION_MIN || duration > DURATION_MAX ||
     selection < 1 || selection > 3) {
    return 0; // fail due to out-of-bounds input
  }
  sendData(position, duration, selection); //Send the position, duration, selection values
  return 1;  // success
}

SLAVE CODE

/*********************************************************************************************************************
Morse Code Communication From Master. Recieves Two Values, NOTE 1-13 and DURATION 0-100
*********************************************************************************************************************/

const int CLOCK_PIN = 3;
const int SELECT_PIN = 8;
const int DATA_PIN = 4;

String receivedMorse = "";
volatile bool newData = false;
bool isReceiving = false;

/*********************************************************************************************************************
Morse Code Setup
*********************************************************************************************************************/

void MorseSetup() {
  pinMode(CLOCK_PIN, INPUT);
  pinMode(SELECT_PIN, INPUT);
  pinMode(DATA_PIN, INPUT);

  attachInterrupt(digitalPinToInterrupt(SELECT_PIN), selectISR, CHANGE);
  attachInterrupt(digitalPinToInterrupt(CLOCK_PIN), clockISR, RISING);
}

/*********************************************************************************************************************
Morse Code Internal Functions
*********************************************************************************************************************/

void selectISR() {
  if (digitalRead(SELECT_PIN) == LOW) {
    isReceiving = true;
    receivedMorse = "";
  } else {
    isReceiving = false;
    newData = true; // Set the flag to signal that new data has been received
  }
}

void clockISR() {
  if (isReceiving) {
    if (digitalRead(DATA_PIN) == HIGH) {
      receivedMorse += ".";
    } else {
      receivedMorse += "-";
    }
  }
}

String morseCode[13] = {
  "-----",  // 0
  ".----",  // 1
  "..---",  // 2
  "...--",  // 3
  "....-",  // 4
  ".....",  // 5
  "-....",  // 6
  "--...",  // 7
  "---..",  // 8
  "----.",  // 9
  ".-.-.",  // 10
  ".--.-",  // 11
  ".---."   // 12
};

int morseToNumber(String morse) {
  for (int i = 0; i < 13; i++) {
    if (morse == morseCode[i]) {
      return i;
    }
  }
  return -1; // error or not found
}

/*********************************************************************************************************************
Decodes the morse code recieved. Sets the position and duration variables.
Returns "1" if values within range of 1-13 and 0-100 respectively. Returns "0" if they are not.
*********************************************************************************************************************/

#define POSITION_MIN 0
#define POSITION_MAX 12

#define DURATION_MIN 0
#define DURATION_MAX 12

int decodeMorse() {
  if (receivedMorse.length() < 10 || receivedMorse.length() > 15) {
    Serial.println("Error: Received Morse length mismatch!"); 
    Serial.println(receivedMorse); 
    return 0;
  }

  int length = receivedMorse.length();
  String positionMorse = receivedMorse.substring(0, 5);
  String tensMorse = receivedMorse.substring(5, length - 5);
  String onesMorse = receivedMorse.substring(length - 5, length);

  position = morseToNumber(positionMorse);
  int tens = morseToNumber(tensMorse);
  int ones = morseToNumber(onesMorse);

//  Serial.print("Decoded: Position: ");  // Debugging information
//  Serial.print(position);
//  Serial.print(", Tens: ");
//  Serial.print(tens);
//  Serial.print(", Ones: ");
//  Serial.println(ones);

  duration = tens * 10 + ones;

  if (position >= POSITION_MIN && position <= POSITION_MAX && duration >= DURATION_MIN && duration <= DURATION_MAX) {
    return 1;
  } else {
    return 0;
  }
}

/*********************************************************************************************************************
Main Morse Code Function. Call This From Main Program. Sets the position and duration values. 
Returns 1 = new data 0 = no new data
*********************************************************************************************************************/

int MorseCode() {
  if (newData) {
//    Serial.println("Received New Data: " + receivedMorse); // Print received Morse code
    int isValid = decodeMorse();
    newData = false;
    return isValid;
  } else {
    return 0;
  }
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.