(SOLVED) DUE and two MCP 3208 ADC on SPI: wrong readings

Hello everyone,

I a mworking on the Arduino DUE with 2 MCP3208 and I am having some trouble.
Setup: On the SPI bus there are two MCP2308 ADCs and one SD card reader (on a tft shield). The CS pins are controlled by a MCP23017.
The card reader works, so the chip select circuit seems to be correct.

I am reading strange values on the ADCs:
if I connect 5V on ch0, I read about 4096 on ch0 AND ch1.
5V on ch1 -> ~4096 on ch2 AND ch3
5V on ch2 -> ~4096 on ch4 AND ch5
5V on ch3 -> ~4096 on ch6 AND ch7
5V on ch4 to ch7 : nothing

I am using this code:

uint16_t readADC_I(int channel)
{
uint16_t output;

  //Channel must be from 0 to 7
  //Shift bits to match datasheet for MCP3208
  byte channelBits = channel | 0x00;
  channelBits = channelBits<<5;

  //Select chip by pulling down
  MCP23017.digitalWrite(3, LOW);  // first MCP2308
  MCP23017.digitalWrite(4, HIGH); // second MCP2308
  MCP23017.digitalWrite(5, HIGH); // SD card reader

  //send start bit and bit to specify single or differential mode (single mode chosen here)
  SPI.transfer(B00000110);
  //Read in MSB
  byte msb = SPI.transfer(channelBits);
  //Read in LSB
  byte lsb = SPI.transfer(0x00);

  MCP23017.digitalWrite(3, HIGH);
  
  //Don't care about the first 4 bits of MSB
  msb = msb & B00001111;
  
  //Combine to make 12 bit value
  output = msb<<8 | lsb;
  return output;
}

I found it here and modified a little: Updated Code for MCP3208 - Sensors - Arduino Forum

Any ideas what could cause this? What should I look closer at?
Seems to me to be software related. Is there a difference between UNO and DUE concerning the SPI bus? I have tried different SPI clock dividers, but it did not work.

Since this function is very important for my application I would appreciate any help.

EDIT: All channels should be in single-ended mode.

Vile

First, the code you based your code on is not complying with the datasheet, especially if you are using a Due. The Due has an 84MHz clock speed and uses 3.3v I/O. If that IC is powered with 3.3 volts, the max clock speed is 1MHz. You are trying to run it with a 4MHz clock (default).

You must set the clock divider to 84 for a 1MHz SPI clock on the Due.

Thanks for the answer! I thought the DUE SPI runs on 5V, but there is only the 5V pin, MISO, MOSI and SCK run on 3,3V. That was new to me.

I did try different SPI clock dividers, 84, 21, 168, 255 but nothing worked.

I'll try running the MCP3208 at 3,3V.

But what's wrong with the code? Or did you just mean the SPI clock divider?

I meant both the SPI clock and the 3.3 volt I/O. The code actually looks ok if you are reading channel 0 in single-ended mode.

You will need to either power the MCP3208 with 3.3 volts or use a logic level converter.

I am now running the MCP3208 at 3.3V, it sill doens't work.

What's wrong about reading channel 0 single-ended? I want to read all channels single-ended to measure 8 voltages. I should have mentioned this in my first post.

I recommend getting one 3208 working on channel 0 first. Then add the second unit once you have the first unit working. Have you tried that?

edit: The way you are sending the first byte, you will only read 4 channels. The last 0 in this transfer is the MSB for the channel number. It will always be 0, so only channels 0 to 3 will be read.

SPI.transfer(B00000110);

I would try this with only one ADC using D3 as the slave select.

#include <SPI.h>

void setup() {
  // Note I use 115200 baud
  Serial.begin(115200);

  // set ADC slave select HIGH
  pinMode(3,OUTPUT);
  digitalWrite(3,HIGH);
  SPI.begin();

  // set SPI to 1MHz clock on Due
  SPI.setClockDivider(84);
}

void loop() {
  for(byte i = 0; i < 8; i++) {
    int thisADC = checkADC(i);
    Serial.print("channel ");
    Serial.print(i);
    Serial.print(": ");
    Serial.println(thisADC);
    delay(500);
  }
}

int checkADC(byte channel) {
  
  // enable ADC SPI
  digitalWrite(3,LOW);

  // compute 16 bit value for channel and shift into position
  int tInt = (channel + 24) << 6;
  
  // transfer the high byte
  SPI.transfer(highByte(tInt));

  // transfer the low byte and get high 4 bits
  byte hiRes = SPI.transfer(lowByte(tInt));

  // get the low 8 bits
  byte lowRes = SPI.transfer(0);

  // disable ADC SPI
  digitalWrite(3,HIGH);
  
  // combine both bytes into an integer
  int intResult = (hiRes << 8) | lowRes;

  return(intResult);
}

Thank you very much for your support!

But: Your code does not work correctly, I get random values with 5 digits.

I will have a closer look at the datasheet, I thought I could simply use the code I found.

Then use your original code and replace only the part that computes the channel. I checked this and it appears to place the bits correctly for each of the 8 channels. The "+ 24" is adding the start bit and the single-ended bit.

 // compute 16 bit value for channel and shift into position
  int tInt = (channel + 24) << 6;
  
  // transfer the high byte
  SPI.transfer(highByte(tInt));

  // transfer the low byte and get high 4 bits
  byte hiRes = SPI.transfer(lowByte(tInt));

The result below is exactly what I would expect if you are not computing the channel address correctly. If bit 0 of the first byte is zero, and you are shifting the channel number left 5 bit positions for the second byte instead of the required 6 bit shift left.

I am reading strange values on the ADCs:
if I connect 5V on ch0, I read about 4096 on ch0 AND ch1.
5V on ch1 -> ~4096 on ch2 AND ch3
5V on ch2 -> ~4096 on ch4 AND ch5
5V on ch3 -> ~4096 on ch6 AND ch7
5V on ch4 to ch7 : nothing

Yes! It works fine, thank you so much! Made my day and saved my project :slight_smile: :slight_smile:

Here's the code that works for me (if anyone else has the same problem):

uint16_t readADC_I(int channel)
{
  uint16_t output;

  // enable ADC SPI
  //Select chip by pulling down CS
  MCP23017_II.digitalWrite(3, LOW);


  // compute 16 bit value for channel and shift into position
  int tInt = (channel + 24) << 6;

  // transfer the high byte
  SPI.transfer(highByte(tInt));

  // transfer the low byte and get high 4 bits
  byte hiRes = SPI.transfer(lowByte(tInt));

  // get the low 8 bits
  byte lowRes = SPI.transfer(0);

  // disable ADC SPI
  MCP23017_II.digitalWrite(3, HIGH);

  hiRes = hiRes & B00001111;

  //Combine to make 12 bit value
  int intResult = hiRes << 8 | lowRes;
  return intResult;
}