ADC + SD Card

Hello all,

I am trying to use an Analog to digital converter to read a voltage, then save that voltage to an SD card. Should be pretty simple right? Both components are connected to my Arduino Fio on the same SPI bus. I know my wiring is okay and my code will work for a while. But eventually, something goes wrong and I begin receiving only all 0's or all 1's from my ADC when I should be getting a different voltage reading. If I change the voltage being sent to my ADC, I still receive the same incorrect values of all 0's or all 1's.

My Arduino Sketch:

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

//ADC on pin 5
Adc adc(5);

void setup()
{
  Serial.begin(57600);
  
  delay(1000);
  
  pinMode(5,OUTPUT); //ADC Chip select
  pinMode(7,OUTPUT); //SD Chip select
  pinMode(10,OUTPUT);  
 
  //initialize the ADC to start SPI and set pins
  adc.initialize();
        
  //see if the card is present and can be initialized:
  if (!SD.begin(7)) {
    Serial.println("Card failed, or not present");
  }
  digitalWrite(7,HIGH); //delelect SD card
  
  delay(1000);
}

void loop()
{ 
    delay(1000);             
    adc.getVoltage();
}

My Library header file:

  #include "ADC.h"

int ADC_pin;

Adc::Adc(int pin)
{
    _pin = pin;

}

void Adc::initialize()
{
    //setup SPI
    SPI.begin();
       
    SPI.setBitOrder(MSBFIRST);
    
    SPI.setDataMode(SPI_MODE2);//KEEP!!
    
    //setup slave select pin
    pinMode(_pin, OUTPUT);

    SPI.setClockDivider(SPI_CLOCK_DIV128);
    delay(5);
    
    configFilterRegisters();
    configModeRegister(); //starts calibration
    
    Serial.println("Initialized");
    
    
}

double Adc::getVoltage()
{
    
    while(digitalRead(RDY)); //once RDY pin is pulled low
        
    digitalWrite(_pin, LOW);//pull CS low
    
    selectADC();

    delay(100); // a delay here could help
    
    SPI.transfer(0x5C); // Channel 1 Data Register
    
    byte inputBytes[4];
    inputBytes[3] = 0x00; //MSB all 0's
    inputBytes[2] = SPI.transfer(0x00);
    inputBytes[1] = SPI.transfer(0x00);
    inputBytes[0] = SPI.transfer(0x00);
    
    digitalWrite(_pin, HIGH);
            
    String dataString = "";
    
    for (int i = 3; i >= 0; i--) {
        int specificByte = inputBytes[i];
        dataString += String(specificByte);
        dataString += ",";
    }
    dataString += "end";
        
    Serial.println(dataString);
    

    writeToSD(dataString); //change in code
    
    return 0;
}

void Adc::configFilterRegisters() {			//used
    //select chip
    digitalWrite(_pin, LOW);
    
    byte to_Comm_Register_1 = mergeBytes(WRITE, FILTER_HI_REGISTER, _channel);
    byte to_FILTER_HI_REG   = mergeBytes(_polarity, _word_Length, _hi_Filter_Selection);
    byte to_Comm_Register_2 = mergeBytes(WRITE, FILTER_LOW_REGISTER, _channel);
    byte to_FILTER_LOW_REG  = mergeBytes(_low_Filter_Selection, 0x00, 0x00);

    //Setup a write to Filter High Register
    SPI.transfer(to_Comm_Register_1);
        
    //Write to Filter High Register
    //select required values
    //select bipolar, 24bit (For Filter HI Register), does something with filter cut-off frequency
    SPI.transfer(to_FILTER_HI_REG); // 0100 1111
    
    //deselect chip
    digitalWrite(_pin, HIGH);
    
    //select chip
    digitalWrite(_pin, LOW);
            
    //Setup a write to Filter Low Register
    //SPI.transfer(0x37); //and select 'test mode' channel
    SPI.transfer(to_Comm_Register_2);//Or select channel  
            
    //Write to filter low register
    //Select some other magical cut-off frequency stuff
    SPI.transfer(to_FILTER_LOW_REG); 
    
    //deselect chip
    digitalWrite(_pin, HIGH);
}

void Adc::configModeRegister() {   //used
    //select chip
    digitalWrite(_pin, LOW);
    
    byte to_Comm_Register = mergeBytes(WRITE, MODE_REGISTER, _channel);
    byte to_Mode_Register = mergeBytes(_calibration, _gain, 0x00);
    
    //Setup a write to Mode Register
    SPI.transfer(to_Comm_Register);//or select channel 1
        
    //Write to Mode Register
    SPI.transfer(to_Mode_Register); // 
        
    //deselect chip
    digitalWrite(_pin, HIGH);
}

byte Adc::mergeBytes(byte first, byte second, byte third) {
    byte result = BUFFER | first;
    result = result | second;
    result = result | third;
    return result;
} 

void Adc::writeToSD(String dataString) 
{
    selectSD();
    
        File dataFile = SD.open("datalog.txt", FILE_WRITE);
        
        if (dataFile) {
            dataFile.println(dataString);
            dataFile.close();
            Serial.println(dataString);      //debug
        }
        else {
            Serial.println("Error opening datalog.txt");
        }
   }


void Adc::selectADC()  //used
{
    SPI.begin();
    SPI.setBitOrder(MSBFIRST);
    SPI.setDataMode(SPI_MODE2);
    SPI.setClockDivider(SPI_CLOCK_DIV128);
}

void Adc::selectSD() {
    if(!SD.begin(7)) { //begin SD SPI
    }
    else {
        Serial.println("SD does not work");
    }
    pinMode(10, OUTPUT);//required by library
}

My library cpp file

#ifndef ADC_h
#define ADC_h

#include "Arduino.h"
#include "SPI.h"
#include "SD.h"

//digital IO pins
#define RDY 9  

 //Comm register
#define BUFFER 0x00
#define COMM_REGISTER 0x00 //0000 0000
#define MODE_REGISTER 0x10 //0001 0000
#define FILTER_HI_REGISTER 0x20 //0010 0000
#define FILTER_LOW_REGISTER 0x30 //
#define TEST_REGISTER 0x40
#define DATA_REGISTER 0x50
#define ZERO_SCALE_CALIB_REGISTER 0x60
#define FULL_SCALE_CALIB_REGISTER 0x70
#define READ 0x08 // 0000 1000
#define WRITE 0x00 // 0000 0000
//_channel specs
#define CHANNEL_1_6 0x00
#define CHANNEL_2_6 0x01
#define CHANNEL_3_6 0x02
#define CHANNEL_4_6 0x03
#define CHANNEL_1_2 0x04
#define CHANNEL_3_4 0x05
#define CHANNEL_5_6 0x06
#define CHANNEL_6_6 0x07 //test mode

//Mode Register
#define NORMAL_MODE 0x00
#define SELF_CALIB 0x20            //0010
#define ZERO_SCALE_SYS_CALIB 0x40  //0100
#define FULL_SCALE_SYS_CALIB 0x60  //0110
#define SYSTEM_OFFSET_CALIB 0x80   //1000
#define BACKGROUND_CALIB 0xA0      //1010
#define ZERO_SCALE_SELF_CALIB 0xC0 //1100
#define FULL_SCALE_SELF_CALIB 0xE0 //1110
#define BURNOUT_CURRENT_OFF 0x00
#define BURNOUT_CURRENT_ON 0x02 //default is off
#define FILTER_SYNC_OFF 0x00
#define FILTER_SYNC_ON 0x01 //default is off
//_gain
#define GAIN_1 0x00
#define GAIN_2 0x0    //0000 0100
#define GAIN_4 0x08   //0000 1000
#define GAIN_8 0x0C   //0000 1100
#define GAIN_16 0x10  //0001 0000
#define GAIN_32 0x14  //0001 0100
#define GAIN_64 0x18  //0001 1000
#define GAIN_128 0x1C //0001 1100

//Filter high Register
//_polarity 
#define BIPOLAR 0x00
#define UNIPOLAR 0x80
//_word_Length
#define WORD_LENGTH_16 0x00
#define WORD_LENGTH_24 0x40
#define CURRENT_BOOST_OFF 0x00 //for gains 1 - 4
#define CURRENT_BOOST_ON 0x20  //for gains 8 - 128
#define MASTER_CLK_DISABLE 0x10 //which version do I have??
//_hi_Filter_Selection 
#define DEFAULT_HI_FILTER 0x0F 

//Filter low register
//_low_Filter_Selection
#define DEFAULT_LOW_FILTER 0xA0

//SRAM parameters
#define SRAM_BUFFER_SIZE 32768

class Adc
{
  public:
    Adc(int pin);
    
    //start SPI, enable pins, etc.
    void initialize();
    
	boolean reset();
    void calibrate();
    void calibrate(byte);
    
	byte getID();
	byte getStatus();
	word getMode();
	word getConfig();
    
    double getGain();
    double getAVdd();
    
    //use in application
    void selectADC();
    void selectSD();
    double getVoltage(); //conversion takes ~50ms
    double getVoltageFast(); //conversion takes ~1.2 ms
    unsigned long getVoltageFastLong();
    
    //transfer values to the test register to test connection to the ADC
	void writeTestRegister(); // switched from private
    
	//transfer values from the test register to test connection to the ADC
	void readTestRegister(); // switched from private
    
//    bool ADC_to_SRAM();  //writes a single ADC value to SRAM in sequential mode
                        // returns TRUE when SRAM full
    
//    void SRAM_to_Serial(); //prints all SRAM values to Serial port (radio)
    
    //used for debugging
    unsigned long getVoltage(byte &MSB, byte &SB, byte &LSB);
    
    //use to continuosly sample
    void readVoltages(double *volts, const unsigned int SIZE);
		
    void setFreq(byte freq);
	void setChannel(byte channel);
    void setConversion(byte conversion);
    void setGain(byte gain); 
    void setPolarity(byte polarity);
    
    void configFilterRegisters();
    void configModeRegister();
    void readFilterRegister();
    byte mergeBytes(byte first, byte second, byte third);
    void convertToVoltage(byte inputBytes[4]);
    //String convertDoubleToString(double sample);
    void writeToSD(String dataString);



  private:
    int _pin;
	byte _channel;
	byte _gain;
	byte _polarity;
	byte _freq;
    byte _conversion;
    byte _calibration;
    byte _word_Length;
    byte _spi_mode;
    byte _spi_clock;
    byte _hi_Filter_Selection;
    byte _low_Filter_Selection;
    
    
    //call function to change mode register:
    // used to change the operating mode (single vs. continuous) and sampling rate
    void writeModeRegister();
    
    // change configuration register:
    // used to change unipolar vs. bipoloar, select gain, and change channel
    void writeConfigRegister();
    
    //used to convert ADC code to voltage (given channel's gain).
    double convertCodeToVoltage(unsigned long);
    
    unsigned int SRAM_ptr;
};
#endif

May the force be with you.

Pretty hard to understand, but I think the culprit might hide here:

String dataString = "";
    
    for (int i = 3; i >= 0; i--) {
        int specificByte = inputBytes[i];
        dataString += String(specificByte);
        dataString += ",";
    }
    dataString += "end";
        
    Serial.println(dataString);
    

    writeToSD(dataString); //change in code

A bug in the String class is often mentioned as the cause of out-of-ram errors in long running sketches.
First thing I'd try is substitute String with a char array.

thanks for the reply, I am not sure how to replace the string with a char array. Unless I go from my byte to the string then to a char array.

If I understand what you're saying, problems arrise when I send a string over the SPI interface to my SD card?

I tried changing my code to this (see below) to see if that was the problem, but I am still getting the same problem :frowning:

    String dataString = "";
    const char dummy = 'D';
    
    for (int i = 3; i >= 0; i--) {
        int specificByte = inputBytes[i];
        dataString += String(specificByte);
        dataString += ",";
    }
    
    Serial.println(dataString);
    
    writeToSD(dummy);

And in my writeToSD function:

void Adc::writeToSD(char dummy) ///SD
{    
    delay(100);

    selectSD();
    
    delay(100);
    
    File dataFile = SD.open("datalog.txt", FILE_WRITE);
    if (dataFile) {
        dataFile.println(dummy);
        dataFile.close();
        delay(100);
    }
    else {
        Serial.println("Error opening datalog.txt");
    }
    //state = SLEEP;
    
    delay(100);
    
    digitalWrite(7, HIGH);

}

Does that address the potential string problem?

Just to make sure, I got rid of any uses of string in my code. and I am still getting the same problem.

I don't know. Can you post your String-less code ?

Heres my new get voltage function

double Adc::getVoltage()
{
    
    while(digitalRead(RDY)); //once RDY pin is pulled low
        
    digitalWrite(_pin, LOW);//pull CS low
    
    selectADC();

    delay(100); // a dealy here could help
    
    SPI.transfer(0x5C); // Channel 1 Data Register
    //(0101 1100)
    
    byte inputBytes[4];
    inputBytes[3] = 0x00; //MSB all 0's
    inputBytes[2] = SPI.transfer(0x00);
    inputBytes[1] = SPI.transfer(0x00);
    inputBytes[0] = SPI.transfer(0x00);
    
    digitalWrite(_pin, HIGH);
    
    convertToVoltage(inputBytes);
    
    const char dummy = 'D';
    
    writeToSD(dummy);
    
    return 0;
}

and the convert to voltage function i'm using:

void Adc::convertToVoltage(byte inputBytes[4]) {
    double MSB = inputBytes[2];
    double SB = inputBytes[1];
    double LSB = inputBytes[0];
    
    double result = (MSB * 256 * 256);
    result = result + (SB * 256);
    result = result + LSB ;
    result = (result * 6.6 / 256 / 256 / 256) - 3.3;
    
    Serial.println("result is");
    Serial.print(result);
    Serial.print(" volts");
    
    }