Building an SPI Command with Arduino Due

From Visual Basic to GNU C, this is the place to talk programming.

Moderators: SecretSquirrel, just brew it!

Building an SPI Command with Arduino Due

Postposted on Mon Aug 19, 2013 8:06 am

I need to construct a 24bit command which will be sent out 8bits at a time over the Arduino Due's SPI interface to a MAX5318 (a DAC with 18bit resolution). The first 4 bits are a command, and the next 18bits are the value to output, and the last 2 bits ignored. And since I have no experience at bit twiddling, I wanted to see if you guys knew a better, more efficient way than my proposal, which is as follows:

I have a struct of 3 bytes (a byte is a datatype in Arduino language), and several functions that take a int (a 32bit 2's complement in the Arduino Due). Currently my code looks like this:

Code: Select all
dacSPIframe dac_writeDIN(int number){
dacSPIframe command;
bitWrite(command.first,7,0);
bitWrite(command.first,6,0);
bitWrite(command.first,5,0);
bitWrite(command.first,4,1);
bitWrite(command.first,3,bitRead(number,31)); //since this is two's complement, gotta copy the sign over
bitWrite(command.first,2,bitRead(number,17)); //crap, this means I only get 17bit resolution?
bitWrite(command.first,1,bitRead(number,16));
bitWrite(command.first,0,bitRead(number,15));
... and maybe two for loops here to save myself from writing extra lines, finishing off with...
bitWrite(command.third,1,0);
bitWrite(command.third,0,0);
return command;
}


My question is: there's got to be a better, more efficient way of doing this, right?
Mothership: Thuban 1055T@3.7GHz, 12GB DDR3, M5A99X EVO, GTX470+Icy Vision Rev.2@840/3800, Vertex 2E 60GB
Supply ship: Sargas@2.8GHz, 12GB DDR3, M4A88TD-V EVO/USB3
Corsair: Macbook Air Ivy Bridge
Crayon Shin Chan
Minister of Gerbil Affairs
 
Posts: 2246
Joined: Fri Sep 06, 2002 11:14 am
Location: Malaysia

Re: Building an SPI Command with Arduino Due

Postposted on Mon Aug 19, 2013 11:38 am

Crayon Shin Chan wrote:I need to construct a 24bit command which will be sent out 8bits at a time over the Arduino Due's SPI interface to a MAX5318 (a DAC with 18bit resolution). The first 4 bits are a command, and the next 18bits are the value to output, and the last 2 bits ignored. And since I have no experience at bit twiddling, I wanted to see if you guys knew a better, more efficient way than my proposal, which is as follows:

I have a struct of 3 bytes (a byte is a datatype in Arduino language), and several functions that take a int (a 32bit 2's complement in the Arduino Due). Currently my code looks like this:

Code: Select all
dacSPIframe dac_writeDIN(int number){
dacSPIframe command;
bitWrite(command.first,7,0);
bitWrite(command.first,6,0);
bitWrite(command.first,5,0);
bitWrite(command.first,4,1);
bitWrite(command.first,3,bitRead(number,31)); //since this is two's complement, gotta copy the sign over
bitWrite(command.first,2,bitRead(number,17)); //crap, this means I only get 17bit resolution?
bitWrite(command.first,1,bitRead(number,16));
bitWrite(command.first,0,bitRead(number,15));
... and maybe two for loops here to save myself from writing extra lines, finishing off with...
bitWrite(command.third,1,0);
bitWrite(command.third,0,0);
return command;
}


My question is: there's got to be a better, more efficient way of doing this, right?


Not familiar with the Arduino library calls, but perhaps something like this?

Code: Select all
union {
  int value;
  struct {
    byte byte3;
    byte byte2;
    byte byte1;
    byte byte0;
   } bytes;
}  DAC_cmd_value;

//"value" is an int representing the value to be passed to the DAC
//"cmd" is a byte representing the command to be passed to the DAC;

//This assumes that it is a positive integer that fits in 18 bits. 
//If it is signed and 0 represents half scale of the then you will need to scale the
//value appropriately.  You should also bounds check as the upper 14 bits are
//lopped off

//store the lower 18 bits of the value.
DAC_cmd_value.value=value & 0x3FFFF;
//store the cmd in front of the value (bits 18-21)
DAC_cmd_value.bytes.byte2|=cmd << 2
//Now shift everything to line up with the format the DAC expects
DAC_cmd_value.value<<=2;
SPI.transfer(DAC_cmd_value.bytes.byte2);
SPI.transfer(DAC_cmd_value.bytes.byte1);
SPI.transfer(DAC_cmd_value.bytes.byte0);


The above assumes that the int is stored big endian and that the DAC expects the data in that order. If the DAC expects an endianness different that what is using by the Arduino, then you will need to adjust the byte indexes, bit shifts, and masks as appropriate.
SecretSquirrel
Gerbil Jedi
Gold subscriber
 
 
Posts: 1719
Joined: Tue Jan 01, 2002 7:00 pm
Location: The Colony, TX (Dallas suburb)

Re: Building an SPI Command with Arduino Due

Postposted on Tue Aug 20, 2013 10:47 am

Code: Select all
union {
  int value;
  struct {
    byte byte3;
    byte byte2;
    byte byte1;
    byte byte0;
   } bytes;
}  DAC_cmd_value;

//"value" is an int representing the value to be passed to the DAC
//"cmd" is a byte representing the command to be passed to the DAC;

//This assumes that it is a positive integer that fits in 18 bits. 
//If it is signed and 0 represents half scale of the then you will need to scale the
//value appropriately.  You should also bounds check as the upper 14 bits are
//lopped off

//store the lower 18 bits of the value.
DAC_cmd_value.value=value & 0x3FFFF;
//store the cmd in front of the value (bits 18-21)
DAC_cmd_value.bytes.byte2|=cmd << 2
//Now shift everything to line up with the format the DAC expects
DAC_cmd_value.value<<=2;
SPI.transfer(DAC_cmd_value.bytes.byte2);
SPI.transfer(DAC_cmd_value.bytes.byte1);
SPI.transfer(DAC_cmd_value.bytes.byte0);


Let me get this straight:
DAC_cmd_value.value=value & 0x3FFFF;
This is a mask that lops off the more significant 14 bits of the 32bit number. byte3, although unused, is there so that the compiler won't complain about having to truncate a 32bit number into a 24bit number.
0000000 000000?? ???????? ????????

Now the int value within the union is no longer needed, so we refer to the bytes within the DAC_cmd_value union. DAC_cmd_value.value is gone forever, I assume?
DAC_cmd_value.bytes.byte2|=cmd << 2
How exactly does this line work? The cmd is first shifted to the left by 2:
0000//// becomes 00////00
ORed with byte2, which looks like 000000??, which ensures that the last two bits are preserved:
00////??

DAC_cmd_value.value<<=2;
Wait, I thought in a union you can only access the last written thing? How did value suddenly come back? In any case, I want to shift the bytes 0-2 to the left by two, not int value, which should be irrelevant at this point.
Mothership: Thuban 1055T@3.7GHz, 12GB DDR3, M5A99X EVO, GTX470+Icy Vision Rev.2@840/3800, Vertex 2E 60GB
Supply ship: Sargas@2.8GHz, 12GB DDR3, M4A88TD-V EVO/USB3
Corsair: Macbook Air Ivy Bridge
Crayon Shin Chan
Minister of Gerbil Affairs
 
Posts: 2246
Joined: Fri Sep 06, 2002 11:14 am
Location: Malaysia

Re: Building an SPI Command with Arduino Due

Postposted on Tue Aug 20, 2013 7:56 pm

Wait, I thought in a union you can only access the last written thing? How did value suddenly come back? In any case, I want to shift the bytes 0-2 to the left by two, not int value, which should be irrelevant at this point.


A union represents multiple ways to access the same set of bits in memory. There are a number of ways to access four consecutive bytes in memory: as an array of bytes, as an int, as a float, etc. I called out the bytes explicitly in a struct to make it more obvious which bytes you were dealing with, but you could also do the following, which is how I would actually write the code.

Code: Select all
union {
  int value;
  byte bytes[4];
} DAC_cmd_value;


The 32 bit int and the four byte array occupy the same address in memory.

DAC_cmd_value.value=value & 0x3FFFF;
This is a mask that lops off the more significant 14 bits of the 32bit number. byte3, although unused, is there so that the compiler won't complain about having to truncate a 32bit number into a 24bit number.
0000000 000000?? ???????? ????????


Correct, though most systems and compilers do not have a native 24 bit data type so you use a 32 bit size and mask off the top bits. You will get more efficient code once compiled if you do the masking this way though.
Code: Select all
DAC_cmd_value.value=value;
DAC_cmd_value.bytes.byte2&=0x03;

In reality, you don't have to mask the top eight bits here because, as you point out, you won't use them . You do have to ensure that bits 18-21 are zero in order for the bit operations that set the cmd value to work and it is just as easy to zero the rest of the bits in byte2 as well.

Now the int value within the union is no longer needed, so we refer to the bytes within the DAC_cmd_value union. DAC_cmd_value.value is gone forever, I assume?

Not quite. We just move from acting on all 32 bits to working with just bits 16-23. Later we move back to acting on all 32. The union allows you to do this, providing you and way to do the simplest operation possible. Setting the command value could be done with the int as follows.

Code: Select all
DAC_cmd_value.value|=cmd<<18;

The compiler will expand "cmd" from a byte to an int, shift it left 18 times (padding with 0s) then doing a bitwise or with value. On a system with a native 32bit data size, this is likley just as efficient as the example I gave. However, on the Arduino, the Atmel microcrontroller only supports 8bit data types which means the compiler will decompose the shift left 18 on a 32 bit value into a number of shifts and logical operations on each of the four bytes that make up the int. The example I gave keeps the compiler from having to do that and will generate much more efficient code.

How exactly does this line work? The cmd is first shifted to the left by 2:
0000//// becomes 00////00
ORed with byte2, which looks like 000000??, which ensures that the last two bits are preserved:
00////??

Exactly. Ensuring that the upper six bits of byte three were zero by applying the mask when we first set the value allows us to apply a bit pattern with a simple bitwise OR. As you note, when you shift left, the low bits are padded with 0s and the high bits are dropped.

DAC_cmd_value.value<<=2;
Wait, I thought in a union you can only access the last written thing? How did value suddenly come back? In any case, I want to shift the bytes 0-2 to the left by two, not int value, which should be irrelevant at this point.

We treat the collection of four bytes as an int again for our ease and sanity. In the end, we still ignore the top eight bits, but the compiler doesn't handle 24 bit numbers so we have to use the next largest size. Using your above nomenclature, before the shift, the value is 00000000 00////?? ???????? ????????. After the shift it is 00000000 ////???? ???????? ??????00. We ignore the high byte and the other three contain the bit pattern we need to send out.

As I noted before, the microcontroller on the Arduino doesn't understand 32 bit numbers, so the compiler is going to change that shift into something like this.
Code: Select all
byte3=byte3<<2 | byte2 >>6;
byte2=byte2<<2 | byte1 >>6;
byte1=byte1<<2 | byte0 >>6;
byte0=byte0<<2;

Much easier to understand and simpler to write using the union to treat all 32 bits as a chunk. However, if I were writing this as raw code for an 8 bit micro, I would use the latter and not include the shift and assignment for byte3 as I know we are never going to use it. That level of optimization is probably overkill for someone using the Arduino environment.

--SS
SecretSquirrel
Gerbil Jedi
Gold subscriber
 
 
Posts: 1719
Joined: Tue Jan 01, 2002 7:00 pm
Location: The Colony, TX (Dallas suburb)

Re: Building an SPI Command with Arduino Due

Postposted on Wed Aug 21, 2013 9:07 am

Just a note that I've seen lots of code that does this kind of thing by using bitfields in a union. Don't do that, it's not portable. As K&R says "Almost everything about fields is implementation-dependent".
notfred
Grand Gerbil Poohbah
 
Posts: 3742
Joined: Tue Aug 10, 2004 10:10 am
Location: Ottawa, Canada

Re: Building an SPI Command with Arduino Due

Postposted on Wed Aug 21, 2013 10:40 am

notfred wrote:Just a note that I've seen lots of code that does this kind of thing by using bitfields in a union. Don't do that, it's not portable. As K&R says "Almost everything about fields is implementation-dependent".


This is code that twiddles bits for hardware. It is, by definition, not portable. Even using a byte array in a union like this is inherently not portable. It depends entire on the endianness of the processor to work correctly.

For the OP, here is how you would do this with bitfields.

Code: Select all
union {
  byte bytes[4];
  struct {
    int                  :8;   //high eight bits to pad out to a 32 bit int.
    int cmd           :4;   //four bits for the command.
    int value         :18;  //18 bits for the DAC value.
    int                  :2;    //2 bits of padding
  } bitfield;
} DAC_cmd_value;

DAC_cmd_value.bitfield.value=value;
DAC_cmd_value.bitfield.cmd=cmd;
SPI.transfer(DAC_cmd_value.bytes[2]);
SPI.transfer(DAC_cmd_value.bytes[1]);
SPI.transfer(DAC_cmd_value.bytes[0]);


The bitfield definition sets the position and number of bits for each item. As notfred pointed out, this isn't portable across architectures or compilers, though neither is my original example. The above code is much shorter and if you understand bitfields, it certainly easy to read. However, it will not generate particularly efficient code. At best, the compiler is going to convert that into something close to the example I gave. If you are not lucky, it is going to be significantly longer and slower than my first examples once compiled. This is due to the fact that you are compiling for an 8 bit micro. On a 32 bit micro the bitfield version should be pretty close to as efficient as you could hand code. Especially if the processor doesn't support variable sized registers like the x86.

--SS
SecretSquirrel
Gerbil Jedi
Gold subscriber
 
 
Posts: 1719
Joined: Tue Jan 01, 2002 7:00 pm
Location: The Colony, TX (Dallas suburb)

Re: Building an SPI Command with Arduino Due

Postposted on Wed Aug 21, 2013 11:48 am

SecretSquirrel wrote:
notfred wrote:Just a note that I've seen lots of code that does this kind of thing by using bitfields in a union. Don't do that, it's not portable. As K&R says "Almost everything about fields is implementation-dependent".

This is code that twiddles bits for hardware. It is, by definition, not portable.

I meant even portable across compilers on the same hardware, or if you are very unlucky same compiler on same hardware with different optimisation options. Also there's no rule about packing for bitfields. Only place to use them is talking between two pieces of code on the same processor. Anything going out to the real world (talking to hardware, network packets etc) should be properly shifted and masked.
notfred
Grand Gerbil Poohbah
 
Posts: 3742
Joined: Tue Aug 10, 2004 10:10 am
Location: Ottawa, Canada

Re: Building an SPI Command with Arduino Due

Postposted on Wed Aug 21, 2013 1:53 pm

notfred wrote:
SecretSquirrel wrote:
notfred wrote:Just a note that I've seen lots of code that does this kind of thing by using bitfields in a union. Don't do that, it's not portable. As K&R says "Almost everything about fields is implementation-dependent".

This is code that twiddles bits for hardware. It is, by definition, not portable.

I meant even portable across compilers on the same hardware, or if you are very unlucky same compiler on same hardware with different optimisation options. Also there's no rule about packing for bitfields. Only place to use them is talking between two pieces of code on the same processor. Anything going out to the real world (talking to hardware, network packets etc) should be properly shifted and masked.


All true, and for the record, I use bitfields so infrequently that I have to look up syntax and rules almost every time I do. However, shifting and masking fall prey to many of the problems that bitfields do unless you are working on a data type that matches the native register size of the processor, mainly because the compiler turns bitfield access into a set of shifts and masks. The short version is that whenever you are accessing both an intrinsic datatype larger than a byte and some subset of that data type (whether bits, bytes, words, etc), you need to comprehend the endianess of your machine and your compiler's behavior. Both of these make the code un-portable without pre-processor conditionals.

To the OP:

Welcome to the world of low level C programming. Here be monsters!

--SS
SecretSquirrel
Gerbil Jedi
Gold subscriber
 
 
Posts: 1719
Joined: Tue Jan 01, 2002 7:00 pm
Location: The Colony, TX (Dallas suburb)

Re: Building an SPI Command with Arduino Due

Postposted on Wed Aug 21, 2013 6:09 pm

The Arduino Due has a ARM Cortex-M3, it's a 32bit CPU, so it's OK, and portability is not a problem - there's only one compiler for Arduino sketches. I like the bitfield example - it's concise, although somehow I get the feeling that it might cause really obscure bugs because it's not as explicit as your first example.

1. The "int" in the struct is only called an "int" so that one can say to the compiler that "this thing is meant to be treated as if it were an int", right?
2. Since we assume that the compiler will know to mask and shift stuff around so that the cmd and value will fit into the respective parts of the bitfield, the first byte of the union is never needed.
3. Arduino's int is a 2's complement. I will tell the DAC to accept a 2's complement value. Can I really trust that when cramming the input value into the 18 bits inside the struct, the compiler will know that it's a 2's complement and thus preserve the first bit and throw away the ones between the first bit and the last 18? I don't think so...
Mothership: Thuban 1055T@3.7GHz, 12GB DDR3, M5A99X EVO, GTX470+Icy Vision Rev.2@840/3800, Vertex 2E 60GB
Supply ship: Sargas@2.8GHz, 12GB DDR3, M4A88TD-V EVO/USB3
Corsair: Macbook Air Ivy Bridge
Crayon Shin Chan
Minister of Gerbil Affairs
 
Posts: 2246
Joined: Fri Sep 06, 2002 11:14 am
Location: Malaysia

Re: Building an SPI Command with Arduino Due

Postposted on Wed Aug 21, 2013 7:36 pm

Crayon Shin Chan wrote:3. Arduino's int is a 2's complement. I will tell the DAC to accept a 2's complement value. Can I really trust that when cramming the input value into the 18 bits inside the struct, the compiler will know that it's a 2's complement and thus preserve the first bit and throw away the ones between the first bit and the last 18? I don't think so...
Another of the pitfalls of the bitfield, you probably want unsigned integers rather than the default signed int. When I'm coding uint32_t is my default type.
notfred
Grand Gerbil Poohbah
 
Posts: 3742
Joined: Tue Aug 10, 2004 10:10 am
Location: Ottawa, Canada

Re: Building an SPI Command with Arduino Due

Postposted on Wed Aug 21, 2013 8:41 pm

Crayon Shin Chan wrote:The Arduino Due has a ARM Cortex-M3, it's a 32bit CPU, so it's OK, and portability is not a problem - there's only one compiler for Arduino sketches. I like the bitfield example - it's concise, although somehow I get the feeling that it might cause really obscure bugs because it's not as explicit as your first example.

1. The "int" in the struct is only called an "int" so that one can say to the compiler that "this thing is meant to be treated as if it were an int", right?
2. Since we assume that the compiler will know to mask and shift stuff around so that the cmd and value will fit into the respective parts of the bitfield, the first byte of the union is never needed.
3. Arduino's int is a 2's complement. I will tell the DAC to accept a 2's complement value. Can I really trust that when cramming the input value into the 18 bits inside the struct, the compiler will know that it's a 2's complement and thus preserve the first bit and throw away the ones between the first bit and the last 18? I don't think so...


1.) Actually, it does two things. It sets the intrinsic size of the bitfield. The C99 spec does allow for non int sized bitfields, and many compilers accept them though some with a warning about being non standard. On an 8 bit micro for example, using a char type for a bitfield would make sense, even if the compiler gave a warning about it. The other thing it does is set the bitfield as signed or unsigned. I actually made a small mistake in defining the bitfield. The cmd field should be defined as "unsigned int cmd :4;" This allows cmd to have a range of 0-16 and value to have a range of -131,072 to 131,071.

2.) The first bit is needed to ensure that the bytes line up with the int properly. You will need to determine the endian mode that the M3 is running in. See this page for a good description of how the bytes would line up in each mode. This matters whether you do the mask and shift explicitly or use bitfields. In the case of bitfields you will also need to determine if the compiler packs from the high bit down or the low bit up. This is what notfred was refering to about portability problems with bitfields. My example assumes the compiler packs from the high bit down, which is actually probably backwards, in which case you need to reverse the order of the fields.

3.) 2's compliment is sign extended. So long as you bounds check the value and insure that it is between -131,072 and 131,071 you can lop off bits 19-31 and have the same value as an 18bit signed value. -131072 in 32 bit 2's complement is 11111111 11111110 00000000 00000000. In 18 bits it is 10 00000000 00000000. 131071 in 32 bit 2's complement is 00000000 00000001 11111111 11111111. In 18 bits it is 01 11111111 11111111.

-SS
SecretSquirrel
Gerbil Jedi
Gold subscriber
 
 
Posts: 1719
Joined: Tue Jan 01, 2002 7:00 pm
Location: The Colony, TX (Dallas suburb)


Return to Developer's Den

Who is online

Users browsing this forum: No registered users and 0 guests