SPI-读写串行FLASH

简介

是由摩托罗拉公司提出的通讯协议,即串行外围设备接口,是一种高速全双工的通信总线。它被广
泛地使用在 ADC LCD 等设备与 MCU 间,要求通讯速率较高的场合。

特性

1、全双工(即可以同时收发)
2、最少需要占用4条线:
  •   SS:从设备选择信号线,常称为片选信号线,也称为NSS、CS
  •   SCK(Serial Clock):时钟信号线,用于通讯数据同步
  •   MOSI (Master Output Slave Input) : 主设备输出/ 从设备输入引脚。
  •   MISO(Master Input, Slave Output) : 主设备输入/ 从设备输出引脚。

3、 多从机只需要增加SS片选信号线

4、速率高,最高频率可达到fplck/2,受限于低速设备(例如STM32F407的APB2总线最高可达42MHz)

通讯过程

  •  NSS(片选信号线)由高变低,是SPI的起始信号
  • 触发:是数据在交换位,此时数据无线
  • 采样:是数据有效,读取数据采样
  • NSS线又低变高,意味着SPI通讯结束
  • MOSI和MISO是同步的,每发送一位就可以接收一位

采样模式

 

 通过切换时钟极性(CPOL)和时钟相位(CPHA)可以更改SPI的采样模式

CPOL = 0 :SCK起始信号为低电平

CPOL = 1 :SCK起始信号为高电平

CPHA = 0 :对奇数边缘采样

CPHA = 1 :对偶数边缘采样

 一般常用的是模式0和模式3,例如flash的W25Q128只支持模式0和模式3

详细通讯过程

模式3

 对flash发送数据需要等待TXE发送寄存器为reset,接收则需要等待RXNE接收非空寄存器reset。

下面是对flash写入和接收1字节数据的函数代码

/* ------------------SPI对flash写入1字节数据----------------------- */
// data   :要写入flash的数据
uint32_t SPI_WriteByte(uint8_t data)
{
	//等待事件响应
	TimeOut_count = SPI_time_out;
	while (SPI_I2S_GetFlagStatus(SPI_FLASH,SPI_I2S_FLAG_TXE) == RESET)
	{
		if ((TimeOut_count--) == 0)			return SPI_timeout_callback(0);
	}

	//发送要写入的数据
	SPI_I2S_SendData(SPI_FLASH, data);

	//等待事件响应
	TimeOut_count = SPI_time_out;
	while (SPI_I2S_GetFlagStatus(SPI_FLASH,SPI_I2S_FLAG_RXNE) == RESET)
	{
		if ((TimeOut_count--) == 0)			return SPI_timeout_callback(1);
	}

	//接受返回的数据
	return SPI_I2S_ReceiveData(SPI_FLASH);
}

发送和接收都是这个函数,因为SPI是全双工的,在发送1个字节的同时就会返回1个字节的数据

代码编写过程

对SPI在总线上查找

 

 查找spi1对应引脚,对应开发板硬件原理图,我的开发板是STMF407

 

根据开发板原理图

cs片选引脚:PG6

SCK:PB3      

MISO:PB4  

MOSI:PB5

根据引脚可以编写对应的SPI头文件宏

/*SPI引脚参数定义*/
#define SPI_FLASH                           SPI1
#define SPI_FLASH_CLK                       RCC_APB2Periph_SPI1
#define SPI_FLASH_INIT									    RCC_APB2PeriphClockCmd
/*SCK引脚*/
#define SPI_FLASH_SCK_PIN                  GPIO_Pin_3                 
#define SPI_FLASH_SCK_GPIO_PORT            GPIOB                       
#define SPI_FLASH_SCK_GPIO_CLK             RCC_AHB1Periph_GPIOB
#define SPI_FLASH_SCK_SOURCE               GPIO_PinSource3
#define SPI_FLASH_SCK_AF                   GPIO_AF_SPI1
/*MISO引脚*/
#define SPI_FLASH_MISO_PIN                  GPIO_Pin_4                 
#define SPI_FLASH_MISO_GPIO_PORT            GPIOB                       
#define SPI_FLASH_MISO_GPIO_CLK             RCC_AHB1Periph_GPIOB
#define SPI_FLASH_MISO_SOURCE               GPIO_PinSource4
#define SPI_FLASH_MISO_AF                   GPIO_AF_SPI1
/*MOSI引脚*/
#define SPI_FLASH_MOSI_PIN                  GPIO_Pin_5                 
#define SPI_FLASH_MOSI_GPIO_PORT            GPIOB                       
#define SPI_FLASH_MOSI_GPIO_CLK             RCC_AHB1Periph_GPIOB
#define SPI_FLASH_MOSI_SOURCE               GPIO_PinSource5
#define SPI_FLASH_MOSI_AF                   GPIO_AF_SPI1
/*CS引脚*/
#define SPI_FLASH_CS_PIN                	  GPIO_Pin_6                 
#define SPI_FLASH_CS_GPIO_PORT          	  GPIOG                       
#define SPI_FLASH_CS_GPIO_CLK           	  RCC_AHB1Periph_GPIOG
/*拉高拉低CS引脚*/
#define SPI_FLASH_CS_LOW()									GPIO_ResetBits(SPI_FLASH_CS_GPIO_PORT,SPI_FLASH_CS_PIN)
#define SPI_FLASH_CS_HIGH()									GPIO_SetBits(SPI_FLASH_CS_GPIO_PORT,SPI_FLASH_CS_PIN)	

 功能函数

对应flash W25Q128数据手册,编写对应的功能函数

根据对应的功能写出对应的宏增加代码可读性

上图的DUMMY是无效数据就用0xFF

#define DUMMY							0xFF

/*命令定义-开头*******************************/
#define W25X_WriteEnable		      0x06 
#define W25X_WriteDisable		      0x04 
#define W25X_ReadStatusReg		      0x05 
#define W25X_WriteStatusReg		      0x01 
#define W25X_ReadData			      0x03 
#define W25X_FastReadData		      0x0B 
#define W25X_FastReadDual		      0x3B 
#define W25X_PageProgram		      0x02 
#define W25X_BlockErase			      0xD8 
#define W25X_SectorErase		      0x20 
#define W25X_ChipErase			      0xC7 
#define W25X_PowerDown			      0xB9 
#define W25X_ReleasePowerDown	      0xAB 
#define W25X_DeviceID			      0xAB 
#define W25X_ManufactDeviceID         0x90 
#define W25X_JedecDeviceID		      0x9F 

下面是函数介绍

写使能功能函数

/*
	写使能函数
*/
void	SPI_FLASH_WriteEnable(void)
{
		SPI_FLASH_CS_LOW();
	
		SPI_WriteByte(W25X_WriteEnable);

		SPI_FLASH_CS_HIGH();
}

等待写完毕状态函数 

/*
	等待BUSY位为0,即等待Flash内部数据写入完毕
*/
void SPI_FLASH_WaitForWriteEnd(void)
{
	uint8_t flash_status = 0;
	
	SPI_FLASH_CS_LOW();
	
	SPI_WriteByte(W25X_ReadStatusReg);
	TimeOut_count = SPI_time_out;
	
	do
	{
		flash_status = SPI_WriteByte(DUMMY);
		
		if((TimeOut_count--) == 0)
		{
				SPI_timeout_callback(2);
				break;
		}
	}	while((flash_status & 0x01) == SET);
	
	SPI_FLASH_CS_HIGH();
}

扇区擦除功能函数(Sector_Erase)

/*
	扇区擦除函数
	addr:要擦除的扇区
*/
void	Sector_Erase(uint32_t addr)
{
		SPI_FLASH_WriteEnable();
		SPI_FLASH_WaitForWriteEnd();
		SPI_FLASH_CS_LOW();
	
		SPI_WriteByte(W25X_SectorErase);
		SPI_WriteByte((addr>>16) & 0xFF);
		SPI_WriteByte((addr>>8) & 0xFF);
		SPI_WriteByte(addr & 0xFF);
	
		SPI_FLASH_CS_HIGH();
		SPI_FLASH_WaitForWriteEnd();
}

页读取功能函数(Page_write)

/*
	写一页flash数据
	addr:要写入的地址起始
	buff:写入的的暂存缓冲区
	size:写的字节数  page一定要在256以内
*/
void Page_write(uint32_t addr,uint8_t *buff,uint32_t size)
{
		SPI_FLASH_WriteEnable();
		SPI_FLASH_WaitForWriteEnd();
		SPI_FLASH_CS_LOW();
	
		SPI_WriteByte(W25X_PageProgram);
		SPI_WriteByte((addr>>16) & 0xFF);
		SPI_WriteByte((addr>>8) & 0xFF);
		SPI_WriteByte(addr & 0xFF);
	
		while(size--)
		{
			SPI_WriteByte(*buff);
			buff++;
		}
			
		SPI_FLASH_CS_HIGH();
		SPI_FLASH_WaitForWriteEnd();
}

读取Flash_ID函数

/*
	发送0xAB读取flashID
*/
uint8_t Read_flash_ID(void)
{
	uint8_t id;
	//拉低CS片选引脚
	SPI_FLASH_CS_LOW();
	
	//写指令
	SPI_WriteByte(W25X_ReleasePowerDown);
	SPI_WriteByte(DUMMY);
	SPI_WriteByte(DUMMY);
	SPI_WriteByte(DUMMY);
	//读指令
	id = SPI_WriteByte(DUMMY);
	//拉高CS片选引脚   传输结束
	SPI_FLASH_CS_HIGH();
	
	return id;
}

大量数据写入函数(不限制与page页大小)

基于Page_write函数做了逻辑处理

/*
	写flash数据
	addr:要写入的地址起始
	buff:写入的的暂存缓冲区
	size:写的字节数  
*/
void Buffer_write(uint32_t addr,uint8_t *buff,uint32_t size)
{
	u8 num_signgle , num_page , count ,temp;
	
	num_signgle = addr % 256;			//求出首地址是否对齐
	count = 256 - num_signgle;		//首页剩余要写的字节
	num_page = size / 256;				//若对齐的页数
	
	temp = size % 256;						//如果对齐的话最后一页剩余要补的字节
	
	if(num_signgle != 0)		//首页没对齐的情况
	{
		num_page = (size - count)/256;	//重新算出没对齐后的页数
		if(num_page == 0)			
		{
			if(size > count)		//虽然是0页但也可能存在尾部跨页数的存在
			{
				Page_write(addr , buff , count);
				addr += count;
				buff += count;
				
				Page_write(addr , buff , (size-count));
			}
			else								//没跨页数
			{
				Page_write(addr , buff , size);
			}
		}
		else		//没对齐且不止一页
		{
				Page_write(addr , buff , count);		//补齐首页
				addr += count;
				buff += count;
			
				while(num_page--)										//写中间完整页
				{
					Page_write(addr , buff , 256);
					addr += 256;
					buff += 256;
				}
				
				temp = (size-count)%256;						//若有剩余补尾页页
				if(temp != 0)
				{
					Page_write(addr , buff , temp);
				}
		}
	}
	else
	{
		if(num_page == 0)						//对齐0页直接写
		{
			Page_write(addr , buff , size);
		}
		else
		{
			while(num_page--)					//对齐直接完整页
			{
				Page_write(addr , buff , 256);
				addr += 256;
				buff += 256;
			}
			if(temp != 0)							//若有剩余补尾页页
			{
				Page_write(addr , buff , temp);
			}
		}
	}
}

  • 0
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wiyoo0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值