Shop OBEX P1 Docs P2 Docs Learn Events
Spin to C Translation Assistance, Please [SOLVED] — Parallax Forums

Spin to C Translation Assistance, Please [SOLVED]

JonnyMacJonnyMac Posts: 9,161
edited 2020-01-16 00:19 in Propeller 1
I have a Spin program that can extract five 16-bit Little Endian values from a byte array that is returned by a sensor. As Spin aligns variables of the same size in the order they're declared, I can update the five word variables with a single line.
    bytemove(@value1, @array[6], 10)

Am I correct in thinking that a C struct maintains the alignment of the member variables as they're declared, in other words, can I do this and now that the memory layout is variable1.lowbyte, variable1.highbyte, variable2.lowbyte, ...
struct block {
  uint16_t variable1;
  uint16_t variable2;
  uint16_t variable3;
  uint16_t variable4;
  uint16_t variable5;
};
And if that is the case, how can I move 10 bytes from array[6] to the structure? I have the K&R on my desk and am experimenting, but would appreciate some coaching from those with experience.

Thanks.


Comments

  • I think the order of variables in a struct isn't well defined in the standard (unless that changed recently), but almost all compilers will do what you think of. (unless you specifically tell it not to. GCC has __attribute__((pack)) to ignore the natural alignment of data types and I think there's also an attribute to specifiy the byte order for scalar types)
  • How about something like this:
    struct packed
    {
      unsigned short v1;
      unsigned short v2;
      unsigned short v3;
      unsigned short v4;
      unsigned short v5;
    } data;
    
    unsigned char array[10];
    
      
    int main()
    {
      array[0] = 1;
      array[1] = 0;
      array[2] = 2;
      array[3] = 0;
      array[4] = 3;
      array[5] = 0;
      array[6] = 4;
      array[7] = 0;
      array[8] = 5;
      array[9] = 0;
    
      memcpy(&data, array, 10);
      
      unsigned short v = data.v3;
      
      print("Value: %d\n", v);
    

    Mike
  • Yes, memcpy is the way to do it in C. And the struct elements are maintained in the same order in memory as they are declared.
  • TorTor Posts: 2,010
    edited 2020-01-15 14:11
    Yep, the order is as declared. However, C will use padding between elements to preserve alignment, where necessary. 4-byte variables will be aligned on a 4-byte bounday, 2-byte variables on a 2-byte boundary, and so on (at least that's what the compilers will do on an x86 platform, and many others. What it'll do for the Propeller someone else will have to comment. To avoid padding one would place the largest elements first, e.g.
    struct something {
      int varA;
      int varB;
      short varC;
      char varD;
    };
    
    Or, if GCC, use that non-standard "__attribute__((pack))" option. I prefer the more portable approach though.
  • JonnyMacJonnyMac Posts: 9,161
    edited 2020-01-15 20:11
    I am not a practiced C programmer, so I'm going to ask for some help (because I'm doing this for Ken Gracey and I want to get it finished for him).

    This if for the Pixy2 camera (used to be called CMU cam. It responds with a variable length array of bytes. In the case of a block (tracked target), the response will be six bytes (header) plus 14 bytes of block information -- I only care about the first 10 bytes which are unsigned, 16-bit values. Note that if the Pixy returns two targets, the stream will be 24 bytes: header (6) + block (14) + block (14), etc., up to the maximum number of blocks allowed to be returned in one call.

    In another case, I pass two pointers to a function and update the variables like this:
    int32_t pixy2_getResolution(uint16_t *fWidth, uint16_t *fHeight)
    {
      clearBuffer();
    
      serial_txChar(pixy2, 0xAE);
      serial_txChar(pixy2, 0xC1);
      serial_txChar(pixy2, REQUEST_RESOLUTION);
      serial_txChar(pixy2, 0x01);
      serial_txChar(pixy2, 0x00);
    
      recvPacket();
    
      if (validateChecksum() != RESULT_OK) {
        return RESULT_CHECKSUM_ERROR;
      }
      else {
        if (response[2] == RESPONSE_RESOLUTION) {
          *fWidth  = extract16(6);
          *fHeight = extract16(8);
          return RESULT_OK;
        }
        else
          return (int32_t)(0xFFFFFF00 | response[2]);
      }
    }
    
    This works great. I could duplicate this and pass five pointers for the block values, but that would make the function call unwieldy. I want to grab 10 consecutive bytes out of the stream based on the block (if multiples) that I want to extract.

    In my demo app I have these variables defined:
    uint16_t  signature;
    uint16_t  xpos;
    uint16_t  ypos;
    uint16_t  width;
    uint16_t  height;
    

    This is the function that I'm attempting to use but it's giving me grief.
    int32_t pixy2_extractBlock(uint8_t i, uint16_t *block)
    {
      uint8_t ofs = 6 + (i * BLOCK_SIZE);
    
      if (response[2] == RESPONSE_BLOCKS) {
        memcpy(*block, response[ofs], 10);
        return RESULT_OK;
      }
      else
        return RESULT_ERROR;
    }
    
    The program tells me I have blocks, but the data always comes back as 0.

    Note that the compiler complains, too -- this is the output.
    pixy2.c: In function 'pixy2_extractBlock':
    pixy2.c:283:5: warning: passing argument 1 of 'memcpy' makes pointer from integer without a cast [enabled by default]
    c:\program files (x86)\simpleide\propeller-gcc\bin\../lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/include/string.h:11:8: note: expected 'void *' but argument is of type 'uint16_t'
    pixy2.c:283:5: warning: passing argument 2 of 'memcpy' makes pointer from integer without a cast [enabled by default]
    c:\program files (x86)\simpleide\propeller-gcc\bin\../lib/gcc/propeller-elf/4.6.1/../../../../propeller-elf/include/string.h:11:8: note: expected 'const void *' but argument is of type 'uint8_t'
    I doesn't like what I'm doing with memcpy, but I don't understand the error messages vis-a-vis type arguments.

    Again, C is not my normal language -- please be patient and gentle with me.

  • David BetzDavid Betz Posts: 14,516
    edited 2020-01-15 20:56
    JonnyMac wrote: »
        memcpy(*block, response[ofs], 10);
    
    You'll want to get rid of the "*" before "block". The expression *block dereferences the pointer and gives you the first int16_t value in the array. Just using block by itself gives the address of the array which I imagine is what you want.
        memcpy(block, &response[ofs], 10);
    

    Edit: You also need & in front of response[ofs] to get the address of that array element.

  • Well, that's getting closer, but not there yet. As you can see by the screen caps, I get the correct values in Spin. Still struggling with doing the same thing in C.

    Do I need to change the way I'm defining my block variables in C? ATM, they are consecutive words -- just as in my Spin program.
    1920 x 1040 - 137K
    1920 x 1040 - 294K
  • JonnyMac wrote: »
    Well, that's getting closer, but not there yet. As you can see by the screen caps, I get the correct values in Spin. Still struggling with doing the same thing in C.

    Do I need to change the way I'm defining my block variables in C? ATM, they are consecutive words -- just as in my Spin program.
    I don't see where you use these values:
    uint16_t  signature;
    uint16_t  xpos;
    uint16_t  ypos;
    uint16_t  width;
    uint16_t  height;
    
    I also don't see the definition of this function:
          *fWidth  = extract16(6);
    
    Maybe you could post all of your code?
  • JonnyMacJonnyMac Posts: 9,161
    edited 2020-01-16 00:33
    I think the problem is solved. The end goal was to be able to pass a block index and pointer to a group of 5 words (block variables) as I do in Spin.

    I put the block variables into a struct to force their order in memory:
    struct block {
      uint16_t signature;
      uint16_t xpos;
      uint16_t ypos;
      uint16_t width;
      uint16_t height;
    } b;
    

    Here's the updated function that moves the values from in receive buffer to the block variables. Again, a buffer can contain more than one block, hence the use of an index.
    int32_t pixy2_extractBlock(uint8_t i, uint16_t *block)
    {
      uint8_t ofs = 6 + (i * BLOCK_SIZE);
    
      if (response[2] == RESPONSE_BLOCKS) {
        memcpy(block, &response[ofs], 10);
        return RESULT_OK;
      }
      else
        return RESULT_ERROR;
    }
    

    Here's the little test loop that requests and displays blocks. All seems well now.
    while(1) {
        blockCount = pixy2_getBlocks(SIG_MAP, MAX_BLOCKS);
        print("%d blocks detected\n\n", blockCount);
        if (blockCount > 0) {
          for (i = 0; i < blockCount; i++) {
            pixy2_extractBlock(i, &b.signature);
            print("sig: %1d  x: %3d  y: %3d  w: %3d  h: %3d\n", b.signature, b.xpos, b.ypos, b.width, b.height);
          }
          print("\n");      
        }    
        pause(1000);
      }
    
    1920 x 1040 - 302K
  • I'm glad you were able to resolve your issue. Congratulations!
Sign In or Register to comment.