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]);}