transfer 19 bit word, SPI, AD5535

J-M-L:
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);

Thanks a lot. Would you mind explaining the last two lines? What does the "*" doafter the uint32_t ? I am not so familiar with this.

Also, just to be sure: The SPI.transfer will split up the resultPtr in 3 bytes of 8bit? In which order (from left to right, or from right to left) are the bits split up?

totyped:
Also, just to be sure: The SPI.transfer will split up the resultPtr in 3 bytes of 8bit? In which order (from left to right, or from right to left) are the bits split up?

The SPI.transfer() function will transfer bytes starting at the address specified by the pointer provided as its first argument. It will then increment that pointer and continue transferring until the specified number of bytes (second argument) have been sent.

Your result variable is a uint32_t, so 4 bytes are used and This is stored somewhere in memory. The address of the first byte of that variable is what you get with the "address of" operator, that’s the &. So when I do &result I’m asking for the address of a uint32_t variable, which is what you call a pointer to the variable. Such pointers type is denoted with the star * notation: uint32_t * is the type representing this.

So this code

uint32_t* resultPtr = &result;

says let’s declare a variable named resultPtr which type is pointer to a uint32_t and let’s assign as a value to that pointer the address of our result variable.

The SPI.transfer() call can handle a byte but can also transfer multiple bytes in one go. For that you need to pass the address of the first byte and how many bytes you want to send starting from that address.

So when I do

SPI.transfer(resultPtr, 3);

I’m telling send the 3 consecutive bytes you will find starting at the address in memory resultPtr

The only catch is you built an uint32_t and you need to know how data is arranged in memory.

Say your result was the 4 bytes 0000.0000 0000.0000 0011.1001 1001.0000

computers/compilers have two ways for representing data that span multiple bytes: either the Lowest significant byte comes first in memory (little endian) or the Most significant byte comes first (big endian).

Here we have a little endian architecture so the memory looks like this

[color=red]1001.0000[/color]
[b][color=blue]0011.1001[/color][/b]
0000.0000
[b]0000.0000[/b]

So that’s good because the address (pointer) of the first byte of our result variable is where you have (or need to have) the right informatjon that needs to be sent first.

@totyped, please do not cross-post. Threads merged.

J-M-L:
Your result variable is a uint32_t, so 4 bytes are used and This is stored somewhere in memory. The address of the first byte of that variable is what you get with the "address of" operator, that’s the &. So when I do &result I’m asking for the address of a uint32_t variable, which is what you call a pointer to the variable. Such pointers type is denoted with the star * notation: uint32_t * is the type representing this.

So this code

uint32_t* resultPtr = &result;

says let’s declare a variable named resultPtr which type is pointer to a uint32_t and let’s assign as a value to that pointer the address of our result variable.

The SPI.transfer() call can handle a byte but can also transfer multiple bytes in one go. For that you need to pass the address of the first byte and how many bytes you want to send starting from that address.

So when I do

SPI.transfer(resultPtr, 3);

I’m telling send the 3 consecutive bytes you will find starting at the address in memory resultPtr

The only catch is you built an uint32_t and you need to know how data is arranged in memory.

Say your result was the 4 bytes 0000.0000 0000.0000 0011.1001 1001.0000

computers/compilers have two ways for representing data that span multiple bytes: either the Lowest significant byte comes first in memory (little endian) or the Most significant byte comes first (big endian).

Here we have a little endian architecture so the memory looks like this

[color=red]1001.0000[/color]

0011.1001
0000.0000
0000.0000




So that’s good because the address (pointer) of the first byte of our result variable is where you have (or need to have) the right informatjon that needs to be sent first.

Thank you so much for this awesome explanation! It's much clearer to me what you are actually doing now. Just a few remaining questions:

  1. Why do you have to pass a pointer to the SPI.transfer() function and not just the unit32_t variable directly?

  2. For my application (connecting Arduino to AD5535) I need to send the data MSB (Most significant bit first). This is why I shifted my 19bit word such that it comes first (from the left to right) in the 32bit datatype.
    i.e. 00111001 10010000 0000.0000 0000.0000

where the green bits represent the address of the channel (need to send them first)
and the blue bits represent the output voltage value that I want to have on that channel

Hence for my case, I would want a "big endian" way to send the data. How could that be achieved?

Thanks a lot !

for question #1 --> read the documentation of 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

Do you see a version of that function that knows how to send a uint32_t ?

careful that the potential returned info is stored in the buffer so you cannot rely on the calculated value after calling SPI.transfer

for question #2 --> the easiest way is to arrange the bytes in the right order in the first place, knowing your architecture is little endian.

Alternative is (for 24 bits) to simply clock out the 3 bytes without de-asserting the select. Since you know the your architecture is little endian, and you can have the pointer to the first byte (lets call it byte0), you want to send byte3, byte2 and byte1 in that order. so you would do

  uint32_t result; // assume that's your 19 bits
...
  uint8_t* resultPtr = (uint8_t* ) &result; // here we say we want a pointer to bytes, not uint32_t so we cast
  SPI.beginTransaction(mySpiSettings);
  digitalWrite (slaveAPin, LOW);
  SPI.transfer(resultPtr+3); // send MSB
  SPI.transfer(resultPtr+2); // send previous byte
  SPI.transfer(resultPtr+1); // send previous byte
  digitalWrite (slaveAPin, HIGH);
  SPI.endTransaction();

J-M-L:
for question #2 --> the easiest way is to arrange the bytes in the right order in the first place, knowing your architecture is little endian.

I have been thinking about that, but could not find a good method to implement it, other than flipping the bits of the 19bit word (such as done here: quickly "reversing" a byte?).

Do you know a convenient way of doing it? Here again a clarification of what I need to send:

I have the following variables that together constitute the 19bit word:

channel = 31 //11111 -> needs to be 5 bits
value = 800 //00001100100000 -> needs to be 14 bits

What I need to send to the Chip is the following word (MSB, from left to right):

First, the bits of the channel:

MSB: 1,
then 1,
then 1,
then 1,
then 1

... then, the bits of the value:

0,0,0,0,1,1, etc.

From the datasheet discussing the 68HC11

It is important to left justify the data in
the SPDR register so that the first 19 bits transmitted contain
valid data.

So, if you have this - (where A = address, d = data, x = don't care)

xxxxxxxxxxxxxAAAAAdddddddddddddd
byte 3 |byte 2 |byte 1 |byte 0 |

residing in a 32-bit variable you'll have to left shift to get the MSB to a byte boundary.

xxxxxxxxAAAAAddddddddddddddxxxxx
byte 3 |byte 2 |byte 1 |byte 0 |

Then arrange it so byte 2 is sent first.

dougp:
From the datasheet discussing the 68HC11

So, if you have this - (where A = address, d = data, x = don't care)

xxxxxxxxxxxxxAAAAAdddddddddddddd

byte 3 |byte 2 |byte 1 |byte 0 |





residing in a 32-bit variable you'll have to [left shift](https://www.arduino.cc/reference/en/) to get the MSB to a byte boundary. 




xxxxxxxxAAAAAddddddddddddddxxxxx
byte 3 |byte 2 |byte 1 |byte 0 |





Then arrange it so byte 2 is sent first.

Thanks a lot for this input and nice visualization, which makes it much easier to understand. >:(
A few questions:

  1. I always thought that left justification means: all value bits are shifted to the left.
    Hence:
    AAAAAddddddddddddddxxxxxxxxxxxxx
    byte 0 |byte 1 |byte 2 |byte 3 |

Is that wrong? Also, why do you start byte 0 on the right and not on the left (as I have done here). I am quite a beginner in this, so please bear with me. :confused:

  1. If I assume that your visualization of the left-shifted binary value is correct (and it probably is!), then if I follow your very good suggestion and effectively left-shift the variable to get the MSB to a byte boundary, I would still need to send 4 bytes to the Chip, right?
uint32_t*32bit_data_Pointe = &32bit_data;
SPI.transfer(32bit_data_Pointer, 4);

to send:
byte 3 |xxxxxxxx

byte 2 |AAAAAddd

byte 1 |dddddddd

byte 0 |dddxxxxx

Although... I am not quite sure if the order is correct. Based on the previous discussions, Arduino is a a little endian architecture, so LSB comes first in memory.

Hence, it might also be byte 0 , byte 1, byte 2, byte 3, ... but then that would not be the order that the chip wants the data to arrive (MSB)

what he meant was that the spec says you need to send something like this

AAAAAddd
dddddddd
dddxxxxx

so you can have AAAAA starting at bytebit 31 of your uint32_t or at bytebit 23 it does not really matter, what matters is that the byte holding AAAAA left aligned is really the first byte transferred.

if you want to use the buffer approach, since the Arduino is little endian, it means you need to be a bit smarter in constructing the uint32_t as it needs to read the opposite direction

xxxxxxxx.d[sub]11[/sub]d[sub]12[/sub]d[sub]13[/sub]xxxxx.d[sub]3[/sub]d[sub]4[/sub]d[sub]5[/sub]d[sub]6[/sub]d[sub]7[/sub]d[sub]8[/sub]d[sub]9[/sub]d[sub]10[/sub].A[sub]0[/sub]A[sub]1[/sub]A[sub]2[/sub]A[sub]3[/sub]A[sub]4[/sub]d[sub]0[/sub]d[sub]1[/sub]d[sub]2[/sub]

or it could be

xxxxxxxx.d[sub]2[/sub]d[sub]1[/sub]d[sub]0[/sub]xxxxx.d[sub]10[/sub]d[sub]9[/sub]d[sub]8[/sub]d[sub]7[/sub]d[sub]6[/sub]d[sub]5[/sub]d[sub]4[/sub]d[sub]3[/sub].A[sub]4[/sub]A[sub]3[/sub]A[sub]3[/sub]A[sub]1[/sub]A[sub]0[/sub]d[sub]13[/sub]d[sub]12[/sub]d[sub]11[/sub]

depending on what's expected (would need to read the spec how the data is provided)

EDIT: byte -> bit of course. thx @dougp

J-M-L:
so you can have AAAAA starting at byte bit 31 of your uint32_t or at byte bit 23 it does not really matter, what matters is that the byte holding AAAAA left aligned is really the first byte transferred.

:wink:

Seems I got byten bitten by the black dog :slight_smile: :grinning:

Yes indeed, bit not byte

totyped:
Also, why do you start byte 0 on the right and not on the left (as I have done here). I am quite a beginner in this, so please bear with me. :confused:

Whether bits or bytes it's customary to put the least significant on the right.

Maybe some help here.

Instead of getting wrapped around the axle with the endianness of a uint32_t, you could use an array of bytes:

  SPISettings mySpiSettings(3000000, MSBFIRST, SPI_MODE0);
  uint8_t bytesToSend[3];
  uint8_t address = 7;
  uint16_t voltage = 0xC80;

  bytesToSend[0] = (address << 3) & 0xF8;
  bytesToSend[0] |= (voltage >> 11) & 0x7;
  bytesToSend[1] = (voltage >> 3) & 0xFF;
  bytesToSend[2] = (voltage << 5) & 0xE0;

  SPI.beginTransaction(mySpiSettings);
  SPI.transfer(bytesToSend, 3);
  SPI.endTransaction();

gfvalvo:
Instead of getting wrapped around the axle with the endianness of a uint32_t, you could use an array of bytes:

  SPISettings mySpiSettings(3000000, MSBFIRST, SPI_MODE0);

uint8_t bytesToSend[3];
 uint8_t address = 7;
 uint16_t voltage = 0xC80;

bytesToSend[0] = (address << 3) & 0xF8;
 bytesToSend[0] |= (voltage >> 11) & 0x7;
 bytesToSend[1] = (voltage >> 3) & 0xFF;
 bytesToSend[2] = (voltage << 5) & 0xE0;

SPI.beginTransaction(mySpiSettings);
 SPI.transfer(bytesToSend, 3);
 SPI.endTransaction();

Thank you very much! :slight_smile: I implemented your code, but unfortunately, I was not able to change a voltage on any of the output channels. :frowning:
So, I had a closer looked at your code, line by line, and I have a few questions:
1.

bytesToSend[0] = (address << 3) & 0xF8;

& 0xF8 is not really needed, right?

bytesToSend[0] |= (voltage >> 11) & 0x7;

Here the same, do you really need & 0x7 ?

  1. I understand this line (no question here, just to make sure I understood it correctly)
bytesToSend[1] = (voltage >> 3) & 0xFF;

0-0-D1-D2-D3-D4-D5-D6-D7-D8-D9-D10-D11-D12-D13-D14

3
0-0-0-0-0-D1-D2-D3-D4-D5-D6-D7-D8-D9-D10-D11 & 00000000.11111111
D4-D5-D6-D7-D8-D9-D10-D11

bytesToSend[2] = (voltage << 5) & 0xE0;

Here I would get this:
0-0-D1-D2-D3-D4-D5-D6-D7-D8-D9-D10-D11-D12-D13-D14
<< 5
D4-D5-D6-D7-D8-D9-D10-D11-D12-D13-D14 -0-0-0-0-0 (&00000000.11100000)
0-0-0-0-0- D12-D13-D14
Don't we have to left shift this result? Hence:

(((voltage << 5) & 0xE0) << 5);

Also, a more general question: Since the Arduino is little-endian, don't we have to fill in the array in reversed order?
Thanks a lot for your help!

This is an array of byte, so bytes are ordered in the same way as the index. Little endian only matters for the way you represent multi bytes numbers.

For Q1 and Q2:
Q1: this is to build the address, the first & is useless since the location is overwritten anyway by the second write but it’s for completeness. You have this way 3 zero in lsb (F8 is 11111000 so you clear whatever is in the 3 least significant bits)

Q2: this is to get the right data in the empty 3 bits. The mask here is key to ensure you don’t mess with the address. (0x07 is 00000111)

Q2:
The mask stops you from shooting yourself in the foot if you accidently supply a voltage value > 14 bits. You could accomplish the same thing with 'voltage &= 0x3FF;' before starting.

Clamping the voltage at the maximum value seems like a far better choice.

J-M-L:
This is an array of byte, so bytes are ordered in the same way as the index. Little endian only matters for the way you represent multi bytes numbers.

For Q1 and Q2:
Q1: this is to build the address, the first & is useless since the location is overwritten anyway by the second write but it’s for completeness. You have this way 3 zero in lsb (F8 is 11111000 so you clear whatever is in the 3 least significant bits)

Q2: this is to get the right data in the empty 3 bits. The mask here is key to ensure you don’t mess with the address. (0x07 is 00000111)

Q2: What exactly do you mean by "mess with the address"? I'm not sure that I understand.
Q4: What about my last question? Don't we have to write...?

(((voltage << 5) & 0xE0) << 5);

gfvalvo:
Q2:
The mask stops you from shooting yourself in the foot if you accidently supply a voltage value > 14 bits. You could accomplish the same thing with 'voltage &= 0x3FF;' before starting.

I see! Indeed, this sounds useful. However, the Ad5535 chip will ignore any bits after the 14th bit.
Also, with

bytesToSend[1] = (voltage >> 3) & 0xFF;

we just grab part of the voltage value (D4-D11), hence I don't quite understand how this will avoid having a value >14. I think I am simply not quite understanding it. Would you mind explaining it a bit more detailed? Thank you so much!