首页 > 解决方案 > 嵌入式系统上的 C 代码内存对齐问题?

问题描述

在对嵌入式系统(TI CC2530 上的 IAR C)进行了重大重构之后,我遇到了以下情况:

在外围设备的基本初始化和全局中断启用后,执行错误地以与外部硬件通信的中断处理程序结束。由于此硬件尚未准备好(请记住,我们最终错误地进入了 ISR),因此程序冻结触发看门狗复位。

如果我在 main() 中插入 1、2、3、5、6、7 等 NOP,一切正常。但是如果我插入 0、4、8 等 NOP,我会得到错误的行为。

CC2530 在 4 字节边界上从闪存中获取 4 字节指令。

这告诉我在代码内存方面有些东西是错位的,但我根本不知道从哪里开始。当涉及到目标设置 AFAIK 时,什么都没有改变。

以前见过这种情况的任何人,或者可以指出我正确的方向吗?

#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;

软件初始化的屏幕截图。

示波器截图

CH1 = 外部中断信号,低电平有效(信号唤醒)。

CH2 = main.c 中的 TP (setTp / clrTp)。

CC-debugger上的reset按钮好像没有去抖,所以TP信号在稳定之前开关了几次(应该不是问题)。VCC 在复位之前很长时间是稳定的。当 TP 最后一次变低时,所有外设都被初始化。

当存在 NFC 场时,外部 NFC IC 用于将 MCU 从睡眠模式中唤醒。NFC IC 由 CC2530 I/O 引脚之一供电。通常,IC 会断电以保持电力。在这种状态下,来自 NFC 场的能量足以产生唤醒信号(低电平有效)。当 MCU 检测到此信号时,它会唤醒,为 NFC IC 供电,NFC 通信开始。

NFC IC 在通电或存在 NFC 场时生成信号。

复位后,所有 I/O 引脚都配置为带上拉的输入。这个上拉输入足以为 NFC IC 供电,这就是产生唤醒信号的原因。复位后立即配置 I/O(在函数 PortsInit() 中),并关闭 NFC IC 的电源。这使得唤醒信号变低。缓慢的上升和下降时间可能是由于我现在将移除的电容器造成的。

这就是事情变得奇怪的地方。尽管唤醒信号为低电平,外部中断配置为下降沿,并且挂起的 int 标志在启用全局输入之前被清除,几毫秒后我最终进入 ISR(在屏幕截图中没有看到)。但如上所述,只有正确数量的 NOP。

如果我在全局 int 启用之前添加 > 15 ms 的延迟,一切都很好。这与从 TP 低点到唤醒高点的时间相吻合。

有人可能认为 int 被错误地配置为低电平有效,但在这种情况下,我应该得到多个 int,而我没有。此外,这并不能解释神奇的 NOP ......

编译器生成的 ISR 汇编代码:

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

标签: cembedded

解决方案


据 TI 称,该部件有一个 8051 内核。除了是恐龙废话,8051 是 8 位的,所以对齐不适用。

当对代码的随机修改导致完全不相关的错误或代码失控时,通常是由以下原因之一引起的:

  • 你有一个堆栈溢出,或者
  • 您遇到了未定义的行为错误,例如未初始化的变量、数组越界访问等。

还要确保所有 ISR 都注册在中断向量表中。


在问题更改 6/4 后编辑:

你通常 return应该从中断!我不知道您的具体设置是如何工作的,但是对于通用嵌入式系统编译器,非标准中断关键字意味着两件事:

  • 确保进入ISR 时的调用约定是正确的,通过堆叠 CPU/ABI 状态不是由硬件而是由软件堆叠的任何寄存器。
  • 确保在离开ISR时恢复相同的寄存器使用正确的返回指令。

在 8051 上,这意味着反汇编的 ISR 绝对必须以RETI指令而不是RET指令结尾!很有可能returnRET破坏你的筹码。拆开看看是否确实如此。


推荐阅读