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

    物联沃分享整理
    物联沃-IOTWORD物联网 » SPI 读写串行 Flash 的技术指南

    发表评论