在嵌入式項目中,數據存儲管理往往是讓工程師頭疼的環節。本文將結合實際專案經驗,詳細講解 AT45DB041D DataFlash 的驅動程序設計,並深入探討頁式存儲管理算法的實現思路,幫助讀者構建可靠、高效的非易失性存儲方案。
驅動開發主要分為三個層次:GPIO 初始化、模擬 SPI 通訊協議、Flash 具體操作命令封裝。
配置 LPC 系列單片機的 P0.14~P0.18 及 P0.27 引腳,分別對應 SCLK、SSEL、MOSI、nRESET、nWP、MISO,並設置初始電平:
void DFlashGpoiInit(void) {
IOCON_PIO_CFG_Type PIO_mode;
IOCON_StructInit(&PIO_mode);
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; // 寫保護拉高
}
通過 GPIO 翻轉實現 SPI Mode 0 時序(SCK 低電平有效,上升沿採樣,下降沿輸出):
// 寫一個字節
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;
}
}
// 讀一個字節
uint8_t Spi_Read(void) {
uint8_t temp = 0;
for (uint8_t i = 0; i < 8; i++) {
F_SCLKH; F_SCLKL; // 上升沿採樣
temp = (temp << 1) | (GPIO_GetPinValue(SPI_PORT, IO_MISO) & 0x01);
}
return temp;
}
根據數據手冊封裝常用命令:連續陣列讀取、Buffer 寫入、Buffer 搬移至主存頁(帶內置擦除)。關鍵點是在發送命令前輪詢狀態寄存器的 Ready 位(Bit 7),確保 Flash 空閒。
// 連續陣列讀取
void DFlash_ContinuousArrayRead(uint32_t PA, uint32_t BFA, uint8_t *pHeader, uint32_t len) {
// 等待 Ready
uint8_t i = 0;
while (i++ < 255) {
if (DFlash_RegisterStatusRead() & 0x80) break;
}
F_CSL; // 片選拉低
Spi_Write(0xE8); // 命令碼
Spi_Write((uint8_t)(PA >> 7)); // 頁地址高位
Spi_Write((uint8_t)((PA << 1) | (BFA >> 8))); // 頁地址低位 + 字節地址高位
Spi_Write((uint8_t)BFA); // 字節地址低位
for (i = 0; i < 4; i++) Spi_Write(0x00); // 4 個虛擬字節
for (uint32_t j = 0; j < len; j++) pHeader[j] = Spi_Read();
F_CSH; // 片選拉高
}
// Buffer 寫入 (Buffer 1: 0x84, Buffer 2: 0x87)
void DFlash_BufferWrite(uint8_t buffer, uint32_t BFA, uint8_t *pHeader, uint32_t len) {
// 等待 Ready...
F_CSL;
Spi_Write(buffer == 1 ? 0x84 : 0x87);
Spi_Write(0x00);
Spi_Write((uint8_t)(BFA >> 8));
Spi_Write((uint8_t)BFA);
for (uint32_t j = 0; j < len; j++) Spi_Write(pHeader[j]);
F_CSH;
delay_ms(40); // 等待內部寫入完成
}
// Buffer 搬移至主存頁(帶擦除) Buffer1: 0x83, Buffer2: 0x86
void DFlash_BufferToMainMemoryPageProgramWithBuilt_inErase(uint8_t buffer, uint32_t PA, uint32_t BFA, uint8_t *pHeader, uint32_t len) {
DFlash_BufferWrite(buffer, BFA, pHeader, len);
// 等待 Ready...
F_CSL;
Spi_Write(buffer == 1 ? 0x83 : 0x86);
Spi_Write((uint8_t)(PA >> 7));
Spi_Write((uint8_t)(PA << 1));
Spi_Write(0x00);
F_CSH;
delay_ms(40);
}
驅動層就緒後,上層需要解決:掉電斷點續傳、多參數存儲、時標記錄、存儲週期策略(P001)、打印/通訊模式(P002)、採樣率配置(P081)等問題。採用面向對象思想,在 C 語言中通過結構體封裝狀態與數據。
// 時間戳結構
typedef struct {
uint16_t year;
uint8_t month, day, hour, min, sec;
} TIME;
// Flash 存儲狀態:記錄當前寫入頁、頁內偏移、溢出標誌
typedef struct {
uint16_t CPage_num; // 當前頁號 (0~2047)
uint16_t CBuf_addr; // 當前頁內字節偏移 (0~263)
uint8_t Cflash_overflow; // 溢出標誌
} CFlash_State;
// 單條存儲數據記錄:攜帶時間戳 + 2 字節 ADC 數據
typedef struct {
uint8_t day, hour, min, sec;
uint8_t adcdata[2];
} CFlash_DataRecord;
CPage_num、CBuf_addr,從中斷處繼續寫入。TIME 戳,配合 RTC 模組定期校準,確保數據可追溯。項目初期出現「數據正常讀寫,放置一段時間後丟失」的現象。經過多輪驅動代碼審查、電源波形分析、甚至更換編程器燒錄方式,問題依舊。最終更換為原裝進口 AT45DB041D 芯片後故障消失。教訓:存儲類器件務必選擇正規渠道原裝片,兼容性與可靠性差異巨大。
本文提供了從底層 GPIO/SPI 驅動、Flash 命令封裝,到上層頁式存儲管理算法的完整實現思路。結構體封裝狀態與數據,配合狀態機思想處理掉電恢復、循環覆蓋、多參數策略,可構建穩健的嵌入式數據存儲系統。代碼模塊化程度高,移植至其他 SPI Flash(如 AT45DB 系列、W25Q 系列)僅需調整命令碼與時序參數。