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.
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