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.
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.
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.
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.
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