Dimlite
Dimlite

Reputation: 11

C code memory alignment issue on embedded system?

After a major refactoring of an embedded system (IAR C on TI CC2530), I've ended up in the following situation:

After basic initialization of peripherals and global interrupt enable, the execution incorrectly ends up in an interrupt handler that communicates with external hardware. Since this hardware is not ready (remember, we end up in the ISR incorrectly), the program freezes triggering a watchdog reset.

If I insert 1, 2, 3, 5, 6, 7 etc NOPs in main(), everything works fine. But If I insert 0, 4, 8 etc NOPs, I get the faulty behaviour.

CC2530 fetches 4 bytes of instructions from flash memory, on 4-byte boundaries.

This tells me that something is misaligned when it comes to code memory, but I simply doesn't know where to start. Nothing has changed when it comes the target settings AFAIK.

Anyone here who has seen this situation before, or can point me in the right direction?

#include <common.h>
#include <timer.h>
#include <radio.h>
#include <encryption.h>

#include "signals.h"
#include "lock.h"
#include "nfc.h"
#include "uart1_trace.h"
#include "trace.h"


//------------------------------------------------------------------------------
// Public functions
//------------------------------------------------------------------------------

void main(void)
{   
    setTp;    

    // Initialize microcontroller and peripherals
    ClockSourceInit();
    WatchdogEnable();
    PortsInit();
    TraceInit();
    Timer4Init();
    SleepInit();
    RadioInit();
    Uart1Init();
    LoadAesKey("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");

    clrTp;

    NfcInit();    

    __enable_interrupt();

    asm("nop");

    // Initialize threads
    LockInit();

    while (true)
    {        
        WDR();
        LockRun();
    }
}
void NfcInit(void)
{
    // Enable wake up interrupt on external RF field present    
    // The process for enabling interrupts is described in section 2.5.1 in the CC2530 datasheet.

    // Configure interrupt source: interrupt on falling edge, Port 0, pin 7:0
    PICTL |= BIT(0);   

    // 1. Clear port 0 individual interrupt flag. Read-modify-write is not allowed.
    // Writing 1 to a bit in this register has no effect, so 1 should be written to flags that are not to be cleared.
    P0IFG = ~BIT(3);

    // Clear port 0 interrupt flag. This register is bit-accessible.
    P0IF = 0;

    // 2. Set pin 3 interrupt-enable
    P0IEN |= BIT(3);

    // 3. Set port 0 interrupt-enable
    IEN1 |= BIT(5);

    // 4. Global interrupt enable is set in main()
}
// Interrupt handler: falling edge on signal Wake.
// This interrupt will only occur when device is powered off and NFC field present.
// When device is powered on, VCORE is always asserted.
#pragma vector = 0x6B
__interrupt static void NFC_WAKE_ISR(void)
{
    static uint16 cnt = 0;

    TracePutUint16(cnt); TracePuts("\r\n");

    if (++cnt > 10)
        IEN1 &= ~BIT(5);


    P0IFG = ~BIT(3);    // Clear port 1 individual interrupt flag. Read-modify-write is not allowed.
    P0IF = 0;           // Clear port 1 CPU interrupt flag. This register is bit-accessible.

    return;

Screenshot of software init.

Oscilloscope screenshot

CH1 = External interrupt signal, active low (signal Wake).

CH2 = TP in main.c (setTp / clrTp).

The reset button on CC-debugger seems not to be debounced, so the TP signal turns on and off a few times before stabilizing (should not be an issue). VCC is stable long before the reset. When TP goes low for the last time, all peripherals are initialized.

An external NFC IC is used to wake up the MCU from sleep mode when a NFC field is present. The NFC IC is powered by one of the CC2530 I/O-pins. Normally the IC is powered off to preserve power. In this state, the energy from the NFC field is enough to generate the wake signal (active low). When this signal is detected by the MCU, it wakes up, applies power to the NFC IC, and NFC communication starts.

The NFC IC generates the signal either when powered, or when a NFC field is present.

After reset, all I/O-pins are configured as inputs with pullups. This pulled up input is enough to power the NFC IC, which is why the wake-signal is generated. Immediatly after reset, the I/O is configured (in function PortsInit()), and power to NFC IC is turned off. This makes the wake signal go low. The slow rise- and fall times are probably due to a capacitor, that I will now remove.

Here is where things get weird. Despite the wake signal being low, the external interrupt is configured for falling edge and pending int flag is cleared right before global in enabled, I end up in the ISR a few ms later (not seen in the screen shot). But only with the right number of NOPs, as described above.

If I add a > 15 ms delay before global int enable, all is fine. This coincides with the time measured from TP low to wake high.

One might think that the int is incorrectly configured for active low, but in that case I should get multiple ints, and I don't. Also, that does not explain the magic NOPs...

Compiler generated ISR assembly code:

//   77 // Interrupt handler: falling edge on signal Wake.
//   78 // This interrupt will only occur when device is powered off and NFC field present.
//   79 // When device is powered on, VCORE is always asserted.
//   80 #pragma vector = 0x6B

        RSEG NEAR_CODE:CODE:NOROOT(0)
//   81 __interrupt static void NFC_WAKE_ISR(void)
NFC_WAKE_ISR:
//   82 {
        PUSH    A
        MOV     A,#-0xe
        LCALL   ?INTERRUPT_ENTER_XSP
        ; Saved register size: 15
        ; Auto size: 0
//   83     static uint16 cnt = 0;
//   84     
//   85     TracePutUint16(cnt); TracePuts("\r\n");
        ; Setup parameters for call to function PutUint16
        MOV     R4,#(TPutc & 0xff)
        MOV     R5,#((TPutc >> 8) & 0xff)
        MOV     DPTR,#??cnt
        MOVX    A,@DPTR
        MOV     R2,A
        INC     DPTR
        MOVX    A,@DPTR
        MOV     R3,A
        LCALL   PutUint16
        ; Setup parameters for call to function TPuts
        MOV     R2,#(`?<Constant "\\r\\n">` & 0xff)
        MOV     R3,#((`?<Constant "\\r\\n">` >> 8) & 0xff)
        LCALL   TPuts
//   86     
//   87     if (++cnt > 10)
        MOV     DPTR,#??cnt
        MOVX    A,@DPTR
        ADD     A,#0x1
        MOV     R0,A
        INC     DPTR
        MOVX    A,@DPTR
        ADDC    A,#0x0
        MOV     R1,A
        MOV     DPTR,#??cnt
        MOV     A,R0
        MOVX    @DPTR,A
        INC     DPTR
        MOV     A,R1
        MOVX    @DPTR,A
        CLR     C
        MOV     A,R0
        SUBB    A,#0xb
        MOV     A,R1
        SUBB    A,#0x0
        JC      ??NFC_WAKE_ISR_0
//   88         IEN1 &= ~BIT(5);
        CLR     0xb8.5
//   89     
//   90     
//   91     P0IFG = ~BIT(3);    // Clear port 1 individual interrupt flag. Read-modify-write is not allowed.
??NFC_WAKE_ISR_0:
        MOV     0x89,#-0x9
//   92     P0IF = 0;           // Clear port 1 CPU interrupt flag. This register is bit-accessible.
        CLR     0xc0.5
//   93     
//   94     return;
        MOV     R7,#0x1
        LJMP    ?INTERRUPT_LEAVE_XSP
        REQUIRE _A_P0
        REQUIRE P0IFG
        REQUIRE _A_P1
        REQUIRE _A_IEN1
        REQUIRE _A_IRCON
////////////////////////////////////////////////////////////////////////////////
//    lnk51ew_CC2530F64.xcl: linker command file for IAR Embedded Workbench IDE
//    Generated: Mon May 24 00:00:01 +0200 2010
//
////////////////////////////////////////////////////////////////////////////////
//
//  Segment limits
//  ==============
//
//    IDATA
//    -----
-D_IDATA0_START=0x00
-D_IDATA0_END=0xFF
//
//    PDATA
//    -----
// We select 256 bytes of (I)XDATA memory that can be used as PDATA (see also "PDATA page setup" below)
-D_PDATA0_START=0x1E00
-D_PDATA0_END=0x1EFF
//
//
//    IXDATA
//    ------
-D_IXDATA0_START=0x0001       // Skip address 0x0000 (to avoid ambiguities with NULL pointer)
-D_IXDATA0_END=0x1EFF         // CC2530F64 has 8 kB RAM (NOTE: 256 bytes are used for IDATA)
//
//
//    XDATA
//    -----
-D_XDATA0_START=_IXDATA0_START
-D_XDATA0_END=_IXDATA0_END
//
//    NEAR CODE
//    ---------
-D_CODE0_START=0x0000
-D_CODE0_END=0xFFFF           // CC2530F64 has 64 kB code (flash)
//
//  Special SFRs
//  ============
//
//    Register bank setup
//    -------------------
-D?REGISTER_BANK=0x0          // Sets default register bank (0,1,2,3)
-D_REGISTER_BANK_START=0x0    // Start address for default register bank (0x0, 0x8, 0x10, 0x18)
//
//    PDATA page setup
//    ----------------
-D?PBANK_NUMBER=0x1E          // High byte of 16-bit address to the PDATA area
//
//    Virtual register setup
//    ----------------------
-D_BREG_START=0x00
-D?VB=0x20
-D?ESP=0x9B                   //Extended stack pointer register location
////////////////////////////////////////////////////////////////////////////////
//
//  IDATA memory
//  ============
-Z(BIT)BREG=_BREG_START
-Z(BIT)BIT_N=0-7F
-Z(DATA)REGISTERS+8=_REGISTER_BANK_START
-Z(DATA)BDATA_Z,BDATA_N,BDATA_I=20-2F
-Z(DATA)VREG+_NR_OF_VIRTUAL_REGISTERS=08-7F
-Z(DATA)PSP,XSP=08-7F
-Z(DATA)DOVERLAY=08-7F
-Z(DATA)DATA_I,DATA_Z,DATA_N=08-7F
-U(IDATA)0-7F=(DATA)0-7F
-Z(IDATA)IDATA_I,IDATA_Z,IDATA_N=08-_IDATA0_END
-Z(IDATA)ISTACK+_IDATA_STACK_SIZE#08-_IDATA0_END
-Z(IDATA)IOVERLAY=08-FF
//
//  ROM memory
//  ==========
//
//    Top of memory
//    -------------
-Z(CODE)INTVEC=0
-Z(CODE)CSTART=_CODE0_START-_CODE0_END
//
//    Initializers
//    ------------
-Z(CODE)BIT_ID,BDATA_ID,DATA_ID,IDATA_ID,IXDATA_ID,PDATA_ID,XDATA_ID=_CODE0_START-_CODE0_END
//
//    Program memory
//    --------------
-Z(CODE)RCODE,DIFUNCT,CODE_C,CODE_N,NEAR_CODE=_CODE0_START-_CODE0_END
//
//    Checksum
//    --------
-Z(CODE)CHECKSUM#_CODE0_END
//
//  XDATA memory
//  ============
//
//    Stacks located in XDATA
//    -----------------------
-Z(XDATA)EXT_STACK+_EXTENDED_STACK_SIZE=_EXTENDED_STACK_START
-Z(XDATA)PSTACK+_PDATA_STACK_SIZE=_PDATA0_START-_PDATA0_END
-Z(XDATA)XSTACK+_XDATA_STACK_SIZE=_XDATA0_START-_XDATA0_END
//
//    PDATA - data memory
//    -------------------
-Z(XDATA)PDATA_Z,PDATA_I=_PDATA0_START-_PDATA0_END
-P(XDATA)PDATA_N=_PDATA0_START-_PDATA0_END
//
//    XDATA - data memory
//    -------------------
-Z(XDATA)IXDATA_Z,IXDATA_I=_IXDATA0_START-_IXDATA0_END
-P(XDATA)IXDATA_N=_IXDATA0_START-_IXDATA0_END
-Z(XDATA)XDATA_Z,XDATA_I=_XDATA0_START-_XDATA0_END
-P(XDATA)XDATA_N=_XDATA0_START-_XDATA0_END
-Z(XDATA)XDATA_HEAP+_XDATA_HEAP_SIZE=_XDATA0_START-_XDATA0_END
-Z(CONST)XDATA_ROM_C=_XDATA0_START-_XDATA0_END
//
//  Core
//  ====
-cx51



////////////////////////////////////////////////////////////////////////////////
//
// Texas Instruments device specific
// =================================
//
//    Flash lock bits
//    ---------------
//
// The CC2530 has its flash lock bits, one bit for each 2048 B flash page, located in
// the last available flash page, starting 16 bytes from the page end. The number of
// bytes with flash lock bits depends on the flash size configuration of the CC2530
// (maximum 16 bytes, i.e. 128 page lock bits, for the CC2530 with 256 kB flash).
// Note that the bit that controls the debug interface lock is always in the last byte,
// regardless of flash size.
//
-D_FLASH_LOCK_BITS_START=(_CODE0_END-0xF)
-D_FLASH_LOCK_BITS_END=_CODE0_END
//
// Define as segment in case one wants to put something there intentionally (then comment out the trick below)
-Z(CODE)FLASH_LOCK_BITS=_FLASH_LOCK_BITS_START-_FLASH_LOCK_BITS_END
//
// Trick to reserve the FLASH_LOCK_BITS segment from being used as normal CODE, avoiding
// code to be placed on top of the flash lock bits. If code is placed on address 0x0000,
// (INTVEC is by default located at 0x0000) then the flash lock bits will be reserved too.
//
-U(CODE)0x0000=(CODE)_FLASH_LOCK_BITS_START-_FLASH_LOCK_BITS_END
//
////////////////////////////////////////////////////////////////////////////////

Upvotes: 1

Views: 643

Answers (3)

Dimlite
Dimlite

Reputation: 11

I am starting to believe that this is a hardware issue, related to the connection between CC2530 and the NFC IC.

The power and reset to the NFC IC that sends the external interrupt request is controlled by a CC2530 I/O pin with 20 mA current drive capacity. At reset, before execution of the program starts, all the I/O pins defaults to inputs with internal weak pull-up. It seems like the current through the pull-up resistor is enough to power up the NFC IC. The interrupt signal from the NFC IC is high whenever the NFC is powered or a NFC field is present, and inverted by a FET transistor before reaching CC2530. Hence the ISR is triggered by a falling edge on the input.

So what happens at startup is that the NFC IC is incorrectly powered on (and later off, when the ports are initialized), and the WAKE signal falls and rises very slowly due to the poor drive capacity of a pull-up (to make things worse, a large capacitor of 1 uF is connected in parallel with the gate of the FET, and another 1uF filters the NFC IC power pin).

WAKE is supposed to trigger an interrupt only on falling edge, but staying in the transition region for up to 10 ms as seen in the oscilloscope screenshot above seems to cause CC2530 to fire the interrupt even when WAKE is rising. The ISR starts to communicate with the NFC IC via SPI, but at this time, the NFC IC seems to be messed up due to the spurious transitions on VCC and reset. It refuses to respond, the execution halts in the ISR and the watchdog bites. And the process starts over, forever.

When I insert a delay that ensures WAKE to be stable high before enabling the interrupt, all is well. If I remove the 1 uF cap on the FET gate, WAKE rises very quickly, and there is no need for the delay anymore. And when I add a 4k7 pulldown to the NFC power, it is no longer powered up at reset.

Problem seems to be solved. The refactoring rearranged the code and changed the startup sequence, which led to a different delay that revealed the issue. With the proper hardware update, no delay will be needed.

But what still disturbes me is that I don't understand the magic NOPs. When CC2530 had the interrupt enabled and encountered a slowly rising WAKE, it wouldn't always end up incorrectly in the ISR. And when it did, I could always make it run by adding 1..3 NOPs. Naturally, whenever I added or removed a line of code, the number of NOPs required changed, which as you can imagine, drove me crazy.

It took me some time to narrow things down, and I am very grateful to all your comments and proposed solutions, especially Clifford that forced me to bring out the oscilloscope.

Upvotes: 0

D Krueger
D Krueger

Reputation: 2476

The user's guide for the CC2530 states:

The instruction that sets the PCON.IDLE bit must be aligned in a certain way for correct operation. The first byte of the assembly instruction immediately following this instruction must not be placed on a 4-byte boundary.

This is likely why the system fails on NOP multiples of four.

Just below the warning, there is an implementation for fixing this alignment specifically targeted at the IAR compiler.

Upvotes: 0

Lundin
Lundin

Reputation: 213791

According to TI, that part has got an 8051 core. Apart from being dinosaur crap, 8051 is an 8-bitter so alignment does not apply.

When random modifications to the code result in completely unrelated errors or run-away code, it is most often caused by one of these things:

  • You got a stack overflow, or
  • You got undefined behavior bugs, such as uninitialized variables, array out of bounds access etc.

Also ensure that all ISRs are registred in the interrupt vector table.


EDIT after question change 6/4:

You should normally not return from interrupts! I don't know how your specific setup works, but with a general embedded systems compiler, the non-standard interrupt keyword means two things:

  • Ensure that the calling convention upon entering the ISR is correct, by stacking whatever registers the CPU/ABI state are not stacked by hardware, but software.
  • Ensure that the same registers are restored upon leaving the ISR and that the correct return instruction is used.

On 8051 this means that the disassembled ISR absolutely must end with a RETI instruction and not a RET instruction! Chances are high that return results in RET which will sabotage your stack. Disassemble to see if this is indeed the case.

Upvotes: 5

Related Questions