C 内存缓冲区破坏微控制器内存分配 - Atmel ATxmega

C Memory Buffer corrupting micro-controller memory allocation - Atmel ATxmega

我正在使用 ATxmega128A1 8 位微控制器,我想弄清楚为什么我的内存缓冲区行为异常。

我有一个具有以下结构的 FIFO 缓冲区:

typedef struct
{
  FIFOid ID;                        //ID value to determine the type of FIFO being used
  BOOL LOCK;                        //A lock to prevent multiple FIFO accessing (i.e. data corruption)
  UINT16 Start, End;                //Value of the current start and end FIFO index
  UINT16 volatile NbBytes;          //Number of bytes currently inside the FIFO
  UINT8 Buffer[FIFO_SIZE];          //Memory buffer for the FIFO buffer
} TFIFO;

我定义了 2 个 FIFO:

//FIFO Buffers for USART
TFIFO RxFIFO;           //Creates the Receive FIFO
TFIFO TxFIFO;           //Creates the Transmit FIFO

其中 FIFO_SIZE 是

#define FIFO_SIZE 256       

当我想从 Buffer[] 中获取一个字节时,我通过这个函数传递一个指向 FIFO 的指针:

BOOL FIFO_Get(TFIFO * const FIFO, UINT8 * const dataPtr);

这工作正常,但是,一旦我将 FIFO_SIZE 增加到 ~510 字节以上 - 通过此函数传递此 FIFO 指针会导致 另一个 我的 FIFO 缓冲区之一待修改。

我在调试器上看到,这一行没有问题:

if(FIFO_Get(&TxFIFO, &data)){       //Get a byte from TxFIFO

一旦我越过,它被传递给函数,RxFIFO->Buffer[] 数组被修改,即更大的地址值。 FIFO_Get 函数:

// ----------------------------------------
// FIFO_Get
// ----------------------------------------
// Remove one character from the FIFO
// Input: 
//   FIFO is a  pointer to a FIFO struct with data to be retrieved
//   dataPtr is a pointer to a memory location to place the retrieved byte
// Output:
//   TRUE if the operation was successful and the data is valid
// Conditions:
//   Assumes that FIFO_Init has been called

BOOL FIFO_Get(TFIFO * const FIFO, UINT8 * const dataPtr)
{
    BOOL FIFOGetSuccess;
    FIFOGetSuccess = bFALSE;

    //disable interrupts
    //if we're GETTING from the USART receive FIFO
    //we need to disable the receive interrupt so
    //there is no data being 'Put' during a 'Get'
    if(FIFO->ID == URX){
        USARTD0.CTRLA = USART_RXCINTLVL_OFF_gc; //Rx interrupts off
        USARTD1.CTRLA = USART_RXCINTLVL_OFF_gc; //Rx interrupts off
    }

    //Attempt to get a byte from the FIFO
    if(FIFO->LOCK == bFALSE){                           //Check that the FIFO is not locked
        FIFO->LOCK = bTRUE;                             //lock the FIFO
        if (FIFO->NbBytes == 0){                        //Checks whether FIFO is empty; number of bytes in FIFO = 0
            FIFOGetSuccess = bFALSE;                    //If empty, false is Returned
        }else{
            *dataPtr = FIFO->Buffer[FIFO->Start];       //Reads the byte in the FIFO start position, and saves it to the location of the data pointer
            if (FIFO->Start == (FIFO_SIZE - 1)){        //Checks whether the start position is equal to FIFO size, the last valid 8 bit number
                FIFO->Start = 0;                        //If so, the position is set to 0
            }else{                                      //If not fifo size;
                FIFO->Start++;                          //Increments the start position by 1
            }
            FIFO->NbBytes--;                            //Decrements the number of bytes in the FIFO by 1
            FIFOGetSuccess =  bTRUE;                    //Returns true if this is successful
        }
        FIFO->LOCK = bFALSE;                            //unlock the FIFO
    }

    //re-enable interrupts
    if(FIFO->ID == URX){
        USARTD0.CTRLA = USART_RXCINTLVL_LO_gc;  //low level interrupts on Rx
        USARTD1.CTRLA = USART_RXCINTLVL_LO_gc;  //low level interrupts on Rx        
    }

    return FIFOGetSuccess;
}

我的猜测是我没有在微控制器中安全地分配内存,但是在构建之后芯片中似乎还有很多内存:

        Program Memory Usage    :   21460 bytes   15.4 % Full
        Data Memory Usage       :   6967 bytes   12.1 % Full

我该如何解决这个问题以及我做错了什么?

编辑:

我发现增加 FIFO SIZE 会将 FIFO 缓冲区映射到 SRAM 地址范围(ATxmega128A1 的 SRAM 从 0x2000 变为 0x3FFF)

FIFO address mapping

当另一个FIFO通过一个函数时,地址0x3FF7到0x3FFF正在改变

编辑 2: Elf Files

没有。只是没有。

你的观念是错误的。使用 FIFO 缓冲区,您应该在您的程序中只使用一个写入点和一个读取点。在你的情况下,写入将在 UART RX 中断和读取中,例如在程序的 while (1) 循环中。

足够的结构信息是这样的:

#define FIFO_SIZE       100
typedef struct {
    uint16_t Write, Read;
    uint8_t Buffer[FIFO_SIZE];
} FIFO;

然后你需要 2 个(最好是 3 个)函数:

  • FIFO_Write(FIFO* f, uint8_t ch)
  • FIFO_Get(FIFO* f, uint8_t* ch)
  • FIFO_Init(先进先出* f);

实现类似于:

void FIFO_Init(FIFO* f) {
    f->Write = 0;
    f->Read = 0;
}

//This function needs check if buffer is full, will update it.
void FIFO_Write(FIFO* f, uint8_t ch) {
    //Check here if buffer is full first
    f->Buffer[f->Write++] = ch;
    if (f->Write >= FIFO_SIZE) {
        f->Write = 0;
    }
}

uint8_t FIFO_Read(FIFO* f, uint8_t* ch) {
    volatile uint16_t write = f->Write, read = f->Read;
    if (write == read) {
        return 0;  //FIFO empty, return 0
    }
    *ch = f->Buffer[f->Read++];
    if (f->Read >= FIFO_SIZE) {
        f->Read = 0;
    }
    return 1;      //character is valid
}

写入和读取功能现在彼此安全。您可以从 IRQ 或任何地方写入,同时读取,完全不用担心。

您可以轻松地在中断内部使用FIFO_Write,并且当您从 FIFO 中读取时,您不需要禁用中断。这就是 FIFO 的目的。单写点,单读点。

希望对您有所帮助。

从 ELF 文件中,我可以看到这个内存映射:

00802000 B __bss_start
00802000 D __data_end
00802000 D __data_start
00802000 D _edata
00802000 00000002 b n.4418
00802002 00000001 b packetEnd.1686
00802003 00000002 b byteCount.1684
00802005 00000002 b StepsCounted.4253
00802007 00000001 b ErrorCode.4252
00802008 00000001 b SuccessNb.4251
00802009 00000001 b ReturnNb.4250
0080200a 00000004 b stepsOutOpto.4211
0080200e 00000004 b HomingError.4212
00802012 00000008 b stepscounted.4210
0080201a 00000004 b CntHomeState.4209
0080201e 00000001 b byteCount.4166
0080201f 00000002 B EjectMsgID
00802021 00000001 B VersionMinor
00802022 00000002 B QueueEnd
00802024 00000001 B VersionMonth
00802025 00000001 B ACC1_IN
00802026 0000001f B PacketQRx
00802045 0000001f B PacketTx
00802064 00000001 B VersionMajor
00802065 00000001 B DeviceTypeId
00802066 00000001 B debugCycler_bytes
00802067 00000001 B debugAXIS_ACK
00802068 000000d4 B AXIS
0080213c 00000001 B LVL_SENSE_IN
0080213d 00000001 B LID_DETECTION_ON
0080213e 00000001 B readyimmediateTx
0080213f 0000001f B PacketQTx
0080215e 00000004 B RobotSerialNumber
00802162 00000001 B USBIN
00802163 00000001 B startupComplete
00802164 000007c0 B PacketQueue
00802924 0000001f B PacketRx
00802943 00000001 B ACC2_IN
00802944 00000002 B QueueNb
00802946 00000001 B QueuedCommandCalled
00802947 0000001f B dummyPkt
00802966 00000001 B CYCLER_IN
00802967 00000002 B QueueStart
00802969 00000001 B HardwareRevision
0080296a 00000001 B VersionYear
0080296b 00000980 B AXISRxFIFO
008032eb 00000004 B RTS
008032ef 00000980 B AXISTxFIFO
00803c6f 00000068 B AXIS_SPI_Tx_Packet
00803cd7 00000004 B ReturnPacketMissed
00803cdb 00000068 B AXIS_SPI_Rx_Packet
00803d43 00000004 B NbPacketsInAxisFIFO
00803d47 00000100 B CyclerEEPROM
00803e47 00000260 B TxFIFO
008040a7 00000260 B RxFIFO
00804307 B __bss_end

注意:我得到这张地图的方式是使用 nm 和命令 nm -S -n MotorBoard\ Xmega\ Large\ FIFO.elf。第一列是地址,第二列是变量的大小。

ATMEL ATxmega128A1 Datasheet 表示您在 0x20000x3FFF 之间有 8KB 的 SRAM。正如您所看到的,您的两个 FIFO 缓冲区都与 0x3FFF 限制重叠(0x3E47 代表 TxFIFO0x40A7 代表 RxFIFO)。

解决问题的一种方法是将一些变量声明为 const,从而将它们移动到 ROM 中。例如,我可以看到 VersionMonthVersionMajor 可能是常量。