Daniel Jorge
Daniel Jorge

Reputation: 73

Problem with DPTR arithmetic in 8051 assembly

We have a problem with a firmware project in 8051 assembly. It is used in an embedded system and now needs to be adapted to changes in the hardware. It contains a subroutine that sequentially reads bits from a shift register and puts them into a data segment of fixed length. These bits are then used as the basis for calculations that are too complex to describe in this post. Due to the hardware changes we no longer get these bits in sequential order. The order has been reversed and each block of eight is permuted following a complex logic. We had problems implementing the adaption to the new bit order in the assembly.

The original subroutine would roughly look like this if implemented in C:

char TimeBuffer[LEN];
char DataBuffer[LEN];

    for (int i = 0; i < LEN; i++)
    {
       if (TimeBuffer[i] != 0)
       {
          DataBuffer[i] == GPIO_Read();
       }
    }

The re-implementation would look like this in C:

char TimeBuffer[LEN];
char DataBuffer[LEN];
int NewIndex;

    for (int i = 0; i < LEN; i++)
    {
       NewIndex = GetNewIndex(i);

       if (TimeBuffer[NewIndex] != 0)
       {
          DataBuffer[NewIndex] == GPIO_Read();
       }
    }

Our problem revolves around pointer arithmetic. The old implementation simply used the statement INC DPTR to move to the next byte in both data segments after each loop cycle. The new implementation requires that we re-initialize the DPTR for both data segments at the beginning of each loop cycle. This re-initialization consists of first calling a subroutine that calculates the new index. Then this new index is added to the start address of both data segments.

We already have an implementation of this new solution and it gives us no errors or warnings in Keil uVision 2. However it does not give us the expected results. As we have no way of debugging the project in the IDE we can only compile new versions of the firmware and see what it does when it is running on the embedded device.

The last thing we tried was to take out the CALL of the subroutine that gives us the new index because we wanted to exclude the subroutine as the point of failure. Instead of the new index we are now adding a value to the pointers that is simply increased after each loop cycle. The result that we expected to see is that this would behave just like the original implementation. However it behaves in the same wrong way as the version that still has the CALL of the subroutine and uses its result.

We now suspect that we have some fundamental error in the way we add the index to the pointers. We have double - and triple checked it and cannot find the mistake. This is where we would like to ask for help. These are the relevant parts of the original version in the 8051 assembly:

MOV DPS,#1
MOV DPTR,#DataBuffer

MOV DPS,#0  
mov dptr,#TimeBuffer
mov R0,LEN
                
readD:   

MOV C,P1.1   ; read data pin of shift register
JNC shiftD
MOV DPS,#0      
movx    a,@dptr     
JNZ shiftD
MOV DPS,#1      
movx    a,@dptr
INC ACC         
MOVX    @DPTR,A

shiftD:setb P1.0  ; clock pin high of shift register

clr P1.0   ; clock pin low of shift register
MOV DPS,#0      
inc dptr            
MOV DPS,#1      
INC DPTR
djnz    R0,readD            

This is what it looks like now, with our recent changes:

;MOV    DPS,#1
;MOV    DPTR,#DataBuffer

;MOV    DPS,#0  
;mov    dptr,#TimeBuffer
mov R0,LEN
        
mov R1,#0h

leseD:

MOV R7,REG1

MOV DPS,#1
MOV DPTR,#DataBuffer
MOV A,DPL
ADD A,R7
MOV DPL,A
MOV A,DPH
ADDC A,#0h
MOV DPH,A

MOV DPS,#0
mov dptr,#TimeBuffer
MOV A,DPL
ADD A,R7
MOV DPL,A
MOV A,DPH
ADDC A,#0h
MOV DPH,A

MOV C,P1.1      ; read data pin of shift register
JNC shiftD
MOV DPS,#0      
movx    a,@dptr 
JNZ shiftD
MOV DPS,#1      
movx    a,@dptr
INC ACC 
MOVX    @DPTR,A

shiftD:setb P1.0    ; clock pin high of shift register


clr P1.0    ; clock pin low of shift register
;MOV    DPS,#0      
;inc    dptr
;MOV    DPS,#1      
;INC    DPTR
inc R1
djnz    R0,readD

Could you please point us to any mistakes you see in the part that calculates the pointers or elsewhere?

Upvotes: 1

Views: 276

Answers (2)

Miroslav Janković
Miroslav Janković

Reputation: 1

Essentially solution is mcu model-specific:

6 instructions that use active DPTR (which is selected via DPS.SEL bit 0/1) are:

MOVX @DPTR,A MOVX A,@DPTR MOVC A,@DPTR JMP @A+DPTR INC DPTR MOV DPTR,#16bit

These 6 instructions use banked (so called shadow) addressing for DPTR

(similar to R0-R7 banked register addressing)

But other instructions addressing SFR byte addresses: DPL(0)=0x82 / DPH(0)=0x83 (& optionally DPL1=0x84 / DPH1=0x85)

via direct addressing mode e.g. push/pop/move etc.

use addressing type: "shadow"(banked) or "separate"(absolute) (mcu model-specific)

(shadow : relative to current active DPTR e.g. DPL,DPH (& "inactive" one if dual DPTR) meaning DPTR == [DPX:]DPH:DPL == DPTR[DPSEL_BANK_BITS]

separate : absolute,disregarding which is active DPTR e.g. DPTR0==[DPX0:]DPH0:DPH0 , DPTR1==[DPX1:]DPH1:DPH1

meaning for:

case1 shadow : DPTR==[DPX:]DPH:DPL (your source code assumption)

case2 separate:[DPX:]DPH,DPL does not exist , only DPTR0,DPTR1 e.g.

SFR address 0x82 is DPL0, DPL=SFR[ADDR_L(DPSEL_BANK_BITS)]

SFR address 0x83 is DPH0, DPH=SFR[ADDR_H(DPSEL_BANK_BITS)]

in that case DPL,DPH are not addressable, in assembly those are actually DPL0,DPH0

in order to access DPL,DPH calculate their addresses from DPSEL or

access DPL0 (aka "DPL" in assembly) & DPH0 ("DPH") if DPSEL==0

& access DPL1 & DPH1 if DPSEL==1

Upvotes: 0

Daniel Jorge
Daniel Jorge

Reputation: 73

Meanwhile, we have found the solution on our own. Due to platform limitations, no one in our company knew this kind of implementation was impossible.

It turns out that, while you can switch the data segment that DPTR refers to, by moving a new value into DPS, both DPL and DPH remain unaffected by it and always point to the first data segment. As a consequence, we were always writing to TimeBuffer without knowing it.

We found this out after we sent the values of the pointers to a terminal emulator via the serial port on the embedded device.

Upvotes: 1

Related Questions