XC878 unexpected NMIECC

Tip / Sign in to post questions, reply, level up, and achieve exciting badges. Know more

cross mob
Not applicable
I have the strangest of occurrences. I'm entering ISR14 (NMI) with NMISR.FNMIECC = 1, even though NMICON.NMIECC = 0.

I have no clue, yet, what triggers it, but I don't think that should happen.

It seems to be connected to the flash timer and read access to the D-Flash.
0 Likes
3 Replies
Not applicable
A compiler bug caused DPTR corruption, which caused MOVC (@DPTR++),A to an address in the wrong type of flash. I suppose undefined behaviour is to be expected in such cases.
0 Likes
Juergen
Employee
Employee
thanks for sharing this with us here 🙂 it sure is useful info.
0 Likes
Not applicable
I have some more details.

Seeing that all the multibyte SFRs like DPTR (which is part of the 8051/8052 spec after all) or CAN_DATA have little endian byte order, I have been working under the assumption that 8051 is a little endian architecture.

As it turns out C51 uses big endian for multibyte variables (they even mention that somewhere in the docs, but I never got the idea to check), whereas SDCC uses little endian. This caused me to load XDATA and CODE pointers in the wrong byte order when using C51.

I'm using the following hack to circumvent this issue in my inline assembler code:

#ifdef SDCC

#define MOVCI .db 0xA5
#define DPL dpl
#define DPH dph
#define VAR_AT(type, name, addr) type __at(addr) name
#define VAR_ASM(name) _##name

#elif defined __C51__

#define MOVCI db 0xA5
#define DPL dph
#define DPH dpl
#define VAR_AT(type, name, addr) type name _at_ addr
#define VAR_ASM(name) name

#else
#error Assembly code for the current compiler missing
#endif


MOVCI, VAR_AT and VAR_ASM cover syntax differences. DPL and DPH are used to switch the low and high bytes when loading pointers with C51.

The following inline assembler block makes the dummy write to a flash wordline required to kick off the flash writing:

#ifdef SDCC
__asm
#elif defined __C51__
#pragma asm
#endif
; Backup used registers
push psw
push acc
push ar0
; Load hsk_flash_flashDptr into r0, a
mov dptr,#VAR_ASM(hsk_flash_flashDptr)
movx a,@dptr
mov r0,a
inc dptr
movx a,@dptr
; Follow hsk_flash_flashDptr
mov DPL,r0
mov DPH,a
MOVCI
; Restore used registers
pop ar0
pop acc
pop psw
#ifdef SDCC
__endasm;
#elif defined __C51__
#pragma endasm
#endif


hsk_flash_flashDptr is a global code pointer in xdata (if that wasn't less convenient to access in inline asm it would be part of a struct). Its low byte is loaded into r0, its high byte in a. Because a is irrelevant the high byte is just left there.

Now the low byte is loaded into DPL (DPTR low byte) and the high byte into DPH. Except when using C51 where DPL will be replaced with dph and DPH with dpl. The dummy write to the flash can commence.

I'll try to elaborate the issue. Assume that C51 is used and the hack is not in place:

Now consider consider that hsk_flash_flashDptr = 0xF000, pointing to the first byte of the D-Flash.

EECON.PROG is set and without the DPL/DPH hack the dummy write would target C:0x00F0. That's already bogus, because the D-Flash PROG bit is set and that's a P-Flash address. However at this point we don't yet recognize that something is wrong.

Now EECON.NVSTR is set, the charge pump is loaded and in the next step an actual write to an address in the wrong flash is attempted. A couple of µs later the debugger looses the µC.

Placing a break point in ISR 14 I can see that NMISR.FNMIECC = 1 (see the first post). I suppose it's the µCs way to panic in the presence of undefined actions. After the reti, the debugger looses contact to the µC.


Now that this issue is resolved, this has further implications. I'm simply writing a struct byte for byte to the flash the struct starts with a prefix that is neither 0 nor 0xFF and ends with a checksum byte. If said struct contains multibyte values such as int or long I loose the ability to switch between compilers or the data will get corrupted.

There are two ways to deal with this. I can introduce a byte order word, e.g. bow = 0x1234. Now if bow reads 0x3412 after loading it from the flash I know I have to flip all the values in my struct.

I decided to use a different approach, I avoid the use of multibyte values:
ubyte ulongMember[sizeof(ulong)];


The value can be set like this:

myStruct.ulongMember[0] = ulongValue;
myStruct.ulongMember[1] = ulongValue >> 8;
myStruct.ulongMember[2] = ulongValue >> 16;
myStruct.ulongMember[3] = ulongValue >> 24;

Reading looks like this:

ulongValue = (ubyte)myStruct.ulongMember[0];
ulongValue |= (uword)myStruct.ulongMember[1] << 8;
ulongValue |= (ulong)myStruct.ulongMember[2] << 16;
ulongValue |= (ulong)myStruct.ulongMember[3] << 24;


Now that's some ugly C code, but I think it's no different from what the compiler does when accessing multibyte variables. Also I don't plan to store long values in my struct any time soon. I might have to accomodate a couple of 10bit values, though.

Another alternative is is to just put a single ubyte[] array into the struct and access that with my CAN signal write/read functions. This way I don't have to waste bits on byte aligning data.
0 Likes