SPI read write for 32bits data

Hi Jay,

You also working on the same IC? Any luck? What hardware connection are you using? Any schematic to show? Thanks

@Jay

You are correct about the changes from byte to char in the for loops, that was a typo on my end. Also setting SS high at the beginning is necessary (though setting it to an output is done by the SPI library).

One thing I have noticed is that you are trying to use SPI mode 0, and I believe the IC to be SPI mode 3 (data is read on the first rising edge after a falling one according to the timing diagrams).

@Paradigm

The union works by placing an array of 4 bytes in the same memory location as the struct. The struct contains a 24bit variable and a byte variable.
Due to the way unions work this basically means that the byte variable 'command' is stored at the same place as the element 3 in the array. Similarly the variable 'value' is stored in the same memory space as the three other bytes in the array (elements 0,1,2).
Basically what this means is that you can convert between the array and the struct without doing any calculations, making it a very efficient method.

I will have a play with the code to see if I can find anything which may be causing a problem.

Right, all of the code appears to be doing what is expected, however I have just had it up on the oscilloscope and the default SCLK rate (8MHz) is far far faster than the chips absolute maximum SCLK rate (2MHz).

Also, it is definitely MODE_3. The datasheet talks about the SDI setup time before rising edge which is indicative of MODE_3 which the timing diagrams confirm.

Try this:

#include <SPI.h>

union FourByte{
    struct {
      unsigned long value:24; //24bit register values go in here
      byte command:8; //8bit command goes in here.
    };
    byte bit8[4]; //this is just used for efficient conversion of the above into 4 bytes.
};

void setup(){
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE3); //I believe it to be Mode3
  SPI.setClockDivider(SPI_CLOCK_DIV16);
  pinMode(SS, OUTPUT); //not really necessary as it is done by the SPI library.
  digitalWrite(SS, HIGH);
  //Page
  //example of reading data
  unsigned long voltage = SPI_read(4);//Instantaneous Voltage Channel 1
  
  //example of writing data
  union FourByte data;
  data.command = 0b01000000; //Write to config register
  data.value = 1; //This is the default value from datasheet, just using it as an example.
  SPI_write(data);
}

void loop(){
  
}

void SPI_write(union FourByte data) {
  digitalWrite(SS,LOW); //Using CS pin, so sync1/sync0 bytes not needed
  for(char i = 3; i >= 0; i--){
    SPI.transfer(data.bit8[i]); //transfer all 4 bytes of data - command first, then Big Endian transfer of the 24bit value.
  }
  digitalWrite(SS,HIGH);
}

unsigned long SPI_read(byte command){
  digitalWrite(SS,LOW); //SS goes low to mark start of transmission
  union FourByte data = {0xFEFEFE,command}; //generate the data to be sent, i.e. your command plus the Sync bytes.
  for(char i = 3; i >= 0; i--){
    data.bit8[i] = SPI.transfer(data.bit8[i]); //send the data whilst reading in the result
  }
  digitalWrite(SS,HIGH); //SS goes high to mark end of transmission
  return data.value; //return the 24bit value recieved.
}

Hi Tom, I saw that the chip can run at 4MHz. Where you found the 2MHz? Because from the breakout board supplied by my vendor, the crystal connected to my IC is a 4MHz IC. My Uno is using 16MHz so I have to use the default value of 4 to get it to 4Mhz? Or must I still write SPI.setClockDivider(SPI_CLOCK_DIV4);

Or maybe I can try on different clock speed :slight_smile:

You are correct that the clock for the IC should be 4MHz. However at 4MHz, the serial clock frequency for its SPI interface is limited to a maximum of 2MHz, which is specified on page 11 of the datasheet.

As the Uno runs at 16MHz, then
SPI_CLOCK_DIV2 = 8MHz,
SPI_CLOCK_DIV4 = 4MHz,
SPI_CLOCK_DIV8 = 2MHz,
SPI_CLOCK_DIV16 = 1MHz.

As such you will see this line in the code in my last post:
SPI.setClockDivider(SPI_CLOCK_DIV16);

So that the SPI interface will run at 1MHz. It is usually best to run below the maximum value which is why I suggest using 1MHz not 2MHz.

@Paradigm yes,I'm on the same boat as you. :slight_smile: and I used Energy IC diagram as in the data sheet page 42 to join the Arduino Uno....working out on the program..

@Tom Thanks for the correction.. How can I print the voltage?I mean when I print out,the voltage value remains at 0.Should this line be in the loop?

unsigned long voltage = SPI_read(4);//Instantaneous Voltage Channel 1

Hi Jaay, did you fabricate the circuit yourself or bought it from vendor? How you come out with all the values and rating of the components? Hope you can help me also. As the breakout board that I had is from a vendor. Wish to fabricate it myself

Hi Tom,

From your code

//example of writing data
  union FourByte data;
  data.command = 0b01000000; //Write to config register
  data.value = 1; //This is the default value from datasheet, just using it as an example.
  SPI_write(data);
}

The data.value = 1; and you said its a default value from datasheet. Can I ask where you find this statement? Can you kindly advise? Thanks a lot. Because I always thought the data.value should be the sync0 or sync1 value example: data.value = 0xFEFFFE; is it? Thanks a lot for your help

I also got it from vendor :).All I do is read the data sheet and managing for the code based on the data sheet.But I'm also still on the way so we can help each other out.

@Tom BTW What's this line also...Thanks a lot

data.value = 1; //This is the default value from datasheet, just using it as an example.

Hi Jaay,

Too bad, I thought I can replicate the vendor breakout board. Its ok. As long as I can start reading from the board, I can always buy more from the vendor. Hahaha

Also, it is definitely MODE_3. The datasheet talks about the SDI setup time before rising edge which is indicative of MODE_3 which the timing diagrams confirm.

How do you figure that? The way I see it, the timing diagram shows the capture is on the first rising edge after the CS goes LOW. That timing specification is marked (on the write timing) as "t3" (time required between the CS Falling and SCLK Rising) (edit: and "t6" on the read timing).

The read timing shows this best. SCLK is idle LOW with clock pulses going HIGH. That is SPI Mode 0.

Are we having fun yet? :slight_smile:

This is the test code I would use to test read. It reads the same register every second.

#include <SPI.h>

// change this to your SS pin
#define CS5464 8

void setup() {
  Serial.begin(9600);

  // disable CS5464 SPI
  pinMode(CS5464,OUTPUT);
  digitalWrite(CS5464,HIGH);

  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV8);

  Serial.println("Setup complete");  
}

void loop() {
  delay(1000);
  long testVal = readMeter(4);
  Serial.println(testVal);
}

long readMeter(byte meterCommand) {
  long lBuffer = 0;
  byte rtnVal[3];
  
  // enable CS5464 SPI
  digitalWrite(CS5464,LOW);

  // delay 1us after CS LOW 
  delayMicroseconds(1);

  // send your command
  SPI.transfer(meterCommand);

  // read the 3 bytes
  // you could use a for loop here
  rtnVal[0] = SPI.transfer(0xFF);
  rtnVal[1] = SPI.transfer(0xFF);
  rtnVal[2] = SPI.transfer(0xFF);

  // disable CS5464 SPI
  digitalWrite(CS5464,HIGH);  

  // assemble into long type
  lBuffer = lBuffer | rtnVal[0];
  lBuffer = lBuffer << 8;
  lBuffer = lBuffer | rtnVal[1];
  lBuffer = lBuffer << 8;
  lBuffer = lBuffer | rtnVal[2];
  
  return lBuffer;
}

Jaay:
I also got it from vendor :).All I do is read the data sheet and managing for the code based on the data sheet.But I'm also still on the way so we can help each other out.

@Tom BTW What's this line also...Thanks a lot

data.value = 1; //This is the default value from datasheet, just using it as an example.

Basically that is setting the value you want to write to a register.

The bit of code relavent to that snippit is:

  //example of writing data
  union FourByte data;
  data.command = 0b01000000; //Write to config register
  data.value = 1; //This is the default value from datasheet, just using it as an example.
  SPI_write(data);

In the code, the command is set to write to the config register (0b01000000 from the datasheet - page 26), and then the value to write to that register is 1 (if you look at the list of commands in the datasheet it shows that the default value for the config register is 1, this was just an example of usage, not a requirement).

When you are reading a register, the lower three bytes should be 0xFEFEFE as you pointed out. This is done in the SPI_Read() function for you.

Jaay:
@Paradigm yes,I'm on the same boat as you. :slight_smile: and I used Energy IC diagram as in the data sheet page 42 to join the Arduino Uno....working out on the program..

@Tom Thanks for the correction.. How can I print the voltage?I mean when I print out,the voltage value remains at 0.Should this line be in the loop?

unsigned long voltage = SPI_read(4);//Instantaneous Voltage Channel 1

That is an example of how to read a register, it doesn't actually do anything with the returned value. You could do something like this:

void loop() {
  delay(1000);
  unsigned long voltage = SPI_read(4);//Instantaneous Voltage Channel 1
  Serial.println(voltage); //print the voltage to the serial monitor (you will need to have called Serial.begin(...) in the setup() for it to work)
}

SurferTim:

Also, it is definitely MODE_3. The datasheet talks about the SDI setup time before rising edge which is indicative of MODE_3 which the timing diagrams confirm.

How do you figure that? The way I see it, the timing diagram shows the capture is on the first rising edge after the CS goes LOW. That timing specification is marked (on the write timing) as "t3" (time required between the CS Falling and SCLK Rising) (edit: and "t6" on the read timing).

The read timing shows this best. SCLK is idle LOW with clock pulses going HIGH. That is SPI Mode 0.
Serial Peripheral Interface - Wikipedia

Are we having fun yet? :slight_smile:

The way I see the timing diagrams, it doesn't matter where the clock IDLEs in SPI. The clock is high when/as the SS line goes low. So we have:

SCLK is high.
SS drops low.
SCLK drops low.
MOSI is set to the correct value
SCLK rises high -> data is clocked in on the rising edge

All of that is indicative of MODE3.

Another way of putting it is that there is always a falling edge of SCLK before the first rising edge on which data is clocked - this is MODE3.

Another way of putting it is that there is always a falling edge of SCLK before the first rising edge on which data is clocked - this is MODE3.

I don't know what datasheet you are looking at, but not according to the read timing in the datasheet posted here. There is no falling edge before the rising edge. The write timing indicates there could be a falling edge prior to the rising edge, but it won't propagate the data.

It is probably just me, since yours is working, correct?

Hi Tim and Tom,

I did the Mode 3 and ClockDiv. But still cant read anything from the IC. Do I miss out something? Must we initalise the IC before it can be use? How can I send the continuos conversion command to the IC to continue reading data?

Hi Jaay,

Any luck? You program can work on your hardware?

Thanks a lot

Did you try my test code a few posts above? Reply #26. It did not work? What did the serial monitor say?

If it did not work, try lowering the SPI speed to 1M.

It looks like there is more to this than meets the eye. For example there are two types of write - one which writes commands, one which writes registers. As such there are now two write functions.

Secondly, it looks like you need to send a reset command before you can use it.

Thirdly you wanted continuous conversion mode, so that needs to be set.

As for which mode it is, the datasheet seems totally ambiguous. In the diagrams on page 12, compare SDI Write and SDO Read diagrams, in the first you will see the additional falling edge I mentioned, in the second you will see none. So who knows which mode it is - could be 0 could be 3 - try both.

Finally make sure that the MODE pin on the IC is connected to ground.

#include <SPI.h>

union FourByte{
    struct {
      unsigned long value:24; //24bit register values go in here
      byte command:8; //8bit command goes in here.
    };
    byte bit8[4]; //this is just used for efficient conversion of the above into 4 bytes.
};

void setup(){
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE3); //I believe it to be Mode3
  SPI.setClockDivider(SPI_CLOCK_DIV16);
  pinMode(SS, OUTPUT); //not really necessary as it is done by the SPI library.
  digitalWrite(SS, HIGH);  
  
  Serial.begin(9600);
  
  unsigned long check = SPI_read(0x00); //read the config register.
  Serial.print("Config = ");
  Serial.println(check);
  
  //Software reset:
  SPI_writeCommand(0x80);
  
  unsigned long status;
  do {
    status = SPI_read(0b00011110); //read the status register
    status &= (1UL<<23);
  } while (!status); //keep checking until DRDY bit gets set.

  SPI_writeCommand(0xE8); //Set continuous conversion mode  
  
  
  check = SPI_read(0x00); //read the config register.
  Serial.print("Config = ");
  Serial.println(check);

/*
  //example of writing a register
  union FourByte data;
  data.command = 0b01000000; //Write to config register
  data.value = 1; //This is the default value from datasheet, just using it as an example.
  SPI_writeRegister(data);*/
}

void loop(){
  delay(1000);
    //example of reading data
  unsigned long voltage = SPI_read(4);//Instantaneous Voltage Channel 1
  Serial.print("Voltage = ");
  Serial.println(voltage);
}

void SPI_writeCommand(byte command) {
  digitalWrite(SS,LOW); //SS goes low to mark start of transmission
  union FourByte data = {0xFEFEFE,command}; //generate the data to be sent, i.e. your command plus the Sync bytes.
  for(char i = 3; i >= 0; i--){
    SPI.transfer(data.bit8[i]); //transfer all 4 bytes of data - command first, then Big Endian transfer of the 24bit value.
  }
  digitalWrite(SS,HIGH);
}
void SPI_writeRegister(union FourByte data) {
  digitalWrite(SS,LOW); //Using CS pin, so sync1/sync0 bytes not needed
  for(char i = 3; i >= 0; i--){
    SPI.transfer(data.bit8[i]); //transfer all 4 bytes of data - command first, then Big Endian transfer of the 24bit value.
  }
  digitalWrite(SS,HIGH);
}

unsigned long SPI_read(byte command){
  digitalWrite(SS,LOW); //SS goes low to mark start of transmission
  union FourByte data = {0xFEFEFE,command}; //generate the data to be sent, i.e. your command plus the Sync bytes.
  for(char i = 3; i >= 0; i--){
    data.bit8[i] = SPI.transfer(data.bit8[i]); //send the data whilst reading in the result
  }
  digitalWrite(SS,HIGH); //SS goes high to mark end of transmission
  return data.value; //return the 24bit value recieved.
}

Could you try running that with:

SPI.setDataMode(SPI_MODE3);

And with

 SPI.setDataMode(SPI_MODE0);

In each case, what value is returned on the Serial Monitor for the line "Config = "

Hi Tom,

Thanks a lot. The IC now works. I am able to get the voltage value now. Previously it was always 0. Now I can get some value. Long int. But it was not the actual reading. I think I had to just convert the value into voltage. As from the datasheet, the value is from -1 to 1 to represent the voltage.

BTW, I am using Mode 3 and it works. I believe its the writecommand and writeregister that works. After initialisation and continous conversion setting, the IC can work now. I try other register like current and power and the value will change accordingly to represent current and power. Its just that all value are from -1 to 1 represented by 24bits. FFFFFF to 7FFFFF. I think I only get the positive value therefore max only 7FFFFF.

SPI.setDataMode(SPI_MODE3); //I believe it to be Mode3
SPI.setClockDivider(SPI_CLOCK_DIV8);

I really appreicated your help throughout. Thanks a lot again. Have a nice day

Hi Tim,

Thanks for your help alo. Have a nice day