微雪K9F1G08U0E NAND Flash模块读数据的时候出现部分字节漏读的问题及其解决方案

【现象】

数据能正常写入到存储器中,但读的时候会发现总是有一些字节出现丢失,如下图所示。

【程序】

以下程序直接用GPIO模拟了FSMC的时序来读写NAND Flash,方便从时序上诊断问题。

main.c:

#include <stdio.h>
#include <stm32f10x.h>
#include "NAND.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(USART2, USART_FLAG_TXE) == RESET);
      USART_SendData(USART2, '\r');
    }
    while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
    USART_SendData(USART2, ch);
  }
  return ch;
}

void test(void)
{
  NAND_ReadID(buffer);
  printf("Device ID: ");
  dump_data(buffer, 5);
  
  NAND_ReadPage(64, buffer);
  dump_data(buffer, 2048);
}

int main(void)
{
  GPIO_InitTypeDef gpio;
  USART_InitTypeDef usart;
  
  RCC_SYSCLKConfig(RCC_SYSCLKSource_HSE);
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE);
  
  gpio.GPIO_Mode = GPIO_Mode_AF_PP;
  gpio.GPIO_Pin = GPIO_Pin_2;
  gpio.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOA, &gpio);
  
  GPIO_SetBits(GPIOD, GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7); // NOE=NWE=NE1=1
  gpio.GPIO_Mode = GPIO_Mode_Out_PP;
  gpio.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_11 | GPIO_Pin_12; // NOE, NWE, CLE, ALE
  GPIO_Init(GPIOD, &gpio);
  
  // NWAIT接了外部上拉电阻, 只需保持默认的浮空输入
  
  gpio.GPIO_Mode = GPIO_Mode_Out_OD; // NE1外部接了上拉电阻
  gpio.GPIO_Pin = GPIO_Pin_7;
  GPIO_Init(GPIOD, &gpio);
  
  USART_StructInit(&usart);
  usart.USART_BaudRate = 115200;
  USART_Init(USART2, &usart);
  USART_Cmd(USART2, ENABLE);
  printf("STM32F103ZE NAND Flash by GPIO\n");
  
  test();
  
  while (1);
}
NAND.h:
#define NAND_SetALE(v) GPIO_WriteBit(GPIOD, GPIO_Pin_12, v)
#define NAND_SetCLE(v) GPIO_WriteBit(GPIOD, GPIO_Pin_11, v)
#define NAND_SetNE1(v) GPIO_WriteBit(GPIOD, GPIO_Pin_7, v)
#define NAND_SetNOE(v) GPIO_WriteBit(GPIOD, GPIO_Pin_4, v)
#define NAND_SetNWE(v) GPIO_WriteBit(GPIOD, GPIO_Pin_5, v)

uint8_t NAND_Read(void);
uint8_t NAND_ReadData(void);
void NAND_ReadID(uint8_t data[5]);
void NAND_ReadPage(uint16_t page, uint8_t data[2048]);
void NAND_SetMode(GPIOMode_TypeDef mode);
void NAND_Wait(void);
void NAND_Write(uint8_t data);
void NAND_WriteAddress(uint8_t addr);
void NAND_WriteCommand(uint8_t cmd);
NAND.c:
#include <stm32f10x.h>
#include "NAND.h"

//#define NAND_RANDOMREAD

uint8_t NAND_Read(void)
{
  uint16_t data = GPIO_ReadInputData(GPIOD);
  data = ((data & 0x03) << 2) | (data >> 14);
  data |= (GPIO_ReadInputData(GPIOE) >> 3) & 0xf0;
  return (uint8_t)data;
}

uint8_t NAND_ReadData(void)
{
  uint8_t data;
  NAND_SetNE1(Bit_RESET);
  NAND_SetNOE(Bit_RESET);
  data = NAND_Read();
  NAND_SetNOE(Bit_SET);
  NAND_SetNE1(Bit_SET);
  return data;
}

void NAND_ReadID(uint8_t data[5])
{
  uint8_t i;
  NAND_WriteCommand(0x90);
  NAND_WriteAddress(0x00);
  NAND_Wait();
  for (i = 0; i < 5; i++)
    data[i] = NAND_ReadData();
}

void NAND_ReadPage(uint16_t page, uint8_t data[2048])
{
  uint16_t i;
  NAND_WriteCommand(0x00);
  NAND_WriteAddress(0x00);
  NAND_WriteAddress(0x00);
  NAND_WriteAddress(page & 0xff);
  NAND_WriteAddress(page >> 8);
  NAND_WriteCommand(0x30);
  for (i = 0; i < 2048; i++)
  {
    data[i] = NAND_ReadData();
#ifdef NAND_RANDOMREAD
    // 每读完一个字节, 都指明下一个字节的地址 (随机读)
    NAND_WriteCommand(0x05);
    NAND_WriteAddress((i + 1) & 0xff);
    NAND_WriteAddress((i + 1) >> 8);
    NAND_WriteCommand(0xe0);
#endif
  }
}

void NAND_SetMode(GPIOMode_TypeDef mode)
{
  GPIO_InitTypeDef gpio;
  gpio.GPIO_Mode = mode;
  gpio.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_14 | GPIO_Pin_15;
  gpio.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOD, &gpio);
  gpio.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10;
  GPIO_Init(GPIOE, &gpio);
}

void NAND_Wait(void)
{
  while (GPIO_ReadInputDataBit(GPIOD, GPIO_Pin_6) == Bit_RESET);
}

void NAND_Write(uint8_t data)
{
  uint16_t port[2];
  port[0] = GPIO_ReadOutputData(GPIOD);
  port[1] = GPIO_ReadOutputData(GPIOE);
  
  port[0] = (port[0] & 0x3ffc) | ((data & 0x03) << 14) | ((data >> 2) & 0x03);
  port[1] = (port[1] & 0xf87f) | ((data & 0xf0) << 3);
  GPIO_Write(GPIOD, port[0]);
  GPIO_Write(GPIOE, port[1]);
}

void NAND_WriteAddress(uint8_t addr)
{
  NAND_SetALE(Bit_SET);
  NAND_SetNE1(Bit_RESET);
  
  NAND_SetMode(GPIO_Mode_Out_PP);
  NAND_SetNWE(Bit_RESET);
  NAND_Write(addr);
  NAND_SetNWE(Bit_SET);
  NAND_SetMode(GPIO_Mode_IN_FLOATING);
  
  NAND_SetNE1(Bit_SET);
  NAND_SetALE(Bit_RESET);
}

void NAND_WriteCommand(uint8_t cmd)
{
  NAND_SetCLE(Bit_SET);
  NAND_SetNE1(Bit_RESET);
  
  NAND_SetMode(GPIO_Mode_Out_PP);
  NAND_SetNWE(Bit_RESET);
  NAND_Write(cmd);
  NAND_SetNWE(Bit_SET);
  NAND_SetMode(GPIO_Mode_IN_FLOATING);
  
  NAND_SetNE1(Bit_SET);
  NAND_SetCLE(Bit_RESET);
}

【解决方案】

不使用默认的顺序读方式,改用随机读。即每读完一个字节,都指明下一个字节的地址(在上面的程序中定义NAND_RANDOMREAD)。这是唯一、最可靠的解决办法,基本上能够100%保证读到的数据是正确的。


笔者已经在很多开发板上测试过这个程序了。

当连接NAND模块和单片机开发板的杜邦线为40cm的时候,无论是顺序读还是随机读都不能正确的读出数据,就连5字节的器件ID都不能正确读取!

当杜邦线的长度为20cm的时候,器件ID两种方式都可以正确读出来,顺序读数据基本上是100%出错,但随机读数据能够100%读正确。

当线的长度改为5cm的时候(这大概是在洞洞板上焊接飞线的极限了),单片机不打开串口,只开FSMC,可以做到用顺序读方式读完125页只有一两页是错误的,一旦打开了串口,就会有50%以上的页出错。

在上面的用GPIO方式模拟时序的程序中,在NAND_ReadPage函数和NAND_Read函数里面加delay()函数延时,均不能解决问题,花了几十秒时间读出来的一页数据,仍然会提前结束。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

巨大八爪鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值