前言:
为了方便查看博客,特意申请了一个公众号,附上二维码,有兴趣的朋友可以关注,和我一起讨论学习,一起享受技术,一起成长。
1. 简述
SPI是串行外设通信接口,主要实现主从设备之间的通信。硬件上由CS、SCK、MISO、MOSI四根通信线连接而成。
硬件连接如下:
2.软件实现
//头文件
#ifndef __FLASH__H__
#define __FLASH__H__
#include "nrf51.h"
#define FLASH_WRITE_ENABLE_CMD 0x06
#define FLASH_WRITE_DISABLE_CMD 0x04
#define FLASH_READ_SR_CMD 0x05
#define FLASH_WRITE_SR_CMD 0x01
#define FLASH_READ_DATA 0x03
#define FLASH_FASTREAD_DATA 0x0b
#define FLASH_WRITE_PAGE 0x02
#define FLASH_ERASE_SECTOR 0x20
#define FLASH_ERASE_BLOCK 0xd8
#define FLASH_ERASE_CHIP 0xc7
#define FLASH_POWER_DOWN 0xb9
#define FLASH_RELEASE_POWER_DOWN 0xab
#define FLASH_READ_DEVICE_ID 0x90
#define FLASH_READ_JEDEC_ID 0x9f
#define FLASH_SIZE (2*1024*1024) // 2M字节
#define PAGE_SIZE 8192 // 256 bytes
#define SECTOR_SIZE 512 // 4-Kbyte
#define BLOCK_SIZE 32 // 64-Kbyte
#define PAGE_LEN 255 //一页256字节
//定义FLASH相关属性定义
struct Flash_Attr {
uint16_t flash_id;
uint16_t page_size;
uint16_t sector_size;
uint8_t block_size;
};
//#if 0
//#define FLASH_CS 29
//else
#define FLASH_CS 30
#define FLASH_CS_LOW nrf_gpio_pin_clear(FLASH_CS)
#define FLASH_CS_HIGH nrf_gpio_pin_set(FLASH_CS)
void Flash_Gpio_Init(void);
void Flash_Write_Enable(void);
void Flash_Write_Disable(void);
uint8_t Flash_Read_SR(void);
void Flash_Write_SR(uint8_t dat);
void Flash_Wait_Nobusy(void);
void Flash_Read_Byte(uint8_t *pBuffer,uint32_t ReadAddr,uint16_t nByte);
void Flash_FastRead_Byte(uint8_t *pBuffer,uint32_t ReadAddr,uint16_t nByte);
void Flash_Write_Page(uint8_t *pBuffer,uint32_t WriteAddr,uint16_t nByte);
void Flash_Write_NoCheck(uint8_t *pBuffer,uint32_t WriteAddr,uint16_t nByte);
void Flash_Write_Byte(uint8_t *pBuffer,uint32_t WriteAddr,uint16_t nByte);
void Flash_Erase_Sector(uint32_t SectorAddr);
void Flash_Erase_Block(uint32_t BlockAddr);
void Flash_Erase_Chip(void);
void Flash_Power_Down(void);
void Flash_Wake_Up(void);
uint16_t Flash_Read_Device_ID(void);
uint32_t Flash_Read_JEDEC_ID(void);
#endif
/******************************************************
说明:SPI驱动Flash(25Q16)
W25X16芯片容量:2MB (16Mbit)
页数:16*16*32 (2M/256)
扇区数:16*32
块数:32
2、读写操作:
读 ------------ 一次最大读一页(256B)
写 ------------ 页
擦出 ---------- 扇区、块、整个芯片
3、控制和状态寄存器命令(默认:0x00)
BIT位 7 6 5 4 3 2 1 0
SPR RV TB BP2 BP1 BP0 WEL BUSY
SPR:默认0,状态寄存器保护位,配合WP使用
TB,BP2,BP1,BP0:FLASH区域写保护设置
WEL:写使能锁定
BUSY:忙标记位(1,忙;0,空闲)
******************************************************/
#include "nrf51.h"
#include "nrf_gpio.h"
#include "flash.h"
#include "spi.h"
struct Flash_Attr flash_pt;
void Flash_Gpio_Init(void)
{
nrf_gpio_cfg_output(FLASH_CS); //片选
FLASH_CS_HIGH;
}
void Flash_Write_Enable(void)
{
FLASH_CS_LOW;
Spi_Write_Byte(FLASH_WRITE_ENABLE_CMD);//开启写使能
FLASH_CS_HIGH;
}
void Flash_Write_Disable(void)
{
FLASH_CS_LOW;
Spi_Write_Byte(FLASH_WRITE_DISABLE_CMD);//开启写失能
FLASH_CS_HIGH;
}
//读状态寄存器
uint8_t Flash_Read_SR(void)
{
uint8_t dat;
FLASH_CS_LOW;
Spi_Write_Byte(FLASH_READ_SR_CMD);
dat = Spi_Read_Byte();
FLASH_CS_HIGH;
return dat;
}
//写状态寄存器
void Flash_Write_SR(uint8_t dat)
{
Flash_Write_Enable();
Spi_Write_Byte(FLASH_WRITE_SR_CMD);
Spi_Write_Byte(dat);//写入一个字节
FLASH_CS_HIGH;
}
//判断状态寄存器的R0位:执行结束操作清零
void Flash_Wait_Nobusy(void)
{
while(((Flash_Read_SR()) & 0x01)==0x01);//等待BUSY位清空
}
/*********************************************************************
* 读取数据:ReadAddr开始的地址,连续读出nByte长度的字节
* pBuffer ---- 数据存储区首地址
* ReadAddr --- 要读取SFLASH Flash的首地址地址
* nByte ------ 要读取的字节数(最大65535B = 64K 块)
**********************************************************************/
void Flash_Read_Byte(uint8_t *pBuffer,uint32_t ReadAddr,uint16_t nByte)
{
FLASH_CS_LOW;
Spi_Write_Byte(FLASH_READ_DATA);//连续读取数据
Spi_Write_Byte((uint8_t)(ReadAddr>>16));//写入24位地址
Spi_Write_Byte((uint8_t)(ReadAddr>>8));
Spi_Write_Byte((uint8_t)(ReadAddr));
while(nByte--)
{
*pBuffer = Spi_Read_Byte();
pBuffer++;
}
FLASH_CS_HIGH;
}
//快速读取数据
void Flash_FastRead_Byte(uint8_t *pBuffer,uint32_t ReadAddr,uint16_t nByte)
{
FLASH_CS_LOW;
Spi_Write_Byte(FLASH_FASTREAD_DATA);//快速读取数据
Spi_Write_Byte((uint8_t)(ReadAddr>>16));//写入24位地址
Spi_Write_Byte((uint8_t)(ReadAddr>>8));
Spi_Write_Byte((uint8_t)(ReadAddr));
Spi_Write_Byte(0xff);//等待8个时钟
while(nByte--)
{
*pBuffer = Spi_Read_Byte();
pBuffer++;
}
FLASH_CS_HIGH;
}
//页编程:256 Bytes x 8192
/************************************************
函数名称 : Flash_Write_Page
功 能 : 在SFLASH内写入少于1页(256个字节)的数据
参 数 : pBuffer ----- 写入数据区首地址
WriteAddr --- 要写入Flash的地址
nByte ------- 要写入的字节数(最大1页)
返 回 值 : 无
作 者 : strongerHuang
*************************************************/
void Flash_Write_Page(uint8_t *pBuffer,uint32_t WriteAddr,uint16_t nByte)
{
FLASH_CS_LOW;
Spi_Write_Byte(FLASH_WRITE_PAGE);//页编程指令
Spi_Write_Byte((uint8_t)(WriteAddr>>16));//写入24位地址
Spi_Write_Byte((uint8_t)(WriteAddr>>8));
Spi_Write_Byte((uint8_t)(WriteAddr));
while(nByte--)
{
Spi_Write_Byte(*pBuffer);
pBuffer++;
}
FLASH_CS_HIGH;
Flash_Wait_Nobusy();//等待写入结束
}
//无检验写入
//无检验写SPI FLASH
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//nByte:要写入的字节数(最大65535)
//CHECK OK
void Flash_Write_NoCheck(uint8_t *pBuffer,uint32_t WriteAddr,uint16_t nByte)
{
uint16_t PageByte = 256 - WriteAddr % 256;//单页剩余可写字节数
if(nByte <= PageByte) //不大于256字节
{
PageByte = nByte;
}
while(1)
{
Flash_Write_Page(pBuffer,WriteAddr,PageByte);
if(nByte == PageByte) //写入结束
break;
else
{
pBuffer += PageByte;//下一页写入的数据
WriteAddr += PageByte;//下一页写入的地址
nByte -= PageByte;//待写入的字节数递减
if(nByte > 256)
{
PageByte = 256;
}
else
{
PageByte = nByte;
}
}
}
}
/*********************************************************************
* 写入数据:WriteAddr开始的地址,连续写入nByte长度的字节
* pBuffer ---- 数据存储区首地址
* WriteAddr --- 要读取SFLASH Flash的首地址地址
* nByte ------ 要写入的字节数(最大65535B = 64K 块)
**********************************************************************/
void Flash_Write_Byte(uint8_t *pBuffer,uint32_t WriteAddr,uint16_t nByte)
{
static uint8_t SectorBuf[4096];//扇区buf
uint32_t SecPos; //得到扇区位置
uint16_t SecOff; //扇区偏移
uint16_t SecRemain; //剩余扇区
uint16_t i;
SecPos = WriteAddr / 4096;//地址所在扇区(0--511)
SecOff = WriteAddr % 4096;//扇区内地址偏移
SecRemain = 4096 - SecOff;//扇区除去偏移,还剩多少字节
if(nByte <= SecRemain) //写入数据大小 < 剩余扇区空间大小
{
SecRemain = nByte;
}
while(1)
{
Flash_Read_Byte(SectorBuf, SecPos*4096, 4096);//读出整个扇区的内容
for(i=0;i<SecRemain;i++) //校验数据
{
if(SectorBuf[SecOff + i] != 0xff)//储存数据不为0xff,需要擦除
break;
}
if( i< SecRemain) //需要擦除
{
Flash_Erase_Sector(SecPos); //擦除这个扇区
for(i=0;i<SecRemain;i++) //保存写入的数据
{
SectorBuf[SecOff + i] = pBuffer[i];
}
Flash_Write_NoCheck(SectorBuf, SecPos*4096, 4096);//写入整个扇区(扇区=老数据+新写入数据)
}
else
{
Flash_Write_NoCheck(pBuffer,WriteAddr,SecRemain);//不需要擦除,直接写入扇区
}
if(nByte == SecRemain) //写入结束
{
Flash_Write_Disable();
break;
}
else
{
SecPos++; //扇区地址增加1
SecOff = 0; //扇区偏移归零
pBuffer += SecRemain; //指针偏移
WriteAddr += SecRemain; //写地址偏移
nByte -= SecRemain; //待写入的字节递减
if(nByte > 4096)
{
SecRemain = 4096; //待写入一扇区(4096字节大小)
}
else
{
SecRemain = nByte; //待写入少于一扇区的数据
}
}
}
}
//擦除扇区:4K Bytes x 512
void Flash_Erase_Sector(uint32_t SectorAddr)
{
SectorAddr *= 4096;
Flash_Write_Enable();
Flash_Wait_Nobusy();
FLASH_CS_LOW;
Spi_Write_Byte(FLASH_ERASE_SECTOR);//页编程指令
Spi_Write_Byte((uint8_t)(SectorAddr>>16));//写入24位地址
Spi_Write_Byte((uint8_t)(SectorAddr>>8));
Spi_Write_Byte((uint8_t)(SectorAddr));
FLASH_CS_HIGH;
Flash_Wait_Nobusy();//等待写入结束
}
//擦除块:64K Bytes x 32
void Flash_Erase_Block(uint32_t BlockAddr)
{
BlockAddr *= 65536;
Flash_Write_Enable();
Flash_Wait_Nobusy();
FLASH_CS_LOW;
Spi_Write_Byte(FLASH_ERASE_BLOCK);//块擦除
Spi_Write_Byte((uint8_t)(BlockAddr>>16));//写入24位地址
Spi_Write_Byte((uint8_t)(BlockAddr>>8));
Spi_Write_Byte((uint8_t)(BlockAddr));
FLASH_CS_HIGH;
Flash_Wait_Nobusy();//等待写入结束
}
//整个芯片擦除
void Flash_Erase_Chip(void)
{
Flash_Write_Enable();
Flash_Wait_Nobusy();
FLASH_CS_LOW;
Spi_Write_Byte(FLASH_ERASE_CHIP);//芯片擦除
FLASH_CS_HIGH;
Flash_Wait_Nobusy();//等待写入结束
}
//进入掉电模式
void Flash_Power_Down(void)
{
FLASH_CS_LOW;
Spi_Write_Byte(FLASH_POWER_DOWN);//掉电指令
FLASH_CS_HIGH;
}
//唤醒
void Flash_Wake_Up(void)
{
FLASH_CS_LOW;
Spi_Write_Byte(FLASH_RELEASE_POWER_DOWN);//唤醒指令
FLASH_CS_HIGH;
}
//读取器件ID
uint16_t Flash_Read_Device_ID(void)
{
uint16_t ID=0;
FLASH_CS_LOW;
Spi_Write_Byte(FLASH_READ_DEVICE_ID);
Spi_Write_Byte(0x00);//写入24位地址;假地址
Spi_Write_Byte(0x00);
Spi_Write_Byte(0x00);
ID |= Spi_Read_Byte()<<8;
ID |= Spi_Read_Byte();
FLASH_CS_HIGH;
return ID;
}
//读取厂商ID
uint32_t Flash_Read_JEDEC_ID(void)
{
uint32_t ID=0;
FLASH_CS_LOW;
Spi_Write_Byte(FLASH_READ_JEDEC_ID);
ID |= Spi_Read_Byte()<<16;
ID |= Spi_Read_Byte()<<8;
ID |= Spi_Read_Byte();
FLASH_CS_HIGH;
return ID;
}
//测试
void Flash_Init_Test()
{
flash_pt.flash_id = Flash_Read_Device_ID();
Lcd_Display_String(0,35,"id:");
Lcd_Display_Num(0,65,flash_pt.flash_id,5,16);//读出Flash的设备ID=0XEF13(61203)
}
//FLASH循环擦写测试
void Flash_Circle_Test(void)
{
uint16_t i;
uint32_t Addr;
LCD_Display_Clear();
for(i=0;i<256;i++)
{
tx_data[i] = 'A';
}
Lcd_Display_OneChar(0, 0, tx_data[255]);
Flash_Write_Enable(); //写使能
//分16次写入4096字节(1扇区=16x256)
for(i=0;i<2;i++)
{
Addr = 256 * i;
Flash_Write_Enable();//经过写操作,擦除后,必须使能写,才能写入
Flash_Write_NoCheck(tx_data,Addr,256);
//Flash_Write_NoCheck(tx_data,0,1023);//无检测写可以直接读出,每次写入不超过256字节
//Flash_Write_Byte(tx_data,Addr,255);//检查写需要写入后屏蔽(注释掉),再读出来
nrf_delay_ms(100);
Lcd_Display_String(2,1,"flash write...");
}
Addr = 0;
Lcd_Display_String(4,1,"flash write ok");
//Flash_Read_Byte(rx_data+512,512,256);//注意写入地址偏移
//Flash_FastRead_Byte(rx_data+512,512,256);
Flash_Read_Byte(rx_data,0,512);//读出一扇区数据
Lcd_Display_OneChar(0, 12, rx_data[500]);
Flash_Erase_Sector(0);//擦除第一个扇区
nrf_delay_ms(100);
Lcd_Display_String(6,64,"erase ok");
cnt++;
Lcd_Display_Num(6,0,cnt,6,16); //显示读写次数
//写入读出是否正确测试
/* if((rx_data[0]=='A')&&(rx_data[254]=='A'))
{
Lcd_Display_String(4,1,"flash write ok");
}
else
{
Lcd_Display_String(4,1,"flash write err");
}*/
}
参考:
1.STM32F10x_SPI(硬件接口 + 软件模拟)读写Flash(25Q16)
2.SPI—读写串行 FLASH
3.Flash存储W25Q16芯片