Teensy + ADS129x ADC + SD card

Status
Not open for further replies.

tingo

Well-known member
I'm try to interface the three components in the title to make a data-logger capable of sampling physiological signals directly. To minimise the number of connections, the ADC and the SD card are to share the SPI bus. The problem I've encountered is that if I initialise and setup the files on the SD card first, then access the ADC and then write the data to the card I get an error during the sd.card()->writeData(pCache) stage (see snippets below). When I then plug the card into my computer and check file contents, it's full of garbage, i.e. the file is filled with ˇ characters (exactly 512 of these). I tested the ADC+Teensy and SD+Teensy (with sdfatlib) separately and they work as expected. I tried tracing the error and it seems like the card doesn't respond with the correct code after the data is sent, m_status in Sd2Card::writeData stays at 0xFF. I also checked the activity on the MOSI pin and I can see that something is being sent to the SD card.

From what I've found so far one cannot access SPI bus during multiple block write (http://forum.arduino.cc/index.php?topic=50010.0). My assumption is that what's meant is that when the data is actually being sent to the SD card one cannot access SPI bus, which is fine, but then another interpretation is that one cannot access SPI bus between sd.card()->writeStart() and sd.card()->writeStop() commands. So which one is correct? If the latter is correct is software SPI for ADC the only way forward?

Components used:
Teensy 3.1
ADS1294
Swissbit 1GB microSD card (http://www.mouser.com/ds/2/615/S-200u_data_sheet_SD-NxBN_Rev110-263222.pdf)

The connections between the components are as follows:

ADC connections to Teensy
ADC MOSI -> pin 11 Teensy
ADC MISO -> pin 12 Teensy
ADC SCK -> pin 13 Teensy
ADC SS -> pin 10 Teensy

ADC PWDN -> pin 2 Teensy
ADC RESET -> pin 3 Teensy
ADC START -> pin 4 Teensy
ADC DRDY -> pin 5 Teensy
ADC CLKSEL -> pin 6 Teensy

SD card connections to Teensy
SD MOSI -> pin 11 Teensy
SD MISO -> pin 12 Teensy
SD SCK -> pin 13 Teensy
SD SS -> pin 9 Teensy


The exact sequence of steps I'm following is below:

  1. Initialise standard SPI.
  2. Initialise ADC by first sending SDATAC command to stop continuous data output, then writing to the configuration registers and finally getting the ID of the ADC indicating number of channels.
  3. Initialise SD card, create contiguous file, get its range, erase, setup buffer and write start.
  4. Change SPI mode to MODE1, since SD card uses MODE0 while ADC uses MODE1.
  5. Repeat ADC initialisation.
  6. Send 512 byte block of dummy data to the card and write stop.

Basically if I do steps 3 and 6 one after another, then everything works as expected and I don't get any errors, but as soon as I try to access ADC in between those steps I get the write error.
I tried running this procedure on a setup without the ADC, i.e. just an SD card connected to Teensy and it still throws the same error (I had to modify the adcSetup() function to not read ID register at the end). So running the SDSetup(), then sending some data through the SPI (albeit to a non-existent, not connected module) and then finally writing dummy data to the SD card fails.


Here's the setup() function and all the relevant code snippets:

Code:
void setup() 
{
    using namespace ADS1298;
    
    // Setup I/O pins
    pinMode(IPIN_DRDY,      INPUT);
    pinMode(IPIN_CS,        OUTPUT);
    pinMode(chipSelect,     OUTPUT);
    pinMode(PIN_START,      OUTPUT);
    pinMode(PIN_CLKSEL,     OUTPUT);
    pinMode(IPIN_RESET,     OUTPUT);
    pinMode(IPIN_PWDN,      OUTPUT);

    // Set output pins to safe defaults
    digitalWrite(PIN_START,     LOW);
    digitalWrite(IPIN_CS,       HIGH);
    digitalWrite(chipSelect,    HIGH);
    
    //  Start serial port
    Serial.begin(115200);
    while (Serial.read() >= 0) {} //http://forum.arduino.cc/index.php?topic=134847.0
    
    // Start Arduino SPI to interface with ADC
    SPI.begin();
    SPI.setBitOrder(MSBFIRST);
    SPI.setClockDivider(SPI_CLOCK_DIV2); //http://forum.pjrc.com/threads/1156-Teensy-3-SPI-Basic-Clock-Questions
    SPI.setDataMode(SPI_MODE1);
    
    // Setup ADC
    uint8_t gMaxChan = adcSetup();
    
    // If ADC is not detected print "ADC Error" every 1 s to the serial terminal
    if (gMaxChan == 0) {
        while(1) {
            Serial.println("ADC Error");
            delay(1000);
        }
    }
    
    Serial.print("ADC detected has ");
    Serial.print(gMaxChan);
    Serial.print(" channels\n");
    
    // Setup SD card and print error # if setup fails
    SDSetup();
    Serial.print("error is ");
    Serial.print(error);
    Serial.print("\n");

    // Change SPI mode to MODE1 since that's the mode used by ADC
    SPI.setDataMode(SPI_MODE1);
    
    // Setup ADC
    gMaxChan = adcSetup();
    
    // If ADC is not detected print "ADC Error" every 1 s to the serial terminal
    if (gMaxChan == 0) {
        while(1) {
            Serial.println("ADC Error");
            delay(1000);
        }
    }
    
    Serial.print("ADC detected has ");
    Serial.print(gMaxChan);
    Serial.print(" channels\n");
    
    // Send dummy data to the SD card and print error # if it fails
    sendSDdata(true);
    Serial.print("error is ");
    Serial.print(error);
    Serial.print("\n");
}


Code for adcSetup() function which runs successfully:

Code:
uint8_t adcSetup()
{
    using namespace ADS1298;
    
    //  Power-up sequence for ADS129x
    digitalWrite(IPIN_PWDN, LOW);
    digitalWrite(PIN_CLKSEL, HIGH);
    digitalWrite(IPIN_PWDN, HIGH);
    digitalWrite(IPIN_RESET, HIGH);
    delay(1000);
    digitalWrite(IPIN_RESET, LOW);
    delayMicroseconds(1);
    digitalWrite(IPIN_RESET, HIGH);
    delayMicroseconds(9);
    adc_send_command(SDATAC);
    delayMicroseconds(5);
    
    // FOR RLD: Power up the internal reference
    adc_wreg(CONFIG3, RLDREF_INT | PD_RLD | PD_REFBUF | CONFIG3_const);
	// Only use channels IN1P and IN1N for the RLD Measurement
    adc_wreg(RLD_SENSP, 0x01);
	adc_wreg(RLD_SENSN, 0x01);
    
    // All GPIO set to output 0x0000: (floating CMOS inputs can flicker on and off, creating noise)
	adc_wreg(GPIO, 0x00);
    
    // Set ADC to work at high resolution 1 KS/s sampling rate
    adc_wreg(CONFIG1, HIGH_RES_1k_SPS);
    
    // Generate internal test signal
    adc_wreg(CONFIG2, INT_TEST);
    
    // Set the first channel as differential input with x12 gain
    adc_wreg(CH1SET, ELECTRODE_INPUT | GAIN_12X);
    //    adc_wreg(CH1SET, TEST_SIGNAL | GAIN_12X); //create square wave
    
    // Power down and short all the rest of the channels since they aren't used
	for (int i = 2; i <= 4; i++) {
        adc_wreg(CHnSET + i, PDn | SHORTED);
	}
    
    // Get ADC ID
    int IDval = adc_rreg(ID) ;
    switch (IDval & B00011111 ) { //5 least significant bits report channels
        case  B10000:
            return 4; //ADS1294
            break;
        case B10001:
            return 6; //ADS1296
            break;
        case B10010:
            return 8; //ADS1298
            break;
        case B11110:
            return 8; //ADS1299
            break;
        default:
            return 0;
    }
}


Code for SDsetup() function:

Code:
void SDSetup()
{
    if (!sd.begin(chipSelect,  SPI_FULL_SPEED)) {
        error = 1;
        sd.initErrorHalt();
    }

    // Delete possible existing file
    sd.remove("RAW.TXT");
    
    // Create a contiguous file
    if (!file.createContiguous(sd.vwd(), "RAW.TXT", CACHE_SIZE*BLOCK_COUNT)) error = 2;

    // Set the location of the file's blocks
    if (!file.contiguousRange(&bgnBlock, &endBlock)) error = 3;
    file.close();
    
//    memset(pCache, '-', CACHE_SIZE);

    // Tell card to setup for multiple block write with pre-erase
    if (!sd.card()->erase(bgnBlock, endBlock)) error = 4;
    
    pCache = (uint8_t*)sd.vol()->cacheClear();

    if (!sd.card()->writeStart(bgnBlock, BLOCK_COUNT)) error = 5;
}


Code for sendSDdata (bool dummy) function which throws error 6:

Code:
void sendSDdata(bool dummy)
{
    if (dummy) {
        // Populate cache with dummy data
        uint32_t adc_dat = 4276803;
        
        for (int iii = 1; iii < 511; iii += 6) {
            pCache[iii - 1]     =   adc_dat;
            pCache[iii]         =   adc_dat >> 8;
            pCache[iii + 1]     =   adc_dat >> 16;
            pCache[iii + 2]     =   'X';
            pCache[iii + 3]     =   '\r';
            pCache[iii + 4]     =   '\n';
        }
        
        for (int iii = 1; iii < 511; iii += 6) {
            Serial.print(pCache[iii - 1], DEC);
            Serial.print(",");
            Serial.print(pCache[iii], DEC);
            Serial.print(",");
            Serial.print(pCache[iii + 1], DEC);
            Serial.print(",");
            Serial.print(pCache[iii + 2], DEC);
            Serial.print(",");
            Serial.print(pCache[iii + 3], DEC);
            Serial.print(",");
            Serial.print(pCache[iii + 4], DEC);
            Serial.print("\n");
        }
    }
    
    // Send data to the SD card and measure latency
    uint32_t tw = micros();
    if (!sd.card()->writeData(pCache)) error = 6;
    tw = micros() - tw;
    
    delay(1000);
    if (!sd.card()->writeStop()) error = 7;
//    file.close();
    
    latency[block_count-1] = tw;
}

I don't fully understand the SD card operation and that's the most likely reason my setup doesn't work, so I'd really appreciate if someone could point out any problems with this setup and the sequence of steps I'm following.
 
Last edited:
I'm assuming you are using sd and not sdfat. sdfat could do it differently. I'm pretty fuzzy on this, so just trying to point you possibly in a useful direction.

I believe this is the inherent difficulty with sharing these types of resources (SPI, DMA, etc). Paul has been working on a more generalized solution to get things to play nice together.
You might find it useful to look at the code Paul did for the audio adapter, cause he basically had to do this. I hope I have that right.

Check out this thread: http://forum.pjrc.com/threads/25582-SPI-sharing-between-interrupts-and-main-program

I've used the interval timer for sampling from the t3 ADC but not an external ADC like yours, using block writes to the uSD card.

Also, I have one of these ads129x chips. Was there a board that you found easily enough to solder it onto, or a certain technique?
 
I'm assuming you are using sd and not sdfat. sdfat could do it differently. I'm pretty fuzzy on this, so just trying to point you possibly in a useful direction.

I'm using the sdfatlib from here: https://code.google.com/p/sdfatlib/downloads/list. Based on what I've read it provides the fastest access to the card. Based on this post (http://forum.pjrc.com/threads/25582...and-main-program?p=45745&viewfull=1#post45745) Paul is considering to replace the old SD code with the latest from Bill Greiman (author of sdfatlib) when Arduino jumps to 1.5.x.

I believe this is the inherent difficulty with sharing these types of resources (SPI, DMA, etc). Paul has been working on a more generalized solution to get things to play nice together.
You might find it useful to look at the code Paul did for the audio adapter, cause he basically had to do this. I hope I have that right.

Check out this thread: http://forum.pjrc.com/threads/25582-SPI-sharing-between-interrupts-and-main-program

I've used the interval timer for sampling from the t3 ADC but not an external ADC like yours, using block writes to the uSD card.

Thanks for the link to the thread, yes it's definitely relevant for the overall project, because I was planning to use interrupts to get data form the ADC and that is bound to create contention for the SPI bus between the ADC and the SD card.

On the other hand what I'm doing right now is purely sequential access to the SPI bus. As far as I understand each one of these functions accessing the SD card are logically complete:

Code:
if (!sd.begin(chipSelect, SPI_SIXTEENTH_SPEED)) {
        error = 1;
        sd.initErrorHalt();
    }

    // Delete possible existing file
    sd.remove("RAW.TXT");
    
    // Create a contiguous file
    if (!file.createContiguous(sd.vwd(), "RAW.TXT", CACHE_SIZE*BLOCK_COUNT)) error = 2;

    // Set the location of the file's blocks
    if (!file.contiguousRange(&bgnBlock, &endBlock)) error = 3;
    file.close();
    
//    memset(pCache, '-', CACHE_SIZE);

    // Tell card to setup for multiple block write with pre-erase
    if (!sd.card()->erase(bgnBlock, endBlock)) error = 4;
    
    pCache = (uint8_t*)sd.vol()->cacheClear();

    if (!sd.card()->writeStart(bgnBlock, BLOCK_COUNT)) error = 5;

    if (!sd.card()->writeData(pCache)) error = 6;

    if (!sd.card()->writeStop()) error = 7;

so accessing the SPI bus between them (to communicate with another device) shouldn't create any contention issues, or am I wrong?

EDIT: I have tried the SDFat library fix suggested by Paul here: http://forum.pjrc.com/threads/25582...and-main-program?p=47263&viewfull=1#post47263 but the error is still there.

Also, I have one of these ads129x chips. Was there a board that you found easily enough to solder it onto, or a certain technique?

I got this LQFP-64 to DIP adapter from Proto Advantage: http://www.proto-advantage.com/store/product_info.php?products_id=2200113.
You can just use the "drag" soldering techniques to solder ADC to it, as described here: https://www.youtube.com/watch?v=wUyetZ5RtPs
Then I put everything on the breadboard, here's the pic (sorry that it's upside down, but I can't figure out how to rotate it since both my mac and windows inside parallels see it the correct way round:))):
photo.JPG.

Obviously this setup is not ideal in terms of noise, but the idea is to make the different components work together and then create a PCB (with ADC+passives and SD card socket on it) which would attach to Teensy. Even with the breadboard I was able to obtain fairly clean ECG and EEG signals.
 
Last edited:
Ok, thanks and good to know things are looking better.

I'm curious to hear how the ADS1294 works out. Occasionally I hear requests for a library or sample code for this chip or the 8 channel version. If you get it working, I hope you'll consider sharing the code. I'm sure other people will find it really useful.
 
Got extremely busy with another project, thus couldn't dedicate much time to this, but here's the very preliminary ADS129x library if anyone's interested.

Hopefully I'll get back to it in the next few weeks.

View attachment ADC129xADC.zip
 
Hey that's awesome and thanks. I have no plans to use that ADC for now but it's great of you to make the code available for the day that any of us in the community might need it!
 
hello

i am facing an error , i am using ads1298 with teensy3.2 it is detecting id and channels but when i am using matlab to decode this through GUI everything thing looking good but there is an error occurring about timerfcn please help me out...:(

error is

Channels available: 8 Device ID register value: 146
ads Data: Recording 5 channels at 2000 Hz attached to port "COM3"
Error while evaluating TimerFcn for timer 'timer-3'

Double inputs must have integer values in the range of ASSUMEDTYPE.
 
hello

i am facing an error , i am using ads1298 with teensy3.2 it is detecting id and channels but when i am using matlab to decode this through GUI everything thing looking good but there is an error occurring about timerfcn please help me out...:(

error is

Channels available: 8 Device ID register value: 146
ads Data: Recording 5 channels at 2000 Hz attached to port "COM3"
Error while evaluating TimerFcn for timer 'timer-3'

Double inputs must have integer values in the range of ASSUMEDTYPE.

It's hard to tell what the problem is without further details... Which GUI are you using? What firmware do you have running on Teensy? Is is matlab that's decoding the sample values or the Teensy?
 
Status
Not open for further replies.
Back
Top