外置FLASH读写实验

描述

15.1 FLASH

Flash,全名叫做Flash EEPROM Memory,即平时所说的“闪存”,它结合了ROM和RAM的长处,不仅可以反复擦除,还可以快速读取数据,STM32运行的程序其实就是存放在Flash当中,但是由于STM32的Flash一般1M左右,只能存储程序大小的数据,所以往往需要外扩Flash来存储数据,比如LCD界面当中的汉字字库,以及文件系统中读取的文件内容。

但是一般Flash的擦除次数有限制,STM32F1系列最新的文档指出,片内的FLASH擦写次数大约在1W次左右,所以一般Flash用于擦除次数不多,但是数据量很大的场合。

这个Flash读写实验我们用到的芯片是W25Q128,这是一款采用SPI协议进行读写的Flash芯片,存储容量为128Mbit,合计16Mbyte,工作电压2.7V~3.6V。这个实验我们采用STM32内置的SPI模块来进行对芯片的读写操作,STM32F1的SPI功能很强大,SPI时钟最高可以到18MHz,支持DMA,可以配置为SPI协议或者I2S协议。

15.2 硬件SPI模块

通过之前51单片机开发我们可以知道,SPI协议一共需要四根线来完成数据通信,即片选CS,总线时钟SCK,主机输入从机输出MISO和主机输出从机输入MOSI四根数据线。STM32的内部SPI模块结构框,硬件SPI的优势就在于开发者不需要考虑SPI的详细参数以及时序,只需要配置内部的寄存器,设置速率,电平就可以实现SPI通信。

15.3 相关寄存器

15.3.1 SPI控制寄存器1:SPIx_CR1

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
BIDIMODE BIDIOE CRCEN CRCNEXT DFF RXONLY SSM SSI LSBFIRST SPE BR[2:0] MSTR CPOL CPHA

Bit 15:双向数据模式使能

0:选择双线双向模式

1:选择单线双向模式

Bit 14:双向模式下的输出使能

0:输出禁止(只收模式)

1:输出使能(只发模式)

Bit 13:硬件CRC校验使能

0:禁止CRC计算

1:启动CRC计算

Bit 12:下一个发送CRC

0:下一个发送的值来自发送缓冲区

1:下一个发送的值来自发送CRC寄存器

Bit 11:数据帧格式

0:使用8位数据帧格式进行发送/接收

1:使用16位数据帧格式进行发送/接收

Bit 10:只接收

0:全双工(发送和接收)

1:禁止输出(只接收模式)

Bit 9:软件从设备管理

0:禁止软件从设备管理

1:启用软件从设备管理

Bit 8:内部从设备选择

注:该位只在SSM位为1时有意义。它决定了NSS上的电平,在NSS引脚上的I/O操作无效

Bit 7:帧格式

0:先发送MSB

1:先发送LSB

Bit 6:SPI使能

0:禁止SPI设备

1:开启SPI设备

Bit 5~Bit 3:波特率控制

000:f PCLK /2

001:f PCLK /4

010:f PCLK /8

011:f PCLK /16

100:f PCLK /32

101:f PCLK /64

110:f PCLK /128

111:f PCLK /256

Bit 2:主设备选择

0:配置为从设备

1:配置为主设备

Bit 1:时钟极性

0:空闲状态时,SCK保持低电平

1:空闲状态时,SCK保持高电平

Bit 0:时钟相位

0:数据采样从第一个时钟边沿开始

1:数据采样从第二个时钟边沿开始

15.3.2 SPI状态寄存器:SPIx_SR

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
- BSY OVR MODF CRCERR UDR CHSIDE TXE RXNE

Bit 7:忙标志

0:SPI不忙

1:SPI正忙于通信,或者发送缓冲非空

Bit 6:溢出标志

0:没有出现溢出错误

1:出现溢出错误

Bit 5:模式错误(在SPI模式下不使用)

0:没有出现模式错误

1:出现模式错误

Bit 4:CRC错误标志(在SPI模式下不使用)

0:收到的CRC值和SPI_RXCRCR寄存器中的值匹配

1:收到的CRC值和SPI_RXCRCR寄存器中的值不匹配

Bit 3:下溢标志位(在SPI模式下不使用)

0:未发生下溢

1:发生下溢

Bit 2:声道(在SPI模式下不使用)

0:需要传输或者接收左声道

1:需要传输或者接收右声道

Bit 1:发送缓冲为空

0:发送缓冲非空

1:发送缓冲为空

Bit 0:接收缓冲非空

0:接收缓冲为空

1:接收缓冲非空

15.4 实验例程

功能:在Flash中写入一段字符串,而后读出来并显示在TFTLCD上。

(1)创建w25q128.h并输入以下代码。

/*********************************************************************************************************
                FLASH    驱    动    文    件
*********************************************************************************************************/
#ifndef _W25Q128_H_
#define _W25Q128_H_


#include "sys.h"
/*********************************************************************************************************
                  端    口    定    义
*********************************************************************************************************/
#define  W25QXX_CS  PBout( 12 )                                      //W25QXX的片选信号
/*********************************************************************************************************
                  数    据    定    义
*********************************************************************************************************/
//SPI总线速度设置
#define SPI_SPEED_2       0
#define SPI_SPEED_4       1
#define SPI_SPEED_8       2
#define SPI_SPEED_16      3
#define SPI_SPEED_32     4
#define SPI_SPEED_64     5
#define SPI_SPEED_128     6
#define SPI_SPEED_256     7
//指令表
#define W25X_WriteEnable    0x06
#define W25X_WriteDisable    0x04
#define W25X_ReadStatusReg    0x05
#define W25X_WriteStatusReg    0x01
#define W25X_ReadData      0x03
#define W25X_FastReadData    0x0B
#define W25X_FastReadDual    0x3B
#define W25X_PageProgram    0x02
#define W25X_BlockErase      0xD8
#define W25X_SectorErase    0x20
#define W25X_ChipErase      0xC7
#define W25X_PowerDown      0xB9
#define W25X_ReleasePowerDown  0xAB
#define W25X_DeviceID      0xAB
#define W25X_ManufactDeviceID  0x90
#define W25X_JedecDeviceID    0x9F
/*********************************************************************************************************
                  函    数    列    表
*********************************************************************************************************/
void W25QXX_Init( void ) ;                                        //初始化Flash
void W25QXX_Read( u8* pBuffer, u32 Address, u16 Len ) ;                          //读取Flash
void W25QXX_Write( u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite ) ;                  //写入Flash
void W25QXX_Erase_Chip( void ) ;                                    //整片擦除
void W25QXX_Erase_Sector( u32 Dst_Addr ) ;                                //扇区擦除


#endif

(2)创建w25q128.c并输入以下代码。

/*********************************************************************************************************
                FLASH    驱    动    程    序
*********************************************************************************************************/
#include "w25q128.h" 
#include "delay.h"
/***************************************************
Name    :SPI2_SetSpeed
Function  :SPI2速度设置函数
Paramater  :
      SpeedSet:0~7
Return    :None
***************************************************/
void SPI2_SetSpeed( u8 SpeedSet )
{
  SpeedSet &= 0x07 ;                                          //限制范围
  SPI2->CR1 &= 0xFFC7 ;
  SPI2->CR1 |= SpeedSet<<3 ;                                      //设置SPI2速度
  SPI2->CR1 |= 1<<6 ;                                          //SPI设备使能
}
/***************************************************
Name    :SPI2_ReadWriteByte
Function  :SPI2读写一个字节
Paramater  :
      TxData:要写入的字节
Return    :读取到的字节
***************************************************/
u8 SPI2_ReadWriteByte( u8 TxData )
{
  u16 retry=0;
  //等待发送区空
  while( ( SPI2->SR&0x02 )==0 )
  {
    retry ++ ;
    //超时退出
    if( retry>=0xFFFE )
      return 0 ;
  }
  SPI2->DR = TxData ;                                          //发送一个byte
  //等待接收完一个byte
  retry = 0 ;
  while( ( SPI2->SR&0x01 )==0 )
  {
    retry ++ ;
    //超时退出
    if( retry>=0xFFFE )
      return 0 ;
  }
  return SPI2->DR ;                                          //返回收到的数据
}
/***************************************************
Name    :W25QXX_Init
Function  :初始化W25Q128芯片
Paramater  :None
Return    :None
***************************************************/
void W25QXX_Init()
{ 
  RCC->APB2ENR |= 1<<3 ;                                        //PORTB时钟使能     
  GPIOB->CRH &= 0x0000FFFF ;
  GPIOB->CRH |= 0xBBB30000 ;                                      //PB12推挽输出+PB13/14/15复用
  GPIOB->ODR |= 0x7<<13 ;                                        //PB13/14/15上拉
  W25QXX_CS = 1 ;                                            //SPI FLASH不选中
  //初始化SPI
  RCC->APB1ENR |= 1<<14 ;                                        //SPI2时钟使能
  SPI2->CR1 |= 0<<10 ;                                        //全双工模式
  SPI2->CR1 |= 1<<9 ;                                          //软件nss管理
  SPI2->CR1 |= 1<<8 ;
  SPI2->CR1 |= 1<<2 ;                                          //SPI主机
  SPI2->CR1 |= 0<<11 ;                                        //8bit数据格式
  SPI2->CR1 |= 1<<1 ;                                          //空闲模式下SCK为1 CPOL=1
  SPI2->CR1 |= 1<<0 ;                                          //数据采样从第二个时间边沿开始,CPHA=1
  //对SPI2属于APB1的外设.时钟频率最大为36M
  SPI2->CR1 |= 3<<3 ;                                          //Fsck=Fpclk1/256
  SPI2->CR1 |= 0<<7 ;                                          //MSBfirst
  SPI2->CR1 |= 1<<6 ;                                          //SPI设备使能
  SPI2_ReadWriteByte( 0xFF ) ;                                    //启动传输
  SPI2_SetSpeed( SPI_SPEED_2 ) ;                                    //设置为18M时钟,高速模式
}
/***************************************************
Name    :W25QXX_Wait_Busy
Function  :等待空闲
Paramater  :None
Return    :None
***************************************************/
void W25QXX_Wait_Busy()   
{
  u8 byte=0 ;
  // 等待BUSY位清空
  do
  {
    W25QXX_CS = 0 ;                                          //使能器件
    SPI2_ReadWriteByte( W25X_ReadStatusReg ) ;                            //发送读取状态寄存器命令
    byte = SPI2_ReadWriteByte( 0xFF ) ;                                //读取一个字节
    W25QXX_CS = 1 ;                                          //取消片选
  }while( ( byte&0x01 )==0x01 ) ;
}
/***************************************************
Name    :W25QXX_Erase_Chip
Function  :擦除整个芯片
Paramater  :None
Return    :None
***************************************************/
void W25QXX_Erase_Chip()
{
    W25QXX_CS = 0 ;                                            //使能器件
    SPI2_ReadWriteByte( W25X_WriteEnable ) ;                              //发送写使能
  W25QXX_CS = 1 ;                                            //取消片选
    W25QXX_Wait_Busy() ;   
    W25QXX_CS=0 ;                                            //使能器件   
    SPI2_ReadWriteByte( W25X_ChipErase ) ;                                //发送片擦除命令  
  W25QXX_CS = 1 ;                                            //取消片选             
  W25QXX_Wait_Busy() ;                                        //等待芯片擦除结束
}
/***************************************************
Name    :W25QXX_Erase_Sector
Function  :擦除一个扇区
Paramater  :
      Address:扇区地址
Return    :None
***************************************************/
void W25QXX_Erase_Sector( u32 Address )
{
   Address *= 4096 ;
    W25QXX_CS = 0 ;                                            //使能器件
    SPI2_ReadWriteByte( W25X_WriteEnable ) ;                              //发送写使能
  W25QXX_CS = 1 ;                                            //取消片选
    W25QXX_Wait_Busy();   
    W25QXX_CS = 0 ;                                            //使能器件
    SPI2_ReadWriteByte( W25X_SectorErase ) ;                              //发送扇区擦除指令
    SPI2_ReadWriteByte( ( u8 )( Address>>16 ) ) ;                            //发送24bit地址
    SPI2_ReadWriteByte( ( u8 )( Address>>8 ) ) ;
    SPI2_ReadWriteByte( ( u8 )Address ) ;
  W25QXX_CS = 1 ;                                            //取消片选
    W25QXX_Wait_Busy() ;                                        //等待擦除完成
}
/***************************************************
Name    :W25QXX_Read
Function  :在指定地址开始读取指定长度的数据
Paramater  :
      pBuffer:数据存储区
      Address:开始读取的地址
      Len:要读取的字节数
Return    :None
***************************************************/
void W25QXX_Read( u8 *pBuffer, u32 Address, u16 Len )
{ 
   u16 i ;                           
  W25QXX_CS = 0 ;                                            //使能器件
    SPI2_ReadWriteByte( W25X_ReadData ) ;                                //发送读取命令
    SPI2_ReadWriteByte( ( u8 )( Address>>16 ) ) ;                            //发送24bit地址
    SPI2_ReadWriteByte( ( u8 )( Address>>8 ) ) ;
    SPI2_ReadWriteByte( ( u8 )Address );
    for( i=0; i>16 ) ) ;                            //发送24bit地址
    SPI2_ReadWriteByte( ( u8 )( Address>>8 ) ) ;
    SPI2_ReadWriteByte( ( u8 )Address ) ;
    for( i=0; ipageremain
     else
    {
      pBuffer += pageremain ;
      Address += pageremain ;
      Len -= pageremain ;                                      //减去已经写入了的字节数
      //一次可以写入256个字节
      if( Len>256 )
        pageremain = 256 ;
      //不够256个字节了
      else
        pageremain = Len ;
    }
  }
}
/***************************************************
Name    :W25QXX_Write_NoCheck
Function  :在指定地址开始写入指定长度的数据
Paramater  :
      pBuffer:数据存储区
      Address:开始写入的地址
      Len:要写入的字节数
Return    :None
***************************************************/
u8 W25QXX_BUFFER[ 4096 ] ;
void W25QXX_Write( u8 *pBuffer, u32 Address, u16 Len )
{
  u32 secpos ;
  u16 secoff ;
  u16 secremain ;
   u16 i ;
  u8 *W25QXX_BUF ;
     W25QXX_BUF = W25QXX_BUFFER ;
   secpos = Address/4096 ;                                        //扇区地址
  secoff = Address%4096 ;                                        //在扇区内的偏移
  secremain = 4096-secoff ;                                      //扇区剩余空间大小
  //不大于4096个字节
   if( Len<=secremain )
    secremain = Len ;
  while( 1 )
  {
    W25QXX_Read( W25QXX_BUF, secpos*4096, 4096 ) ;                          //读出整个扇区的内容
    //校验数据
    for( i=0; i4096 )
        secremain = 4096 ;
      //下一个扇区可以写完了
      else
        secremain = Len ;
    }
  }
}

(3)在1.c文件中输入以下代码。

#include "sys.h"
#include "delay.h"
#include "usart1.h"
#include "lcd.h"
#include "w25q128.h"


const u8 TEXT_Buffer[] = { "WarShip STM32F1 SPI TEST" } ;
#define SIZE sizeof( TEXT_Buffer )


int main()
{
  u8 datatemp[ SIZE ] ;
  u32 FLASH_SIZE ;
  STM32_Clock_Init( 9 ) ;                                        //STM32时钟初始化
  SysTick_Init( 72 ) ;                                        //SysTick初始化
  USART1_Init( 72, 115200 ) ;                                      //初始化串口1波特率115200
  LCD_Init() ;                                            //LCD初始化
  W25QXX_Init() ;                                            //W25QXX初始化
   POINT_COLOR = RED ;                                          //设置字体为红色
  FLASH_SIZE = 128*1024*1024 ;                                    //FLASH 大小为16M字节
  W25QXX_Write( (u8*)TEXT_Buffer, FLASH_SIZE-100, SIZE ) ;                      //从倒数第100个地址处开始,写入SIZE长度的数据
  W25QXX_Read( datatemp, FLASH_SIZE-100, SIZE ) ;                            //从倒数第100个地址处开始,读出SIZE个字节
  LCD_ShowString( 0, 0, datatemp ) ;                                  //显示读到的字符串
  while( 1 )
  {

  }
}
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分