transfer 19 bit word, SPI, AD5535

I am trying to drive the 32 channels of the AD5535 using the SPI library of Arduino. I think that I have almost finished setting up the code, but I don’t know how to correctly send the data.

The AD5535 is a chip that allows to individually control 32 different channels. Each channel can output a different voltage.

According to the AD5535 datasheet, I need to send a 19bit word:

The first 5 bits (A4-A0) say which channel to open (0-31)
The following 14 bits (DB13-DB0) say which voltage to output (v_set). The bit value to send is computed from this equation (given in the datasheet):

v_set*pow(2,14)/(50*4.096)

The question is: How do I send this 19bit correctly? (btw. if it’s longer, the AD5535 will just ignore the dummy bits)

More specific:

  1. If I write for example:
SPI.transfer(40.2)

will it automatically convert the decimal number into bytes?

  1. I think the SPI.transfer() only sends 8bits. How do I send more?

Here is my complete code:

/* AD5535B Evaluation Board Controller 
 *  
 * Connections:
     Arduino/Evaluation Board
     SS: pin 10 -> SYNC : K1
     MOSI: pin 11 -> DIN: K2
     SCK: pin 13 -> SCLK: K4
 */

// inslude the SPI library:
#include <SPI.h>

// Set SPI Settings
// Max SCLK Freq: 30 MHz -> Arduino will automatically use the best speed that is equal to or less than the number you use with SPISettings.
// Most-Siginificant Bit First
// SPI_MODE0 -> Clock Polarity 0, Clock Phase 0, Output Edge Falling
SPISettings AD5535(30000000, MSBFIRST, SPI_MODE0);

// GLOBAL VARIABLES
float v_set = 20; // Output voltage
int channel = 28; // Channel
// DAC SECTION -> Binaray Value to load into into DAC register
int v_out_bin = v_set*pow(2,14)/(50*4.096);


// set pin 10 as the slave select pin (SS)
const int slaveSelectPin = 2;

void setup() {
  // initialize SPI:
  // set the slaveSelectPin as an output:
  pinMode(slaveSelectPin, OUTPUT);
  // Put SCK, MOSI, SS pins into output mode
  // also put SCK, MOSI into LOW state, and SS into HIGH state.
  // Then put SPI hardware into Master mode and turn SPI on
  SPI.begin();
}

void channel_write(int address, int value) {

  // Specify which SPI connected device to send data to
  SPI.beginTransaction(AD5535);
  
  // take the SS pin low to select the chip:
  digitalWrite(slaveSelectPin, LOW);
  delay(100);
  //  send in the address and value via SPI:
  SPI.transfer(address);
  SPI.transfer(value);
  delay(100);
  // take the SS pin high to de-select the chip:
  digitalWrite(slaveSelectPin, HIGH);
  
  // 
  SPI.endTransaction();
}

void loop() {
 
  channel_write(channel, v_out_bin); // Write to channel
  delay(1000);                       // wait for a second
  channel_write(channel, 0);         // Write to channel
  delay(1000);                       // wait for a second
  }

Here is how I send 32 bits of SPi dataL

int fWriteSPIdata32bits( spi_device_handle_t &h, int _sendData0, int _sendData1, int _sendData2, int _sendData3 )
{
  esp_err_t intError;
  spi_transaction_t trans_desc;
  trans_desc = { };
  trans_desc.addr =  0;
  trans_desc.cmd = 0;
  trans_desc.flags = 0;
  trans_desc.length = (8 * 4); // total data bits
  trans_desc.tx_buffer = txData;
  trans_desc.rxlength = 0 ; // Number of bits NOT number of bytes
  trans_desc.rx_buffer = NULL;
  txData[0] = (uint8_t)_sendData0; // command bits
  txData[1] = (uint8_t)_sendData1; // lower bits
  txData[2] = (uint8_t)_sendData2; // higher bits
  txData[3] = (uint8_t)_sendData3; // address
  intError = spi_device_transmit( h, &trans_desc);
  return intError;
} // void fWriteSPIdata8bits(  spi_device_handle_t &h, uint8_t address, uint8_t sendData )
////

Ands here is some code I use to break down a 32 bit number into unit8_t:

  rx_frame.data.u8[0] = *item & 0xFF;
          rx_frame.data.u8[1] = (*item >> 8) & 0xFF;
          rx_frame.data.u8[2] = (*item >> 16) & 0xFF;
          rx_frame.data.u8[3] = (*item >> 24) & 0xFF;

Hopefully, the code will provide clues for you.

Thank you very much for your code, but I have trouble understanding it. Could you describe a bit more what it does? It seems like you are breaking a 32bit word into 4*8bit word, but I don’t quite understand what the “trans_desc”?

Sorry my example does not make sense. Perhaps someone can help, good luck.

Let's say I have the following:

int a = 28  //11100
int b = 400 //110010000

How can I "concatenate" the bits of these two values?

i.e.

int c = concat(a,b) //11100110010000

Bitwise 'shift' then 'or'.
https://www.tutorialspoint.com/cprogramming/c_bitwise_operators.htm

How about a bit shift then OR them together ?

You will really need to know how many bits each one can be and whether you need a simple shift like 8 or 16 bits and how big the end result could be if > 65535 you would need to use longs.

Thanks a lot. So, If I knew how long the value c would be I could do ?

c = (a << 12) | b

To be safe, I'd make everything uint32_t.

int a = 28  //11100
int b = 400 //110010000

That is not the binary value of a or b if they are held in an int variable. Arduino may print them like that but it ignores the leading zeroes

on AVR an int is on 16 bits

->  28 [color=red]00000000 000[/color]11100
-> 400 [color=red]0000000[/color]1 10010000

are you willing to drop the leading 0 to concat the two? (how will you tell on the receiving end what's what if this is the use case)

Thanks a lot for all your input!
Based on all the comments I have written the following code:

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

void loop() {

  uint32_t a = 28;  //11100
  uint32_t b = 400; //110010000
  uint32_t c = (a<<(32-5)) | (b<<(32-9-5));
  
  Serial.print("\t");    // prints a tab
  Serial.print("a: ");    // prints a tab
  Serial.println(a,BIN);

  Serial.print("\t");    // prints a tab
  Serial.print("b: ");    // prints a tab
  Serial.println(b,BIN);
   
  Serial.print("\t");    // prints a tab
  Serial.print("c: ");    // prints a tab
  Serial.println(c,BIN);

}

Will a ever reach a value that exceeds 31 ?

and what do you do if it's not 28 but 128? your code no longer works because you hardcoded 5 and 9...

this __builtin function can help:

void setup() {
  Serial.begin(115200);
  uint32_t a = 28;  //11100
  uint32_t b = 400; //110010000
  Serial.println(32 - __builtin_clzl(a));
  Serial.println(32 - __builtin_clzl(b));
}

void loop() {}

It would be interesting to know why you want to concatenate the binary values in this way and what the source of the values is. It is quite possible that there is a more elegant way to do what you describe or possibly no need to do it at all

J-M-L:
and what do you do if it’s not 28 but 128? your code no longer works because you hardcoded 5 and 9…

this __builtin function can help:

void setup() {

Serial.begin(115200);
 uint32_t a = 28;  //11100
 uint32_t b = 400; //110010000
 Serial.println(32 - __builtin_clzl(a));
 Serial.println(32 - __builtin_clzl(b));
}

void loop() {}

Thanks a lot for this input. I did not know such a function existed. It basically tells me the number of bits needed to hold the variable. Very useful. I adapted my code as follows to make it more general:

void setup() {
  Serial.begin(115200);
}

void loop() {

  uint32_t a = 31;  //11100 bits: 5
  uint32_t b = 1600; //11001000000 bits: 11
  int nbr_bits_a = 32 - __builtin_clzl(a);
  int nbr_bits_b = 32 - __builtin_clzl(b);
  uint32_t c = (a<<(32-nbr_bits_a)) | (b<<(32-nbr_bits_b -nbr_bits_a));
  
  Serial.print("\t");    // prints a tab
  Serial.print("a: ");    // prints a tab
  Serial.println(a,BIN);

  Serial.print("\t");    // prints a tab
  Serial.print("b: ");    // prints a tab
  Serial.println(b,BIN);
   
  Serial.print("\t");    // prints a tab
  Serial.print("c: ");    // prints a tab
  Serial.println(c,BIN);

}

better :wink:

we are curious, we would love to know what this is for though ;D

UKHeliBob:
It would be interesting to know why you want to concatenate the binary values in this way and what the source of the values is. It is quite possible that there is a more elegant way to do what you describe or possibly no need to do it at all

Thank you for your question.

What I would like to do: I have a chip where I need to send via SPI a 19bit word. The first (MSB) 5 bits are an address (0-31) and the last 14 bits are a value. Since Arduino UNO only sends bytes of 8 bits (SPI.transfer()), I wanted to:

  1. Create the word to send using a uint32_t -> By "concatenating" the 5bit address and the 14bit value into this 32bit word

  2. Then split this word in 4x8bit byte and send it one after the other to the chip. (I am still struggling with splitting it into 4 bytes)

Hope this clears up the "why" I would ask this question. :slight_smile:

Forgot to mention: The chip does not care about any "dummy" bits after the 19bits, hence sending 32 bits instead of 19 is fine.

totyped:
2. Then split this word in 4x8bit byte and send it one after the other to the chip. (I am still struggling with splitting it into 4 bytes)

Hope this clears up the "why" I would ask this question. :slight_smile:

Forgot to mention: The chip does not care about any "dummy" bits after the 19bits, hence sending 32 bits instead of 19 is fine.

Since the "word" is only 19 bits, you can get away with only sending 3 bytes. From SPI.h:

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

You could then only send 3 bytes (24), no need for the trailing byte

You are on a little endian architecture, so LSB comes first in memory. Thus to send the 3 bytes you could do:

uint32_t result; // assume that’s your 19 bits
...
uint32_t* resultPtr = &result;
SPI.transfer(resultPtr, 3);