chipKIT® Development Platform

Inspired by Arduino™

Parsing with Union and variable types

Created Wed, 02 Mar 2016 15:43:59 +0000 by FredCailloux


FredCailloux

Wed, 02 Mar 2016 15:43:59 +0000

I am using the following part of sketch to extract a long type value (32 bits) from an array of 5 unsigned char type. Using DSPI1 on a proMX7 board JE I connect an LS7366 chip via SPI communication. The chip transfer a 5 bytes chunk of data representing the Quadrature encoder counter value. Only the 4 last bytes are relevant, hence, only bytes array[1][2][3][4] must take part in "building" the long type value as a variable called lngEncoderValue. My sketch will compile OK and upload to the board but when I run the encoder the only value I read on my display unit is from 0 to 255. It is obvious that the 3 most significant bytes from the extracted value are missing. The encoder chip is known to work fine after some previously successful less elegant codes. I am trying to render thing more swift and quick by using the Union C++ capability. Somehow I am missing something and just cant get to grasp what is going wrong here.

// LS7366 Quadrature Encoder Test Sketch
union LSstructure { unsigned char Ob40[5]  ; struct { unsigned char Ob8      ;
                                                               long lngCount ; } LSmiso ;
    } LStransfer ;
unsigned char * pOb40 = LStransfer.Ob40    ; // pointer to Ob40 array 5 elements
void loop() { getEncoderValue ; delay(500) ; }

void	getEncoderValue() {
	myDSPI1.setSelect (             LOW ) ;
	myDSPI1.transfer  ( 5, pMOSI, pOb40 ) ;
	myDSPI1.setSelect (            HIGH ) ;
lngEncoderValue = LStransfer.LSmiso.lngCount ; 
myDisplay ( lngEncoderValue , , ) ; } // The display will only show value from 0 to 255 and go back to 0 and vice versa

Looking at how the Union is built I assume that whatever goes in the Ob40[] array will distribute throughout both variables of the structure Ob8 and lngCount. Somehow, the idea is working, but for some peculiar reason the long lngCount is not getting 3 out of 4 bytes from Ob40[]. Thank you for your hints and help on this one. I really am puzzled here :?


majenko

Wed, 02 Mar 2016 15:55:41 +0000

You most probably need to pack the union because you're not working with word-aligned data.

union LSstructure { 
    unsigned char Ob40[5];
    struct { 
        unsigned char Ob8;
        long lngCount; 
    } __attribute__((packed)) LSmiso;
} __attribute__((packed)) LStransfer;

Personally I prefer an anonymous-struct-in-a-union-in-a-struct arrangement. It means you can then also add other values in there as well if you wish that aren't part of the union. Also by keeping the inner struct anonymous you can then just access myData.data or myData.value

struct LSData {
    union {
        uint8_t data[5];
        struct {
            uint8_t unused;
            uint32_t value;
        } __attribute__((packed));
    } __attribute__((packed));
} __attribute__((packed));

struct LSData myData;

FredCailloux

Wed, 02 Mar 2016 17:13:54 +0000

Thank you Majenko, brilliant as usual :)

However, for some reason it doesn't work for my code. Don't know why? did some reading. It sure does make sense in the actual situation, but no success. Although the behavior drastically changed. I changed my code to investigate what were the values in the 5 bytes array. I can see that all 4 bytes constituting the long union number lngCount, those 4 bytes are getting the proper values from the LS7366 reading. I can even calculate the final value of lngCount. I am getting the proper 4 bytes whether I use the attribute((packed)) option it make no difference, I get the 4 bytes properly. However, what I observe when I use the attribute is that instead of my display showing only the value of 1 byte, now it display the "Error" word. So, whatever this attribute((packed)) feature is doing to my code it actually messes up the LKM1638 display board.

Any hints :?:


majenko

Wed, 02 Mar 2016 23:41:09 +0000

You might have a problem with endianness. Your 4 bytes might not be i the right order for a pic32 word.

Sent from my SM-T555 using Tapatalk


FredCailloux

Thu, 03 Mar 2016 18:42:14 +0000

After investigating each byte value I now can be assured that it is indeed an Endian problem. Thanks for the tip and for the never ending C++ education I am getting from you and this forum. Now the question is: How am I going to correct the situation? Not that I am fishing for quick ready meal answer ( I am going to cook this myself ), the fact is I am contemplating two approaches here. First, instead of using Structure and Union to parse the data I may just decide to go back to the original solution that was simply to use calculations based of 4 seperate bytes such as

myLong    =    Byte1 * 2^24   +   Byte2 * 2^16   +   Byte3 * 2^8   +   Byte4 ;

This is a simple way of getting my number. But I like things that are fast, efficient and elegant so I am also contemplating the second approach which is the use of Structure and Union. For which I foresee the following approach for rearranging the bytes. It look like this:

long RevEndian(uint8_t* i) { // i is a pointer to the array of 5 bytes
i++ ; pointer to the second of 5 elements
return (*i++&0xf)<<24 | (*i++&0xf)<<16 | (*i++&0xf)<<8 | (*i & 0xf ; }

I do not know what kind of burden that second approach will have on the μController. Is the processor going to use as many manipulation cycles as 24 + 16 + 8 = 48 shift cycles. It seems to me a lot of work for just getting one long number. Or is the processor going to work in bunches of 8bits, in such case this approach will necessitate only 6 shifts cycles. If the choice is limited between 6 x 8bits shifts or a math calculation then I need to determine which of the two solutions will impose the least burden on the μController. Can you please guide me here on how I can find which or the two approaches would be the most efficient ?


majenko

Thu, 03 Mar 2016 21:37:02 +0000

I don't know what the most efficient of those would be, but the method I usually use when I want to combine manually is:

uint32_t out = (a[3] << 24) | (a[2] << 16) | (a[1] << 8) | a[0];

Of course, using whatever order you need to use to make the right value.

However, if you have a big-endian number stored in a 32-bit variable you can swap the whole thing round using the compiler's builtin bswap32 function:

uint32_t swapped = __builtin_bswap32(bigendian);

So you could combine that with your union if you wanted.