Search code examples
carraysliteralskeil8051

Embedded C Array indexing literal vs variable issue


I am in my first embedded C project at work and I have run into a behavior that perplexes me.

I am usind KEILs uVision 5 IDE and have found how to map to special function registers (SFR) memory space via the following link http://www.keil.com/support/docs/2998.htm

Which I did with my following two file snippets

dataStructures.h

typedef unsigned char byte;
typedef struct DAC {        
    byte loc[15]; 
} DAC;
extern DAC DAC_MAP; 

DAC_MAP.a51

PUBLIC DAC_MAP

DAC_MAP     DATA  0xB9

END

Then I have C code which works only when the literal value of 1 is used.

byte i = 1;
DAC_MAP.loc[i] = value; // Does not write to the SFR
DAC_MAP.loc[1] = value; // Writes to the SFR

I really would like to be able to write to a location by index and not have to write large switch cases to solve this problem.

Any thoughts as to why this is happening?

DAC_MAP.a51

PUBLIC DAC_MAP

DAC_MAP     DATA  0xB9

END

driver.c

#include <DP8051XP.H> 

typedef unsigned char byte;

typedef struct DAC {        
    byte loc[15]; 
} DAC;

extern DAC DAC_MAP; 

int main(void) {
    byte i = 1;

    DAC_MAP.loc[i] = 0xAA; // Does not write to the SFR
    DAC_MAP.loc[1] = 0xAA; // Writes to the SFR

    return 0;
}

When I run this code in KEIL uVision 5 debugger, you can see in the memory map that the when indexing is carried out by a variable, there is no change but when the literal is used the value changes as expected.


Solution

  • The issue is because you are attempting to access the 8051's Special Function Registers with indirect addressing, which is not supported. You'll have to force the compiler to use direct addressing instead.

    From GENERAL PURPOSE SFR INTERFACE:

    The SFRs in the 8051 are directly addressable only. That means that the address must be a part of the program. There is no way to indirectly address the SFRs. So, pointers won't work. Take a look at the Intel documentation on the internal data memory and it should become clear.

    One way to "sort of" do this is to define an SFR for each SFR address and use a big switch statement as follows:

    sfr SFR_0x80 = 0x80;
    sfr SFR_0x81 = 0x81;
    sfr SFR_0x82 = 0x82;
    .
    .
    .
    void write_sfr (
      unsigned char sfr_address,
      unsigned char value)
    {
    switch (sfr_address)
      {
      case 0x80: SFR_0x80 = value; break;
      case 0x81: SFR_0x81 = value; break;
      case 0x82: SFR_0x82 = value; break;
      }
    

    Since it looks like your compiler is smart enough to translate DAC_MAP.loc[1] into a direct address, this driver.c would probably work for you:

    #include <DP8051XP.H> 
    
    typedef unsigned char byte;
    typedef struct DAC {        
        byte loc[15]; 
    } DAC;
    extern DAC DAC_MAP;
    
    static void write_dac_map(byte i, byte d) {
        switch (i) {
            case 0:  DAC_MAP.loc[0]  = d; break;
            case 1:  DAC_MAP.loc[1]  = d; break;
            case 2:  DAC_MAP.loc[2]  = d; break;
            case 3:  DAC_MAP.loc[3]  = d; break;
            case 4:  DAC_MAP.loc[4]  = d; break;
            case 5:  DAC_MAP.loc[5]  = d; break;
            case 6:  DAC_MAP.loc[6]  = d; break;
            case 7:  DAC_MAP.loc[7]  = d; break;
            case 8:  DAC_MAP.loc[8]  = d; break;
            case 9:  DAC_MAP.loc[9]  = d; break;
            case 10: DAC_MAP.loc[10] = d; break;
            case 11: DAC_MAP.loc[11] = d; break;
            case 12: DAC_MAP.loc[12] = d; break;
            case 13: DAC_MAP.loc[13] = d; break;
            case 14: DAC_MAP.loc[14] = d; break;
            default: //error
        }
    }
    
    int main(void) {
        byte i = 1;
        write_dac_map(i, 0xAA);
        return 0;
    }
    

    If you look at the assembly your code generates (provided by stargateur), the issue is at C:0x0806:

         9: int main(void) { 
        10:     byte i = 1; 
        11:  
    C:0x0800    7F01     MOV      R7,#0x01
        12:     DAC_MAP.loc[i] = 0xAA; // Does not write to the SFR 
    C:0x0802    74B9     MOV      A,#DAC_MAP(0xB9)
    C:0x0804    2F       ADD      A,R7
    C:0x0805    F8       MOV      R0,A
    C:0x0806    76AA     MOV      @R0,#0xAA
        13:     DAC_MAP.loc[1] = 0xAA; // Writes to the SFR 
        14:  
    C:0x0808    75BAAA   MOV      0xBA,#0xAA
        15:     return 0; 
    C:0x080B    E4       CLR      A
    C:0x080C    FE       MOV      R6,A
    C:0x080D    1F       DEC      R7
        16: }
    C:0x080E    22       RET      
    C:0x080F    787F     MOV      R0,#0x7F
    C:0x0811    E4       CLR      A
    C:0x0812    F6       MOV      @R0,A
    C:0x0813    D8FD     DJNZ     R0,C:0812
    C:0x0815    758107   MOV      SP(0x81),#0x07
    C:0x0818    020800   LJMP     main(C:0800)
    

    MOV @R0,#0xAA uses indirect addressing, and will write 0xAA to the internal RAM at address 0xBA (R0 is set to 0xB9 + 1).

    The MOV 0xBA,#0xAA instruction at C:0x0808 uses direct addressing and will write 0xAA to the SFR at address 0xBA. (When using direct addressing, addresses between 0x00 and 0x7F refer to SFRs instead of locations in RAM).

    This website has more information on the different addressing modes of the 8051: http://www.8052.com/tutaddr.phtml