天天看点

NRF51822——FLASH驱动

前言:

为了方便查看博客,特意申请了一个公众号,附上二维码,有兴趣的朋友可以关注,和我一起讨论学习,一起享受技术,一起成长。

NRF51822——FLASH驱动

1. 简述

SPI是串行外设通信接口,主要实现主从设备之间的通信。硬件上由CS、SCK、MISO、MOSI四根通信线连接而成。

硬件连接如下:

NRF51822——FLASH驱动

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芯片