天天看點

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