在嵌入式儀表項目中,需將采樣數據可靠存儲至 AT45DB041D (4Mbit DataFlash)。早期版本出現「數據寫入正常,放置一段時間後丟失」的離譜現象。經過多輪驗證排除驅動邏輯、電源、焊接問題,最終更換原裝進口芯片解決 —— 揭示了器件真實性對存儲可靠性的決定性影響。
本文系統梳理:
/* 配置 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 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。
/* 從指定頁(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(); // 片選拉高
}
/* 向 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)
}
/* 將 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);
}
業務需求:支持多參數 (P001 存儲時長、P002 打印模式、P081 采樣周期) 的循環存儲,掉電斷點續傳,攜帶時間戳。
/* 時間戳結構 (緊湊 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;
/* 初始化:掃描 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;
}
/* 根據 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; // 總采樣點數
}
FLASH_STATE_T 必須掉電保存,且採用雙備份+CRC機制 (頁 0 和 頁 2047 各存一份),防止斷電寫壞單一狀態頁。STORAGE_MANAGER_T 封裝狀態與操作,實現「類」語義,多實例互不干擾,代碼清晰可維護。Status Register 循環必須加計數器上限,防止芯片故障導致系統死鎖。從 GPIO 翻轉到頁式管理,AT45DB041D 驅動的完整實現展示了嵌入式存儲系統的全鏈路設計。核心洞見在於:可靠性不僅在代碼,更在器件選型、狀態持久化策略、以及面對失效模式的防禦性設計。本文提供的結構體與算法框架可直接移植至同類 DataFlash (AT45DB 系列、Adesto/Dialog 兼容片) 項目中。