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 )
{
}
}
-
FlaSh
+关注
关注
10文章
1633浏览量
147937 -
STM32
+关注
关注
2270文章
10895浏览量
355715 -
文件系统
+关注
关注
0文章
284浏览量
19903
发布评论请先 登录
相关推荐
评论