CANBus with SD-card

In case anyone else are facing similar issues and ends up here...
Topic changed to CANBus with SD-card, as I've moved away from the CANBed for now.

The new setup involves:

Here is a schematic (including RTC and battery (untested)):

Below is the first code I tested.
This one opens the dataFile for each main loop.
Which leads to a normal write cycle at about 21ms, since I intend to use this for a RC servo signal (50Hz) sent over CANBus, I need a write cycle far less than 20ms.
Using the SD library.

Please keep in mind that the example uses a custom version of MCP_CAN_lib

#include "mcp_can.h"

to fit the 10MHz crystal at MIKROE CAN SPI Click 3.3V board.
The code is not added in this post as the changes can be found at MCP_CAN_lib / Pull requests #33

// CAN datalogger example

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

#define SD_CD_PIN 20
#define SD_CS_PIN 21
#define CAN0_INT_PIN 9
#define CAN0_CS_PIN 10

long unsigned int rxId;
unsigned char len = 0;
unsigned char rxBuf[8];

MCP_CAN CAN0(CAN0_CS_PIN);

bool CAN0_init_OK = false;
bool SD_init_OK = false;

void setup() {
  Serial.begin(115200);
  while ((!Serial) && (millis() < 3000));  // Wait for Serial

  // Initialize MCP2515
  CAN0_init_OK = (CAN0.begin(MCP_ANY, CAN_500KBPS, MCP_10MHZ) == CAN_OK);
  Serial.println(CAN0_init_OK ? "MCP2515 Initialized Successfully!" : "Error Initializing MCP2515...");

  if (CAN0_init_OK) {
    CAN0.setMode(MCP_NORMAL);  // Set MCP2515 to normal mode
  }

  pinMode(CAN0_INT_PIN, INPUT);

  // Initialize SD card
  pinMode(SD_CD_PIN, INPUT_PULLUP);
  SD_init_OK = (!digitalRead(SD_CD_PIN) && SD.begin(SD_CS_PIN));
  Serial.println(SD_init_OK ? "SD initialization OK." : "SD initialization failed.");
}

void loop() {
  delay(1);

  // Check if a CAN message is available
  if (CAN0_init_OK && (CAN_MSGAVAIL == CAN0.checkReceive())) {
    CAN0.readMsgBuf(&rxId, &len, rxBuf);

    // Open datalog file
    File dataFile;
    if (SD_init_OK && !digitalRead(SD_CD_PIN)) {
      dataFile = SD.open("datalog.txt", O_CREAT | O_WRITE | O_APPEND);
    }

    if (dataFile) {
      // Write timestamp, CAN ID, and data length
      dataFile.print(millis());
      dataFile.write(' ');
      dataFile.print(rxId, HEX);
      dataFile.write(' ');
      dataFile.print(len, HEX);

      // Write data bytes
      for (byte i = 0; i < len; i++) {
        if (rxBuf[i] < 10) {
          dataFile.write('0');  // Add leading zero for single-digit hex values
        }
        dataFile.print(rxBuf[i], HEX);
      }

      dataFile.println();
      dataFile.close();
    }
  }
}

The next code, dataFile are opened at setup and kept open as the main loop runs.
Resulting in a normal write cycle at about 11ms.

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

#define SD_CD_PIN 20
#define SD_CS_PIN 21
#define CAN0_INT_PIN 9
#define CAN0_CS_PIN 10

long unsigned int rxId;
unsigned char len = 0;
unsigned char rxBuf[8];

MCP_CAN CAN0(CAN0_CS_PIN);

bool CAN0_init_OK = false;
bool SD_init_OK = false;
File dataFile;

void setup() {
  Serial.begin(115200);
  while ((!Serial) && (millis() < 3000));  // Wait for Serial

  // Initialize MCP2515
  CAN0_init_OK = (CAN0.begin(MCP_ANY, CAN_500KBPS, MCP_10MHZ) == CAN_OK);
  Serial.println(CAN0_init_OK ? "MCP2515 Initialized Successfully!" : "Error Initializing MCP2515...");

  if (CAN0_init_OK) {
    CAN0.setMode(MCP_NORMAL);  // Set MCP2515 to normal mode
  }

  pinMode(CAN0_INT_PIN, INPUT);

  // Initialize SD card
  pinMode(SD_CD_PIN, INPUT_PULLUP);
  SD_init_OK = (!digitalRead(SD_CD_PIN) && SD.begin(SD_CS_PIN));
  Serial.println(SD_init_OK ? "SD initialization OK." : "SD initialization failed.");

  // Open the datalog file once for faster writes
  if (SD_init_OK && !digitalRead(SD_CD_PIN)) {
    dataFile = SD.open("datalog.txt", O_CREAT | O_WRITE | O_APPEND);
    if (!dataFile) {
      Serial.println("Error: Could not open datalog.txt for writing.");
      SD_init_OK = false;
    }
  }
}

void loop() {
  delay(1);

  // Check if a CAN message is available
  if (CAN0_init_OK && (CAN_MSGAVAIL == CAN0.checkReceive())) {
    CAN0.readMsgBuf(&rxId, &len, rxBuf);

    if (SD_init_OK && dataFile) {
      // Write timestamp, CAN ID, and data length
      dataFile.print(millis());
      dataFile.write(' ');
      dataFile.print(rxId, HEX);
      dataFile.write(' ');
      dataFile.print(len, HEX);

      // Write data bytes
      for (byte i = 0; i < len; i++) {
        if (rxBuf[i] < 10) {
          dataFile.write('0');  // Add leading zero for single-digit hex values
        }
        dataFile.print(rxBuf[i], HEX);
      }

      dataFile.println();

      // Flush data to SD card periodically for safety
      static unsigned long lastFlushTime = 0;
      if (millis() - lastFlushTime > 1000) {
        dataFile.flush();
        lastFlushTime = millis();
      }
    }
  }
}

This might be acceptable, but I could not refrain from tuning it further.
The next code, at setup tries to find the fastest frequency the SD card will accept.
Normal write cycle at about 8.51ms.
Here then CAN messages are put into a struct and written binary to a file.
Even though it is not proven here in this post, this has little practical effect on the write time.

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

#define SD_CD_PIN 20
#define SD_CS_PIN 21
#define CAN0_INT_PIN 9
#define CAN0_CS_PIN 10

#ifndef SD_SCK_MHZ
#define SD_SCK_MHZ(mhz) (1000000 * mhz)
#endif

uint32_t calculateCRC32(const uint8_t *data, size_t length) {
  uint32_t crc = 0xFFFFFFFF;
  for (size_t i = 0; i < length; i++) {
    crc ^= data[i];
    for (int j = 0; j < 8; j++) {
      if (crc & 1) {
        crc = (crc >> 1) ^ 0xEDB88320;  // CRC32 polynomial
      } else {
        crc >>= 1;
      }
    }
  }
  return ~crc;
}

struct CanMessage {
  uint64_t timestamp;  // Time of the message
  uint32_t rxId;       // CAN ID
  uint8_t length = 0;  // Length of CAN data (0-8 bytes)
  uint8_t rxBuffer[8]; // CAN data (payload)
  uint32_t crc;        // CRC value
};
CanMessage canMessage;
MCP_CAN CAN0(CAN0_CS_PIN);

bool CAN0_init_OK = false;
bool SD_init_OK = false;
File dataFile;

void setup() {
  Serial.begin(115200);
  while ((!Serial) && (millis() < 3000));  // Wait for Serial

  pinMode(CAN0_INT_PIN, INPUT);
  pinMode(SD_CD_PIN, INPUT_PULLUP);

  CAN0_init_OK = (CAN0.begin(MCP_ANY, CAN_500KBPS, MCP_10MHZ) == CAN_OK);
  Serial.println(CAN0_init_OK ? "MCP2515 Initialized Successfully!" : "Error Initializing MCP2515...");
  if (CAN0_init_OK) {
    CAN0.setMode(MCP_NORMAL);  // Set MCP2515 to normal mode
  }

  if (!digitalRead(SD_CD_PIN)) {
    uint8_t frequencies[] = {50, 25, 20, 16, 12, 10, 8, 4, 1};
    for (uint8_t i = 0; i < sizeof(frequencies) / sizeof(frequencies[0]); i++) {
      SD_init_OK = SD.begin(SD_SCK_MHZ(frequencies[i]), SD_CS_PIN);
      if (SD_init_OK) {
        Serial.print("SD initialization successful at ");
        Serial.print(frequencies[i]);
        Serial.println(" MHz.");
        break;
      } else {
        Serial.print("SD failed at ");
        Serial.print(frequencies[i]);
        Serial.println(" MHz. Trying lower frequency...");
      }
    }
  } else {
    Serial.println("SD card not detected (CD pin indicates no card). Skipping initialization.");
  }

  // Open the datalog file once for faster writes
  if (SD_init_OK && !digitalRead(SD_CD_PIN)) {
    String logName = "canlog.bin";
    dataFile = SD.open(logName, O_CREAT | O_WRITE | O_APPEND);
    if (!dataFile) {
      Serial.println("Error: Could not open '" + logName + "' for writing.");
      SD_init_OK = false;
    }
  }
}

void loop() {
  delay(1);

  // Check if a CAN message is available
  if (CAN0_init_OK && (CAN_MSGAVAIL == CAN0.checkReceive())) {
    canMessage.timestamp = millis();
    CAN0.readMsgBuf(&canMessage.rxId, &canMessage.length, canMessage.rxBuffer);
    canMessage.crc = calculateCRC32((const uint8_t *)&canMessage, sizeof(CanMessage) - sizeof(canMessage.crc));

    if (SD_init_OK && !digitalRead(SD_CD_PIN)) {
      dataFile.write((const uint8_t *)&canMessage, sizeof(canMessage));
      dataFile.flush();  // Ensure data is saved
    }
  }
}

I have briefly tested with SdFat library without any great immediate success. This might be subject for another reply or post. As I understand from my source list below, this library should be even faster than what I've made so far.

Source list increasing SD writing speed search:

Happy tinkering! :smiley: