Teensy 4.1 - Multiple MCP3208 (SPI) chips

Status
Not open for further replies.
I am trying to get two MCP3208 chips working on the same SPI bus, but my program crashes when attempting to read data via the SPI class. I am using this library: https://github.com/labfruits/mcp320x.

I have two MCP3208 chips. They are both connected to the SPI1 interface as such:
Code:
Teensy       MCP3208(both chips)
MOSO1(26) -> Data Out(12)
MISI1(39) -> Data In(11)
SCK1(27) -> Clock(13)

(41 and 40 are arbitrary pins I chose to use for device selection)
Teensy    MCP3208(chip 1)
CS1(41) -> CS(10)

Teensy    MCP3208(chip 2)
CS2(40) -> CS(10)

Here is my code:

Code:
#include <SPI.h>
#include <Arduino.h>
#include "../lib/mcp320x/Mcp320x.h"

#define CS1 41
#define CS2 40
#define CLK 1600000
#define ADC_VREF 3300

MCP3208 adc1(ADC_VREF, CS1);
MCP3208 adc2(ADC_VREF, CS2);

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);

  pinMode(CS1, OUTPUT);
  pinMode(CS2, OUTPUT);

  digitalWrite(CS1, HIGH);
  digitalWrite(CS2, HIGH);

  Serial.begin(9600);

  SPISettings settings(CLK, MSBFIRST, SPI_MODE0);
  SPI1.begin();
  SPI1.beginTransaction(settings);
}

uint16_t readAdc(byte csPin, byte channel) {
  MCP3208::Channel chan;
  switch (channel) {
    case 0:
      chan = MCP3208::Channel::SINGLE_0;
      break;
    case 1:
      chan = MCP3208::Channel::SINGLE_1;
      break;
    case 2:
      chan = MCP3208::Channel::SINGLE_2;
      break;
    case 3:
      chan = MCP3208::Channel::SINGLE_3;
      break;
    case 4:
      chan = MCP3208::Channel::SINGLE_4;
      break;
    case 5:
      chan = MCP3208::Channel::SINGLE_5;
      break;
    case 6:
      chan = MCP3208::Channel::SINGLE_6;
      break;
    case 7:
      chan = MCP3208::Channel::SINGLE_7;
      break;
    default:
      chan = MCP3208::Channel::SINGLE_0;
  }
  if(csPin == 41) {
    digitalWrite(CS1, LOW);
    digitalWrite(CS2, HIGH);
    uint16_t raw = adc1.read(chan); // Code stops on the first execution of this line
    return adc1.toAnalog(raw);
  }
  else if(csPin == 40) {
    digitalWrite(CS1, HIGH);
    digitalWrite(CS2, LOW);
    uint16_t raw = adc2.read(chan);
    return adc2.toAnalog(raw);
  }
}

void loop() {
  int chip1Chan0 = readAdc(41, 0);
  Serial.println("Chip 1 Channel 0: " + chip1Chan0);

  int chip1Chan1 = readAdc(41, 1);
  Serial.println("Chip 1 Channel 1: " + chip1Chan1);

  int chip2Chan0 = readAdc(40, 0);
  Serial.println("Chip 2 Channel 0: " + chip2Chan0);

  int chip2Chan1 = readAdc(40, 1);
  Serial.println("Chip 2 Channel 1: " + chip2Chan1);
}

I did some quick and dirty debugging with the LED and found that the code stops at the line I marked with a comment. Inside the adc1.read(chan) call I can see that the code stops on this line in the mcp3208 library.

I'm not sure where to go from here or how I can gather more info about what's happening. From all my research I think I'm doing this right.
 
Sorry, I don't see anywhere in you code that says you are using SPI1?
I have not used this library, but my guess is that you need to change:
Code:
MCP3208 adc1(ADC_VREF, CS1);
MCP3208 adc2(ADC_VREF, CS2);
to
Code:
MCP3208 adc1(ADC_VREF, CS1, &SPI1);
MCP3208 adc2(ADC_VREF, CS2, &SPI1);
But I am just guessing from looking at the header file/cpp file you linked to
 
Huh, that gets me farther now! In the examples the third parameter was not provided, and I thought the SPI selection was done by the "SPI1.begin()" call at the bottom of the setup function. With your suggestion now it's no longer stopping, but the output value seems to be coming back empty or null. I'm only seeing the strings on the console without the output value on the end.
 
Huh, that gets me farther now! In the examples the third parameter was not provided, and I thought the SPI selection was done by the "SPI1.begin()" call at the bottom of the setup function. With your suggestion now it's no longer stopping, but the output value seems to be coming back empty or null. I'm only seeing the strings on the console without the output value on the end.

I spoke too soon. I just had to convert the integers to strings with "String(intValue)" first. But now that I've done that, it's only outputting zeros for everything.
 
Sorry I may not be much help on some of this. As I don't have these chips, nor have used this library...

One of these days, this old dog will learn more new tricks and get more comfortable with these new fangle C++ things like templates ;)

But if it were me, some of the debugging I would do next includes:

Look at your main work horse function:
Code:
uint16_t readAdc(byte csPin, byte channel) {
  MCP3208::Channel chan;
  switch (channel) {
    case 0:
      chan = MCP3208::Channel::SINGLE_0;
      break;
    case 1:
      chan = MCP3208::Channel::SINGLE_1;
      break;
    case 2:
      chan = MCP3208::Channel::SINGLE_2;
      break;
    case 3:
      chan = MCP3208::Channel::SINGLE_3;
      break;
    case 4:
      chan = MCP3208::Channel::SINGLE_4;
      break;
    case 5:
      chan = MCP3208::Channel::SINGLE_5;
      break;
    case 6:
      chan = MCP3208::Channel::SINGLE_6;
      break;
    case 7:
      chan = MCP3208::Channel::SINGLE_7;
      break;
    default:
      chan = MCP3208::Channel::SINGLE_0;
  }
  if(csPin == 41) {
    digitalWrite(CS1, LOW);
    digitalWrite(CS2, HIGH);
    uint16_t raw = adc1.read(chan); // Code stops on the first execution of this line
    return adc1.toAnalog(raw);
  }
  else if(csPin == 40) {
    digitalWrite(CS1, HIGH);
    digitalWrite(CS2, LOW);
    uint16_t raw = adc2.read(chan);
    return adc2.toAnalog(raw);
  }
}

a) Maybe print out debug data like for example:
Code:
if(csPin == 41) {
    digitalWrite(CS1, LOW);
    digitalWrite(CS2, HIGH);
    uint16_t raw = adc1.read(chan); // Code stops on the first execution of this line
    Serial.printf("41 - cs: %d, ch: %d, chan: %x, raw: %d\n", csPin, channel, chan, raw);
    return adc1.toAnalog(raw);
  }
  else if(csPin == 40) {
    digitalWrite(CS1, HIGH);
    digitalWrite(CS2, LOW);
    uint16_t raw = adc2.read(chan);
    Serial.printf("40 - cs: %d, ch: %d, chan: %x, raw: %d\n", csPin, channel, chan, raw);
    return adc2.toAnalog(raw);
  }
And see if your calls are going through to the right place... And if anything is coming in on RAW...


b) I would back off and maybe concentrate on one chip/channel at a time. That is maybe start with simple sketch which only has one of these chips active and try reading from one channel, are you getting any data?

c) If not on a) and b), I would if possible hook up my Logic Analyzer (or scope) to the pins, and watch the SPI communications to see if it looks like correct speed and the like and would check the CS pins are properly changing and look at the analog voltage. coming into one or more inputs that I am trying to get data from. Am I seeing a valid analog voltage? Note: My newer Saleae Logic Analyzers can show analog data...

d) Wonder about timing... Maybe need a delay after changing the CS pins to allow the chip to work. I would do something like:

Code:
if(csPin == 41) {
    digitalWrite(CS1, LOW);
    digitalWrite(CS2, HIGH);
    delay(1);
    uint16_t raw = adc1.read(chan); // Code stops on the first execution of this line
    Serial.printf("41 - cs: %d, ch: %d, chan: %x, raw: %d\n", csPin, channel, chan, raw);
    return adc1.toAnalog(raw);
  }
  else if(csPin == 40) {
    digitalWrite(CS1, HIGH);
    digitalWrite(CS2, LOW);
    delay(1);
    uint16_t raw = adc2.read(chan);
    Serial.printf("40 - cs: %d, ch: %d, chan: %x, raw: %d\n", csPin, channel, chan, raw);
    return adc2.toAnalog(raw);
  }
And see if that makes a difference. If so, would probably change the delay(1) to delyMicroseconds(100); and keep make shorter and shorter delays until
it stops working and then go back up a bit.

OOPS - Looks like some of the above may help may not... If you look into the library: At their code you see:
Code:
template <typename T>
uint16_t MCP320x<T>::read(Channel ch) const
{
  return execute(createCmd(ch));
}
and
Code:
template <>
uint16_t MCP3208::execute(Command<MCP3208Ch> cmd) const
{
  return transfer(cmd);
}
and
Code:
emplate <typename T>
uint16_t MCP320x<T>::transfer(SpiData cmd) const
{
  SpiData adc;

  // activate ADC with chip select
  digitalWrite(mCsPin, LOW);

  // send first command byte
  mSpi->transfer(cmd.hiByte);
  // send second command byte and receive first(msb) 4 bits
  adc.hiByte = mSpi->transfer(cmd.loByte) & 0x0F;
  // receive last(lsb) 8 bits
  adc.loByte = mSpi->transfer(0x00);

  // deactivate ADC with slave select
  digitalWrite(mCsPin, HIGH);

  return adc.value;
}

So they are processing their own CS pin as part of the read operation... So you don't need to do that part in your functions...
But this also might imply you need to muck with their functions to add the delays to see if that helps.

Might also slow down SPI as maybe 1.6mhz too fast 3.3v is too fast? It shows something like 1mhz for 2.7v and 2mhz for 5v... So not sure for 3.3? Yes their example had your speed, but try at 1mhz at see if that helps...

Sorry, Again just throwing darts.
 
Alright so I got another chance to sit down and look at this again. I took apart and rebuilt my circuit more carefully to make sure I just didn't make a mistake there. I rewrote the code for just one chip, one channel. Still reading out zero.

Here is my new code:
Code:
#include <SPI.h>
#include <Arduino.h>
#include "../lib/mcp320x/Mcp320x.h"

#define CS1 41
#define CLK 1000000
#define ADC_VREF 3300

MCP3208 adc1(ADC_VREF, CS1, &SPI1);

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);

  pinMode(CS1, OUTPUT);

  digitalWrite(CS1, HIGH);

  Serial.begin(9600);

  SPISettings settings(CLK, MSBFIRST, SPI_MODE0);
  SPI1.begin();
  SPI1.beginTransaction(settings);
}

void loop() {
  uint16_t raw = adc1.read(MCP3208::Channel::SINGLE_0);
  uint16_t value = adc1.toAnalog(raw);
  Serial.println(String(value));
  delay(500);
}

I turned the speed down to 1mhz this time as well. BTW I didn't mention how the rest of my wiring was done other than the SPI pins. I'm using the 3.3v and ground pins that are in the middle of the board, not the ones in the corner, since the headers for the those ones are taken by my audio shield. That 3.3v pin is connected to both the Vdd and Vref pins of the chip. The Teensy ground is connected to the DGND and the AGND of the chip.

For input to the chip, I have a 2k potentiometer hooked up across the 3.3v and ground, with the wiper connected to the channel 1 input of the chip.

I'll be able to get to a digital scope this weekend and I can probe the outputs to see if the chip is doing anything at all.
 
Again it may really help to see your setup.

I ran your code and it is showing some stuff on Logic Analyzer so I know SPI1 is doing something.

But for example which IO pin are you using for MISO pin for SPI1?
On T4.1 there are two possible pins: 39 and 1 and unfortunately the card does not make it clear that pin 1 is the default. So if you are expecting to receive data back on 39, than you will likely see anything...

There are two fixes.
a) Use pin 1 and hopefully it works without change.

b) tell system to use pin 39
Code:
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);

  pinMode(CS1, OUTPUT);

  digitalWrite(CS1, HIGH);

  Serial.begin(9600);

  SPISettings settings(CLK, MSBFIRST, SPI_MODE0);
  SPI1.setMISO(39);
  SPI1.begin();
  SPI1.beginTransaction(settings);
}

Also I could not tell from your last posting. if you are still wired up for both sensors when you ran this test. If so you should probably add the:
Code:
  pinMode(CS2, OUTPUT);
  digitalWrite(CS2, HIGH);
As to tell the other chip they are not active. If it was floating it may see the data and think it is him as well and then both chips may try to write to MOSI...
 
Hah! "SPI1.setMISO(39);" was all I was missing. I have to use 39 because my header for pin 1 is taken by the audio shield already and I don't really want to just solder a wire right onto it.

Is there a recommended way to calibrate my pots now? I should be getting a range of 0 - 4095. None of my pots seem to be going above ~3300. Is this just because the 3.3v pin doesn't really get up that high? Is it recommended that I use a separate, more accurate power supply just for the pots? I know I can just scale it up to the correct range in code, but I'm wondering if it would be possible to get the full 12-bit resolution that the chip provides.

Thanks so much for all your help.
 
Status
Not open for further replies.
Back
Top