Question regard SPI and libraries

Hello all,

I am using a Analog Devices chip set that is programmed using three wire SPI communication (the chip doesn't talk back to the arduino, only 1 way).
I am currently using a great library to communicate with the chip. I've worked by way though the code to try and come to an understanding of what is happening, but I had a few questions I couldn't resolve myself.

Regarding the function which transmits the data:

  void ADF4351::writeDev(int n, Reg r)
  332 {
  333   byte  txbyte ;
  334   int i ;
  335   digitalWrite(pinSS, LOW) ;
  336   delayMicroseconds(10) ;
  337   i=n ; // not used 
  338   for ( i = 3 ; i > -1 ; i--) {
  339     txbyte = (byte) (r.whole >> (i * 8)) ;
  340     SPI.transfer(txbyte) ;
  341   }
  342 
  343   digitalWrite(pinSS, HIGH) ;
  344   delayMicroseconds(5) ;
  345   digitalWrite(pinSS, LOW) ;
  346 }

Can someone explain the syntax in line 339. The goal is to transfer over the 4 bytes of data MSB first, but I don't understand how the bitshift is doing that.

Also, the actual spi.transfer command is only operating with bytes. If I plan to write to a register that isn't a multiple of 8, how would I? I guess I could write two bytes with some leading zeros?

Thanks,
Sami
Also,

txbyte = (byte) (r.whole >> (i * 8)) ;

Without knowing the definition of r.whole I am just guessing that this is picking out the 4 bytes of a 32 bit number and sending them one at a time

...R

samihawasli:
Also, the actual spi.transfer command is only operating with bytes. If I plan to write to a register that isn't a multiple of 8, how would I?

In general you need to write the address as a high part of the address and then a low part of the address.

But that is a device specific issue, the data sheet of the device you are using will tell you how to use it.

SPI devices do not have a rigid universal concept of 'registers' anyway.

srnet:
SPI devices do not have a rigid universal concept of 'registers' anyway.

I was mostly reference the SPI.h file that comes with the Arduino compiler. Forgive me if this is obvious, but I'm new to this... If you go the Arduino site describing the spi.transfer,

Syntax
receivedVal = SPI.transfer(val)
receivedVal16 = SPI.transfer16(val16)
SPI.transfer(buffer, size)

Parameters
val: the byte to send out over the bus
val16: the two bytes variable to send out over the bus
buffer: the array of data to be transferred"

The quote above makes me assume the spi.transfer command only works on individual bytes at a time? I suppose this is why the library parses up the 32 bits into 4 bytes to send? If so, what would be the solution to sending data not in multiples of 8?

Hope that makes sense,
Sami

samihawasli:
The quote above makes me assume the spi.transfer command only works on individual bytes at a time? I suppose this is why the library parses up the 32 bits into 4 bytes to send? If so, what would be the solution to sending data not in multiples of 8?

If you want to send (say) a 12 bit value then you need to extend it to 2 bytes (16 bits) and send them. In an Arduino a 12 bit value is almost certainly in a 2 byte int.

...R

Without seeing more of the code, I'm guessing that Reg is defined as some sort of struct, probably with 4 bytes in it and that there is also a union going on (sorry, my c is a little rusty now) so that r.whole overlays the 4 individual bytes in the struct.

The loop causes the 4 bytes that make up r.whole to be written to the SPI transmit register one after the other with MSB first, then NMSB, then NLSB, and finally the LSB.

With i taking on the values of 3,2,1 & 0 in the for loop, r.whole is shifted 24 bits (3x8) to the right when i is 3, 16 bits when i is 2, 8 bits when i is 1, and no shift when i is 0.

When you say

If so, what would be the solution to sending data not in multiples of 8?

I guess you might be referring to figure 23 on page 14 of the datasheet. If so, you don't actually send 12 bits. You would need to create the 32bit value by shifting and ORing in the various parts of the "message", and then write out the 32bit value as 4 bytes.

Thank you everyone! so many great replies!

markd833:
Without seeing more of the code, I’m guessing that Reg is defined as some sort of struct, probably with 4 bytes in it and that there is also a union going on (sorry, my c is a little rusty now) so that r.whole overlays the 4 individual bytes in the struct.

You are very close. Reg is defined as below. In this case, as you saw from the datasheet for the ADF4351 every Reg is a 32 bit integer to coincide with their 32 bit register.

   59 Reg::Reg()
   60 {
   61   whole = 0 ;
   62 }
   63 
   64 uint32_t Reg::get()
   65 {
   66   return whole ;
   67 }
   68 
   69 void Reg::set(uint32_t value)
   70 {
   71   whole = value  ;
   72 }
   73 
   74 void Reg::setbf(uint8_t start, uint8_t len, uint32_t value)
   75 {
   76   uint32_t bitmask =  ((1UL  << len) - 1UL) ;
   77   value &= bitmask  ;
   78   bitmask <<= start ;
   79   whole = ( whole & ( ~bitmask)) | ( value << start ) ;
   80 }
   81 
   82 uint32_t  Reg::getbf(uint8_t start, uint8_t len)
   83 {
   84   uint32_t bitmask =  ((1UL  << len) - 1UL) << start ;
   85   uint32_t result = ( whole & bitmask) >> start  ;
   86   return ( result ) ;
   87 }

markd833:
you might be referring to figure 23 on page 14 of the datasheet. If so, you don’t actually send 12 bits.

I intend on moving to a different Analog Chip, ADF4152. If you look on page 10 you can see the 4 registers that need to be written: 10, 16, 24, 24 bits long.

I assume the Reg class as defined above only works with 32 bit integers since all the arguments are such? Would it be as easy as copy pasting the class (with a new name of course) and change the arguments to uint10, uint16… ?

Sorry if this is beyond your scope, and very specific to the analog devices chipset… another reply suggested that 10 bit integers would be stored as 16 anyhow. Does that imply the leading 6 MSB would be zero? When spi.transfer is called, will the chip know how to handle those leading zeros? My assumption is the chip knows how many bits to use given the last three addressing bits… but assumptions are probably the worst thing to rely on.

Thanks in advance,
Sami

I had a quick look at that datasheet. It looks like the device wants a 24bit message - see Input Shift Registers on page 9.

You can have integers with as many bits as you like. It's more about how the underlying microcontroller hardware stores them. It's nearly always in multiples of 8 bits. So, if you did have a 10 bit integer, then it would take up 16 bits of actual storage on the microcontroller.

It's always good to start off with a known value in a variable. Somebody more knowledgeable may correct me here, and this may be heading way off topic, but when you define a variable such as "int i;" and don't explicitly give it a value, then if memory serves, the variable gets placed in a memory section called ZEROVARS. When your compiler finishes its job, the linker then takes over and packages it all together , it puts all the variables in the ZEROVARS section together. Then when the code actually starts to run on the microcontroller, there's some housekeeping that happens before your code begins. Part of this housekeeping is to go round a loop (or calling memset() ) explicitly writing zero into all the variables in the ZEROVARS section, that way your uninitialized variables are always set to zero.

But back to your query!

I think you can keep the reg class as it is. That bit of code in your first post just needs a tweak.

If you change the line of code for ( i = 3 ; i > -1 ; i--) to for ( i = 2 ; i > -1 ; i--) then it will shift out the lower 24 bits of the value of r.

338   for ( i = 3 ; i > -1 ; i--) {
339     txbyte = (byte) (r.whole >> (i * 8)) ;
340     SPI.transfer(txbyte) ;
341   }

As has been mentioned, this splits the 32 bit constuct r.whole into individual bytes. The '(byte)' part is what's known as casting. Casting converts one type of variable to another, in this case converting a 32 bit byte into an 8 bit byte. Since you can't fit 32 bits into 8, the leftmost 24 bits are lost and the resulting right-most or least significant bits are transferred into txbyte. Rotating the bits up to 3 times to the right ensures that all 32 bits are captured and sent in sequence.

The is no uint10_t type by default, but you can create such a construct:

struct uint24_t {
  uint32_t value : 24;
  uint32_t _     : 8;
};

struct uint10_t {
  uint16_t value : 10;
  uint16_t _     : 6;
};

struct uint24_t twentyFourBitData;
struct uint10_t tenBitData;

You then extract the 24-bit value with twentyFourBitData.value and the 10 bit value with tenBitData.value. However this is probably not necessary as the data will be transferred over SPI as chunks of 8 bits anyway.

In addition to single bytes, Arduino does have a function to send data over SPI as 16 bit bytes or as a buffer:

https://www.arduino.cc/en/Reference/SPITransfer

I imagine that behind the scenes, the additional functions just send multiple 8-bit bytes in sequence but there is anecdotal evidence that sending a buffer may be faster:

I guess it really depends on how much data there is to send in each operation. The buffer function is defined as:

inline static void transfer(void *buf, size_t count)

So I wonder whether it would be possible to send the 32bit data by constructing a union:

union regObj {
 uint32_t whole;
 uint8_t dataBytes[4];
}

Then in void ADF4351::writeDev(int n, Reg r) doing something like:

union regObj regValue;
regValue.whole = r.whole;

SPI.transfer(&regValue.dataBytes, 4);

No bit shifting or iteration over the 32bit data structure.