This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

problem with casting for arm compiler

Hi all
I have a strange problem when I'm trying to cast buffers by arm compiler.
Here an example:

char* pbuf;
...
unsigned short val = *((unsigned short*)(pbuf+3));


When I compile this code, my generated assembly code uses LDRH,STRH. Then because my pbuf+3 is not multiple of 2 and those 2 instruction can only work with even value addresses, processor hangs up.
What I should to do that compiler generates a code that works in all cases?

Regards

  • you can always use pointers to a char, and assemble your value step by step. but any way, your processor should not hang in case of an un-aligned access (ARM7 and Cortex M). The LDR/STR instructions have a specific behavior when working with un-aligned addresses. refer to your assembly manual, but in any case it is not a good idea!

  • So don't do that, then!

    It is always risky to assume that you can just cast any arbitrary pointer to any other arbitrary type - precisely because this is likely to fall foul of the alignment rules of the underlying architecture.

    It is not a "strange" problem - it is rather to be expected!

    If you've got used to doing this on an 8-bit chip, then it has only been working by luck - because 8-bit processors just happen not to care about any alignment!

    You really need to do this "properly": pull the individual bytes out of the buffer and build them into the larger datatype.

    Either that, or you will have to somehow ensure that the bytes in your buffer are properly aligned to be accessed as the larger data type...

  • "...it has only been working by luck..."

    Not sure I would class it as 'luck' - When the situation, dangers and consequences are understood, it's use is extremely liberating.

  • In answer to the OPs situation - Look at the __packed keyword.

    But remember, look into and understand the consequences.

  • it's use is extremely liberating

    And not portable, as you well know, Sausage man.

  • "the situation, dangers and consequences are understood"

    Yes, when those things have been understood & taken into account, it will work as intended; it will work by design - even though it's probably not a portable design.

    But, in this case, it seems fairly clear that they have not been understood &/or taken into account; therefore anything that just happens to work despite this oversight is working by luck - not by design.

  • "And not portable, as you well know, Sausage man."

    It is not ***guaranteed*** to be portable. There is a (subtle) difference.

    The projects on which I've used it were on 51s, Z80s, V55, 186s, 386s. No problems with portability of the relevant modules there.

    It made data manipulation and storage far more efficient.

    I'm now porting code to the ARM. Looked into using the __packed keyword, but probably won't use it because ARM has such extreme raw processing power.

    The key to this, like nearly every other embedded task, is a thorough understanding of the low level operation of the processors.

  • An issue with ARM chips is that the memory controller can be different between different manufacturers and different models.

    Some memory controllers generates exceptions for invalid accesses. Some do not. A single ARM chip can even have multiple memory regions with different behaviour.

    If not playing with packet data, then the source code should explicitly convert two bytes into a 16-bit value. The compiler will produce the optimum code for it, even if the C code looks ugly with the shifts and the or.

  • "The compiler will produce the optimum code for it, even if the C code looks ugly with the shifts and the or."

    My (limited but growing) experience of the Keil ARM toolset certainly suggests that is the case. Resorting to hand-crafted assembler is now an increasingly rare requirement.

    With some other compilers I have used (including Keil's C51), I would say that this is not so true.

  • Thanks for all answers.
    It seems that need to remove all uses of 'unsigned short' from my code,because I cannot understand when I will reach an odd value address...
    This will increase my code a lot...:(
    I am analyzing data in TLV format,so I cannot prevent 'unsigned short' data in odd addresses...:(

    Regards

  • No, that is an incorrect conclusion!

    "I am analyzing data in TLV format, so I cannot prevent 'unsigned short' data in odd addresses"

    either investigate the use of the 'packed' attribute, or extract the bytes separately to build into a larger-sized type.

  • I have an I2C FRAM, I read data from it with the below code. And the code works.

    But after reading this thread, I am not so sure about that, if my code just works by luck? Do I need to use shift and or to handle this?

    (Performance is not an issue to me.)

    union {
        int  Data_int;
        char Data_char[4];
    } Counter_tmp;
    
    
    void FRAM_Reader( void )
    {
        int i, j, index;
    
        Dummy_Write = 1;
        I2C_M2S_SAddr = SLAVE_ADDR;
        I2CEngine();
    
        if ( (I2C_M2S_DatBuf[0] == 'H') && (I2C_M2S_DatBuf[1] == 'i') &&
             (I2C_M2S_DatBuf[2] == 'd') && (I2C_M2S_DatBuf[3] == 'e') )
        {
            index = 4;
            for ( i = 0; i < 6; i++ )
            {
                for ( j = 0; j < 4; j++ )
                {
                    Counter_tmp.Data_char[j] = I2C_M2S_DatBuf[index++];
                }
                The_DI_Counter[i] = Counter_tmp.Data_int;
            }
        }
        else
        {
            FRAM_Writer();
        }
    }
    

  • My FRAM_Writer:

    void FRAM_Writer( void )
    {
        int i, j, index;
    
        I2C_M2S_DatBuf[0] = 'P';
        I2C_M2S_DatBuf[1] = 'K';
        I2C_M2S_DatBuf[2] = 'G';
        I2C_M2S_DatBuf[3] = 'O';
    
        index = 4;
        for ( i = 0; i < 6; i++ )
        {
            Counter_tmp.Data_int = The_DI_Counter[i];
            for ( j = 0; j < 4; j++ )
            {
                I2C_M2S_DatBuf[index++] = Counter_tmp.Data_char[j];
            }
        }
    
        Dummy_Write = 0;
        I2C_M2S_SAddr = SLAVE_ADDR;
        I2CEngine();
    }
    

  • The compiler will align the union in relation to the member that has the highest alignment requirements. So the int will be properly aligned.

    Your program will store data in the EEPROM in the byte order for the processor and since the EEPROM is not likely to be moved to a different processor with different size of an int or different byte order, you should be fine.

    The problem for the OP was that he used a typecast for a data type larger than a char, when the pointer could be on an odd address.

    So while your code will store different data into the EEPROM if moved between a little-endian and big-endian processor, or between a 16-bit, 32-bit or 64-bit processor, you should be fine. Using a union to get/set bytes from an integer is just an alternative way to using shifts and and/or operations.

  • Hi Per,

    Many thanks to your explanation.

    I remember that I had read some other threads discussing the data alignment issues, and I did encountered a data alignment problem, and fixed that problem with the hints from the KEIL forum.

    I think that I must mix something up, and then got confused.

    Bit field is something implementation dependent.
    Data alignment rule within a struct or union is defined by C-Standard or Processor-Architecture.
    __packed is a feature that KEIL provides.

    These stuff are really difficult to me.