Failing at SPI with Teensy 3.0 and MCP3911... is it the code?

Status
Not open for further replies.

Constantin

Well-known member
Hi everyone,

I am currently trying to get my Teensy 3 and a MCP3911 to communicate via SPI (Code below). Unfortunately, I am getting nowhere. Instead of getting the desired response (i.e. an acknowledgement that the right register (0x0D) has been modified to put the ADCs into reset mode), I get nothing but the following:.

Command Register Received: 1101 - Command Received: 11000000
Write Command Byte Sent: 11010
Read Command Byte Used: 11011
Error for register: 1101 - Command Sent: 11000000 - Response Received: 0

Other registers have responded with nothing but 1's.

For those of you well versed in the art of SPI, is there something obvious that I have missed? The bare electrical connections appear to be good, so I feel like I am running out of options... Any help is appreciated and thanks in advance. Constantin

Code:
// SPI Stuff here
#include "SPI.h"
const uint8_t MCP3911_CS = 9; // Teensy SPI CS1 = MCP3911
const uint8_t SD_Card_CS = 10; // Teensy CS0 = SD Card
const uint8_t MCP3911_DR = 2; //Data Ready Signal for MCP3911

const uint8_t LED = 13; //LED on Teensy 

void setup() {

  //SPI Bus setup
  pinMode (MCP3911_CS, OUTPUT); // MCP3911
  pinMode (SD_Card_CS, OUTPUT); // SD Card
  pinMode (MCP3911_DR, INPUT); //Data Ready Pin on MCP3911
  
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE0);
  SPI.setClockDivider(SPI_CLOCK_DIV8); //i.e. 6MHz on a Teensy running at 48MHz. 
  SPI.begin();
  
  //Setup Serial Comms
  Serial.begin(115200);
  pinMode(LED,OUTPUT);  //enable blinking LED    


}

void loop()
{
  bool led=1;
  for (int i=0;i<2;i++) {
    digitalWrite(LED,led);  
    delay(250);
    led = !led;
  }
 
  Reset_ADC(); //Setup the MCP3911
  
}

uint8_t Write_MCP3911_Register (uint8_t MCP3911_Register_Address, uint8_t Command) {
  Serial.print("Command Register Received: ");
  Serial.print(MCP3911_Register_Address,BIN);
  Serial.print(" - Command Received: ");
  Serial.println(Command,BIN);

  MCP3911_Register_Address = (MCP3911_Register_Address<<1) & B00111110; 
  //right shift address one digit
  // and ensure last digit is a zero for write command
  digitalWrite(MCP3911_CS, LOW);
  SPI.transfer(MCP3911_Register_Address); // send address with write command to MCP3911
  SPI.transfer(Command); //now send payload
  digitalWrite(MCP3911_CS, HIGH); // deselect the CS pin. 

  Serial.print("Write Command Byte Sent: ");
  Serial.println(MCP3911_Register_Address,BIN); // verify what command was sent (i.e. address and write bit = 0)

  //Now Verify all went well
  MCP3911_Register_Address += 1; //Turn on Read Operation by toggling last bit on
  
  Serial.print(" Read Command Byte Used: ");
  Serial.print(MCP3911_Register_Address,BIN); //show command used
  digitalWrite(MCP3911_CS, LOW); //take CS low to initiat SPI  
  SPI.transfer(MCP3911_Register_Address); // send address with read command to MCP3911
  uint8_t Response = SPI.transfer(0x0); //read one response
  digitalWrite(MCP3911_CS, HIGH); //take CS pin back high again to end SPI transmission

  //now verify that the register content matches the command sent. If so, have the function return value of one,
  //otherwise, alert the user that something is amiss. 
  if (Response == Command)  return 1;
  else 
  {
   Serial.println("");
   Serial.print("Error for register: ");
    Serial.print(MCP3911_Register_Address>>1,BIN);
    Serial.print(" - Command Sent: ");
    Serial.print(Command,BIN);
    Serial.print(" - Response Received: ");
    Serial.println(Response,BIN);
    Serial.println("");
    return 0;
  }
}

uint8_t Read_MCP3911_Register (uint8_t MCP3911_Register_Address) {
  Serial.print("Desired Register Address: ");
  Serial.print(MCP3911_Register_Address,BIN);

  MCP3911_Register_Address = (MCP3911_Register_Address<<1) & B00111110; //right shift address one digit and mask first two bits and last
  MCP3911_Register_Address +=1; // add one to toggle read bit
  
  Serial.print(" - Read Command Sent: ");
  Serial.print(MCP3911_Register_Address,BIN);
  
  digitalWrite(MCP3911_CS, LOW); 
  SPI.transfer(MCP3911_Register_Address); // send address with read command to MCP3911
  uint8_t Response = SPI.transfer(0x0); 
  digitalWrite(MCP3911_CS, HIGH);
 
  Serial.print(" - Response Received: ");
  Serial.println(Response,BIN);
  return Response;
}

void Reset_ADC()
{
 // Puts ADC into Reset Mode, i.e. stops ADC conversions until setup is complete.
  /*
   bit 7:6 RESET<1:0>: Reset mode setting for ADCs
   11 = Both CH0 and CH1 ADC are in reset mode 
   10 = CH1 ADC in reset mode
   01 = CH0 ADC in reset mode
   *00 = Neither ADC in reset mode(default)
   
   bit5:4 SHUTDOWN<1:0>: Shutdown mode setting for ADCs
   11 = Both CH0 and CH1 ADC in Shutdown 
   10 = CH1 ADC in Shutdown
   01 = CH0 ADC in Shutdown
   *00 = Neither Channel in Shutdown(default)
   
   bit 3: Not implemented, read as 0
   
   bit 2: VREFEXT Internal Voltage Reference Shutdown Control 
   1 = Internal Voltage Reference Disabled
   *0 = Internal Voltage Reference Enabled (Default)
   
   bit 1: CLKEXT Internal Clock selection bits
   1 = External clock drive by MCU on OSC1 pin (crystal oscillator disabled, no internal power consumption) (Default)
   *0 = Crystal oscillator is enabled. A crystal must be placed between OSC1 and OSC2 pins.
   
   bit 0: Not implemented, read as 0
   */

  Write_MCP3911_Register (0x0D, B11000000); 
}
 
Never mind. I am a lugnut, having attached MISO to MOSI and vice versa (ARGH!).

Also, Paul, it would be great if the incorrect kickstarter pinout image could be updated. That image of the teensy 3 shows all SPI pins next to each other, vs. your current convention of having the SCK on the same pin as the LED (i.e. D13 vs. D12).
 
Last edited:
Also, Paul, it would be great if the incorrect kickstarter pinout image could be updated. That image of the teensy 3 shows all SPI pins next to each other, vs. your current convention of having the SCK on the same pin as the LED (i.e. D13 vs. D12).

I'm sorry, that was indeed an error on my part. Fortunately, I caught it just a few days before we started shipping the rewards, so every reward shipped with a correct diagram on the printed card, and all the pinout images on every page other than Kickstarter are correct.

Kickstarter does not seem to allow editing the page after funding the project. I'll send them a support request to ask if it's possible, or if they will update the image. It would be best for everyone if that error could be corrected.


EDIT: I sent a help request to Kickstarter, ticket #239944. Hopefully they'll allow this update?
 
Last edited:
Thanks Paul!

In the meantime, I had dug up the traces on my PCB and hand-soldered wires with the new configuration until I took another look at your online diagrams at PJRC.com and realized that I had only swapped MISO and MOSI. For what it's worth, I wish the industry would standardize around some sort of nomenclature with SPI, this large number of descriptors for MISO/MOSI is really annoying and potentially confusing. So if I am understanding your diagram correctly, MISO is @ D12, MOSI @ D11, and SCK @ D13?

I am still getting nothing out of the chip and wonder if I have fried it somehow. Responses are always '0'. Would a chip react badly to having a DMM test continuity of pins / traces / connections on the SPI pins of the chip?

Nick Gammon suggested a few changes over at Arduino.cc and I'll post some updated code. His Saleae logic analyzer output seems to suggest that the Teensy should be producing the 'right' code based on the datasheet that MCP has published (see p. 48 for an example, if you have too much time on your hands). I am ordering one of those since the QA100 is sufficiently difficult to use that I give up for now. As a logic analyzer, it doesn't seem terribly intuitive.
 
For use in master mode, DOUT is MOSI and DIN is MISO.

Hardly anyone ever uses slave mode (there also isn't yet much software support for slave mode), but in slave mode DOUT is MISO and DIN is MOSI. In other words, on Teensy3 the pins swap for slave mode.

I've never used this particular chip, so I can't say why its returning zeros.

I have seen the "beeper" mode on a DMM damage a chip. In that mode, the test current is fairly high. But in auto-ranging mode, the meter starts out with the smallest current (meant for megaohm scale). I've used that mode many times on many different chips, without ever damaging any. I've often wished my DMM would do the beeper function without needing to do into the lowest range (.... I wonder if anyone from Fluke will ever read this message??)

The Saleae software is very unstable on Linux. I talked with them at Maker Faire, and at that very moment someone had installed Ubuntu on a machine in their booth and were trying it, seeing first-hand how badly their software crashes. Often the crashes result in complete loss of captured data. One time I did manage to get one good capture that really helped me reverse engineer something once, but it was painful to try over and over to finally get one huge CSV file output before it crashed. Really, it's that bad on Linux. It's probably more stable on the other systems, but if you use Linux (as I do), the software is so buggy that the Saleae products are pretty much unusable.
 
Cripes.

Used beeper mode to confirm traces. With a Fluke 087.

OK, so I probably borked the MCP3911. yay. :(

One way to get an education! So tonight I get to learn how to solder a SSOP20 after carefully unsoldering it, the HC49 crystal, its caps, and made the necessary connections to D6 and GND from OSC1 and OSC2 on the board.
 
So here is the revised code.
Code:
// SPI Stuff here
#include "SPI.h"
const uint8_t MCP3911_CS = 9; // Teensy SPI CS1 = MCP3911
const uint8_t SD_Card_CS = 10; // Teensy CS0 = SD Card
const uint8_t MCP3911_DR = 2; //Data Ready Signal for MCP3911
const uint8_t const MCP3911_CLK = 6; //CLK Signal for MCP3911

const uint8_t LED = 13; //LED on Teensy 

void setup() {

  //SPI Bus setup
  digitalWrite(MCP3911_CS,HIGH); //
  pinMode (MCP3911_CS, OUTPUT); // MCP3911
  pinMode (SD_Card_CS, OUTPUT); // SD Card
  pinMode (MCP3911_DR, INPUT); //Data Ready Pin on MCP3911
  
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE0);
  SPI.setClockDivider(SPI_CLOCK_DIV8); //i.e. 6MHz on a Teensy running at 48MHz. 
  SPI.begin();
  
//setup CLK function on Pin 6
pinMode (MCP3911_CLK, OUTPUT); // MCP3911 OSC1
analogWrite(MCP3911_CLK, 128); //
analogWriteFrequency(MCP3911_CLK, 4000000); //for 4MHz operation

  //Setup Serial Comms
  Serial.begin(115200);
  pinMode(LED,OUTPUT);  //enable blinking LED    


}

void loop()
{
  bool led=1;
  for (int i=0;i<2;i++) {
    digitalWrite(LED,led);  
    delay(250);
    led = !led;
  }
 
  Reset_ADC(); //Setup the MCP3911
  
}

uint8_t Write_MCP3911_Register (uint8_t MCP3911_Register_Address, uint8_t Command) {
  Serial.print("Command Register Received: ");
  Serial.print(MCP3911_Register_Address,BIN);
  Serial.print(" - Command Received: ");
  Serial.println(Command,BIN);

  MCP3911_Register_Address <<= 1;   //left shift address one digit
  MCP3911_Register_Address &= B00111110; // and ensure last digit is a zero for write command
  digitalWrite(MCP3911_CS, LOW); // now take CS low to enable SPI device
  SPI.transfer(MCP3911_Register_Address); // send address with write command to MCP3911
  SPI.transfer(Command); //now send payload
  digitalWrite(MCP3911_CS, HIGH); // deselect the CS pin. 

  Serial.print(" Write Command Byte Sent: ");
  Serial.println(MCP3911_Register_Address,BIN); // verify what command was sent (i.e. address and write bit = 0)

  //Now Verify all went well. If so, have the function return value of one,
  //otherwise, alert the user that something is amiss. 
  uint8_t Response = Read_MCP3911_Register (MCP3911_Register_Address>>1);
  if (Response == Command)  return 1;
  else 
  {
   Serial.println("");
   Serial.print("Error for register: ");
    Serial.print(MCP3911_Register_Address>>1,BIN);
    Serial.print(" - Command Sent: ");
    Serial.print(Command,BIN);
    Serial.print(" - Response Received: ");
    Serial.println(Response,BIN);
    Serial.println("");
    return 0;
  }
}

uint8_t Read_MCP3911_Register (uint8_t MCP3911_Register_Address) {
  
  MCP3911_Register_Address <<=1; //left shift address one bit for command byte 
  MCP3911_Register_Address |=1; // Ensure read bit is set
  
  Serial.print("  Read Byte Command Sent: ");
  Serial.print(MCP3911_Register_Address,BIN);
  
  digitalWrite(MCP3911_CS, LOW); 
  SPI.transfer(MCP3911_Register_Address); // send address with read command to MCP3911
  uint8_t Response = SPI.transfer(0x0); 
  digitalWrite(MCP3911_CS, HIGH);
 
  Serial.print(" - Response Received: ");
  Serial.println(Response,BIN);
  return Response;
}

void Reset_ADC()
{
 // Puts ADC into Reset Mode, i.e. stops ADC conversions until setup is complete.
  /*
   bit 7:6 RESET<1:0>: Reset mode setting for ADCs
   *11 = Both CH0 and CH1 ADC are in reset mode 
   10 = CH1 ADC in reset mode
   01 = CH0 ADC in reset mode
   00 = Neither ADC in reset mode(default)
   
   bit5:4 SHUTDOWN<1:0>: Shutdown mode setting for ADCs
   11 = Both CH0 and CH1 ADC in Shutdown 
   10 = CH1 ADC in Shutdown
   01 = CH0 ADC in Shutdown
   *00 = Neither Channel in Shutdown(default)
   
   bit 3: Not implemented, read as 0
   
   bit 2: VREFEXT Internal Voltage Reference Shutdown Control 
   1 = Internal Voltage Reference Disabled
   *0 = Internal Voltage Reference Enabled (Default)
   
   bit 1: CLKEXT Internal Clock selection bits
   *1 = External clock drive by MCU on OSC1 pin (crystal oscillator disabled, no internal power consumption) (Default)
   0 = Crystal oscillator is enabled. A crystal must be placed between OSC1 and OSC2 pins.
   
   bit 0: Not implemented, read as 0
   */

  Write_MCP3911_Register (0x0D, B11000010); 
}
 
You are fantastic and thank you for the follow up.

Also wanted to report success in getting the MCP3911 and the Teensy 3 to talk to each other. As suggested, I have taken to driving the OSC1 pin directly from the Teensy at 12MHz while grounding OSC2. That saves a few parts but more importantly avoids the issue of getting a crystal to be happy with the MCP3911 altogether. The project 'loses' 4MHz of headroom in the process (16MHz is the maximum frequency allowed per the spec. sheet), leading to 1465 samples/s with 2048x oversampling vs. a theoretical 1.9ksps at the same oversampling rate.

I also got the data ready function enabled - once using SPI (~1435 sps) and once in hardware with a interrupt (~1465 sps). The recently-acquired Saleae Logic8 Analyzer was instrumental in helping me pinpoint issues, visualize SPI transactions, etc. If only my QA100 had been this easy to use. Anyhow, now for the next bit, i.e. attaching more of a front-end to the MCP3911 and measuring its inputs/outputs. Thanks again.
 
Last edited:
Status
Not open for further replies.
Back
Top