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

Ram corruption

Hello,

I have the following piece of code:

static void SETBIT( Pcf8574 xdata *Object, Nat8 Pin, Nat8 Active )
{
    ASSERT( ( 0 <= Pin )
          &&( Pin <= 7 )
          );

    ASSERT( ( Active == TRUE )
          ||( Active == FALSE )
          );

    if ( (Bool)Active )
    {
        Object->Buffer |= (_crol_( 0x01, Pin ) & 0xFF);
    }
    else
    {
        Object->Buffer &= (~_crol_( 0x01, Pin ) & 0xFF);
    }

    WriteI2c( Object );
}

This function receives a pointer to an object (object is a structure with two elements of type unsigned char). The object is located in XDATA, hence the memory specifier.

After some processing on the object (changing a bit in the Buffer member of the structure), the object is passed as an argument to the function WriteI2c().
The prototype of this function is:
static void WriteI2c( Pcf8574 xdata *Object ).

When the function SETBIT is called, the address of Object is (in my case) X:0x000002 and points to location X:0x00014D in Ram, where my array of structures is located (in the example given, the pointer points to the second element of the array).

Up until the call to WriteI2c( Object );, everything is ok. However, when I enter the function WriteI2c(), all of a sudden the address of the object Object is changed from X:0x000002 to D:0x04 (still points to the Ram location X:0x00014D). How can this change of memory space from XDATA to DATA be explained for the address of Object???.

I also encounter another problem:

Below, you can find the implementation of the function WriteI2c():

static void WriteI2c( Pcf8574 xdata *Object )
{
    i2c_swi2c_Write( Object->I2cAddress
                   , &( Object->Buffer )
                   , 1
                   );
}

The assembly code of this function is:

   129: static void WriteI2c( Pcf8574 xdata *Object )
C:0x45AC    8F82     MOV      DPL(0x82),R7
C:0x45AE    8E83     MOV      DPH(0x83),R6
   130: {
C:0x45B0    E0       MOVX     A,@DPTR
C:0x45B1    FF       MOV      R7,A
C:0x45B2    AC83     MOV      R4,DPH(0x83)
C:0x45B4    AD82     MOV      R5,DPL(0x82)
C:0x45B6    ED       MOV      A,R5
C:0x45B7    2401     ADD      A,#0x01
C:0x45B9    FD       MOV      R5,A
C:0x45BA    E4       CLR      A
C:0x45BB    3C       ADDC     A,R4
C:0x45BC    FA       MOV      R2,A
C:0x45BD    A905     MOV      R1,0x05
C:0x45BF    7B01     MOV      R3,#0x01
C:0x45C1    90000A   MOV      DPTR,#0x000A
C:0x45C4    7401     MOV      A,#0x01
C:0x45C6    F0       MOVX     @DPTR,A			<===!!!
C:0x45C7    0231D2   LJMP     i2c_swi2c_Write(C:31D2)

This function also gives me problems: when the command on the line indicated by "<===!!!" is executed, I get a Ram corruption.

Earlier in the process, there has been declared a buffer in XDATA as follows:
xdata Nat8 buf[ 40 ];.

This buffer is filled with the result of the following command:
sprintf( buf, "Date: %s - Time: %s", __DATE__, __TIME__ );

This buffer is (again in my situation) located on address X:000008. When the line with the indication above is executed, it overwrites (among other locations) the content of my buffer on address X:0x00000A.

What happens is that the value #0x000A is loaded into the DPTR (third last assembly line). After that, the value 0x01 is loaded into A and then the content of A is written to locaion X:0x00000A via a MOVX instruction, so to XDATA.
But I have a buffer which is already assigned to this memory location!!!
"buf" is positioned between the locations X:0x000008 and X:0x000038, so why is the code generating instructions to write on location X:0x00000A (so, overwriting other memory areas)??? Can someone explain this? To my humble opinion, this should not happen!

Also creating temporary variables like given in the code below, doesn't solve the issue:
static void WriteI2c( Pcf8574 xdata *Object )
{
    Nat8 TempAddress = Object->I2cAddress;
    Nat8 TempData    = Object->Buffer;

    i2c_swi2c_Write( TempAddress
                   , &TempData
                   , 1
                   );
}

I've looked already for hours to this problem but can't find neither an explanation nor a solution.

Any help is highly appreciated.

Rgds,

Geert

PS.: uVision2, compiler = V7.02b

Parents
  • Up until the call to WriteI2c( Object );, everything is ok. However, when I enter the function WriteI2c(), all of a sudden the address of the object Object is changed from X:0x000002 to D:0x04 (still points to the Ram location X:0x00014D). How can this change of memory space from XDATA to DATA be explained for the address of Object???.

    Probably because there is more than one object named object. The argument to the WriteI2C function is stored in registers which are in data memory.

    Your second problem appears strange. It appears that the last argument passed to the i2c_swi2c_Write function is being stored in that function's argument space in XDATA. You can find out where this is located by looking for the ?XD?filename?I2C_SWI2C_WRITE symbol. Its address will probably be something like 0x000A.

    Actually, there's not enough information to really figure out what's going on here. If you continue having problems you may want to contact technical support.

    Jon

Reply
  • Up until the call to WriteI2c( Object );, everything is ok. However, when I enter the function WriteI2c(), all of a sudden the address of the object Object is changed from X:0x000002 to D:0x04 (still points to the Ram location X:0x00014D). How can this change of memory space from XDATA to DATA be explained for the address of Object???.

    Probably because there is more than one object named object. The argument to the WriteI2C function is stored in registers which are in data memory.

    Your second problem appears strange. It appears that the last argument passed to the i2c_swi2c_Write function is being stored in that function's argument space in XDATA. You can find out where this is located by looking for the ?XD?filename?I2C_SWI2C_WRITE symbol. Its address will probably be something like 0x000A.

    Actually, there's not enough information to really figure out what's going on here. If you continue having problems you may want to contact technical support.

    Jon

Children

  • all of a sudden the address of the object Object is changed from X:0x000002 to D:0x04


    Remember that 8051 registers also show up i the internal memory map. D:0x04 is the same as R4 (bank 0). This just sounds like your pointer, normally stored in X:0x0002, gets moved to R4/R5 when passed into the function.

    What's the Pcf8574 (Object) structure look like? And what's the function prototype for i2c_swi2c_Write? I agree with Jon that this code looks like setup for the call to i2c_swi2c_Write. See detailed comments below.

       129: static void WriteI2c( Pcf8574 xdata *Object )
    
    ; Object passed in R6/R7.  Move to DPTR and
    ; read first byte of Object into R7.
    ; I presume this is Object->I2CAddress, and
    ; it's set up for the 1st arg to
    ; i2c_swi2_Write()
    
    C:0x45AC    8F82     MOV      DPL(0x82),R7
    C:0x45AE    8E83     MOV      DPH(0x83),R6
       130: {
    C:0x45B0    E0       MOVX     A,@DPTR
    C:0x45B1    FF       MOV      R7,A
    
    ; now, set up arg 2.  This code takes DPTR,
    ; which is Object, and adds 1, which is
    ; presumably the offset of some field in
    ; the structure.
    ; I assume it's Object->Buffer
    
    C:0x45B2    AC83     MOV      R4,DPH(0x83)
    C:0x45B4    AD82     MOV      R5,DPL(0x82)
    C:0x45B6    ED       MOV      A,R5
    C:0x45B7    2401     ADD      A,#0x01
    C:0x45B9    FD       MOV      R5,A
    C:0x45BA    E4       CLR      A
    C:0x45BB    3C       ADDC     A,R4
    
    ; Here, we're setting up a generic pointer
    ; argument in R1-R3.  R3 is the type byte;
    ; R2-R1 the value.  So this is a pointer
    ; to X:0005.
    
    C:0x45BC    FA       MOV      R2,A
    C:0x45BD    A905     MOV      R1,0x05
    C:0x45BF    7B01     MOV      R3,#0x01
    
    ; a one-byte arg three ("1") would normally
    ; be passed in R3.  We've already got that
    ; generic pointer using R3, so this arg
    ; gets passed in memory (x:000A).
    
    C:0x45C1    90000A   MOV      DPTR,#0x000A
    C:0x45C4    7401     MOV      A,#0x01
    C:0x45C6    F0       MOVX     @DPTR,A
    
    C:0x45C7    0231D2   LJMP     i2c_swi2c_Write(C:31D2)
    

    The problem with this analysis is that I've got an extra parameter in there that doesn't show up in your example call:

        i2c_swi2c_Write( Object->I2cAddress
                       , &( Object->Buffer )
                       , 1
                       );
    

    Object->I2cAddress should be passed in R7 (if it's a byte), &(Object->Buffer) in R4/R5 (or R1-R3 if it's prototyped as generic or far), and "1" in R3 (unless arg2 is generic, in which case it gets bumped out of the registers).

    It looks like the second arg must be generic. But then I can't see what's getting set up in R4/R5.

    You're writing all your xdata addresses as 24 bits, which makes me think you've got some sort of bank-switching scheme going on, or an extended 8051 variant. That may be part of the problem. If the bank switching isn't configured correctly, so that the compiler doesn't realize that the X:000A it's stuffing a parameter in is really the same address as your X:0008-X:0038 buffer.

  • Hello Drew, Jon,

    As asked by both of you, I'll try to put the problem a little bit more in its context. But it'll be a very long message, so sorry for that.

    First of all, since the total amount of characters per message is limited to 7500, I had to split up the answer in 3 parts. See all 3 parts for the complete picture. Sorry for that inconvenience...

    Next, I'm compiling for the target General 8032 (All variants), so just an 'ordinary' 8032 without lots of extra fancy things.

    Last, I've learned quite a bit from the explanation you both guys gave me.
    Especially the detailed analyzing of the function WriteI2c() was very instructive...

    Now back to the problem:
    I'm currently developing a domotics system and I'll need approx. some 10 IO-expanders of the type PCF8574 (I2c-controllable). I absolutely want to avoid having the same code 10 times, so I've read some lecture about how to do a little bit of C++ in C.
    I've read the book "Langage C norme ANSI vers une approche orientée objet".
    It's a French book, written by Philippe Drix.

    In this book, I found the (or should I say: a) solution to my problem. The author proposes a solution by means of a structure of function pointers.

    So, for my PCF8574, I created the following structure in a file called Pcf8574.h:

    extern struct _PCF8574
    {
        Pcf8574 xdata *( *const New     )( Nat8 I2cAddress, Nat8 DefaultMask );
    
        void           ( *const TurnOn  )( Pcf8574 const xdata *Object );
        void           ( *const TurnOff )( Pcf8574 xdata *Object );
    
        void           ( *const SetBit  )( Pcf8574 *Object, Nat8 Pin, Nat8 Active );
        Bool           ( *const GetBit  )( Pcf8574 const xdata *Object, Nat8 Pin );
    
        void           ( *const SetByte )( Pcf8574 xdata *Object, Nat8 Data );
        Nat8           ( *const GetByte )( Pcf8574 const xdata *Object );
    } Pcf8574Func;
    
    Nat8 being a typedef of unsigned char...
    Just before struct _PCF8574, the structure and typedef given below are declared:
    struct _Pcf8574;
    typedef struct _Pcf8574 Pcf8574;
    
    Thus, I have 7 functions to implement in the corresponding Pcf8574.c-file and then later on map them on the function pointers in the structure.

    One of the most important functions will be the New function, because that will return me a pointer to an object of type struct _Pcf8574 (mind the underscore...).

    The structure struct _Pcf8574 is 'hidden' in the corresponding c-file, according the following definition:
    struct _Pcf8574
    {
        Nat8 I2cAddress;
        Nat8 Buffer;
    };
    
    The first member contains the I2cAddress of a certain IO-expander, while the second member contains the data for the 8 IO-pins of the corresponding expander (so, your assumptions were right, Drew).

    Since there is a max. of 16 Pcf8574 IO-expanders possible without extra hardware, I've foreseen immediately a file-scope array m_MyVarArray, which is an array of 16 Pcf8574 structures (that means: of type struct _Pcf8574):
    static Pcf8574 xdata m_MyVarArray[ 16 ];
    
    This array is defined in Pcf8574.c and is located in XDATA, as given by the memory specifier.

    As already mentioned, I have to implement the 7 functions in the c-file. The prototypes of the 7 static functions are given below:
    static Pcf8574 xdata *NEW( Nat8 I2cAddress, Nat8 DefaultMask );
    static void TURNON( Pcf8574 const xdata *Object );
    static void TURNOFF( Pcf8574 const xdata *Object );
    static void SETBIT( Pcf8574 xdata *Object, Nat8 Pin, Nat8 Active );
    static Bool GETBIT( Pcf8574 const xdata *Object, Nat8 Pin );
    static void SETBYTE( Pcf8574 xdata *Object, Nat8 Data );
    static Nat8 GETBYTE( Pcf8574 const xdata *Object );
    
    There's also the mapping of the functions in the c-file to the function pointers in the structure extern struct _PCF8574 (see above) declared in the header file.
    The mapping is given below:
    struct _PCF8574 Pcf8574Func =
    {
        NEW
    ,   TURNON
    ,   TURNOFF
    ,   SETBIT
    ,   GETBIT
    ,   SETBYTE
    ,   GETBYTE
    };
    

    So far, part 1 of my explanation. Pls. continue reading part 2 in the next message.

    Rgds,

    Geert

  • Hello again Drew, Jon,

    Find below the continuation of my previous mail posted (part 1).

    I will mention two functions, NEW and SETBIT, because they are most relevant for the problem I have:

    static Pcf8574 xdata *NEW( Nat8 I2cAddress, Nat8 DefaultMask )
    {
        Pcf8574 *p;
    
        ASSERT( m_Index < 16 );
    
        p = &( m_MyVarArray[ m_Index++ ] );
        p->I2cAddress = I2cAddress;
        p->Buffer     = DefaultMask;
        return ( p );
    }
    
    
    static void SETBIT( Pcf8574 xdata *Object, Nat8 Pin, Nat8 Active )
    {
        ASSERT( ( 0 <= Pin )
              &&( Pin <= 7 )
              );
    
        ASSERT( ( Active == TRUE )
              ||( Active == FALSE )
              );
    
        if ( (Bool)Active )
        {
            Object->Buffer |= (_crol_( 0x01, Pin ) & 0xFF);
        }
        else
        {
            Object->Buffer &= (~_crol_( 0x01, Pin ) & 0xFF);
        }
    
        WriteI2c( Object );
    }
    
    NEW is returning a pointer to a Pcf8574 structure, which has been assigned to a location in the array m_MyVarArray. I'm using an index to keep track of the already used array elements. The pointer returned, should reside in XDATA, as given by the memory specifier in the return type.

    SETBIT receives a pointer to a Pcf8574 object, together with a byte containing the pin to manipulate and a byte containing the (boolean) value for that pin (hence the cast to Bool inside the function).
    Inside the function SETBIT, the corresponding bit in the byte is modified and then the function WriteI2c() is called, with the pointer to a Pcf8574-object as an argument.

    The function WriteI2c() is given below (see also my original message posted) and simply calls another function in another c-file:
    static void WriteI2c( Pcf8574 xdata *Object )
    {
        i2c_swi2c_Write( Object->I2cAddress
                       , &( Object->Buffer )
                       , 1
                       );
    }
    
    The function i2c_swi2c_WriteI2c() is prototyped as follows:
    extern I2cStatus i2c_swi2c_Write( byte address, byte *msgw, byte lenw );
    
    and implemented as follows:
    extern I2cStatus i2c_swi2c_Write( byte address, byte *msgw, byte lenw )
    {
        ASSERT( ( address >= 0 ) && ( address <= 255 ) );
        ASSERT( msgw != NULL );
        ASSERT( ( lenw > 0 ) && ( lenw <= 255 ) );
        return ( I2cCommand( address, lenw, 0, msgw, NULL ) );
    }
    
    This function is calling a static function I2cCommand() with the following prototype:
    static I2cStatus I2cCommand( byte Address
                               , byte NrSend
                               , byte NrRec
                               , byte *SendBuffer
                               , byte *RecBuffer
                               );
    
    So, that's all for an introduction :-)...

    See next message Re: Ram Corruption - Part 3 for the rest of my explanation.

    Rgds,

    Geert

  • Hello again Drew, Jon,

    Find below the final part of my explanation about the ram corruption issue.

    Next, I'll show you how the functions are called/used:

    1. The function NEW:
    In another file (the caller), I declare a variable of type Pcf8574 xdata * as
    given below:

    Pcf8574 xdata *m_LcdCtrl;
    
    An object of Pcf8574 is 'instantiated' as given below:
    m_LcdCtrl = Pcf8574Func.New( div_PCF8574A_LCDCTRL_I2CADDRESS , 0x00 );
    
    Pcf8574Func is the name of the external declared structure extern struct _PCF8574 { ... } Pcf8574Func; (see above).

    New is the name of one of the function pointers in this structure and is mapped to NEW in the c-file.

    The two parameters are just to be able to fill in the I2cAddress and the default buffer value in one of the array elements of m_MyVarArray in Pcf8574.c.

    2. The function SETBIT:
    Further on in that file (still the same caller as in point 1), a call is done to change a pin of a certain IO-expander.

    Therefore, the function pointer Pcf8574Func.SetBit() is used as given below:
    .
    .
    Pcf8574Func.SetBit( m_LcdCtrl, div_LcdEnPin, FALSE );
    .
    .
    
    As a first parameter, the pointer of an IO-expander object (to be more precise, of an element in the IO-expander array m_MyVarArray), received after the instantiation (see a little bit above), is given.

    The next parameter is a byte, indicating which pin of the IO-expander has to change and the last parameter indicates the value of that pin. In the example given, the pin should be cleared.

    From here onwards, it's possible to follow the flow of the program (see also my original message).
    This function pointer call will call SETBIT() and execute the code as given above.

    I've tried to explain as good as possible the situation where the ram corruption occurs.

    Maybe I'm messing up the memory specifiers from one function call to the other, as I'm not so familiar yet with those things. Maybe pay close attention to this while analyzing...

    Should it not be clear enough, pls. inform me because a solution to this problem is very important to me.

    If you want, you can always contact me via email on the following address:
    g-vcompernolle@tiscali.be

    If necessary, I can send you the complete files (actually the complete uVision project) if it helps you to even better understand the problem.

    Again, thanks very much for the assistance and also for taking the time to read the entire message...

    Rgds,

    Geert


  • I think the key phrase here is "function pointer". Using function pointers on the 8051 brings some interesting new challenges (ahem) to writing C.

    Remember that the 8051 has no stack to speak of. So, the compiler is very clever and does compile-time analysis of memory usage, and creates code that does direct memory accesses to data that would normally be placed on the stack for most processors. Both function parameters that don't fit into registers as well as local ("auto") variables get treated this way.

    Since assigning every parameter and local its own memory location would eat up a lot of memory, the compiler reduces the memory consumption by analyzing the call tree of the program. If, say, main calls f1(), and then calls f2(), you know that f1 and f2 won't use their memory at the same time, and thus you can overlay their two "stack" areas. (Note that the same thing happens on a stack-based architecture. f1 runs, and data gets pushed and popped on the stack. Then f2 runs, and data again gets pushed and popped on the stack, overwriting the same memory locations f1 was using earlier. The difference is just that with most processors, the "overlaying" is happening at run time; with the 8051, the overlaying is determined at compile time.)

    All that said, the compiler thus has to know which functions call which other functions. Function pointers make this difficult, because when you call a function though a function pointer, it can be difficult to tell at compile time what function the pointer might point to.

    The linker provides an OVERLAY directive so that you can fix up the call tree to tell the linker which functions really call which other functions. Study the sections of the manual on function pointers and overlaying, and take a look at your call tree in the map file. I think perhaps you'll find that the call tree is incorrect, and thus the overlaying is incorrect, which is why when that last parameter gets bumped to memory location 000A, it's overwriting your other data. The compiler thinks 000A is currently free at that point in the call tree, but the function pointers have deceived it.

    As an aside, with all due respect to Phillipe, I think the object-oriented virtual function emulation is a bit much for this problem. Certainly, you don't want ten copies of the code for ten devices. But you can probably manage with static (in the C++ sense) routines that take a parameter indicating which device to affect. Just pass the I/O address/buffer structure to the routines that drive the I2C devices. You shouldn't need any function pointers at all, which would avoid the whole problem.


  • "Using function pointers on the 8051 brings some interesting new challenges (ahem) to writing C."

    It certainly does!!

    Refer to Application Note 129: Function Pointers in C51 for a complete discussion of all the ramifications of using function pointers with the C51 compiler:
    http://www.keil.com/appnotes/docs/apnt_129.asp

    And here's a few knowledgebase articles to look at:

    BL51: AVOIDING FUNCTION POINTER PROBLEMS WITH NOOVERLAY:
    http://www.keil.com/support/docs/1026.htm

    C51: PASSING PARAMETERS TO INDIRECTLY CALLED FUNCTIONS:
    http://www.keil.com/support/docs/2066.htm

    C51: PROBLEMS WITH FUNCTION POINTERS OVERWRITING VARIABLES:
    http://www.keil.com/support/docs/210.htm