首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

AT45DB041D DataFlash 驅動設計與頁式存儲管理算法完整實現

     編輯:關於C語言

項目背景與挑戰

在嵌入式儀表項目中,需將采樣數據可靠存儲至 AT45DB041D (4Mbit DataFlash)。早期版本出現「數據寫入正常,放置一段時間後丟失」的離譜現象。經過多輪驗證排除驅動邏輯、電源、焊接問題,最終更換原裝進口芯片解決 —— 揭示了器件真實性對存儲可靠性的決定性影響。

本文系統梳理:

  1. GPIO 初始化與軟件模擬 SPI 時序
  2. 底層讀/寫/狀態查詢原語
  3. Buffer 操作與頁編程 (含內置擦除)
  4. 面向對象風格的頁式存儲管理算法 (C 語言實現)

第一部分:硬件抽象層 (HAL) 實現

1.1 GPIO 初始化 (LPC 系列 MCU)

/* 配置 DataFlash SPI 引腳為 GPIO 模式 */
void DFlashGpioInit(void)
{
    IOCON_PIO_CFG_Type PIO_mode;
    IOCON_StructInit(&PIO_mode);
    
    // P0.14=SCLK, P0.15=SSEL, P0.16=MOSI, P0.17=MISO, P0.18=nRESET, P0.27=nWP
    PIO_mode.type = IOCON_PIO_0_14 | IOCON_PIO_0_15 | IOCON_PIO_0_16 |
                    IOCON_PIO_0_17 | IOCON_PIO_0_18 | IOCON_PIO_0_27;
    IOCON_SetFunc(&PIO_mode);

    GPIO_SetDir(LPC_GPIO0, 14, GPIO_DIR_OUTPUT); // SCLK
    GPIO_SetDir(LPC_GPIO0, 15, GPIO_DIR_OUTPUT); // SSEL (軟件控制)
    GPIO_SetDir(LPC_GPIO0, 16, GPIO_DIR_OUTPUT); // MOSI
    GPIO_SetDir(LPC_GPIO0, 18, GPIO_DIR_OUTPUT); // nRESET
    GPIO_SetDir(LPC_GPIO0, 27, GPIO_DIR_OUTPUT); // nWP
    GPIO_SetDir(LPC_GPIO0, 17, GPIO_DIR_INPUT);  // MISO

    F_nRESET_FlashH(); // 釋放復位
    F_nWPH();          // 解除寫保護
}

1.2 軟件模擬 SPI 位操作 (Mode 0: CPOL=0, CPHA=0)

/* 寫入 1 Byte (MSB First) */
void Spi_Write(uint8_t data)
{
    for (uint8_t i = 0; i < 8; i++) {
        F_SCLKL();                          // 時鐘拉低
        if (data & 0x80) F_MOSIH(); else F_MOSIL(); // 輸出最高位
        data <<= 1;                         // 左移準備下一位
        F_SCLKH();                          // 上升沿采樣 (Mode 0)
    }
}

/* 讀取 1 Byte (數據在 SCK 下降沿輸出) */
uint8_t Spi_Read(void)
{
    uint8_t temp = 0;
    for (uint8_t i = 0; i < 8; i++) {
        F_SCLKH();                          // 時鐘拉高
        F_SCLKL();                          // 下降沿數據有效
        temp <<= 1;
        if (GPIO_GetPinValue(SPI_PORT, IO_MISO) & 0x01)
            temp |= 0x01;
    }
    return temp;
}
關鍵時序說明:AT45DB041D 數據在 SCK 下降沿輸出,上升沿采樣。讀操作必須先拉高再拉低 (SCLKH -> SCLKL) 產生下降沿;寫操作在上升沿 (SCLKL -> SCLKH) 采樣 MOSI。

第二部分:核心命令封裝

2.1 連續陣列讀取 (Opcode 0xE8)

/* 從指定頁(PA)的字節偏移(BFA)開始讀取 len 字節到 rbuff */
void DFlash_ContinuousArrayRead(uint32_t PA, uint32_t BFA, uint8_t* rbuff, uint32_t len)
{
    // 等待 Ready (Status Register bit 7 = 1)
    for (uint8_t i = 0; i < 255; i++)
        if (DFlash_RegisterStatusRead() & 0x80) break;

    F_CSL();                     // 片選拉低
    Spi_Write(0xE8);             // Continuous Array Read (Legacy)
    Spi_Write((uint8_t)(PA >> 7));           // 頁地址高位 (A15-A8)
    Spi_Write((uint8_t)((PA << 1) | (BFA >> 8))); // 頁地址低位+字節偏移高位
    Spi_Write((uint8_t)BFA);                // 字節偏移低位
    Spi_Write(0x00); Spi_Write(0x00); Spi_Write(0x00); Spi_Write(0x00); // 4 個 Don't Care 字節

    for (uint32_t j = 0; j < len; j++)
        rbuff[j] = Spi_Read();

    F_CSH(); // 片選拉高
}

2.2 Buffer 寫入 (Opcode 0x84/0x87)

/* 向 Buffer 1 (buffer=1) 或 Buffer 2 (buffer=2) 的 BFA 偏移寫入 len 字節 */
void DFlash_BufferWrite(uint8_t buffer, uint32_t BFA, uint8_t* pHeader, uint32_t len)
{
    for (uint8_t i = 0; i < 255; i++)
        if (DFlash_RegisterStatusRead() & 0x80) break;

    F_CSL();
    switch (buffer) {
        case 1: Spi_Write(0x84); break; // Buffer 1 Write
        case 2: Spi_Write(0x87); break; // Buffer 2 Write
    }
    Spi_Write(0x00);                   // Don't Care
    Spi_Write((uint8_t)(BFA >> 8));    // Buffer 偏移高位
    Spi_Write((uint8_t)BFA);           // Buffer 偏移低位

    for (uint32_t j = 0; j < len; j++)
        Spi_Write(pHeader[j]);

    F_CSH();
    delay_ms(40); // 等待內部寫入完成 (典型值 20-40ms)
}

2.3 Buffer 到主存頁編程 (含內置擦除, Opcode 0x83/0x86)

/* 將 Buffer 數據寫入指定頁 PA (自動擦除) */
void DFlash_BufferToMainMemoryPageProgramWithBuiltInErase(
    uint8_t buffer, uint32_t PA, uint32_t BFA, uint8_t* pHeader, uint32_t len)
{
    // 1. 先寫 Buffer
    DFlash_BufferWrite(buffer, BFA, pHeader, len);

    // 2. 等待 Ready
    for (uint32_t i = 0; i < 1000; i++)
        if (DFlash_RegisterStatusRead() & 0x80) break;

    // 3. 發送 Buffer-to-Page with Erase 命令
    F_CSL();
    switch (buffer) {
        case 1: Spi_Write(0x83); break; // Buffer 1 to Main Memory with Erase
        case 2: Spi_Write(0x86); break; // Buffer 2 to Main Memory with Erase
    }
    Spi_Write((uint8_t)(PA >> 7));
    Spi_Write((uint8_t)(PA << 1));
    Spi_Write(0x00);
    F_CSH();
    delay_ms(40);
}

第三部分:頁式存儲管理算法 (面向對象 C 設計)

業務需求:支持多參數 (P001 存儲時長、P002 打印模式、P081 采樣周期) 的循環存儲,掉電斷點續傳,攜帶時間戳。

3.1 核心數據結構

/* 時間戳結構 (緊湊 8 字節) */
typedef struct {
    uint16_t year;   // 2000-2099
    uint8_t  month;  // 1-12
    uint8_t  day;    // 1-31
    uint8_t  hour;   // 0-23
    uint8_t  min;    // 0-59
    uint8_t  sec;    // 0-59
} TIME_T;

/* Flash 運行狀態 (掉電保存於固定頁/備份區) */
typedef struct {
    uint16_t currentPage;   // 當前寫入頁號 (0-2047)
    uint16_t currentOffset; // 當前頁內字節偏移 (0-263)
    uint8_t  overflowFlag;  // 是否已循環覆蓋
} FLASH_STATE_T;

/* 單條存儲記錄 (固定 8 字節,便於對齊) */
typedef struct {
    uint8_t  day;       // 時間戳 (精簡版)
    uint8_t  hour;
    uint8_t  min;
    uint8_t  sec;
    uint8_t  adcData[2];// 2 通道 ADC 採樣值 (高8位)
} FLASH_RECORD_T;

/* 存儲管理器對象 (封裝狀態與操作) */
typedef struct {
    FLASH_STATE_T state;       // 運行狀態
    TIME_T        lastTimestamp; // 上次寫入時間
    uint16_t      sampleIntervalMs; // 采樣周期 (P081)
    uint8_t       storePolicy;    // 存儲策略 (P001)
    uint8_t       printMode;      // 打印模式 (P002)
} STORAGE_MANAGER_T;

3.2 關鍵算法:掉電恢復與循環寫入

/* 初始化:掃描 Flash 尋找最後有效記錄,恢復 state */
void StorageManager_Init(STORAGE_MANAGER_T* mgr)
{
    // 1. 讀取狀態頁 (假設第 0 頁保存 FLASH_STATE_T)
    DFlash_ContinuousArrayRead(0, 0, (uint8_t*)&mgr->state, sizeof(FLASH_STATE_T));
    
    // 2. 校驗有效性 (簡單 CRC 或魔數)
    if (!IsStateValid(&mgr->state)) {
        mgr->state.currentPage = 1;   // 跳過狀態頁
        mgr->state.currentOffset = 0;
        mgr->state.overflowFlag = 0;
    }

    // 3. 從當前位置向前掃描最後一條有效記錄,恢復 lastTimestamp
    //    (實際項目中建議在記錄頭部加 Magic+CRC,此處略)
}

/* 寫入一條記錄 (自動換頁、循環覆蓋) */
BOOL StorageManager_WriteRecord(STORAGE_MANAGER_T* mgr, const FLASH_RECORD_T* rec)
{
    // 1. 檢查當前頁剩餘空間
    if (mgr->state.currentOffset + sizeof(FLASH_RECORD_T) > 264) { // 頁大小 264 bytes
        // 換頁
        mgr->state.currentPage++;
        if (mgr->state.currentPage >= 2048) { // 總頁數 2048
            mgr->state.currentPage = 1;       // 循環到第 1 頁 (0 頁留給狀態)
            mgr->state.overflowFlag = 1;
        }
        mgr->state.currentOffset = 0;
    }

    // 2. 寫入 Buffer 1 -> 主存頁 (帶擦除)
    DFlash_BufferToMainMemoryPageProgramWithBuiltInErase(
        1, 
        mgr->state.currentPage, 
        mgr->state.currentOffset, 
        (uint8_t*)rec, 
        sizeof(FLASH_RECORD_T)
    );

    // 3. 更新狀態
    mgr->state.currentOffset += sizeof(FLASH_RECORD_T);
    mgr->lastTimestamp = GetCurrentTime(); // 從 RTC 獲取

    // 4. 定期同步狀態頁 (每 100 條或關鍵事件)
    if ((mgr->state.currentOffset % (100 * sizeof(FLASH_RECORD_T))) == 0)
        StorageManager_SyncState(mgr);

    return TRUE;
}

3.3 存儲策略映射表 (P001 參數驅動)

/* 根據 P001 計算最大保留記錄數 */
uint32_t CalcMaxRecords(uint8_t policy, uint16_t intervalMs)
{
    uint32_t maxSeconds = 0;
    switch (policy) {
        case 0: return 0;           // 不存儲
        case 1: maxSeconds = 24*3600; break;      // 1 天
        case 2: maxSeconds = 7*24*3600; break;    // 1 週
        case 3: maxSeconds = 30*24*3600; break;   // 1 月
        case 4: maxSeconds = 90*24*3600; break;   // 3 月
        case 5: maxSeconds = 180*24*3600; break;  // 6 月
        case 9: return 0;           // 清零 (特殊處理)
        default: return UINT32_MAX;
    }
    return maxSeconds * 1000 / intervalMs; // 總采樣點數
}

工程經驗總結

  1. 器件來源決定成敗:同批次代碼,國產/翻新片高溫/長時間掉電丟數據,原裝片零故障。生產務必建立合格供應商清單 (AVL) 並做高低溫老化篩選。
  2. 狀態機持久化:FLASH_STATE_T 必須掉電保存,且採用雙備份+CRC機制 (頁 0 和 頁 2047 各存一份),防止斷電寫壞單一狀態頁。
  3. 時間戳冗餘設計:記錄內嵌時間戳而非純依賴順序,支持跨頁、跨循環的數據重組與缺失檢測。
  4. C 語言面向對象:通過 STORAGE_MANAGER_T 封裝狀態與操作,實現「類」語義,多實例互不干擾,代碼清晰可維護。
  5. 等待 Ready 的超時保護:所有輪詢 Status Register 循環必須加計數器上限,防止芯片故障導致系統死鎖。

結語

從 GPIO 翻轉到頁式管理,AT45DB041D 驅動的完整實現展示了嵌入式存儲系統的全鏈路設計。核心洞見在於:可靠性不僅在代碼,更在器件選型、狀態持久化策略、以及面對失效模式的防禦性設計。本文提供的結構體與算法框架可直接移植至同類 DataFlash (AT45DB 系列、Adesto/Dialog 兼容片) 項目中。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved