STM32F103ZE单片机FSMC接口读取NAND Flash芯片K9F1G08U0E的数据时出现数据丢失的解决办法

【问题】

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}
#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);
}
用上述函数从该页首地址连续读取50字节内容:
uint8_t data[50];
K9F1G08U0E_ReadPage(0x800000, data, sizeof(data));
则读出来的内容是:54686973206973206120737472696E67 21FFFF7468657365FFFFFFFF776861743FFFFF48656C6C6F20576F726C6421FFFFFF
4个0xff变成了两个0xff。

如果打开串口接收中断,在中断处理函数中每接收到一个字符a,就从0x800000地址开始读取一个字节并打印。收到b接着读取一个字节,收到c读取两个字节,收到d读取四个字节:
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);
}
那么,程序运行时先按下a,然后按住b不放(对于STM32F103,打印每一行都需要花13*8/0.115200≈903μs的时间,相当于903*72=65016个HCLK时钟周期),则只能读到三个0xff:
[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杜邦线短了近四分之一。
工程下载地址: https://pan.baidu.com/s/1nvaHTSt
这一次距离已经近的不能再近了,连在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各占一半)。
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨大八爪鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值