stm32 nand flash 存数据异常

使用stm32h743 + nand flash(64Gbit)进行数据存储,数据通过5个uart dma的方式进入stm32,5个串口获取的数据总共是128字节,然后在1ms定时器中断中进行组帧,凑齐64帧,即8192字节后,一次性写入NAND FLASH (这块flash一个page是8k),就是实现一个实时的数据存储功能。nand flash的驱动程序使用的是正点原子的例程,从例程上修改来的,因为64Gbit的nand分为两个逻辑单元,每个单元32Gbit,正点原子的例程只用来驱动一半的nand flash,也就是实际只使用了一半的空间。该设计在调试过程中可以正常的存储读取数据,并无异常。
简单描述一下应用程序的逻辑:
1.发送擦除指令,上电后先擦除数据,重建lut表
2.发送记录指令,开始记录数据,按页写入
3.发送停止指令,停止记录数据,并在当前page写入8192字节的停止符号:0x7e
4.发送读取指令,读取本次记录的数据,按页读取,每次读取时要判断一个page的数据是不是全为7e,如果不是,把读取到的数据发送出去,继续读取;如果是,表示有效数据读取完毕,给上位机发送6个0xaa,本次读取工作结束。

遇到的问题:在实际使用过程中,出现了异常:
nand flash偶尔出现存不了数据的情况。一种现象为正常执行完1 2 3步后,执行4之后,数据读取立即结束,根据代码逻辑,是在读取第一页的时候遇到了停止符号7e,导致读取结束。给人的感觉像是执行记录操作时根本没有按照设想的那样进行顺序的数据存储,然后执行停止时,却正确的写入了停止符号7e,导致第一个page就是停止符号,所以读数据时直接就完毕了。另一种情况就是:正常执行完1 2 3步后,执行4之后,读取到的全是0xff数据(flash没有存数据时,原始值时0xff)。
我自己想了好久也没有头绪,想请各位诊断一下。
正常存储下来的数据见附件1,异常存储的数据见附件2
附件1

img

附件2

img

#include "UserFunction.h"
#include "Rs485_Bms.h"
#include "string.h"
#include "main.h"
/****************状态标志****************/
uint8_t A_SelfCheck_Flag=0;

uint8_t A_Erase_Flag=0;

uint8_t A_Record_Flag=0;

uint8_t A_Stop_Flag=0;

uint8_t A_Read_Flag=0;

uint8_t T0_State = 0;  // 默认0无效,为1时,T0有效,T0检测结果锁存

/****************RS485接收缓冲区****************/
uint8_t One_BmsCheck = 3;
uint8_t rs485_rcv[64] = {0};    
uint8_t rcv_len = 0;
/**********************************************/
uint8_t WTestBuf[8192] = {0};
uint8_t RTestBuf[8192] = {0};
uint8_t TxBuf2K[2048] = {0};
/***********读数据扇区号,写数据扇区号*********/
uint32_t WSectorNum = 0;
uint32_t RSectorNum = 0;
/********************************************/
void JC_Main(void)
{

/*
    ID=01H     TEST(自检)MIMU、锂电池;
    ID=02H     数据擦除;
    ID=03H     开始记录;
    ID=04H     停止记录;
    ID=05H     读取数据;
    ID=06H     执行空语句  空循环;
    .
    .
    .

*/
    switch(UpperCmd.cmd_id)
    {
        case 0x01:
                            
            // 自检,原始数据反馈至上位机 格式为SELFCHECK_FRAME
            Device_SelfCheck();
            HAL_Delay(2);                        // 适当延时后,再改变状态
          // 自检完成后,再循环检测T0状态
            UpperCmd.cmd_id = 0x09;   // 检测T0
        break;
        
        case 0x02:
            
            // 擦除,擦除结果反馈至上位机
            Erase_Data();
            HAL_Delay(2);
            UpperCmd.cmd_id = 0x06;   // 空循环
            // 先擦除完成,然后状态值反馈至上位机
        break;
        
        case 0x03:
            Begin_Record();
            HAL_Delay(2);
            UpperCmd.cmd_id = 0x07;                 // 跳入真正的记录数据函数:Real_Begin_Record  07 
            if(A_Record_Flag == 1)
            {
                HAL_TIM_Base_Start_IT(&htim3);  // 打开1ms定时器,实现组帧缓存
            }
            // 开始记录,先发送握手协议至上位机,后跳入真正的数据记录程序,
        break;
        
        case 0x04:
                    
            // 停止记录,先停止记录操作,后反馈上位机
            Stop_Record();
        break;
        
        case 0x05:
            // 读取数据,先通知上位机告知具备读数据体条件,后跳入真正的读取数据程序
            Read_Data();
            HAL_Delay(2);
            UpperCmd.cmd_id = 0x08;
        break;
        
        case 0x06:                                    
            blank();
        break;
        
        case 0x07:    
            if(Page_Data_Ready == 1)
            {
                    Real_Begin_Record();
                    Page_Data_Ready = 0;
            }
        break;
            
        case 0x08: 
            Real_Read_Data();
        break;
        
        case 0x09:
            // 自检完成后再跳入检测T0
            Check_T0();
        break;
    
        default: 
            // 默认检测T0
            Check_T0();
        break;
    }
}

/*系统自检,能否收到上位机指令,能否读到MIMU,电池的数据*/    
// 注意自检中发送数据是用的局部变量  union
void Device_SelfCheck(void)
{
    uint8_t check=0,i=0;
    uint8_t imuok = 0;
    uint8_t Len = sizeof(SELFCHECK_FRAME); 
    uint32_t mimu_sta[5] = {0};
    uint8_t read_bms[7] = {0xdd,0xa5,0x03,0x00,0xff,0xfd,0x77};
    
    union {
            uint8_t uch[(int)sizeof(SELFCHECK_FRAME)];
            SELFCHECK_FRAME SendToUpper;
    }Send;
    /*数组清零*/
    memset(Send.uch,0,107);

    /*获取5路MIMU的有效数据,不用翻译*/
    mimu_sta[0] = MIMU1GetData();  // 返回值为2,说明获取数据正常
    mimu_sta[1] = MIMU2GetData();
    mimu_sta[2] = MIMU3GetData();
    mimu_sta[3] = MIMU4GetData();
    mimu_sta[4] = MIMU5GetData();
    // 主惯导  子惯导数据有效,T0检测结果有效,自检标志位置1 //
    if(mimu_sta[0]>=2 && (mimu_sta[1]>=2 || mimu_sta[2]>=2 || mimu_sta[3]>=2 || mimu_sta[4]>=2))
    {
        imuok = 1;
    }
    else
    {
        imuok = 0;
    }
    if(imuok && T0_State)
    {
        A_SelfCheck_Flag = 1;                            // A_SelfCheck_Flag证明可以收到上位机指令,并且MIMU数据发送正常
        // 自检正常,点亮SYS_LED
        HAL_GPIO_WritePin(SYS_LED_GPIO_Port,SYS_LED_Pin,GPIO_PIN_RESET);
    }
    else
    {    
        A_SelfCheck_Flag = 0;
        // 自检不正常,不点亮SYS_LED
        HAL_GPIO_WritePin(SYS_LED_GPIO_Port,SYS_LED_Pin,GPIO_PIN_SET);
    }
    /*填充帧头*/
    Send.SendToUpper.head0 = 0x55;
    Send.SendToUpper.head1 = 0xAA;
    Send.SendToUpper.feedback_id = UpperCmd.cmd_id;
    Send.SendToUpper.A_CheckSelf = A_SelfCheck_Flag;
       
    Send.SendToUpper.B_CheckSelf = 0;
    
    /*按照SELFCHECK_FRAME格式组帧*/
    memcpy(Send.SendToUpper.MIMU1_Data,MIMU1_Valid_Data,VALID_DATA_LEN);
    memcpy(Send.SendToUpper.MIMU2_Data,MIMU2_Valid_Data,VALID_DATA_LEN); 
    memcpy(Send.SendToUpper.MIMU3_Data,MIMU3_Valid_Data,VALID_DATA_LEN); 
    memcpy(Send.SendToUpper.MIMU4_Data,MIMU4_Valid_Data,VALID_DATA_LEN); 
    memcpy(Send.SendToUpper.MIMU5_Data,MIMU5_Valid_Data,VALID_DATA_LEN);     
    
    for(uint8_t g_i = 0; g_i < One_BmsCheck; One_BmsCheck--)
    {
        /* 下发获取电池数据的指令帧 */
        RS485_Send_Data(read_bms,7);//发送7个字节     
        /* 接收电池数据信息 */
        /* 阻塞接收电池数据 */    
        HAL_Delay(60);
        RS485_Receive_Data(rs485_rcv,&rcv_len);
    }
    
    Send.SendToUpper.battery_vol = ((uint16_t)rs485_rcv[4] << 8) + ((uint16_t)rs485_rcv[5]);             // 电压
    Send.SendToUpper.battery_residue_time = ((uint16_t)rs485_rcv[12] << 8) + ((uint16_t)rs485_rcv[13]);  // 使用次数
    Send.SendToUpper.battery_capacity = 0;
    Send.SendToUpper.battery_capacity = (uint16_t)rs485_rcv[23];                                         // 电量
    
    /*校验和先清零*/
    Send.SendToUpper.checksum = 0;
    
    for(i = 0; i < Len-1; i++)
    {
        check += Send.uch[i];
    }
    Send.uch[i] = check;
    
//    HAL_UART_Transmit_DMA(&huart8,Send.uch,107);
    CDC_Transmit_HS(Send.uch,107);
}

/*向上位机反馈擦除重建是否成功,1成功,0失败*/
void Erase_Data(void)
{
    uint8_t temp,check=0,i=0;
  uint8_t erase[6] = {0};
    temp = FTL_Format();    // 格式化既能擦除整片数据,也能重建LUT表
    if (temp == 0)          // 格式化成功向上位机发送握手帧
    {
        A_Erase_Flag = 1;
        One_BmsCheck = 3;
    
        erase[0] = 0x55;
        erase[1] = 0xAA;
        erase[2] = UpperCmd.cmd_id;
        erase[3] = A_Erase_Flag;
        
        erase[4] = 0;
        
        for(i = 0; i < 6-1; i++)
        {
            check += erase[i];
        }
        erase[i] = check;
//        HAL_UART_Transmit_DMA(&huart8,Send.uch,6);
        CDC_Transmit_HS(erase,6);    
        // 擦除成功,代表NAND正常
    }
    else
    {
        A_Erase_Flag = 0;
        One_BmsCheck = 3;
        
        erase[0] = 0x55;
        erase[1] = 0xAA;
        erase[2] = UpperCmd.cmd_id;
        erase[3] = A_Erase_Flag;
    
        erase[4] = 0;
        
        for(i = 0; i < 6-1; i++)
        {
            check += erase[i];
        }
        erase[i] = check;
//        HAL_UART_Transmit_DMA(&huart8,Send.uch,6);
        CDC_Transmit_HS(erase,6);    
        // 擦除失败,代表NAND不正常
    }
}

/*确定采集存储板是否具有数据记录条件,1具备条件,0不具备条件*/
void Begin_Record(void)    //     
{    
    uint8_t check=0,i=0;
    uint8_t begin[6] = {0};
    if (A_Erase_Flag == 1)  // 具备记录数据的条件  反馈至上位机
    {
        A_Record_Flag = 1;
        
        begin[0] = 0x55;
        begin[1] = 0xAA;
        begin[2] = UpperCmd.cmd_id;
        begin[3] = A_Record_Flag;
    
        begin[4] = 0;

        for(i = 0; i < 6-1; i++)
        {
            check += begin[i];
        }
        begin[i] = check;
//        HAL_UART_Transmit_DMA(&huart8,Send.uch,6);
        CDC_Transmit_HS(begin,6);    
    }
    else                   // 不具备记录数据的条件  反馈至上位机
    {
        A_Record_Flag = 0;
        
        begin[0] = 0x55;
        begin[1] = 0xAA;
        begin[2] = UpperCmd.cmd_id;
        begin[3] = A_Record_Flag;
        
        begin[4] = 0;
        
        for(i = 0; i < 6-1; i++)
        {
            check += begin[i];
        }
        begin[i] = check;
//        HAL_UART_Transmit_DMA(&huart8,Send.uch,6);
        CDC_Transmit_HS(begin,6);
    }
}

/*停止数据记录,A_Record_Flag复位,A_Stop_Flag置1*/
void Stop_Record(void)             
{        
        HAL_TIM_Base_Stop_IT(&htim3);  // 关闭1ms定时器
      k_idx = 0;                     // 切片索引清零
        InitBigFrame();                                 // stop后,大帧序号清零  
        A_Stop_Flag = 1;               // A_Stop_Flag置1
    
        uint8_t check=0,i=0;
        uint8_t stop[6] = {0};
        stop[0] = 0x55;
        stop[1] = 0xAA;
        stop[2] = UpperCmd.cmd_id;
        stop[3] = A_Stop_Flag;
        
        stop[4] = 0;

        for(i = 0; i < 6-1; i++)
        {
            check += stop[i];
        }
        stop[i] = check;
//        HAL_UART_Transmit_DMA(&huart8,Send.uch,6);
        CDC_Transmit_HS(stop,6);       // 停止记录后  反馈至上位机
        HAL_Delay(2);
        // 接收停止命令后,向NAND FLASH写入一页的停止符0x7e
        memset(WTestBuf,0x7e,8192);
        FTL_WriteSectors(WTestBuf,WSectorNum,8192,1);
        WSectorNum += 1;
        // 
        UpperCmd.cmd_id = 0x06;        // 程序跳入空循环     所有程序都停止    
        WSectorNum = 0;                // 停止后,写数据序号清零
        A_SelfCheck_Flag = 0;          // 自检标志位初始化
        A_Record_Flag = 0;             // 开始记录标志位初始化
        A_Erase_Flag = 0;              // 擦除标志位初始化
        A_Stop_Flag = 0;               // 停止标志位初始化
        T0_State = 0;                    // 默认0无效,为1时,T0有效
}

/*数据读取,先通知上位机A_Read_Flag置1,再跳入Real_Read_Data函数*/
void Read_Data(void)
{
    uint8_t check=0,i=0;
    uint8_t read[6] = {0};

    A_Read_Flag = 1;
    
    read[0] = 0x55;
    read[1] = 0xAA;
    read[2] = UpperCmd.cmd_id;
    read[3] = A_Read_Flag;
    read[4] = 0x00;          // 数据读取阶段只需要读取A板

    for(i = 0; i < 6-1; i++)
    {
        check += read[i];
    }
    read[i] = check;
//    HAL_UART_Transmit_DMA(&huart8,Send.uch,6);
    CDC_Transmit_HS(read,6);    
}

/*空语句,用于停止工作死循环*/
void blank(void)
{
        // 空着
}

/*正式记录数据前,需要获取MIMU数据,组帧为128字节写入Nand Flash*/
void Real_Begin_Record(void)
{
    if (A_Record_Flag == 1)
    {
        FTL_WriteSectors(WTestBuf,WSectorNum,8192,1);
        WSectorNum += 1;
    }
}

void Real_Read_Data(void)
{
        uint8_t temp_R = 0;
        int Tx_slice = 0,jj = 0;
        // 读取一页的数据
        FTL_ReadSectors(RTestBuf,RSectorNum,8192,1);
        RSectorNum += 1;
        
        temp_R = memcmpD(RTestBuf,0x7e,8192);
        if(temp_R == 1)         // 等于1,全是7E ,读取结束
        {
            // 先把结束符发出
//            for(Tx_slice=0;Tx_slice<4;Tx_slice++)
//            {
//                for(jj=0;jj<2048;jj++)
//                {
//                    TxBuf2K[jj] = RTestBuf[Tx_slice*2048+jj];
//                }
//                CDC_Transmit_HS(TxBuf2K,2048);      // 一次发2K
//                HAL_Delay(2);                       // 每发送一次延时2ms
//            }
            
            UpperCmd.cmd_id = 0x06;
            RSectorNum = 0;
            A_Read_Flag = 0;           // 读数据标志位初始化
            uint8_t chr1[6];
            memset(chr1,0xaa,6);
            HAL_Delay(200);        // 延时200ms把0xaa发送至上位机
            CDC_Transmit_HS(chr1,6); // 发送至上位机,读取数据已经结束
        }
        else   // 否则为有效数据,分四次发出去
        {
            //    HAL_UART_Transmit_DMA(&huart8,RTestBuf,8192);
            //  CDC_Transmit_HS(RTestBuf,8192);   // 一次发8K
            // 8192字节分四次发送,连续两次调用CDC_Transmit_HS,需要间隔不小于100US
            for(Tx_slice=0;Tx_slice<4;Tx_slice++)
            {
                for(jj=0;jj<2048;jj++)
                {
                    TxBuf2K[jj] = RTestBuf[Tx_slice*2048+jj];
                }
                CDC_Transmit_HS(TxBuf2K,2048);      // 一次发2K
                HAL_Delay(2);                       // 每发送一次延时2ms
            }
        }
}

uint8_t memcmpD(uint8_t *tPtr,uint8_t DATA,int Len)
{
    int icmp = 0;
    uint16_t equal_num = 0;
    int Data_Len = Len;
    int All_Len = Len;
    for(icmp = 0; icmp < Data_Len; icmp++)
    {
        if(tPtr[icmp] == DATA)   // 当前指针指向的数据与DATA常数比较,相等则equal_num加1
        {
            equal_num += 1;
        }
    }
    if(equal_num == All_Len)
    {
        return 1;     // 若完全相等,返回1,全是DATA,返回1
    }
    return 0;       // 不全是DATA,返回0
}

void Check_T0(void)
{
    GPIO_PinState ret;
    ret = HAL_GPIO_ReadPin(T0_START_GPIO_Port,T0_START_Pin);
    
    if(ret == GPIO_PIN_RESET)  // T0被按下,点亮T0_LED
    {
         HAL_GPIO_WritePin(T0_LED_GPIO_Port,T0_LED_Pin,GPIO_PIN_RESET);
         T0_State = 1;             // T0一次通过就置位T0_Sta,保持不变
    }
    else
    {
         HAL_GPIO_WritePin(T0_LED_GPIO_Port,T0_LED_Pin,GPIO_PIN_SET);
    }
}

望采纳!!!点击回答右侧采纳即可!!!

问题可能是由于NAND Flash驱动程序存在缺陷或者是在使用过程中出现了访问冲突或数据不同步等问题。

建议检查驱动程序是否在读写操作时正确的锁定和解锁了读写区域,确保数据的正确性。
检查是否有多个线程或者中断同时访问NAND Flash,并确保正确的同步机制。
检查是否有其他的硬件或软件访问NAND Flash,并确保没有冲突。
检查是否有正确的错误处理机制,在读写NAND Flash时能够及时发现并处理错误。

总之,需要深入分析整个系统的工作流程,找出问题的根本原因。

我怀疑是第4步的判断有问题,跟你说的在第一页读取到7e就停止,这就会导致不会读取后面的数据;

可以从这个角度排查下。

有遇到这么个问题,不知道是不是跟你的问题相关,STM32写入FLASH出现hard fault异常:
解决方案就是:生成一个临时数组,将DMA接收的数据重新做一份拷贝,使用拷贝数组进行flash写入,就没有问题了。
希望能对你有点启发。

  1. 电源电压不稳导致的NAND flash程序错误。这个错误一般是电池电量的问题,解决方法可以在程序中加入或者提高电池电量检测的阈值,保证所有芯片在这个阈值上均可以正常工作。
  2. DRAM工作状态不正常导致的NAND flash程序错误,这个错误可能要考虑是否要重新烧录。
  3. 坏块管理未做好
    根据规格说明进行检验等,或从程序方面着手,分析一下程序对于坏块部分管理是否完善。

这种情况下,可以试试先分析整个程序的结构,以及思维,望采纳

可能的原因:
NAND Flash 控制器配置不正确。
NAND Flash 芯片已损坏。
数据传输过程中出现错误。
NAND Flash 中的坏块导致的读写错误。
电源电压不稳定。

刚好也碰到这个问题,同问