STM32F103ZE微控制器FSMC介面讀取NAND Flash晶片K9F1G08U0E的資料時出現數據丟失的解決辦法
阿新 • • 發佈:2019-02-01
【問題】
STM32微控制器使用FSMC讀取K9F1G08U0E NAND Flash時,出現部分位元組丟失的情況。例如:Flash儲存器中儲存有連續的0xff位元組,則在進行連續讀(Page Read)操作時可能會丟失部分0xff。
例如,寫入以下資料到某一頁的開頭(如地址0x00800000):
{0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x21, 0xff, 0xff, 0xff, 0xff, 0x74, 0x68, 0x65, 0x73, 0x65, 0xff, 0xff, 0xff, 0xff, 0x77, 0x68, 0x61, 0x74, 0x3f, 0xff, 0xff, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0xff}用上述函式從該頁首地址連續讀取50位元組內容:#define NANDFLASH2 ((volatile uint8_t *)0x70000000) #define NANDFLASH2C ((volatile uint8_t *)0x70010000) #define NANDFLASH2A ((volatile uint8_t *)0x70020000) #define NANDFLASH2A16 ((volatile uint16_t *)0x70020000) #define NANDFLASH2A32 ((volatile uint32_t *)0x70020000) void K9F1G08U0E_ReadPage(uint32_t addr, void *buffer, uint16_t len) { *NANDFLASH2C = 0x00; *NANDFLASH2A32 = addr; *NANDFLASH2C = 0x30; memcpy(buffer, (void *)NANDFLASH2, len); }
uint8_t data[50];
K9F1G08U0E_ReadPage(0x800000, data, sizeof(data));
則讀出來的內容是:54686973206973206120737472696E6721FFFF7468657365FFFFFFFF776861743FFFFF48656C6C6F20576F726C6421FFFFFF4個0xff變成了兩個0xff。 如果開啟串列埠接收中斷,在中斷處理函式中每接收到一個字元a,就從0x800000地址開始讀取一個位元組並列印。收到b接著讀取一個位元組,收到c讀取兩個位元組,收到d讀取四個位元組:
那麼,程式執行時先按下a,然後按住b不放(對於STM32F103,列印每一行都需要花13*8/0.115200≈903μs的時間,相當於903*72=65016個HCLK時鐘週期),則只能讀到三個0xff:void USART2_IRQHandler(void) { uint8_t data = USART2->DR; if (data == 'a') { K9F1G08U0E_ReadPage(0x800000, &data, 1); printf("[First] 0x%02x\n", data); } else if (data == 'b') printf("[Byte] 0x%02x\n", *NANDFLASH2); else if (data == 'c') printf("[Half Word] 0x%04x\n", *(volatile uint16_t *)NANDFLASH2); else if (data == 'd') printf("[Word] 0x%08x\n", *(volatile uint32_t *)NANDFLASH2); }
[Byte] 0x21
[Byte] 0xff
[Byte] 0xff
[Byte] 0xff
[Byte] 0x74
若按下a後,每隔一段時間按b,則可以完整地讀到4個0xff:[Byte] 0x21
[Byte] 0xff
[Byte] 0xff
[Byte] 0xff
[Byte] 0xff
[Byte] 0x74
如果這4個位元組的內容不是0xff,則不會出現任何問題。
【解決方法】
讀取第一位元組後,每讀取一個位元組,都發送一次Random Data Output命令,指明下一個位元組的地址。這種方式不影響ECC的計算。
void K9F1G08U0E_Read(uint32_t addr, void *buffer, uint16_t len)
{
uint8_t *p = buffer;
uint16_t i;
*NANDFLASH2C = 0x00;
*NANDFLASH2A32 = addr;
*NANDFLASH2C = 0x30;
p[0] = *NANDFLASH2;
for (i = 1; i < len; i++)
{
*NANDFLASH2C = 0x05;
*NANDFLASH2A16 = (addr + i) & 0xffff;
*NANDFLASH2C = 0xe0;
p[i] = *NANDFLASH2;
}
}
筆者發現,之前的程式如果讀寫大塊的資料,幾乎每一頁都有丟失資料的可能,就連4位元組的ECC碼也不例外!
ECC儲存在每一頁的第2048~2052位元組處,必須一個位元組一個位元組讀取,不可連讀!
筆者把NAND Flash模組接到微雪STM32F103VE核心板上發現,當模組上的兩個VCC埠和兩個GND埠都接到3.3V電源上時,原來的函式能正常工作。
另外,STM32微控制器的每個電源引腳上接上電容對於保證微控制器本身和外圍器件工作的穩定性也非常重要。
如圖,介面上兩組VCC和GND都要接到電源上,不能只接一組。另外,ALE接D17(PD12),CLE接D16(PD11)。
儘管在VE核心板上測試成功了,但是筆者在自己焊的ZE板上即使插上5V的外接電源,用AMS1117轉成3.3V後再並聯上100μF的電容,把插在FSMC上的NOR Flash和SRAM都取下來,也未能解決問題。
【示例程式:帶ECC檢測的讀寫】
main.c:
#include <stdio.h>
#include <stm32f10x.h>
#include "K9F1G08U0E.h"
uint8_t buffer[2048];
void dump_data(const void *data, uint16_t len)
{
const uint8_t *p = data;
while (len--)
printf("%02X", *p++);
printf("\n");
}
int fputc(int ch, FILE *fp)
{
if (fp == stdout)
{
if (ch == '\n')
{
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_SendData(USART1, '\r');
}
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_SendData(USART1, ch);
}
return ch;
}
void read_check(void)
{
uint16_t page;
for (page = 64; page <= 188; page++)
{
if (!K9F1G08U0E_ReadPage(page, buffer))
printf("ECC failed at page %d\n", page);
}
printf("Checked!\n");
}
int main(void)
{
uint8_t data[5];
FSMC_NANDInitTypeDef fsmc;
FSMC_NAND_PCCARDTimingInitTypeDef fsmc_timing;
GPIO_InitTypeDef gpio;
USART_InitTypeDef usart;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE | RCC_APB2Periph_USART1, ENABLE);
gpio.GPIO_Mode = GPIO_Mode_AF_PP;
gpio.GPIO_Pin = GPIO_Pin_9;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio);
gpio.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7 | GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_Init(GPIOD, &gpio);
gpio.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10;
GPIO_Init(GPIOE, &gpio);
USART_StructInit(&usart);
usart.USART_BaudRate = 115200;
usart.USART_Mode = USART_Mode_Tx;
USART_Init(USART1, &usart);
USART_Cmd(USART1, ENABLE);
printf("STM32F103ZE FSMC NAND Flash\n");
fsmc.FSMC_AttributeSpaceTimingStruct = &fsmc_timing;
fsmc.FSMC_CommonSpaceTimingStruct = &fsmc_timing;
FSMC_NANDStructInit(&fsmc);
fsmc.FSMC_ECCPageSize = FSMC_ECCPageSize_2048Bytes;
fsmc.FSMC_Waitfeature = FSMC_Waitfeature_Enable;
FSMC_NANDInit(&fsmc);
FSMC_NANDCmd(FSMC_Bank2_NAND, ENABLE);
K9F1G08U0E_ReadID(data);
printf("ID: ");
dump_data(data, sizeof(data));
read_check();
while (1)
__WFI();
}
K9F1G08U0E.h:#define NANDFLASH2 (*(volatile uint8_t *)0x70000000)
#define NANDFLASH2_32 (*(volatile uint32_t *)0x70000000)
#define NANDFLASH2C (*(volatile uint8_t *)0x70010000)
#define NANDFLASH2A (*(volatile uint8_t *)0x70020000)
#define NANDFLASH2A16 (*(volatile uint16_t *)0x70020000)
#define NANDFLASH2A32 (*(volatile uint32_t *)0x70020000)
#define NANDFLASH2S_FAIL 0x01
#define NANDFLASH2S_READY 0x40
#define NANDFLASH2S_NOPROTECTION 0x80
uint8_t K9F1G08U0E_EraseBlock(uint16_t block);
uint8_t K9F1G08U0E_ProgramPage(uint16_t page, const void *data);
void K9F1G08U0E_ReadID(uint8_t data[5]);
uint8_t K9F1G08U0E_ReadPage(uint16_t page, void *data);
void K9F1G08U0E_Reset(void);
K9F1G08U0E.c:#include <stdio.h>
#include <stm32f10x.h>
#include "K9F1G08U0E.h"
/* See [Figure 2] K9F1G08U0E Array Organization */
/* Column (byte) address byte 1 and 2: A7~0, A11~8; Range: 0x0000~0x083f (or 0x07ff) */
/* Row (page) address byte 1 and 2: A19~12, A27~20 Range: 0x00000000~0xffff0000 */
static void K9F1G08U0E_Wait(void);
/* Block count: 1024, each block has 64 pages and each page is 2KB */
uint8_t K9F1G08U0E_EraseBlock(uint16_t block)
{
NANDFLASH2C = 0x60;
NANDFLASH2A16 = block << 6;
NANDFLASH2C = 0xd0;
K9F1G08U0E_Wait();
return (NANDFLASH2 & NANDFLASH2S_FAIL) == 0;
}
/* Page count: 65536 */
uint8_t K9F1G08U0E_ProgramPage(uint16_t page, const void *data)
{
uint16_t i;
NANDFLASH2C = 0x80;
NANDFLASH2A32 = page << 16;
FSMC_NANDECCCmd(FSMC_Bank2_NAND, ENABLE);
for (i = 0; i < 2048; i++)
NANDFLASH2 = *((const uint8_t *)data + i);
while (FSMC_GetFlagStatus(FSMC_Bank2_NAND, FSMC_FLAG_FEMPT) == RESET); // 獲取ECC碼前必須等待FIFO變空
NANDFLASH2_32 = FSMC_GetECC(FSMC_Bank2_NAND);
FSMC_NANDECCCmd(FSMC_Bank2_NAND, DISABLE);
NANDFLASH2C = 0x10;
K9F1G08U0E_Wait();
return (NANDFLASH2 & NANDFLASH2S_FAIL) == 0;
}
void K9F1G08U0E_ReadID(uint8_t data[5])
{
uint8_t i;
NANDFLASH2C = 0x90;
NANDFLASH2A = 0x00;
for (i = 0; i < 5; i++)
data[i] = NANDFLASH2;
}
// 板上兩個VCC和GND都必須接到電源上才可以使用此函式!
uint8_t K9F1G08U0E_ReadPage(uint16_t page, void *data)
{
uint16_t i;
uint32_t ecc[2];
FSMC_NANDECCCmd(FSMC_Bank2_NAND, ENABLE);
NANDFLASH2C = 0x00;
NANDFLASH2A32 = page << 16;
NANDFLASH2C = 0x30;
for (i = 0; i < 2048; i++)
*((uint8_t *)data + i) = NANDFLASH2;
ecc[0] = NANDFLASH2_32;
while (FSMC_GetFlagStatus(FSMC_Bank2_NAND, FSMC_FLAG_FEMPT) == RESET);
ecc[1] = FSMC_GetECC(FSMC_Bank2_NAND);
FSMC_NANDECCCmd(FSMC_Bank2_NAND, DISABLE);
return ecc[0] == ecc[1];
}
/*
// 備用函式, 速度大約比上面的慢4倍
uint8_t K9F1G08U0E_ReadPage(uint16_t page, void *data)
{
uint16_t i;
uint8_t ecc1[4];
uint32_t ecc2;
FSMC_NANDECCCmd(FSMC_Bank2_NAND, ENABLE);
NANDFLASH2C = 0x00;
NANDFLASH2A32 = page << 16;
NANDFLASH2C = 0x30;
for (i = 0; i < 2048; i++)
{
*((uint8_t *)data + i) = NANDFLASH2;
NANDFLASH2C = 0x05;
NANDFLASH2A16 = i + 1;
NANDFLASH2C = 0xe0;
}
for (i = 0; i < 4; i++)
{
ecc1[i] = NANDFLASH2;
if (i != 3)
{
NANDFLASH2C = 0x05;
NANDFLASH2A16 = i + 2049;
NANDFLASH2C = 0xe0;
}
}
while (FSMC_GetFlagStatus(FSMC_Bank2_NAND, FSMC_FLAG_FEMPT) == RESET);
ecc2 = FSMC_GetECC(FSMC_Bank2_NAND);
FSMC_NANDECCCmd(FSMC_Bank2_NAND, DISABLE);
return *(uint32_t *)ecc1 == ecc2;
}
*/
void K9F1G08U0E_Reset(void)
{
NANDFLASH2C = 0xff;
}
static void K9F1G08U0E_Wait(void)
{
NANDFLASH2C = 0x70;
while ((NANDFLASH2 & NANDFLASH2S_READY) == 0);
}
如果最後實在沒有辦法解決問題再使用備用函式。
【示例程式(推薦):主讀取函式執行後若出現ECC校驗錯誤,則呼叫備用讀取函式】
static uint8_t K9F1G08U0E_ReadPage2(uint16_t page, void *data);
uint8_t K9F1G08U0E_ReadPage(uint16_t page, void *data)
{
uint16_t i;
uint32_t ecc[2];
FSMC_NANDECCCmd(FSMC_Bank2_NAND, ENABLE);
NANDFLASH2C = 0x00;
NANDFLASH2A32 = page << 16;
NANDFLASH2C = 0x30;
for (i = 0; i < 2048; i++)
*((uint8_t *)data + i) = NANDFLASH2;
ecc[0] = NANDFLASH2_32;
while (FSMC_GetFlagStatus(FSMC_Bank2_NAND, FSMC_FLAG_FEMPT) == RESET);
ecc[1] = FSMC_GetECC(FSMC_Bank2_NAND);
FSMC_NANDECCCmd(FSMC_Bank2_NAND, DISABLE);
if (ecc[0] == ecc[1])
return 1;
else
return K9F1G08U0E_ReadPage2(page, data);
}
static uint8_t K9F1G08U0E_ReadPage2(uint16_t page, void *data)
{
uint16_t i;
uint8_t ecc1[4];
uint32_t ecc2;
FSMC_NANDECCCmd(FSMC_Bank2_NAND, ENABLE);
NANDFLASH2C = 0x00;
NANDFLASH2A32 = page << 16;
NANDFLASH2C = 0x30;
for (i = 0; i < 2048; i++)
{
*((uint8_t *)data + i) = NANDFLASH2;
NANDFLASH2C = 0x05;
NANDFLASH2A16 = i + 1;
NANDFLASH2C = 0xe0;
}
for (i = 0; i < 4; i++)
{
ecc1[i] = NANDFLASH2;
if (i != 3)
{
NANDFLASH2C = 0x05;
NANDFLASH2A16 = i + 2049;
NANDFLASH2C = 0xe0;
}
}
while (FSMC_GetFlagStatus(FSMC_Bank2_NAND, FSMC_FLAG_FEMPT) == RESET);
ecc2 = FSMC_GetECC(FSMC_Bank2_NAND);
FSMC_NANDECCCmd(FSMC_Bank2_NAND, DISABLE);
return *(uint32_t *)ecc1 == ecc2;
}
通過調整FSMC時序的延時時間可以大幅度提高讀取速度,新增如下程式碼:
fsmc_timing.FSMC_HiZSetupTime = 0;
fsmc_timing.FSMC_HoldSetupTime = 1;
fsmc_timing.FSMC_SetupTime = 0;
fsmc_timing.FSMC_WaitSetupTime = 2;
可以發現,即便是使用K9F1G08U0E_ReadPage2函式來讀取,也能在瞬間完成。
【程式在STM32F407VE開發板上的測試】
使用40cm長的杜邦線時,K9F1G08U0E_ReadPage和K9F1G08U0E_ReadPage2函式均不能正常工作,函式返回0,甚至K9F1G08U0E_ReadID都不能正確讀取。
不修改程式,改接20cm的杜邦線,只有兩頁(4KB)能夠用K9F1G08U0E_ReadPage函式讀取成功,其餘頁用K9F1G08U0E_ReadPage2才能讀取成功。
//STM32F407VE FSMC NAND Flash
//SYSCLK=168.00MHz HCLK=168.00MHz PCLK1=42.00MHz PCLK2=84.00MHz
//HSIRDY=1, HSERDY=1, LSIRDY=0, LSERDY=1, SYSCLK=8
fsmc_timing.FSMC_HiZSetupTime = 0;
fsmc_timing.FSMC_HoldSetupTime = 8;
fsmc_timing.FSMC_SetupTime = 0;
fsmc_timing.FSMC_WaitSetupTime = 3;
【程式在STM32F207VE開發板上的測試】為了確定這個問題是不是因為連線太長了導致的,筆者又把該模組插在了STM32F207VE板子上,線的長度大概為5cm左右,比之前的20cm杜邦線短了近四分之一。 這一次距離已經近的不能再近了,連在MCU上側的連線有6條,右側(最近側)有4條,下側有4條,再加兩根電源線。最長的線也就5cm左右。 先執行一下程式(OLED_ENABLE),把結果顯示在OLED屏上,全是Y,好像都可以成功讀取。。 (Y表示K9F1G08U0E_ReadPage函式直接就能讀取成功,感嘆號表示ECC校驗錯誤但K9F1G08U0E_ReadPage2能讀取成功,X表示兩個函式都不能讀取成功,N表示K9F1G08U0E_ReadPage函式讀取後出現ECC校驗錯誤且程式沒有嘗試用Read2函式再次讀取)
可是,一旦在fputc函式中加入了串列埠輸出,或者把操作OLED屏的程式碼註釋掉,就只有部分頁能成功讀取。問題仍沒有解決。 串列埠輸出內容:啟用了OLED屏(定義了OLED_ENABLE,fputc函式中不含串列埠操作程式碼,只含寫OLED屏的程式碼) 頁面全部讀取成功,ECC校驗通過,但有時有一個頁面讀取不成功。 串列埠輸出內容:禁用了OLED屏,fputc把字元先放入緩衝區,最後統一輸出到串列埠 OLED屏一關,就有一大片的頁面不能正常讀取,必須用Read2函式重讀才能通過ECC校驗。 【結論】 (Read函式為連續讀取,Read2函式為隨機讀取)
當連線的長度為40cm時,該NAND Flash晶片完全無法正常工作(全是X),無論是連續讀取還是隨機讀取都不能成功,就連器件ID也無法讀取。
當連線的長度為20cm時,幾乎所有的頁面都不能連續讀取(全是感嘆號),最多隻有兩個頁面能連續讀取,但所有的頁面均能隨機讀取並通過ECC校驗。 當連線的長度為5cm時,有一部分頁面能夠連續讀取並通過ECC校驗,其他頁面仍需要靠隨機讀取才能成功(感嘆號和Y各佔一半)。