SPI bit-banging - how to read SPI device datasheet?

'ello

My first post here although I have used the forum extensively for some time. I have used I2C successfully but only used SPI for writing to an SD card.

I am seeking to improve a rocket motor test stand that uses a load cell to measure thrust. Version 1 of the stand worked OK but the on-board ADC on my Nano v3.0 'jumps' values if I set any of the digital pins to HIGH/LOW (e.g. to fire the motor), so I got an external 12-bit ADC (ADS7816). Also for Version 1, I wrote some VBA (old skool me) to read the data from the serial port directly into Excel - this is OK but Excel is not very happy with this and crashes often, losing my data... so I got the Adafruit SD card module to save the ADC values (once the motor is fired there is no opportunity for a re-take...).

I am trying to 'bit-bang' (I think that's what the kids call it...) to get data out of the ADC - cycle the clock and read each bit. I have pored over the datasheet, and thought I had a eureka moment when I found this post and read pages 9 and 10 on the datasheet:

I used a modified version of the code I found in the above post. However, even with no power to the ADC I am still getting apparently random readings, as in each digitalRead returns a low or high without any apparent pattern. For example I expected to see a null bit after one of the clock cycles but it returns either low or high (I put some serial prints after each clock cycle to see what was happening).

Here's the code, I have followed what I think it says in the datasheet:

  // LoadCell values
  int LoadCell_time = 0;
  int LoadCell_value = 0;
  int ADC_value = 0;
  
  // SPI pins for ADC
  // note: not using the library, no MOSI pin
  int ADC_CLK = 9;      // DCLOCK
  int ADC_MISO = 8;     // DOUT
  int ADC_CS = 7;       // CS/SHDN

void setup() {                
  
  // configure ADC SPI pins
  pinMode(ADC_CS, OUTPUT);
  pinMode(ADC_CLK, OUTPUT);
  pinMode(ADC_MISO, INPUT);
  digitalWrite(ADC_CS, HIGH);    // unselected
  digitalWrite(ADC_CLK, LOW);    // unselected
}

void loop() {
  GetLoadCell();    // get data
  PrintSerial();    // print to serial
  delay(1000);
}

void GetLoadCell() {
    
    // record the timestamp & clear value variable
    LoadCell_time = millis();
    LoadCell_value = 0;
    ADC_value = 0;
    
    // ChipSelect to LOW, this calls the ADC to take a sample
    digitalWrite(ADC_CS,LOW);
    
    // the ADC will take 1.5 to 2 clock cycles to get the data
    // cycle the clock twice
    digitalWrite(ADC_CLK,HIGH);
    digitalWrite(ADC_CLK,LOW);
    digitalWrite(ADC_CLK,HIGH);
    digitalWrite(ADC_CLK,LOW);

    // after second falling DLCLOCK edge, DOUT will output a NULL (LOW) bit
    // cycle clock again to ignore this bit
    digitalWrite(ADC_CLK,HIGH);
    digitalWrite(ADC_CLK,LOW);
    
    // the next 12 bits are the reading from the ADC, Most Significant Bit (MSB) first
    // read one bit, cycle the clock, then read the next bit
    for (int i=11; i>=0; i--){
      // read the value
      ADC_value+=digitalRead(ADC_MISO)<<i;
      //cycle clock
      digitalWrite(ADC_CLK,HIGH);
      digitalWrite(ADC_CLK,LOW);
    }
    
    // convert to decimal
    LoadCell_value = int(ADC_value);
    
    // turn off the ADC
    digitalWrite(ADC_CS,HIGH);
    
void PrintSerial() {
    Serial.println(strValues);
}

I am not using the normal SPI pins because they are being used by the SD card reader. The SD card reader is very slow at 20ms per record (each record ~15 bits), I have read up on this and found some great options to speed this up e.g. write 512bits at a time. However I am struggling to solve all my problems, so am trying to keep the ADC and SD card completely separate - this is what led me to the bit-banging approach with the ADC...

I have checked and re-checked the wiring and also the voltage going into the ADC with a multimeter:

  • Vcc = Vref = 4.7v (from the arduino)
  • voltage from the load cell at rest (it's unbalanced but that's OK) is 600mV meaning I should see an ADC value of around (4096 * 0.6/4.7 =) ~ 520

Other things:

  • the reading I am getting are random full scale, i.e. from 0 to 4096 with no apparent sequence - however this is to be expected if I am not reading the bits in the correct order
  • the 'voltage to be measured' going into the ADC is not connected to Arduino, i.e. the load cell circuit (with opamp) has a separate power supply

Do I get any points for trying?

Cheers

B

I am not using the normal SPI pins because they are being used by the SD card reader.

Use SCK, MISO, MOSI with your ADC anyway, and select another pin for the ADC chip select.
Processor can only access one device or the one time, so there will not be a conflict.

12-bit ADC has 4.7/4095 = 1.14mV/bit resolution, so 0.6V will return ~ 522 for a reading, so your calculations are good. (>=4.7V returns 0x0FFF = 4095)

With SPI the reading would be pretty simple:

digitalWrite (ADC_CS, LOW);
upperByte = SPI.transfer(0); // x-x-x-D11-D10-D9-D8-D7
lowerByte = SPI.transfer(0); //  D6-D5-D4-D3-D2-D1-D0-D0
digitalWrite (ADC_CS, HIGH);
int result = 0x0FFF &( (upperByte<<5) | (lowerByte>>1)); // this combines & put bits in the right places - may need a little tweaking still

Hi CrossRoads - thanks for the help.

The top part of your code made perfect sense, I could see how it fitted with the data sheet.

I had no idea what the bottom half was about, but after an hour or so reading the reference section a little light came on (I have learned not to trust this little light which has proved over-optimistic in the past...)

But, I think I get it, bitwise! I couldn't work out the significance of the 4095 until I read the tutorial about masking. I did have to do some drawing on a piece of paper, but I think that the bitshift of the upperbyte in your code ("upperByte<<5") should be shifting 7 places and not 5.

Am I bitwise or a bit of an idiot? (the most significant bit)

B

Could be on the shifting, I don't always get it right on the first try. Sometimes you need to run some data thru & see what comes out, then tweak it a little.

Ello again

I finally managed to sort this, using word() and bitshifting the combined bytes.

Here's the section of the code I used:

    digitalWrite (ADC_CS, LOW);
    byte upperByte = SPI.transfer(0); // x-x-x-D11-D10-D9-D8-D7
    byte lowerByte = SPI.transfer(0); //  D6-D5-D4-D3-D2-D1-D0-x(D0)
    digitalWrite (ADC_CS, HIGH);
    word allBits = word(upperByte,lowerByte);
    allBits = allBits >> 1;
    LoadCell_value = int(0x0FFF & allBits); // 0x0FFF = 4095 or all 1's - this is a mask

I found bitWrite() very helpful in trying to work out what was going wrong, as I was seeing values that did not match the voltage going into the ADC (as measured with a multimeter). I printed the binary byte values to the serial port to try and figure out what was happening.

Like this:

Serial.println(lowerByte,BIN);

The problem was that I was shifting the lowerByte one to the left, before joining the two bytes together. This was effectively inserting an extra bit (D7-and-a-bit - geddit?) into the joined values. Instead I just joined the two using word() and then shifted the lot one to the left.

Thanks CrossRoads for your help with this, I didn't go 100% with your approach in the end but I would still be scratching around now without your help.

B

Glad you got it to work. I have to play with actual hardware to make the bit shifting work sometimes, can't always get it the first time :wink: