Reading txt file from SD card and sending over I2C

Hello!

In my project I am using the microSD card on the arduino ethernet shield with a Mega. What I want to do is transfer the data on the SD card via I2C to another mega (the master). I can write to and read from the SD card using the SD library, and I can send small chunks of analog data (as ints) over I2C, but I can't figure out how to send the data on the SD card over I2C. I know that this should be a fairly easy combination of my other two sketches, but I can't get it to work. I have little experience programming (none using C++), and I suspect the problem is coming from using data types improperly or a discrepancy in the speeds at which the arduino can read the SD card and write to the I2C bus. The data on the SD card is stored in the following format:
timestamp: 543; Voltage read: 2.61
timestamp: 1055; Voltage read: 1.89
timestamp: 1566; Voltage read: 0.19
...
The timestamp will actually be coming from a real time clock, but I thought I should solve this problem first before I add anything else. My first attempt was this code:
Slave:

#include <SD.h>
#include <Wire.h>

File file;

void setup()
{
Serial.begin(57600);
pinMode (53,OUTPUT);
if (!SD.begin(4)){
Serial.println("SD not initialized");
}
else {
Serial.println("SD initialized");
}
Wire.begin(2); // join i2c bus with address #2
Wire.onRequest(requestEvent); // register event
}

void loop()
{
delay(100);
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent()
{
file = SD.open("logdata.txt");
if(file) {
Serial.println("File opened");
while (file.available()>0) {
Wire.write(file.read());
}
Serial.println("Data Sent");
}
}

Master:

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

void setup ()
{
//Open Serial communication
Serial.begin(57600);
//Open Wire Communication on I2C and assign the slave an address
Wire.begin(1);
}

void loop ()
{
//Send a request for info to device 2
Wire.requestFrom(2,10);
Serial.println("Data Requested");
//Receive data while it's available
while(Wire.available()) {
//Receive firt byte as c
char c = Wire.read();
//Print c
Serial.print(c);
//move to next byte
}
delay (500); //wait half a second before sending next request
}

I got "Data Requested ÿÿÿÿÿÿÿÿ" on the master Serial port.
I think the error is in the slave in the "Wire.write(file.read());" line. That was just wishful thinking on my part. So next I tried populating a string with characters as the SD card was read and sending that over I2C, but got the same result. (I can print the string to the slave serial port and get the data off of the card).

Sorry for the long post.

Thank you for your help

I always have trouble with the SD card if I do not disable the w5100 SPI. My mega won't even initialize the card. I get a fail.

void setup()
{
  Serial.begin(57600);
  // change this to pin 10
  pinMode (10,OUTPUT);
  // add this
  digitalWrite(10,HIGH);

  if (!SD.begin(4)){
    Serial.println("SD not initialized");
  }
  else {
    Serial.println("SD initialized");
  } 

  // rest of your setup
}

Thanks for the input, but disabling the w5100 didn't solve the problem. I've now replaced the ethernet shield with the sparkfun microSD Transflash breakout board ( SparkFun microSD Transflash Breakout - BOB-00544 - SparkFun Electronics ) and the problem persists. I have the breakout board wired to the SPI pins, 3.3V power, ground, and am using digital pin 22 as my chip select pin. Just like on the ethernet shield, I can read data from the card and print it to a serial port, but I can't write across the I2C bus. I've stuck with the method of populating an array with data from the card, then sending the array across I2C. This time I printed that array to the serial port for debugging and I'm getting a lot nonsense intermixed with the data on the card. Here is my code.

#include <SD.h>
#include <Wire.h>

char* c;
int i = 0;
File file;

void setup()
{
  Serial.begin(57600);    //Begin serial communication
  pinMode (53,OUTPUT);    //Set pin 53 as output for SD lib
  if (!SD.begin(22)){     //Check that SD card is initialized
    Serial.println("SD not initialized");
  }
  else {
    Serial.println("SD initialized");
  }
  file = SD.open("logdata.txt");  //Open file
  if(file) {
    Serial.println("File opened");  
    while (file.available()>0) {
      c[i]=file.read();
      i=i+1;
    }
    Serial.print(c);
  } 
  Wire.begin(2);                // join i2c bus with address #2
  Wire.onRequest(requestEvent); // register event
}

void loop()
{
  delay(100);
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent()
{
  Wire.beginTransmission (1);
  Wire.write(c);
  Wire.endTransmission(1);
}

You should declare 'c' as a character array:

// instead of this
char* c;
// use this
char c[32];

Insure the c array is large enough to hold the entire file.

Also, if you are planning on using that as a zero terminated string, it would be a good idea to insure it is.

   while (file.available()>0) {
      c[i]=file.read();
      i=i+1;
      c[i] = 0;
    }

Thanks Tim! Now I'm reading from the SD card properly, but I still can't get anything sent across I2C.I have two megas hooked up on the SDA and SCL pins (and of course there's a ground wire between them as well). Here's the new slave code:

#include <SD.h>
#include <Wire.h>

char c[100];
int i = 0;
File file;

void setup()
{
  Serial.begin(57600);    //Begin serial communication
  pinMode (53,OUTPUT);    //Set pin 53 as output for SD lib
  if (!SD.begin(22)){     //Check that SD card is initialized
    Serial.println("SD not initialized");
  }
  else {
    Serial.println("SD initialized");
  }
  file = SD.open("logdata.txt");  //Open file
  
  if(file) {
    Serial.println("File opened");  
    while (file.available()>0) {
      c[i]=file.read();
      i=i+1;
      c[i]=0;
    }
    Serial.print(c);
  } 
  Wire.begin(2);                // join i2c bus with address #2
  Wire.onRequest(requestEvent); // register event
}

void loop()
{
  delay(100);
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent()
{
  Serial.print("Sending data...");
  Wire.beginTransmission(1);
  Wire.write(c);
  Wire.endTransmission(1);
  Serial.println("...data sent");
}

The master is just the Wire Master Reader example, but giving the master address 1 and changing the requested number of bytes to 100.

// Wire Master Reader
// by Nicholas Zambetti <http://www.zambetti.com>

// Demonstrates use of the Wire library
// Reads data from an I2C/TWI slave device
// Refer to the "Wire Slave Sender" example for use with this

// Created 29 March 2006

// This example code is in the public domain.


#include <Wire.h>

void setup()
{
  Wire.begin(1);        // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
}

void loop()
{
  Wire.requestFrom(2, 100);    // request 6 bytes from slave device #2

  while(Wire.available())    // slave may send less than requested
  { 
    char c = Wire.read(); // receive a byte as character
    Serial.print(c);         // print the character
  }

  delay(500);
}

I don't get anything printing out from the master, and the slave never prints out "Sending data..." so I don't think the event is registering at the slave.

Thanks again!

I'm not real strong on the TWI stuff, but isn't the I2C master set up with no slave address?

// master
Wire.begin();
// slave 1
Wire.begin(1);

From what I've read and tried, it can be set up with no slave address but doesn't have to be. I did try it with no address for the master, same problem.... If I change the slave code from:

void requestEvent()
{
  Serial.print("Sending data...");
  Wire.beginTransmission(1);
  Wire.write(c);
  Wire.endTransmission(1);
  Serial.println("...data sent");
}

to just:

void requestEvent()
{
  Serial.print("Sending data...");
  Wire.write(c);
  Serial.println("...data sent");
}

I can get some of the file (17 individual characters) but it quickly stops and I get just the ÿÿÿÿÿÿÿÿ stuff again on the Master. Changing the number of bytes I'm trying to receive to anything above 17 doesn't change the serial output of the master. Below 17 and I just get that number of characters.

Thanks again for your help.

I'm facing the same problem. I logging the data of my analog sensor and wanted to send this data from the sd card to my Esp8266 module through I2C. Did you find any solution for that?

ozil_11:
I'm facing the same problem. I logging the data of my analog sensor and wanted to send this data from the sd card to my Esp8266 module through I2C. Did you find any solution for that?

The I2C slave mode on the Arduinos send/receive one block of data.

The slave does not know how big the requested block is, so, The block size must be known before onRequest() is executed.

If you have don't have fixed length data you are going to have to write a 'protocol' that encapsulates this random length data.

Let us say that every onRequest() returns 30 bytes. And I want to be able to send 200 bytes from the Slave to the Master;

on the slave:

#define BUFFERLEN 200
volatile static byte bufPos=0; // next byte to send to master
volatile statc byte buffer[BUFFERLEN];
volatile static byte bufLen=0; // 

void request(void){
byte len = 29; // maximum number of bytes to send to Master, must leave room for count of byte
byte tempBuffer[30];

if(len > (bufLen-bufPos)) len = (bufLen-bufPos); // can't send more than exist in buffer


tempBuffer[0] = len; // send length of the valid data in this block as first byte in block
if(bufPos < BUFFERLEN)
  memmove(&tempBuffer[1],&buffer[bufPos],len); // copy up to the next 29 bytes into a transmit buffer

bufPos = bufPos + len;  // when last partial buffer is send, bufPos will equal bufLen so len ==0, (end of data)

Wire.write(tempBuffer,len+1); // data + count
} 

void receive(int len){
// assume only 1 byte is ever send, use that byte to adjust the bufPos value
bufPos = Wire.read();
bufPos = bufPos % BUFFERLEN; // make sure bufPos is 0..(BUFFERLEN-1)

}

void setup(){
Wire.begin(0x15); // set slave address a 0x15
Wire.onRequest(request);
Wire.onReceive(receive);
strcpy((char*)buffer,"This is a test of the emergency broadcasting system\n");
bufLen = strlen((char*)buffer);
}

void loop(){}
//***************
//master

void readblock(){
byte bkLen=0;
#define BUFFLEN 50
char buffer[BUFFLEN];
// set buffer address in slave to 0, start at beginning of buffer

Wire.beginTransmission(0x15);
Wire.write(0);
Wire.endTransmission();

// read from slave until slave sends a '0' length block.

do{
  buffer[0] = 0; // init to zero byte block length 
  bkLen = 0;
  Wire.requestFrom(0x15,30); // request a 30 byte block of data

  while (Wire.available()&&(bkLen<BUFFLEN)){ //data avail, and room in buffer
    buffer[bklen++] = Wire.read();
    }
  buffer[bkLen] = '\0'; // terminate the string 
  Serial.print((char*)&buffer[1]); // print the data to the serial port

  }while(buffer[0] != 0);
}

void setup(){
Serial.begin(115200);
Wire.begin();
readblock();
}

void loop(){}

Chuck.