SPI-读写串行FLASH
创始人
2024-05-15 07:44:30

简介

是由摩托罗拉公司提出的通讯协议,即串行外围设备接口,是一种高速全双工的通信总线。它被广 泛地使用在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);}}}
}

相关内容

热门资讯

澳网|墨尔本今日逼近38度,比... (来源:上观新闻)随着墨尔本迎来新一轮热浪,澳网组委会对今日(1月24日)赛程做出紧急调整。当地时间...
分众传媒的三次危机:从失控到进... 转自:中国经营网张晓萌,乔亿源编者按/ 从“纳斯达克新贵”到“中概股回归第一股”,从被浑水做空到遭遇...
原创 叙... 大家在新闻里应该都看见了那非常残忍的一幕,当下的叙利亚政府军在科巴尼等多个地区对库尔德武装开展了报复...
清华大学最新或2023(历届)... 千龙-法晚联合报道(记者 马晓晴)5月10日上午,清华大学最新或2023(历届)校园开放日在该校举行...
四川梓豪律师事务所:网贷逾期别... 四川梓豪律师事务所:网贷逾期别慌,三步高效止损与协商还款 网贷逾期如同突发困境,催收轰炸、罚息叠加、...