SPI Communication with IC - Debugging Advice Sought

Hi,

I am working on switching a series of solenoid valves via an NXP/Freescale MCZ33996 controlled over SPI from an Arduino (Pro Mini, eventually, but I also have a Uno & Nanos for testing). I have worked on some electronics before, but not on anything of this scale and not with SPI communication, so it is possible that the below described problem is due to a lack of experience/knowledge on my part.

Based on the MCZ33996 datasheet, I drew the conclusion that this IC operates with clock polarity (CPOL) 0 and clock phase (CPHA) 1 for SPI communication, which would be SPI mode 1, expects the most significant bit to be passed first and can operate up to 6 MHz. Would anyone please be able to confirm that I have read this datasheet correctly? Relevant diagram is on page 8.

As an initial test, I have connected the Arduino to the IC (connections given below), I've connected LEDs to a few outputs in the range 0-7 and I have tried to pass a series of commands of the form 0x00 0x00 0x** where I've run * from 0 to F to try to get some sort of switching occurring, but no switching seems to occur. Additionally, when I check the bytes received back from the IC, the Arduino always reads 0xFF (so it looks like the MISO line is just being held high by the IC).

Info IC Arduino
External 5.3V 3, 5 VIN
External Ground 23, 24, 25, 26, 7, 8, 8, 10 GND
CS 14 10
SCLK 11 13
MISO 22 12
MOSI 19 11
LED - 1, 5, 12, 16 N/A

4 separate LEDs, each with + side attached to external 5V terminal and through a 1k resistor.

I have tested this both with Arduino's own SPI library with frequencies ranging from 4 MHz down to 125 kHz, and a manual implementation with frequency around 500 kHz (I think). I have verified that the Arduino is communicating correctly over SPI as a master by connecting it to another Arduino acting as an SPI slave, which is able to receive bytes successfully over SPI.

I don't currently have access to an oscilloscope/logic analyser, but hopefully will in the near future.

Can anyone spot anything that I'm doing wrong or does anyone have any advice on where I can go next to debug the issue (without an oscilloscope)? I've attached the Arduino code I used for testing. Should we get an oscilloscope soon, I'll also attach the outputs I'm measuring from the Arduino & the IC.

Edit to add relevant parts of code. Firstly, using the Arduino SPI library:

#include <SPI.h>

#define baudRate        19200             // Serial debug baud rate
#define byteA           55                // Byte value for character A

#define spiClockDivider SPI_CLOCK_DIV8    // Highest within chip specification
#define spiMode         SPI_MODE1         // Most standard mode?
#define spiBitOrder     MSBFIRST          // Most common order

byte received[3] = { 11 , 11 , 11 } ;

void setup()
{

  // Deselect slave & pull clock low
  digitalWrite( SS , HIGH ) ;
  digitalWrite( SCK , LOW ) ;

  // Set SPI settings
  SPI.setClockDivider( spiClockDivider ) ;
  SPI.setBitOrder( spiBitOrder ) ;
  SPI.setDataMode( spiMode ) ;

  // Ready SPI hardware
  SPI.begin();

  // Open serial for debug communications
  Serial.begin( baudRate ) ;
  
}

void sendSPIBytes( byte data[3] ) {

  // Make sure clock low
  digitalWrite( SCK , LOW ) ;

  // SS low so slave listens
  digitalWrite( SS , LOW ) ;

  // Transfer bytes
  SPI.transfer( data , 3 ) ;

  // Make sure clock low
  digitalWrite( SCK , LOW ) ;

  // SS high so slave stops listening
  digitalWrite( SS , HIGH ) ;
  
}

void loop()
{
  
  int i , j ;
  byte message0[3] = { 0 , 0 , 0 } ;
  
  // Transfer three different bytes with one second delay
  // Write out received bytes to serial for debug (add byteA to make it easier to read)
  for( i=0 ; i<256 ; i++ ) {
    message0[3] = i ;
    sendSPIBytes( message0 ) ;
    for( j=0 ; j<3 ; j++ ) {
      Serial.write( byteA + received[j] ) ;
    }
    delay( 500 ) ;
  }

}

I realise that received is not actually picking up the response here, this is a holdover from an earlier version of the code (where I was reading a response).

Secondly, my manual implementation

#define baudRate        19200             // Serial debug baud rate
#define byteA           55                // Byte value for character A

#define csPin           10    // Chip/Slave select pin
#define mosiPin         11    // Master data out, slave data in pin
#define misoPin         12    // Slave data in, master data out pin
#define clockPin        13    // Clock pin

#define delayTime       1     // Microseconds between SPI actions

void setup() {

  // Initially / defaults
  // SPI pins except miso are outputs

  // Slave select pin is high
  pinMode( csPin , OUTPUT ) ;
  digitalWrite( csPin , HIGH ) ;

  // Clock pin low
  pinMode( clockPin , OUTPUT ) ;
  digitalWrite( clockPin , LOW ) ;

  // MOSI idles low
  pinMode( mosiPin , OUTPUT ) ;
  digitalWrite( mosiPin , LOW ) ;

  // MISO is an input
  pinMode( misoPin , INPUT ) ; 

  //DEBUG
  Serial.begin( baudRate ) ;
}

void writeThenDelay( byte pinNumber , byte state ) {
  digitalWrite( pinNumber , state ) ;
  delayMicroseconds( delayTime ) ;
}

void spiInitialise() {

  // To start SPI communication

  // Check clock pin is low
  writeThenDelay( clockPin , LOW ) ;

  // Bring select pin low
  writeThenDelay( csPin , LOW ) ;
  
}

void spiClose() {

  // To stop sending bytes over SPI

  // Check clock pin is low
  writeThenDelay( clockPin , LOW ) ;

  // Bring slave select high
  writeThenDelay( csPin , HIGH ) ;
  
}

/* 
 *  Applies a bit mask to a byte where a single bit is 1
 *  Byte is first argument
 *  Byte second argument between 1 and 8
 *  Specifies the bit to be 1
 */
byte bitMask( byte byteToMask , byte nonZeroBit ) {

  switch ( nonZeroBit ) {

    case 8 : // 00000001
      return byteToMask & 1 ;
      break ;
    case 7 : // 00000010
      return byteToMask & 2 ;
      break ;
    case 6 : // 00000100
      return byteToMask & 4 ;
      break ;
    case 5 : // 00001000
      return byteToMask & 8 ;
      break ;
    case 4 : // 00010000
      return byteToMask & 16 ;
      break ;
    case 3 : // 00100000
      return byteToMask & 32 ;
      break ;
    case 2 : // 01000000
      return byteToMask & 64 ;
      break ;
    case 1 :
      return byteToMask & 128 ;
      break ;
  }
  
}

/*
 * Takes a byte and a bit position
 * Returns HIGH if the bit in the byte at that position is 1
 * Returns LOW  if ^^^                                     0
 */

byte bitMaskToBinary( byte byteToWrite , byte bitPosition ) {
  if( bitMask( byteToWrite , bitPosition ) > 0 ) {
    return HIGH ;
  } else {
    return LOW ;
  }
}

// Clock idles low
// Data must be valid on rising edge of clock

void spiWriteByte( byte byteToWrite ) {

  // For each bit in the byte
  for( int i=0 ; i<8 ; i++ ) {
    
    // Prepare bit on MOSI pin first
    writeThenDelay( mosiPin , bitMaskToBinary( byteToWrite , i+1 ) ) ;

    // Clock high
    writeThenDelay( clockPin , HIGH ) ;

    // TODO: READ THE BIT FROM MISO, DELAY IS PLACEHOLDER
    delayMicroseconds( delayTime ) ;

    // Clock low
    writeThenDelay( clockPin , LOW ) ;
    
  }
  
}

void loop() {

  //DEBUG
  Serial.write( 'l' ) ;

  delay( 1000 ) ;

  spiInitialise() ;

  spiWriteByte( 0 ) ;
  spiWriteByte( 0 ) ;
  spiWriteByte( 0 ) ;

  spiClose() ;

  delay( 1000 ) ;

  spiInitialise() ;

  spiWriteByte( 0 ) ;
  spiWriteByte( 0 ) ;
  spiWriteByte( 1 ) ;

  spiClose() ;
  

}

I've also tested with a loop like

for( int i=0 ; i<256 ; i++) {
  
  delay( 1000 ) ;

  spiInitialise() ;

  spiWriteByte( 0 ) ;
  spiWriteByte( 0 ) ;
  spiWriteByte( i ) ;

  spiClose() ;

}

to no avail.

Many Thanks
Scott Smith

spi-slave.ino (1.47 KB)

An Alternative for the operational check of MC33996 Chip

1. Build the UNO-MC33966 circuit as per following diagram.
spi-2.png

2. Upload the following sketch (untested) into UNO and observe that LD0 and LD2 are ON; LD1 and LD3 are OFF.

#include <SPI.h>

byte myData[] = { 0x00, 0x00, 0x0A}; //24-bit command/data
void setup()
{
  Serial.begin(9600); //Serial Moniotr is eanbled
  SPI.begin();  //SPI Port of UNO is enabled; Mode-1, MSB-first; SS/ = Output and HIGH
  pinMode(9, OUTPUT);

  //--hardware reset of the chip (MC33996)
  digitalWrite(9, HIGH);
  delayMicroseconds(100); 
  digitalWrite(9, LOW);
  delayMicroseconds(100); 
  digitalWrite(9, HIGH);   //Hardware reset of chip
}

void loop()
{
  digitalWrite(SS, LOW);    //CS/ of chip is made LOW for data communication
  delayMicroseconds(100);   //for stabilization (optional)
  //----------------------
  SPI.transfer(myData, sizeof(myData));   //command byte and data bits for LED0 - LED3
  delayMicroseconds(100);   //for stabilization (optional)
  digitalWrite(SS, HIGH);   //data is latched at output at rising edge of CS/
  delayMicroseconds(100);   //for stabilization
  //----------------------
  
  digitalWrite(SS, LOW);    //Status information of chip is latched at falling edge 
  byte x = SPDR;            //x hold status information : 00 good and FF bad
  Serial.println(x, HEX);
  digitalWrite(SS, HIGH);    //SS line is brought at LH-state.   (EDIT in respect of Post#2)
  //-----------------------
  
  delay(1000);              //sampling interval
}

spi-2.png

Thanks, GolamMostafa, for going to the trouble of putting this sketch together. I'll test this out tomorrow first thing when I get to the office and I'll let you know if I have any success!

I assume after bringing SS LOW for the second time to get the status output, you'd want to return SS HIGH to stop the chip from listening? In any case, I will test the sketch with & without this additional step.

Thanks,
Scott

snasp:
I assume after bringing SS LOW for the second time to get the status output, you'd want to return SS HIGH to stop the chip from listening? In any case, I will test the sketch with & without this additional step.

Right! One more line should be added in the sketch of Post#1 and I have done that.

I got round to testing your code today and definitely it is progress! I am able to switch on some of the outputs now. Curiously, I can do this only once, even if I completely reset the IC. Here the code I used below - I've moved the reset commands into a subroutine since I am trying to reset the device and switch the outputs on a loop, and I've specified SPI mode 1 since that's what this IC uses (default is mode 0, I believe).

#include <SPI.h>

byte myData[] = { 0x00, 0x00, 0x0F } ; //24-bit command/data

void doReset() {

  //--hardware reset of the chip (MC33996)
  digitalWrite(9, HIGH);
  delay(3000); 
  digitalWrite(9, LOW);
  delay(3000); 
  digitalWrite(9, HIGH);   //Hardware reset of chip

}

void setup()
{
  Serial.begin(19200); //Serial Monitor is eanbled
  SPI.setDataMode( SPI_MODE1 ) ;
  SPI.begin();  //SPI Port of UNO is enabled; Mode-1, MSB-first; SS/ = Output and HIGH
  pinMode(9, OUTPUT);

  doReset() ;

}

void loop()
{

  //Wait for something
  while( !( Serial.available() > 0 ) ) {
    
  }

  // Flush input
  while( Serial.available() > 0 ) {
    Serial.read() ;
  }

  Serial.println( "AAAAAAAA" ) ;
  
  digitalWrite(SS, LOW);    //CS/ of chip is made LOW for data communication
  delayMicroseconds(100);   //for stabilization (optional)
  //----------------------
  SPI.transfer(myData, sizeof(myData));   //command byte and data bits for LED0 - LED3
  delayMicroseconds(100);   //for stabilization (optional)
  digitalWrite(SS, HIGH);   //data is latched at output at rising edge of CS/
  delayMicroseconds(100);   //for stabilization
  //----------------------

  delay(3000);   

  Serial.println( "BBBBBBBB" ) ;

  doReset() ;

  Serial.println( "CCCCCCCC" ) ;
 
//  digitalWrite(SS, LOW);    //Status information of chip is latched at falling edge 
//  byte x = SPDR;            //x hold status information : 00 good and FF bad
//  Serial.println(x, HEX);
//  digitalWrite(SS, HIGH);    //SS line is brought at LH-state.   (EDIT in respect of Post#2)
  //-----------------------
  
  delay(3000);              //sampling interval
}

Apologies for the ridiculous Serial.println debug commands :grinning: . Now the Arduino waits for some (any) input on the serial before it sends a command to the MCZ33996, leaves the device in that state for 3 seconds, then 3 seconds later runs the reset function.

Like I said, this will only allow me to switch the outputs exactly once, even if I let the loop run for say 20 seconds (to ensure that the Arduino is setting waiting for console input) then I manually bring the RST pin (27) on the MCZ33996 low either by removing the connection to pin 9 and/or manually connecting pin 27 to ground (through a resistor, to avoid drawing too much current from the Arduino).

This leads me to wonder whether the reset through pin 27 is actually working correctly, even though it does make the LEDs turn off... but is something I'll have to look into a bit more, apart from figuring out why the code you suggested is working and mine is not, since they appear (to my eyes) to do the same thing aside from the reset sequence (which may not be working for some reason). I should have access to an oscilloscope soon to see what's actually being sent to the MCZ33996.

Thanks again GolamMostafa, I really appreciate it! I'll update the thread when I've done some more investigation and made some more progress.

Scott

Figured it out! It's nothing to do with the reset. It's undocumented Arduino behaviour - the SPI.transfer function, when used in the form SPI.transfer( buffer , bufferSize ) writes the bytes received over SPI back into the buffer, so my command codes were being overridden with all 0s each time.

Turns out not even the resets are strictly necessary (though, no doubt, a good idea)

For reference here is a fairly minimal working example that alternates between outputs 0 & 1 being on, and outputs 0, 1, 2 and 3 being on.

#include <SPI.h>

void setup() {
  
  Serial.begin(19200); //Serial Monitor is eanbled
  SPI.setDataMode( SPI_MODE1 ) ;
  SPI.begin();  //SPI Port of UNO is enabled; Mode-1, MSB-first; SS/ = Output and HIGH
  pinMode(9, OUTPUT);
  
}

void loop() {

  //Set up the two commands to be sent
  byte myData[] = { 0x00, 0x00, 0x0F } ; //24-bit command/data
  byte myData2[] = { 0x00 , 0x00 , 0x03 } ;

  digitalWrite(SS, LOW);    //CS/ of chip is made LOW for data communication
  delayMicroseconds(100);   //for stabilization (optional)
  //----------------------
  SPI.transfer(myData, sizeof(myData));   //command byte and data bits for LED0 - LED3
  delayMicroseconds(100);   //for stabilization (optional)
  digitalWrite(SS, HIGH);   //data is latched at output at rising edge of CS/
  delayMicroseconds(100);   //for stabilization
  //----------------------

  delay( 3000 ) ;  

  digitalWrite(SS, LOW);    //CS/ of chip is made LOW for data communication
  delayMicroseconds(100);   //for stabilization (optional)
  //----------------------
  SPI.transfer(myData2, sizeof(myData));   //command byte and data bits for LED0 - LED3
  delayMicroseconds(100);   //for stabilization (optional)
  digitalWrite(SS, HIGH);   //data is latched at output at rising edge of CS/
  delayMicroseconds(100);   //for stabilization
  //----------------------

  delay( 3000 ) ;   

  // Print out the data received back
  Serial.print( myData[0] , DEC ) ;
  Serial.print( myData[1] , DEC ) ;
  Serial.print( myData[2] , DEC ) ;

  Serial.print( myData2[0] , DEC ) ;
  Serial.print( myData2[1] , DEC ) ;
  Serial.print( myData2[2] , DEC ) ;

}

Thanks again for helping me get to the bottom of this.

Scott

snasp:
Figured it out! It's nothing to do with the reset. It's undocumented Arduino behaviour - the SPI.transfer function, when used in the form SPI.transfer( buffer , bufferSize ) writes the bytes received over SPI back into the buffer, so my command codes were being overridden with all 0s each time.

SPI.transfer(buffer, sizeof(buffer); command is fully documented in the Arduino Reference Manual and the description over there contains what you have found by research. The received bytes from the slave are stored back in the buffer. If you don't want this feature in your application, please send the command bytes one-by-one.

Anyway, thanks+ for the good efforts to work on my codes and found it working.

BTW: Have you received correct status byte from the chip? I don't have the chip to test for myself.

Ahhhhhh, right, it's in the description section.

In case of buffer transfers the received data is stored in the buffer in-place (the old data is replaced with the data received).

I saw the Returns section and didn't even process the description. My bad!

Returns

the received data

In all the tests I did I was receiving just 0x00s back, indicating no faults in the outputs, which is what I expect.

Once again, appreciate your help! I've got enough now to start picking apart the difference between my old code and your code, to understand what I was doing wrong before, and to start building up the behaviour I need.