016 - STM32学习笔记 - SPI访问Flash(二)
上节内容学习了通过SPI读取FLASH的JEDEC_ID,在flash资料的指令表中,还看到有很多指令可以使用,这节继续学习使用其他指令,程序模板采用上节的模板。
为了方便起见,把这节需要用到的指令都可以宏定义出来:
/*FLASH 常用命令*/
#define WriteEnable 0x06 /* 写使能 */
#define WriteDisable 0x04 /* 写失能 */
#define ReadStatusReg 0x05 /* 读状态寄存器 */
#define WriteStatusReg 0x01 /* 写状态寄存器 */
#define ReadData 0x03 /* 读数据 */
#define FastReadData 0x0B /* 快速的读数据 */
#define FastReadDual 0x3B /* 双倍速快读 */
#define PageProgram 0x02 /* 页写入 */
#define BlockErase 0xD8 /* 块擦除 */
#define SectorErase 0x20 /* 扇区擦除 */
#define ChipErase 0xC7 /* 芯片擦除 */
#define PowerDown 0xB9 /* flash掉电 */
#define ReleasePowerDown 0xAB /* 掉电复位 */
#define DeviceID 0xAB /* 设备ID */
#define ManufactDeviceID 0x90 /* 制造商ID */
#define JedecDeviceID 0x9F /* JedecDeviceID */
#define sFLASH_ID 0XEF4018 /* JedecDeviceID宏定义 */
#define Dummy 0xFF /* 任意数据 */
1、Flash上电、掉电
/**
* @brief Flash进入掉电模式
* @param 无
* @retval 无返回值
*/
void SPI_Flash_PowerDown(void)
{
SPI_FLASH_CS_LOW(); /* 开始通讯: CS 低电平 */
SPI_FLASH_SendByte(PowerDown); /* 发送掉电信号 */
SPI_FLASH_CS_HIGH(); /* 开始通讯: CS 高电平 */
}
/**
* @brief 将Flash从掉电模式唤醒
* @param 无
* @retval 无返回值
*/
void SPI_Flash_WakeUp()
{
SPI_FLASH_CS_LOW(); /* 开始通讯: CS 低电平 */
SPI_FLASH_SendByte(ReleasePowerDown); /* 发送掉电复位信号 */
SPI_FLASH_CS_HIGH(); /* 开始通讯: CS 高电平 */
}
2、擦除、读取数据
/**
* @brief 写使能
* @param 无
* @retval 无
*/
void SPI_FLASH_Write_Enable(void)
{
SPI_FLASH_CS_LOW(); /* 开始通讯: CS 低电平 */
SPI_FLASH_SendByte(WriteEnable); /* 发送写使能信号 */
SPI_FLASH_CS_HIGH(); /* 停止通讯: CS 高电平 */
}
/**
* @brief 擦除数据
* @param 地址
* @retval 无返回值
*/
void SPI_Flash_Erase(u32 addr)
{
SPI_FLASH_Write_Enable(); /* 下发指令前,先写使能 */
WateForReady(); /* 等待Flash内部时序完成,主要是读芯片的状态字 */
SPI_FLASH_CS_LOW(); /* 开始通讯: CS 低电平 */
SPI_FLASH_SendByte(SectorErase); /* 发送擦除指令 */
SPI_FLASH_SendByte((addr & 0xFF0000) >> 16 ); /* 取16-23位 */
SPI_FLASH_SendByte((addr & 0xFF00) >> 8); /* 取8-15位 */
SPI_FLASH_SendByte(addr & 0xFF); /* 取0-7位 */
SPI_FLASH_CS_HIGH(); /* 停止通讯: CS 高电平 */
WateForReady();
}
/**
* @brief 整片擦除数据
* @param 地址
* @retval 无返回值
* @attention 整片擦除时间比较耗时,具体擦除需要时间根据芯片容量大小而定
*/
void SPI_Flash_BulkErasse(void)
{
SPI_FLASH_Write_Enable(); //写使能
SPI_FLASH_CS_LOW(); //开始通讯
SPI_FLASH_SendByte(ChipErase); //发送正片擦除指令
SPI_FLASH_CS_HIGH(); //结束通讯
}
/**
* @brief 读取数据
* @param pdata:读取数据缓存
addr:读取起始地址
numByteToRead:读取数据数量
* @retval 无返回值
*/
void SPI_Flash_ReadDate(u8* pdata,u32 addr,u32 numByteToRead)
{
WateForReady(); /* 等待Flash内部时序完成,主要是读芯片的状态字 */
SPI_FLASH_CS_LOW(); /* 开始通讯: CS 低电平 */
SPI_FLASH_SendByte(ReadData); /* 发送读取指令 */
SPI_FLASH_SendByte((addr & 0xFF0000) >> 16 ); /* 取16-23位 */
SPI_FLASH_SendByte((addr & 0xFF00) >> 8); /* 取8-15位 */
SPI_FLASH_SendByte(addr & 0xFF); /* 取0-7位 */
while(numByteToRead--) /* 循环读取数据 */
{
*pdata = SPI_FLASH_SendByte(Dummy); /* 发送Dummy任意数据,返回的数据就是读取到的数据 */
pdata++;
}
SPI_FLASH_CS_HIGH(); /* 停止通讯: CS 高电平 */
}
3、写入数据
/**
* @brief 写入数据
* @param pdata:写入数据缓存
addr:写入起始地址
numByteToWrite:写入数据数量
* @retval 无
*/
void SPI_Flash_WriteData(u8* pdata,u32 addr,u32 numByteToWrite)
{
WateForReady(); /* 等待Flash内部时序完成,主要是读芯片的状态字 */
SPI_FLASH_Write_Enable(); /* 开始写入前先写使能 */
SPI_FLASH_CS_LOW(); /* 开始通讯: CS 低电平 */
SPI_FLASH_SendByte(PageProgram); /* 下发写指令(页) */
SPI_FLASH_SendByte((addr & 0xFF0000) >> 16 ); /* 取16-23位 */
SPI_FLASH_SendByte((addr & 0xFF00) >> 8); /* 取8-15位 */
SPI_FLASH_SendByte(addr & 0xFF); /* 取0-7位 */
while(numByteToWrite--) /* 循环写入数据 */
{
SPI_FLASH_SendByte(*pdata); /* 下发写入数据 */
pdata++;
}
SPI_FLASH_CS_HIGH(); /* 停止通讯: CS 高电平 */
}
4、测试例程
#include "stm32f4xx.h"
#include "bsp_led.h"
#include "bsp_systick.h"
#include "bsp_usart_dma.h"
#include "bsp_spi_flash.h"
#include <stdio.h>
u8 ReadBuffer[4096] = {0x00}; //读取数据缓冲区
u8 WriteBuffer[256] = {0x00}; //写入数据缓冲区
int main(void)
{
u32 device_id = 0;
u32 i = 0;
LED_Config();
DEBUG_USART1_Config();
SysTick_Init();
SPI_GPIO_Config();
printf("\r\n这是SPI读取FLASH_Device_ID的测试实验!\r\n");
SPI_Flash_WakeUp();
/* *********************** 读取Flash ID ************************** */
device_id = SPI_FLASH_ReadID();
printf("\r\ndevice_id = 0x%X\r\n",device_id);
Delay_ms(1000);
/* *********************** 擦除扇区 ************************** */
SPI_Flash_Erase(0x00); /* 擦除扇区 */
SPI_Flash_ReadDate(ReadBuffer,0x00,4096); /* 擦除扇区后读取扇区内的数据,擦除动作是将扇区内寄存器全部置1 */
printf("\r\n**************读出擦除后的数据****************\r\n");
for(i = 0;i<4096;i++)
{
printf("0x%02x ",ReadBuffer[i]); /* 若擦除成功,则读取到的数据应该全部为oxFF */
}
for(i = 0;i<256;i++) /* 向写入缓冲区数据写入数据 */
{
WriteBuffer[i] = i;
}
SPI_Flash_WriteData(WriteBuffer,0x00,256); /* 这里执行的是PageProgram指令,为页写入,一次只能写入256个数据 */
SPI_Flash_ReadDate(ReadBuffer,0x00,256); /* 写入完成后,再读取出来 */
printf("\r\n**************读出写入后的数据****************\r\n");
for(i = 0;i<256;i++)
{
printf("0x%02x ",ReadBuffer[i]); /* 若写入成功,这里读取到的数据应该为0x00 ~ 0xFF */
}
SPI_Flash_PowerDown(); /* 操作完成后,发送掉电指令 */
while(1)
{
}
}
在这里为了测试方便,我将第一次读取的数据亮也改成256个,方便截图,效果如下:
这里需要注意的是,在写入数据的时候,我们用的是PageProgram指令,该指令为页写入,每次最多只能写入1页数据,且数据最多为256个,而且这里写入是只能在单页写入,不能跨页写入,我测试过,起始地址改为0x20,写入256个数据,按道理最后一个写入地址应该是0x1FF,但是写入后再读取数据不对,后来查了一下,这里遇到的问题和I2C读写EEPROM的是一样的,我大概总结了一下,对Flash的数据写入分为以下几种:
1、写入首地址与页首地址相同:
a、写入数据 ≤ 256 byte;
b、写入数据 = (n * 256) + m (n为页数,m为不满1页数据量,m < 256)
2、写入首地址与页首地址不同:
a、写入数据 ≤ 256 byte (一页可以写完);
b、写入数据 = x+ (n * 256) + m(x为前端不满一页的数据量,x = 0时,表示字节对齐,n为页数,m为不满1页数据量,m < 256)
所以综上所述,写入数据量最终公式应该为:
W
r
i
t
e
B
u
f
f
e
r
=
x
+
(
n
∗
256
)
+
m
WriteBuffer = x+ (n * 256) + m
WriteBuffer=x+(n∗256)+m
因此这里将上面的void SPI_Flash_WriteData(u8* pdata,u32 addr,u32 numByteToWrite)
在完善以下,写一个进阶版的void SPI_Flash_WriteBuffer(u8* pdata,u32 addr,u32 numByteToWrite)
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
/* NumOfPage: 计算需要写入的页数;
* NumOfSingle: 计算出不满一页时剩余的数据量
* Addr: 写入地址与SPI_FLASH_PageSize求余,为0则与页首对齐;
* count: 计算前端差多少数据可以与页首对齐;
*/
u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
Addr = WriteAddr % SPI_FLASH_PageSize;
count = SPI_FLASH_PageSize - Addr;
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
/* 情况1:页首对齐 */
if (Addr == 0)
{
/* 情况1.a :写入首地址与页首地址相同,写入数据 ≤ 256 byte */
if (NumOfPage == 0)
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
}
else /* 情况1.b :写入首地址与页首地址相同 ,写入数据超过1页 */
{
while (NumOfPage--) /* 将整页数据逐页写完 */
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
if(NumOfSingle !=0 ) /* 若尾端仍有数据,将剩余数据写完 */
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
else /* 情况2:页首不对齐 */
{
if (NumOfPage == 0) /* 情况2.a :页首不对齐,写入数据 ≤ 256 byte */
{
/* 数据不超过256个,但是跨页,情况可在细分 */
if (NumOfSingle > count) /* 数据不超过256,但当首地址当页不能写完 */
{
temp = NumOfSingle - count;
SPI_FLASH_PageWrite(pBuffer, WriteAddr, count); /* 先将首地址页数据写完 */
WriteAddr += count;
pBuffer += count;
SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp); /* 下一页数据在写入 */
}
else /*数据不超过256个,且首地址当页能将所有数据写完 */
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
}
}
else /* 情况2.b 首地址不对齐,且数据量超256个 */
{
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
while (NumOfPage--) /* 先写整页 */
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
if (NumOfSingle != 0) /* 再写多出来的数据 */
{
SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
}
}
这里的写入数据实现和I2C向EEPROM写入数据基本是一致的,不懂得可以看一下I2C的内容。
最后,将在main函数中调用的测试程序贴出来:
SPI_Flash_Erase(0x20);
SPI_Flash_ReadDate(ReadBuffer,0x20,4096);
printf("\r\n**************读出擦除后的数据****************\r\n");
for(i = 0;i<4096;i++)
{
printf("0x%02x ",ReadBuffer[i]);
}
for(i = 0;i<4096;i++)
{
WriteBuffer[i] = i;
}
SPI_FLASH_BufferWrite(WriteBuffer,0x20,4096);
SPI_Flash_ReadDate(ReadBuffer,0x20,4096);
printf("\r\n**************读出写入后的数据****************\r\n");
for(i = 0;i<4096;i++)
{
printf("0x%02x ",ReadBuffer[i]);
}