3

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.

Tim
  • 4,790
  • 4
  • 33
  • 41
  • 1
    I swapped the byte to size_t for the index and the same behavior is present... – josefflores Jun 05 '17 at 16:15
  • Sorry I missed the request, but I have posted an update. – josefflores Jun 05 '17 at 16:54
  • 2
    I've never worked with any of this technology, but going by the [forum thread](http://www.keil.com/forum/8248/) linked in the support article, pointer arithmetic like that implicit in `DAC_MAP.loc[i]` or even `DAC_MAP.loc[1]` doesn't make sense with SFRs, and trying to use an array at all is probably a bad idea. – user2357112 Jun 05 '17 at 17:01
  • This is minimal, verifiable, and complete. I started a new project and brought it down to that point. The single difference is the use of the variable i and literal 1 during indexing. Why should that not be in the example, as it is part of the problem? 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. Without that value all you are showing is that indexing does not work which is not the problem – josefflores Jun 05 '17 at 17:07
  • so I guess i am stuck with the switch case to get the functionality i want – josefflores Jun 05 '17 at 17:17
  • Are optimizations enabled at compile time? Since both statements do the same, the compiler/optimizer may get rid of the first one. – Harald Jun 05 '17 at 17:23
  • When I run the above code with the either line commented out there is no difference in behavior. So it is not related to optimization. – josefflores Jun 05 '17 at 18:28
  • By the way, why do you need such array ? After look to the doc this is clearly not the purpose of this memory. So maybe the true question is "how to ..." contrary to "Why ...", [XY Problem](https://meta.stackexchange.com/a/66378/350835). – Stargateur Jun 05 '17 at 21:12
  • I wanted to use an array to be able to iterate through the SFR locations, while reducing repetition in my src, I also wanted to reduce the size of the compiled code. I was hoping to learn why this was happening and maybe get a satisfactory solution to the problem – josefflores Jun 05 '17 at 22:26

1 Answers1

2

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

Tim
  • 4,790
  • 4
  • 33
  • 41
  • @Stargateur Both pointers and arrays rely on indirect addressing, which the 8051 does not support for accessing SFRs. – Tim Jun 05 '17 at 19:49
  • What you have written is what I had before I minimized it for the example. Having to write something like this is horrible in both time and space when it could be something as simple as DAC_MAP.loc[i] = d;. – josefflores Jun 05 '17 at 20:10
  • @josefflores I know. Working around hardware limitations is one of the many joys of embedded programming :) – Tim Jun 05 '17 at 20:12
  • 1
    @Stargateur Not on the 8051, see section 2.1 in the Instruction Set Details: http://www.keil.com/dd/docs/datashts/dcd/dp805x_instr.pdf . But yes, assembly output would help to confirm this. – Tim Jun 05 '17 at 20:26
  • Ill post the assembly later today – josefflores Jun 05 '17 at 20:49
  • @Stargateur `MOV @R0,#0xAA` is the problem. That is using indirect addressing which will access the internal RAM instead of the SFR. See http://www.8052.com/tutaddr.phtml . The SFRs can only be accessed with *direct* addressing. – Tim Jun 05 '17 at 20:53
  • 1
    @Tim Indeed, and the compiler handle this address without care about the location. This could be report as a bug, compiler could throw an error. Very instructive question and answer. – Stargateur Jun 05 '17 at 21:09