Pages: [1]   Go Down
Author Topic: [SOLVED] SPI communication failure... anything obvious with the code?  (Read 1263 times)
0 Members and 1 Guest are viewing this topic.
Peoples Republic of Cantabrigia
Offline Offline
God Member
*****
Karma: 6
Posts: 722
Arduino happiness
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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:.

Quote
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:
#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 initiate 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);
}
« Last Edit: July 29, 2013, 06:48:09 am by Constantin » Logged

Global Moderator
Online Online
Brattain Member
*****
Karma: 480
Posts: 18734
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

There is a potential problem in setup, where you do not bring chip select high initially. Try adding this:

Code:
void setup() {

  //SPI Bus setup
  digitalWrite (MCP3911_CS, HIGH);    //  <-------------- start chip select high (inactive)
  pinMode (MCP3911_CS, OUTPUT); // MCP3911
  pinMode (SD_Card_CS, OUTPUT); // SD Card
  pinMode (MCP3911_DR, INPUT); //Data Ready Pin on MCP3911


I loaded your code onto my Uno (I don't have the MCP3911 chip) and tried it.

The write:



The read back:




I haven't read the datasheet, but see if that is what you expect.
Logged


Peoples Republic of Cantabrigia
Offline Offline
God Member
*****
Karma: 6
Posts: 722
Arduino happiness
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Nick,

Thank you so much for that advice! I added the suggested digitalWrite line but it had no effect - the program keeps looping without any change in behavior.

So unless there is something blindingly obvious, I will shift over to a hardware diagnostic. FWIW, I have a saleae logic 8 on order, as the logic analyzer built into the QuantAsylum mixed scope is just too unintuitive to use for me (I obviously acknowledge that others may love it and find it super easy to work with).

One of the things I will send up to MCP for advice is the use of the clock crystal -maybe I am pushing it - but I didn't think it would have any impact on the SPI interface.
Logged

Peoples Republic of Cantabrigia
Offline Offline
God Member
*****
Karma: 6
Posts: 722
Arduino happiness
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

So, rechecked the data sheets on the respective devices and it appears I had MISO and MOSI swapped. Fixed that, but still no go.

Next, I am going to reduce the speed of the external oscillator to see if that is the issue.

FWIW, the datasheet expectations seem to be met per your output. I am trying to modify register 0D (13 decimal). The MCP spec requires the command byte to start with two zeros (unless you use a different version of the chip), followed by the five bits of the register address you want to modify, and then the read/write bit. Thus, in order to construct the write command byte, I start off by left shifting the desired register by one bit (doubling its value) and then applying a mask to turn the LSB to zero.

If you have a look at the bottom of p. 48 on the MCP3911 datasheet, you can see the reset command being illustrated just as I described and exactly like the MOSI output you so graciously created for me. Thank you again for modeling it on your Arduino and confirming the results.

Yet my MC3911 still returns nothing but zeros when I try and read any responses. Argh.
« Last Edit: July 08, 2013, 10:56:22 am by Constantin » Logged

Global Moderator
Online Online
Brattain Member
*****
Karma: 480
Posts: 18734
Lua rocks!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Thus, in order to construct the write command byte, I start off by left shifting the desired register by one bit (doubling its value) and then applying a mask to turn the LSB to zero.

Left shifting by one will make the LSB zero. No mask needed.
Logged


Peoples Republic of Cantabrigia
Offline Offline
God Member
*****
Karma: 6
Posts: 722
Arduino happiness
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Thank you!

So it's likely I destroyed the Mcp3911 by using the continuity beeper mode on my fluke 87 DMM. Now I know the current the DMM produces in that mode is high enough to damage the mcp3911.  Also found out that the teensy 3 offers a simple way to put out a 12MHz Osc signal for the mcp3911. So I will use that instead of the crystal to drive the mcp3911.

Live and learn. Thanks again for your help. The code has been updated per your suggestion.
Logged

Peoples Republic of Cantabrigia
Offline Offline
God Member
*****
Karma: 6
Posts: 722
Arduino happiness
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Just a follow-up, once I replaced the MCP3911 with a fresh chip, all was well.

Moral of the story: DO NOT USE THE BEEPER MODE ON A FLUKE DMM TO VERIFY CONNECTIONS ON A POPULATED PCB, IT CAN FRY YOUR CHIPS!
« Last Edit: July 29, 2013, 03:49:10 pm by Constantin » Logged

Finland
Offline Offline
Jr. Member
**
Karma: 0
Posts: 96
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Just a follow-up, once I replaced the MCP3911 and replaced it with a fresh chip, all was well.

Moral of the story: DO NOT USE THE BEEPER MODE ON A FLUKE DMM TO VERIFY CONNECTIONS ON A POPULATED PCB, IT CAN FRY YOUR CHIPS!

That moral sounds very important to me. Would that probably apply to multimeters in general when using them to measure resistance between two points in a populated pcb or breadboard?
Logged

Peoples Republic of Cantabrigia
Offline Offline
God Member
*****
Karma: 6
Posts: 722
Arduino happiness
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

No, apparently, high currents are only produced in "beeper" mode. So, in general a DMM should be safe - but I would limit measurements for resistance to 'off-mode', i.e. an unpowered PCB.
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 5
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hello all,

I tried to follow your work with the MCP3911 using an Arduino UNO, and communication using your code works,
but unfortunately when i want to read the ADC value from REG 0x00   or REG 0x03 i just get 0XFFFFFF or 0x80
as result.
As it´s a 3.3V device i took care of level shifting (after blowing one converter smiley-cry )
@Constantin
May be you can post some more code , i think i just hav a mismatch in one of the registers.
As English is not my mothers language(I´m from Germany) there might also be misunderstanding of the datasheet.

Thanks for your help!!
RayWB
Logged

Peoples Republic of Cantabrigia
Offline Offline
God Member
*****
Karma: 6
Posts: 722
Arduino happiness
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi Ray,

I am surprised that the code would not work for you. Are you getting the right responses after writing and reading various registers? If you are getting a match between the write and the read then it's pretty likely that there is something else going on like the chip is in reset, the reset pin is not high, etc. So I'd look around the chip electrically and make sure that everything that should have power, has power, that the CLKIN/OSC is happy and in compliance, that there is a good signal going into both ADC channels and so on.

Plus, you may need three reads for each channel read, depending on how you configured the chip (24-bit vs. 16 bit data registers, and so on). The first and second byte may be all zeros because the chip isn't measuring anything but noise. With zero input on the ADC, the 'data' may be limited to just the third byte, i.e. 0x02 and 0x05, respectively.

Cheers, Constantin
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 5
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi Constantin,

thank you for response.
communication to adc works , also the adc reacts on changing the input voltage, but the received data make no sense.
i try to read voltage from a bridge and i´m also in adc´s input range.
I´ll post the used code after making it "readable " for everyone...
I´m nearly shure that I took a mistake setting up one ore more of the registers.

cheers Ray
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 5
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi Constantin,

that´s the code i use. i separately control mcp3911 Reset pin to do a hard reset and i also use crystal oscillator 4 MHz
so my config register shows a different number.
May be you can take a look at my code or try it in your hardware setup.

Thanks a lot

Ray
--------------------------------------


#include "SPI.h"

const uint8_t MCP3911_CS = 4 ; // MCP3911 ChipSelectPin
const uint8_t MCP3911_RES = 5; // MCP3911 ResetPin

byte MCP3911_CTRL = 0;


void setup() {

pinMode (MCP3911_CS, OUTPUT);
digitalWrite(MCP3911_CS, HIGH); // Chipselect High

pinMode (MCP3911_RES, OUTPUT); // MCP3911 ResetPin

pinMode (SS, OUTPUT); // Uno´s SS Pin

//SPI setup

SPI.setBitOrder(MSBFIRST);
SPI.setDataMode(SPI_MODE0);
SPI.setClockDivider(SPI_CLOCK_DIV4); // 4MHz on a Uno running at 16MHz.
SPI.begin();

//Setup Serial Communication

Serial.begin(115200);
Serial.print(" Hardware RESET ");
digitalWrite(MCP3911_RES, LOW);  // pull ADC Reset LOW
delay(100);                      // wait 100ms
digitalWrite(MCP3911_RES, HIGH);

Write_MCP3911_Register (0x09, B10000000); //Gain and Boost register set:  current *1 , Both channels Gain 1
//delay(2500);
Write_MCP3911_16bitRegister (0x0C, B00111000,B00000000);
/*Config register set
1st byte:
AMCLK=MCLK = 00
OSR = 4096 =111
Dithering off =00
AZ-Freq=0 (default)

2nd byte

Reset=00 Neither ADC in reset (default)
shutdown=00 Neither channel in shutdown(default)
bit3=0
Vrefext=0 (internal)
CLKext=0( crystal)
bit0 =0

*/
// Costantin´s reset function just uses 2nd byte at 0x0d

//delay(2500);
Write_MCP3911_16bitRegister (0x0A, B11011111,B10111000);// i´m not shure how to setup this!!!
//delay(2500);


}

void loop()
{
int32_t adc;

adc=Read_MCP3911_24bit(0x00);
delay(1500);
Serial.print("channel 0: ");
Serial.println(adc);
adc=Read_MCP3911_24bit(0x03);
delay(1500);
Serial.print("channel 1: ");
Serial.println(adc);

}


// ***************  Constantin´s Code*** slightly modified (using controlbyte)

uint8_t Write_MCP3911_Register (uint8_t MCP3911_Register_Address, uint8_t Command) {

Serial.print("Command Register : ");
Serial.println(MCP3911_Register_Address,HEX);
MCP3911_CTRL=0;
MCP3911_CTRL|= (MCP3911_Register_Address<<1);//left shift address one digit for write command

digitalWrite(MCP3911_CS, LOW);
SPI.transfer(MCP3911_CTRL); // send address with write command to MCP3911
SPI.transfer(Command); //now send payload
digitalWrite(MCP3911_CS, HIGH); // deselect the CS pin.


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

MCP3911_CTRL |= 1; //Turn on Read Operation by toggling last bit on

Serial.print(" Read Address sent: ");
Serial.print(MCP3911_CTRL,HEX); //show command used

digitalWrite(MCP3911_CS, LOW); //take CS low to initiate SPI
SPI.transfer(MCP3911_CTRL); // 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
Serial.print(" Response : ");
Serial.println(Response);

//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_CTRL>>1,HEX);
Serial.print(" - Command Sent: ");
Serial.print(Command);
Serial.print(" - Response Received: ");
Serial.println(Response);
Serial.println("");
return 0;
}
}

//-------------------------------------------------------------------------
uint8_t Read_MCP3911_Register (uint8_t MCP3911_Register_Address)
{
Serial.print("Register Address: ");
Serial.println(MCP3911_Register_Address,HEX);
MCP3911_CTRL = 0;
MCP3911_CTRL |= (MCP3911_Register_Address<<1); //left shift address one bit
MCP3911_CTRL |=1; // set read bit

Serial.print(" - Read from : ");
Serial.println(MCP3911_CTRL>>1,HEX);// shift back right to get address

digitalWrite(MCP3911_CS, LOW);
SPI.transfer(MCP3911_CTRL); // send Controlbyte to MCP3911
uint8_t Response = SPI.transfer(0x0);
digitalWrite(MCP3911_CS, HIGH);

Serial.print(" - Response Received: ");
Serial.println(Response);
return Response;
}

//----writing the 16Bit registers ------------------------------------------------------------------------------


uint16_t Write_MCP3911_16bitRegister (uint8_t MCP3911_Register_Address, uint8_t CommandHigh,uint8_t CommandLow)
{
Serial.print("selected Register : ");
Serial.println(MCP3911_Register_Address,HEX);
Serial.print(" - Command: ");
uint16_t commando=0;  //connect two bytes to show one number
commando=CommandHigh;
commando=commando<<8;
commando|=CommandLow;
Serial.println(commando);

MCP3911_CTRL=0;
MCP3911_CTRL = (MCP3911_Register_Address<<1); //left shift address one digit for write command
digitalWrite(MCP3911_CS, LOW);
SPI.transfer(MCP3911_CTRL); // send command byte to MCP3911
SPI.transfer(CommandHigh); //now send High Byte of payload
SPI.transfer(CommandLow);  //now send Low Byte of payload
digitalWrite(MCP3911_CS, HIGH); // deselect the CS pin.
Serial.print("Command Byte Sent: ");
Serial.print(MCP3911_CTRL,HEX); // verify what command was sent (i.e. address left shifted 1 Bit and write bit = 0)
Serial.print(" , command : ");
Serial.println(commando);

MCP3911_CTRL |= 1; //Turn on Read Operation by toggling last bit on

Serial.print(" Read Command Byte : ");
Serial.print(MCP3911_Register_Address,HEX); //show command used
digitalWrite(MCP3911_CS, LOW); //take CS low to initiate SPI
SPI.transfer(MCP3911_CTRL); // send control byte(address left shifted 1Bit) with read command to MCP3911
uint8_t ResponseHigh = SPI.transfer(0x0); //read High Byte response
uint8_t ResponseLow = SPI.transfer(0x0);  //read Low Byte response
digitalWrite(MCP3911_CS, HIGH); //take CS pin back high again to end SPI transmission

uint16_t Response2=0;
Response2=ResponseHigh;
Response2=Response2<<8;
Response2|=ResponseLow; //connecting the 2 bytes to one number

Serial.print(" -command Received: ");
Serial.println(Response2);
Serial.println("");

//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 (Response2 == commando) return 1;
else
{
Serial.println("");
Serial.print("Error for register: ");
Serial.print(MCP3911_CTRL>>1,HEX);
Serial.print(" - Command Sent: ");
Serial.print(commando);
Serial.print(" - Response Received: ");
Serial.println(Response2);
Serial.println("");
return 0;
}
}

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_16bitRegister (0x0C, B00111000,B00000000);
//Write_MCP3911_Register (0x0D, B00000000);

}

int32_t Read_MCP3911_24bit( uint8_t MCP3911_Register_Address)
{
  uint8_t HB,MB,LB=0;
  int32_t adc0code=0;


MCP3911_CTRL = 0;
MCP3911_CTRL =(MCP3911_Register_Address<<1); //left shift address one digit for write command
MCP3911_CTRL |= 1; //Turn on Read Operation by toggling last bit on

digitalWrite(MCP3911_CS, LOW);
SPI.transfer(MCP3911_CTRL); // send command byte to MCP3911
HB = SPI.transfer(0x0);//receive High Byte
MB = SPI.transfer(0x0);//receive Middle Byte
LB = SPI.transfer(0x0);//receive Low Byte
digitalWrite(MCP3911_CS, HIGH);

adc0code = HB;
adc0code = adc0code<<8;
adc0code |= MB;
adc0code = adc0code<<8;
adc0code |= LB;         //connecting the 3 bytes to one number


return adc0code;// returning result
}
Logged

0
Offline Offline
Tesla Member
***
Karma: 141
Posts: 9555
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Just a follow-up, once I replaced the MCP3911 with a fresh chip, all was well.

Moral of the story: DO NOT USE THE BEEPER MODE ON A FLUKE DMM TO VERIFY CONNECTIONS ON A POPULATED PCB, IT CAN FRY YOUR CHIPS!

I'm surprised Fluke would have a DMM design that would put more current thru a circuit for a continuity beep function (assuming that is the "beep" function) than is used for resistance measurements.
Logged

Consider the daffodil. And while you're doing that, I'll be over here, looking through your stuff.   smiley-cool

Offline Offline
Newbie
*
Karma: 0
Posts: 5
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

got it running now , reason was missing VREF connection, so inputs seemed out of range smiley-red
Logged

Pages: [1]   Go Up
Jump to: