基于STM32的AT25DF321A型FLASH芯片的驱动调试以及读写测试。

本贴目的为记录分享本人调试过程中遇到的各类问题,填充自己不足。若有疑问欢迎进行交流讨论,谢谢!

本次使用STM32F407型号的MCU使用STD标准固件库通过SPI通信进行驱动片外FLASH(型号为AT25DF321A)进行数据存储读写测试。关于STM32F407芯片我就不多做介绍了,有兴趣了解更多该芯片的可自行查阅相关芯片手册资料。

本次进行此实验,我们主要分解为以下几块步骤:

  1. 确定要使用的对应SPIX引脚。(本次我选择的为SPI2,读者可阅读文档后选择其他SPI通道进行练习)
  2. 初始化对应IO口引脚并复用此部分引脚。
  3. 编写对应AT25XX读写等功能函数的API接口。
  4. 初始化AT25XX后,参考该芯片数据手册使用对应的命令读取该芯片的ID号,确认初始化成功。
  5. 初始化一个数据缓存区A_BUF,调用自行编写的FLASH写函数在该FLASH某一块地址处写入数据缓存区A_BUF的数据。
  6. 初始化另一个数据缓冲区B_BUF为0,然后调用自行编写的AT25读取接口函数,读取该地址中的数据存储到B_BUF中,进行比较两数据缓存区中的数据是否相同。
  7. 根据比较结果返回的值,确认是否数据一致。验证FLASH数据读写是否成功。

首先确定本次使用SPI2进行通信,在以下原理图中可以看到我们选择对应的IO口。

AT25DF312A芯片引脚连接图

STM32F407芯片引脚连接图

PB12:AT25芯片的片选IO口

PB13:SPI通信的时钟引脚CLK

PB14:主机输入,从机输出引脚

PB15:主机输出,从机输入引脚

首先我们进行步骤二,初始化各io引脚复用,然后进行SPI配置初始化:

//AT25 IO、SPI初始化

int AT25FLASH_Init(void)

{           

       GPIO_InitTypeDef GPIO_InitStructure;

       SPI_InitTypeDef  SPI_InitStructure;

      

       RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);//GPIOB时钟

       RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);//SPI时钟

       GPIO_InitStructure.GPIO_Pin = (SPI_SCLK_PIN | SPI_MOSI_PIN | SPI_MISO_PIN);

       GPIO_InitStructure.GPIO_Mode =  GPIO_Mode_AF;

       GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;

       GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;

       GPIO_InitStructure.GPIO_PuPd =  GPIO_PuPd_UP;

       GPIO_Init(SPI_GPIO_PORT,&GPIO_InitStructure);

       GPIO_PinAFConfig(SPI_GPIO_PORT, GPIO_PinSource_SCLK, GPIO_AF_SPI2);

       GPIO_PinAFConfig(SPI_GPIO_PORT, GPIO_PinSource_MISO, GPIO_AF_SPI2);

       GPIO_PinAFConfig(SPI_GPIO_PORT, GPIO_PinSource_MOSI, GPIO_AF_SPI2);

      

       RCC_APB1PeriphResetCmd(RCC_APB1Periph_SPI2,ENABLE);//复位SPI2

       RCC_APB1PeriphResetCmd(RCC_APB1Periph_SPI2,DISABLE);//停止复位SPI2

      

  //NSS片选IO

       GPIO_InitStructure.GPIO_Pin = SPI_GPIO_CS_PIN;

       GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;

       GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;

       GPIO_InitStructure.GPIO_PuPd =  GPIO_PuPd_NOPULL;

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//100MHz   

       GPIO_Init(SPI_GPIO_CS_PORT,&GPIO_InitStructure);

      

  AT25XX_CS = 1;//片选拉高

  //SPI初始化

       SPI_Cmd(SPI2, DISABLE);

       SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;

       SPI_InitStructure.SPI_Mode = SPI_Mode_Master;

       SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;

       SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;

       SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;

       SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;

       SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;

       SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;   

       SPI_InitStructure.SPI_CRCPolynomial = 7;  

       SPI_Init(SPI2, &SPI_InitStructure);

       SPI_Cmd(SPI2, ENABLE);

      

       SPI_FLASH_Write_StatusRegister1();//解锁flash的写入保护**此步骤目的为解锁FLASH  扇区保护。很重要!!否则会导致擦写不成功。

      

       return 0;

}

然后进行步骤三,相应AT25的功能接口函数编写:

/*******************************************************************************

* Description    : send data to AT25            

*******************************************************************************/

uint16_t SPI_FLASH_SendByte(uint16_t byte)

{

       while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);

       SPI_I2S_SendData(SPI2, byte);

       while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET);

       return SPI_I2S_ReceiveData(SPI2);

}

/*******************************************************************************

* Description    : Read id of AT25            

*******************************************************************************/

uint32_t Read_AT25_ID(void)

{

       uint32_t at25_id = 0;

  uint8_t  at25_id_part1, at25_id_part2;

       SPI_CS_LOW();

       SPI_FLASH_SendByte(CMD_AT25_READ_ID);

       at25_id       = SPI_FLASH_SendByte(0xff);   

  at25_id_part1 = SPI_FLASH_SendByte(0xff);

       at25_id_part2 = SPI_FLASH_SendByte(0xff);

       at25_id = ( ( (uint32_t)at25_id_part1 <<8 ) + at25_id_part2 );

       SPI_CS_HIGH();

       return at25_id;

}

/*******************************************************************************

* Description    : Write  Enable of AT25

*******************************************************************************/

void SPI_FLASH_Write_Enable(void)

{

       SPI_CS_LOW();

       SPI_FLASH_SendByte(CMD_AT25_WRITE_ENABLE);

       SPI_CS_HIGH();

}

/*******************************************************************************

* Description    : read the status register

*******************************************************************************/

void SPI_FLASH_WaitForWriteEnd(void)

{

       uint8_t FLASH_Status = 0;

       SPI_CS_LOW();

       SPI_FLASH_SendByte(CMD_AT25_READ_STATUSREGISTER);

       do{

       FLASH_Status = SPI_FLASH_SendByte(0xff);     

       }

       while( (FLASH_Status & 0x01) == 0x01 );

       SPI_CS_HIGH();

}

/*******************************************************************************

* Description    : Write  Enable of AT25

*******************************************************************************/

void SPI_FLASH_Page_Write(u32 WriteAddr,u8* pBuffer,u16 NumByteToWrite)

{

       u16 i;

    

       SPI_FLASH_WaitForWriteEnd();

       SPI_FLASH_Write_Enable();

      

       SPI_CS_LOW();

       SPI_FLASH_SendByte(CMD_AT25_PAGE_PROGRAME);

       SPI_FLASH_SendByte((u8)((WriteAddr)>>16)); //发送24bit地址   

       SPI_FLASH_SendByte((u8)((WriteAddr)>>8));  

       SPI_FLASH_SendByte((u8)WriteAddr);  

       for(i=0;i<NumByteToWrite;i++)

       {

           SPI_FLASH_SendByte(pBuffer[i]);//循环写数 

       }

       SPI_CS_HIGH();

      

       SPI_FLASH_WaitForWriteEnd();  

}

/*******************************************************************************

* Description    : AT25XX_Write_NoCheck

*******************************************************************************/

void AT25XX_Write_NoCheck(uint32_t WriteAddr,uint8_t* pBuffer,uint16_t NumByteToWrite)

{

       u16 pageremain;     

       pageremain=256-WriteAddr%256; //单页剩余的字节数                 

       if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256个字节

       while(1)

       {        

              SPI_FLASH_Page_Write(WriteAddr,pBuffer,pageremain);

              if(NumByteToWrite==pageremain)break;//写入结束了

             else //NumByteToWrite>pageremain

              {

                     pBuffer+=pageremain;

                     WriteAddr+=pageremain;  

                     NumByteToWrite-=pageremain;                 //减去已经写入了的字节数

                     if(NumByteToWrite>256)pageremain=256; //一次可以写入256个字节

                     else pageremain=NumByteToWrite;   //不够256个字节了

              }

       };

      

}

/*******************************************************************************

* Description    : Write Status Register Byte 1 to make All sectors are

                   software unprotected

*******************************************************************************/

void SPI_FLASH_Write_StatusRegister1(void)

{

       SPI_FLASH_Write_Enable();

       SPI_FLASH_WaitForWriteEnd();

       SPI_CS_LOW();

       SPI_FLASH_SendByte(CMD_AT25_WRITE_STATUSREGISTER_BYTE1);

       SPI_FLASH_SendByte(0x00);//注意此处,要根据AT25DF321A芯片手册发送相应的寄存器位

       SPI_CS_HIGH();

       SPI_FLASH_WaitForWriteEnd();

}

/*******************************************************************************

* Description    : SPI_FLASH_Erase_Block 4K

*******************************************************************************/

void SPI_FLASH_Erase_Block(uint32_t Dst_Addr)//擦最小单位4KB

{

      

       SPI_FLASH_Write_Enable();

      

       SPI_FLASH_WaitForWriteEnd();

       SPI_CS_LOW();

       SPI_FLASH_SendByte(CMD_AT25_ERASE_4K);

  SPI_FLASH_SendByte((u8)((Dst_Addr)>>16));  //发送24bit地址   

  SPI_FLASH_SendByte((u8)((Dst_Addr)>>8));  

  SPI_FLASH_SendByte((u8)Dst_Addr);

       SPI_CS_HIGH();

       SPI_FLASH_WaitForWriteEnd();

      

}

/*******************************************************************************

* Description    : SPI_FLASH_Erase_Chip

*******************************************************************************/

void SPI_FLASH_Erase_Chip(void)

{

       SPI_FLASH_Write_Enable();                

       SPI_FLASH_WaitForWriteEnd();  

       SPI_CS_LOW();                             

       SPI_FLASH_SendByte(CMD_AT25_ERASE_CHIP);  //发送片擦除命令 

       SPI_CS_HIGH();                                       

       SPI_FLASH_WaitForWriteEnd();                                //等待芯片擦除结束

}

void AT25DF312A_Read(uint32_t address, uint8_t *data, uint16_t size)

{

             u16 i; 

    SPI_FLASH_WaitForWriteEnd();  

         SPI_CS_LOW();                            //使能器件  

    SPI_FLASH_SendByte(CMD_AT25_READ_ARRAY_03);         //发送读取命令  

    SPI_FLASH_SendByte((u8)((address)>>16));  //发送24bit地址   

    SPI_FLASH_SendByte((u8)((address)>>8));  

    SPI_FLASH_SendByte((u8)address);  

    for(i=0;i<size;i++)

         {

        data[i]=SPI_FLASH_SendByte(0XFF);   //循环读数 

    }

         SPI_CS_HIGH();

              SPI_FLASH_WaitForWriteEnd();

  // TODO: 实现从闪存中读取数据的函数

}

//写SPI FLASH 

//在指定地址开始写入指定长度的数据

//该函数带擦除操作!

//pBuffer:数据存储区

//WriteAddr:开始写入的地址(24bit)                                      

//NumByteToWrite:要写入的字节数(最大65535)  

u8 AT25XX_BUFFER[4096];  

void AT25DF312A_Write(uint32_t WriteAddr, uint8_t *pBuffer, uint16_t NumByteToWrite)

{     

       u32 secpos;

       u16 secoff;

       u16 secremain;        

      u16 i;   

       u8 * AT25XX_BUF;  

  AT25XX_BUF=AT25XX_BUFFER;          

      secpos=WriteAddr/4096;//扇区地址 

       secoff=WriteAddr%4096;//在扇区内的偏移

       secremain=4096-secoff;//扇区剩余空间大小  

      //printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用

      if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096个字节

       while(1)

       {     

              AT25DF312A_Read(secpos*4096,AT25XX_BUF,4096);//读出整个扇区的内容

              for(i=0;i<secremain;i++)//校验数据

              {

                     if(AT25XX_BUF[secoff+i]!=0XFF)break;//需要擦除    

              }

              if(i<secremain)//需要擦除

              {

                     SPI_FLASH_Erase_Block(secpos*4096);//擦除这个扇区

                     for(i=0;i<secremain;i++)         //复制

                     {

                            AT25XX_BUF[i+secoff]=pBuffer[i];        

                     }

                     AT25XX_Write_NoCheck(secpos*4096,AT25XX_BUF,4096);//写入整个扇区 

              }else AT25XX_Write_NoCheck(WriteAddr,pBuffer,secremain);//写已经擦除了的,直接写入扇区剩余区间.                              

              if(NumByteToWrite==secremain)break;//写入结束了

              else//写入未结束

              {

                     secpos++;//扇区地址增1

                     secoff=0;//偏移位置为0      

                pBuffer+=secremain;  //指针偏移

                     WriteAddr+=secremain;//写地址偏移         

                NumByteToWrite-=secremain;                            //字节数递减

                     if(NumByteToWrite>4096)secremain=4096;      //下一个扇区还是写不完

                     else secremain=NumByteToWrite;                   //下一个扇区可以写完了

              }     

       };    

  // TODO: 实现将数据写入到闪存中的函数

}

此时我们已经编写对应的函数接口,接下来我们只需要在初始化后,调用FLASH_ID读取的函数接口验证读取到的ID是否和芯片数据手册上的相同即可,下图为数据手册部分截图:

可以看到我们发送读取ID命令后会得到四个16位数据,我们只需要DEVICE ID的part1和part2部分即可。(读出的数据为0X4701即正确)

关于5,6,7步骤,我自己编写了一个测试函数进行FLASH读写测试,函数代码贴在下面

uint8_t write_data[2000]={0},read_data[2000]={0};//全局变量静态存储区不溢出

uint8_t AT25_TEST(void)

{

       u16 i;

  uint32_t address = CMD_AT25_TEST_ADDR; // 写入数据的起始地址

       for(i=0;i<2000;i++)

       {

              write_data[i] = i;

       }

      

       // 从闪存中写入数据

       AT25DF312A_Write(address, write_data, sizeof(write_data));

  // 从闪存中读取数据

  AT25DF312A_Read(address, read_data, sizeof(read_data));

  // 比较写入和读取的数据

  if (memcmp(write_data, read_data, sizeof(write_data)) == 0)

       {

              return 0;

    // 数据写入成功

    // 在这里执行相应的操作

  }

       else

       {

              return 1;

    // 数据写入失败

    // 在这里执行相应的操作

  }

}

以下为.h部分的代码:

#ifndef AT25FLASH_H

#define AT25FLASH_H

#include "stdint.h"

#include "sys.h"

#define SPI_GPIO_CS_PIN      GPIO_Pin_12

#define SPI_SCLK_PIN         GPIO_Pin_13

#define SPI_MISO_PIN         GPIO_Pin_14

#define SPI_MOSI_PIN         GPIO_Pin_15

#define SPI_GPIO_PORT        GPIOB

#define SPI_GPIO_CS_PORT     GPIOB

#define SPI_CS_LOW()         GPIO_ResetBits(SPI_GPIO_CS_PORT,SPI_GPIO_CS_PIN) //Chip Select pin set low

#define SPI_CS_HIGH()        GPIO_SetBits(SPI_GPIO_CS_PORT,SPI_GPIO_CS_PIN)  //Chip Select pin set high

#define GPIO_PinSource_SCLK  GPIO_PinSource13

#define GPIO_PinSource_MISO  GPIO_PinSource14

#define GPIO_PinSource_MOSI  GPIO_PinSource15

#define   AT25XX_CS                 PBout(12)              //AT25XX的片选信号

//command of AT25

#define CMD_AT25_READ_ID                     0x9f

#define CMD_AT25_WRITE_ENABLE                0x06

#define CMD_AT25_PAGE_PROGRAME               0x02

#define CMD_AT25_READ_STATUSREGISTER         0x05

#define CMD_AT25_READ_ARRAY_1B               0x1b //100Mhz

#define CMD_AT25_READ_ARRAY_0B               0x0b //85Mhz

#define CMD_AT25_READ_ARRAY_03               0x03 //50Mhz

#define CMD_AT25_ERASE_4K                    0x20

#define CMD_AT25_ERASE_32K                   0x52

#define CMD_AT25_ERASE_64K                   0xd8

#define CMD_AT25_ERASE_CHIP                  0x60

#define CMD_AT25_WRITE_STATUSREGISTER_BYTE1  0x01

//personal define

#define CMD_AT25_TEST_ADDR                   0x3F0000

int AT25FLASH_Init(void);

void SPI_FLASH_WaitForWriteEnd(void);

void SPI_FLASH_Erase_Block(uint32_t Dst_Addr);

void SPI_FLASH_Erase_Chip(void);

void SPI_FLASH_Write_StatusRegister1(void);

void SPI_FLASH_Page_Write(uint32_t WriteAddr,uint8_t* pBuffer,uint16_t NumByteToWrite);

void AT25XX_Write_NoCheck(uint32_t WriteAddr,uint8_t* pBuffer,uint16_t NumByteToWrite);

void AT25DF312A_Read(uint32_t address, uint8_t *data, uint16_t size);

void AT25DF312A_Write(uint32_t WriteAddr, uint8_t *pBuffer, uint16_t NumByteToWrite);

uint32_t Read_AT25_ID(void);

uint8_t AT25_TEST(void);

#endif


总结:1、需要了解SPI通信原理,理解信号线在其中拉高拉低的作用

           2、在使用某款芯片时,需要多阅读对应的芯片数据手册,比如该款芯片进行擦写操作之前需要先发出命令完成解锁芯片扇区保护操作,此部分就需要阅读数据手册才知道。

           3、关于编写读写API接口的稳定性以及适用性,只能多读多用多理解了。

以上便是本次AT25XX芯片的测试实验全部内容,若有不足还望指导!!

  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值