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 ); }
static void WriteI2c( Pcf8574 xdata *Object ) { i2c_swi2c_Write( Object->I2cAddress , &( Object->Buffer ) , 1 ); }
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)
static void WriteI2c( Pcf8574 xdata *Object ) { Nat8 TempAddress = Object->I2cAddress; Nat8 TempData = Object->Buffer; i2c_swi2c_Write( TempAddress , &TempData , 1 ); }
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
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)
i2c_swi2c_Write( Object->I2cAddress , &( Object->Buffer ) , 1 );
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;
struct _Pcf8574; typedef struct _Pcf8574 Pcf8574;
struct _Pcf8574 { Nat8 I2cAddress; Nat8 Buffer; };
static Pcf8574 xdata m_MyVarArray[ 16 ];
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 );
struct _PCF8574 Pcf8574Func = { NEW , TURNON , TURNOFF , SETBIT , GETBIT , SETBYTE , GETBYTE };
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 ); }
extern I2cStatus i2c_swi2c_Write( byte address, byte *msgw, byte lenw );
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 ) ); }
static I2cStatus I2cCommand( byte Address , byte NrSend , byte NrRec , byte *SendBuffer , byte *RecBuffer );
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;
m_LcdCtrl = Pcf8574Func.New( div_PCF8574A_LCDCTRL_I2CADDRESS , 0x00 );
. . Pcf8574Func.SetBit( m_LcdCtrl, div_LcdEnPin, FALSE ); . .
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