当前位置: 首页 > news >正文

11.SPI和W25Q64

目录

SPI 通信

常见的数字通信接口和协议

数字通信的共性名词

        所谓的发送和接收 

        一主多从和点对点 

SPI 简介

SPI 物理层和总线结构

SPI 如何传输数据

SPI 通信模式

        如何选择使用哪种模式 

SPI 相关的问题

实现 SPI 的两种方法

        硬件 SPI 

        软件 SPI 

SPI 的参数选择

SPI 的核心

STM32 中的 SPI 讲解

STM32 中 SPI 特点 -- 同步 全双工 单端 串行

STM32 中 SPI 的框图

STM32 的 SPI 主模式发送和接收

STM32 总共有几个硬件 SPI

使用 STM32 的 SPI 的 GPIO 的配置

FLASH 存储—W25Q64 

常见的存储器

        EEPROM 

        FLASH 

存储器的作用

Flash 存储器

W25Q64

W25Q64 的容量布局

W25Q64 引脚和接口

选择W25Q64 通信模式

指令操作

时序图的读取

W25Q64 通信需要注意的细节

代码


SPI 通信

常见的数字通信接口和协议

        常见数字通信接口和协议:UART 单总线(DHT11) IIC SPI CAN 

数字通信的共性名词

        所谓的发送和接收 

                发送(输出):发送方控制数据线的高低电平 

                接收(输入):接收方读取对方控制数据线的高低电平 

        一主多从和点对点 

                一主多从:1 个主机可以同时和多个从机通信 

                点对点:通信只存在与两个设备之间 

SPI 简介

        一主多从(主从结构) 

        CS :片选,选择和谁通信

        SCK :时钟线 有时钟线同步通信,没有时钟线异步通信。 

        MOSI : 主机输出,从机输入 这根线主机控制,控制这根线的高低电平

        MISO : 主机输入,从机输出 这根线从机控制,控制这根线的高低电平 

        M:master S:slave O:out I:in 

        MOSI :(主机)控制这根线的高低电平 (从机)读取这跟线的高低电平 

                如果 STM32 作为主机 MOSI 要配置成(输出)模式 

        MISO :(从机)控制这根线的高低电平,(主机)读取这根线的高低电平

                如果 STM32 作为主机 MISO 要配置成(输入)模式 

        SCK : 一般是主机控制时钟线 

                如果 STM32 作为主机 SCK 要配置成(输出)模式 

        CS : 由主机控制 

                如果 STM32 作为主机 CS 要配置成(输出)模式 

SPI 物理层和总线结构

SPI 如何传输数据

 

SPI 通信模式

        SPI 四种工作模式 

                时钟极性 CPOL(0/1)时钟相位 CPHA(0/1)

                时钟极性 CPOL:空闲时候,时钟线的电平 0 空闲低电平 1 空闲高电平 

                时钟相位 CPHA: 

                        CPHA=0,在串行同步时钟的第一个(奇数)跳变沿(上升或下降)数据被采样(接收) 

                        CPHA=1,在串行同步时钟的第二个(偶数)跳变沿(上升或下降)数据被采样(接收) 

 

        如何选择使用哪种模式 

                看 SPI 接口从设备的手册,确认它支持什么模式 

SPI 相关的问题

实现 SPI 的两种方法

        实现 SPI:两种方法,硬件 SPI 和软件 SPI 

        硬件 SPI 

                硬件 SPI:使用单片机自带的硬件 SPI 控制器 

                需要输出引脚配置成复用功能,需要配置 SPI 的结构体 

                设备必须接在有 SPI 功能的引脚上 

        软件 SPI 

                软件(模拟)SPI:使用单片机的 GPIO 口拉高拉低模拟出来 SPI 的时序 

                输出引脚配置成通用的输出,不需要配置 SPI 的结构体 

                软件 SPI 只要使用普通的 GPIO 口就行 

SPI 的参数选择

        确定模式:根据从设备确定选择 SPI0—SPI3 

        确定高位在前还是低位在前:根据从设备确定 

        数据位宽度:根据从设备确定 

        确定速率:根据从设备确定 

SPI 的核心

        封装出来一个单字节读写函数 

STM32 中的 SPI 讲解

STM32 中 SPI 特点 -- 同步 全双工 单端 串行

STM32 中 SPI 的框图

NSS:

        如果 STM32 作为从机,STM32 的片选引脚必须是 NSS 

        如果 STM32 作为主机,NSS 没有用 

STM32 的 SPI 主模式发送和接收

STM32 总共有几个硬件 SPI

        有三个硬件 SPI,如果使用硬件 SPI,必须将设备连接在有 SPI 功能的引脚上 

        如果使用的模拟(软件)SPI,只需要接在有 GPIO 功能,能拉高拉低电平就可以 

使用 STM32 的 SPI 的 GPIO 的配置

        NSS 这根线,如果 STM32 作为从机,必须配置成硬件主/从模式 

        如果 STM32 作为主机,这根线可以不接,但是这跟线也可以配置成普通的 GPIO 口,用来控制片选,选择从机

FLASH 存储—W25Q64 

常见的存储器

        EEPROM 

                特点:掉电不丢失,写入之前不用擦除,存储空间一般比较小 

                典型型号:AT24C02 

                器件接口:IIC 

                关系:AT24C02 是 EEPROM 的一种 

        FLASH 

                特点:掉电不丢失,写入之前必须擦除,存储空间一般比较大 

                典型型号:W25Q64 

                器件接口:SPI 

                关系:W25Q64 是 FLASH 的一种 

存储器的作用

        存 wifi 名称和密码 存设备编号(阿里云三要素) 存服务器地址等实现掉电不丢失 

Flash 存储器

        FLASH:掉电不丢失的存储 8G+256G 中的 256 就是 FLASH 

                芯片内部 FLASH:STM32F103ZET6 64K+512K 其中 512K 就是 FLASH 

                芯片外部 FALSH:单片机外部外接了 1 个芯片 -- 今天实现的 

        W25Q64:是 FLASH 的一种,不同厂家命名方法不一样 

        SPI:是一种重要的通信接口,和很多 SPI 接口设备通信。今天的 W25Q64 的接口就是 SPI 

W25Q64

W25Q64 的容量布局

W25Q64 引脚和接口

选择W25Q64 通信模式

指令操作

        需要用到的指令:写使能 读状态寄存器 页编程 扇区擦除 读数据 

时序图的读取

以 0x90 为例

 

        从时序图中获取的信息,选择模式 3 

        1. 主机把片选信号拉低 

        2. 主机发送 0x90 的命令,调用单字节发送函数,0x90 数据体现在 MOSI 这根线上,MOSI 接的 W25Q64 的DI,数据是确定的,所以 DI 的波形也是确定,因为是全双工,W25Q64 也会通过 DO(MISO)这根线给单片机发送数据,但是单片机知道这个数据没用,所以波形没有变化,单片机也可以不接收

        3. 主机发送 24 位的地址,调用 3 次单字节发送函数,地址可能不确定,所以波形是胶囊的形状,胶囊证明此时数据,可能是 0/1

        4. 从机接收到指定的命令或者数据之后,然后回复指定的内容,回复两个字节内容,数据体现在 DO(M        ISO)这根线上,因为是全双工,主机也会通过 DI(MOSE)这根线给从机发送数据,主机发送的数据是没有意义的数据,所以 DI 的波形杂乱的。 

        5. 如果主机发送的地址是 0x000000,从机回复的数据是 0xEF 0x16 

            如果主机发送的地址是 0x000001,从机回复的数据是 0x16 0xEF

W25Q64 通信需要注意的细节

        1. FLASH 使用的时候,必须先擦除,再写。擦除之后,里面放的数据全部都是 0xFF, FLASH 只能由 1 变 0, 不能由 0 变 1。 

        2. 最小擦除指令就是扇区(4K)擦除--扇区擦除 

        3. 写之前必须要写使能--写使能 

        4. 不能跨页写,超过 1 页(256 字节)会从该页的起始位置覆盖--页写 

        5. 指令执行完,检测状态寄存器是否操作完成--读状态 

移植别人的代码的问题

1. 单字节读写函数不一致

可以通过宏定义改,或者把你的函数名字改了,或者改它的名字

2. 延时函数不一致

替换成你的延时函数

3. 官方代码示例代码给的是硬件 SPI1,结合板子修改SPI

代码

SPI.c

#include "SPI.h"/*
GPIO结合硬件,W25Q64接在SPI2上PB12 CS		通用推挽输出PB13 SCK	复用推挽输出PB14 MISO	浮空输入PB15 MOSI	复用推挽输出
*/
void SPI2_Config(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);GPIO_InitTypeDef GPIO_InitStruct={0};GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_15;//待配置的引脚GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//引脚速率GPIO_Init(GPIOB,&GPIO_InitStruct);	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;//通用推挽GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12;//待配置的引脚GPIO_Init(GPIOB,&GPIO_InitStruct);	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;//待配置的引脚GPIO_Init(GPIOB,&GPIO_InitStruct);		RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);SPI_InitTypeDef SPI_InitStruct={0};SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;//速率,波特率   根据从设备来确定 W25Q64手册中文 1 一般说明/*SPI2挂载在APB1总线(36M),如果2分频,就变成18M,W25Q64手册描述,支持80M,SPI速率<80M就可以*/SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge;//时钟相位 根据从设备来确定  W25Q64手册中文9.1.1 SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;//时钟极性 根据从设备来确定  W25Q64手册中文9.1.1SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;//数据位的宽度  根据从设备来确定 W25Q64手册中文9.1.1SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;//高位还是低位先发 根据从设备来确定 W25Q64手册中文9.1.1SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//传输的方向SPI_InitStruct.SPI_CRCPolynomial = 0;//CRC校验的多项式  未使用 随便填SPI_InitStruct.SPI_Mode = SPI_Mode_Master;  //主机模式SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; //软件模式/*如果STM32作为从机,必须配置成硬件模式,如果STM32作为主机,NSS没有用,配置成软件模式这样SPI2_NSS引脚就可以作为普通IO口使用,我们的硬件正好让NSS作为片选引脚了,所以配置NSS引脚软件模式,并且NSS引脚配置成通用推挽输出*///8.调用XXX_init函数将参数写入到寄存器中SPI_Init(SPI2,&SPI_InitStruct);//9.调用XXX_Cmd函数,将外设使能
//void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);	 stm32f10x_spi.h 451行SPI_Cmd(SPI2,ENABLE);//10.释放从机GPIO_SetBits(GPIOB,GPIO_Pin_12);	//中文固件库翻译手册  10.2.10	
}//单字节发送和接收,一个字节8位
uint8_t SPI2_Send_Rec_Byte(uint8_t Byte)
{
//FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);  stm32f10x_spi.h 465行
//void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);	  stm32f10x_spi.h 466行//1.先检测一下上次是否发完while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==RESET);//2.上一次发送完成之后,发送新的数据
//void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);  stm32f10x_spi.h 455行SPI_I2S_SendData(SPI2,Byte);//3.检测是否接收到数据while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE)==RESET);	//4.接收数据并返回
//uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);	 stm32f10x_spi.h 456行return SPI_I2S_ReceiveData(SPI2);
}

W25Q64.c

#include "W25Q64.h"
#include "SPI.h"
#include "stdio.h"//通过0x90,获取芯片ID
//按照W25QW64
//验证通信是否成功
void W25Q64_Read_ID_0x90(void)
{uint8_t Buff[2]={0};//1.片选信号拉低GPIO_ResetBits(GPIOB,GPIO_Pin_12);	//2.发送0x90的命令SPI2_Send_Rec_Byte(0x90);//3.发送24位的地址SPI2_Send_Rec_Byte(0x00);	SPI2_Send_Rec_Byte(0x00);	SPI2_Send_Rec_Byte(0x00);	//4.连续接收两个字节数据Buff[0]=SPI2_Send_Rec_Byte(0xFF);   //0xFF假数据,只要不和命令冲突,任意数据都可以	Buff[1]=SPI2_Send_Rec_Byte(0xFF); //5.把片选信号拉高GPIO_SetBits(GPIOB,GPIO_Pin_12);	printf("0x90命令返回的结果:%x\r\n",(Buff[0]<<8)+Buff[1]);
}void W25Q64_Read_ID_0x9F(void)
{uint8_t Buff[3]={0};//1.片选信号拉低GPIO_ResetBits(GPIOB,GPIO_Pin_12);	//2.发送0x90的命令SPI2_Send_Rec_Byte(0x9F);//4.连续接收三个字节数据Buff[0]=SPI2_Send_Rec_Byte(0xFF);   //0xFF假数据,只要不和命令冲突,任意数据都可以	Buff[1]=SPI2_Send_Rec_Byte(0xFF);Buff[2]=SPI2_Send_Rec_Byte(0xFF);//5.把片选信号拉高GPIO_SetBits(GPIOB,GPIO_Pin_12);printf("0x90命令返回的结果:%x\r\n",(Buff[0]<<16)+(Buff[1]<<8)+Buff[2]);
}//写使能 参考W25Q64英文的11.2.4编程
void sFLASH_WriteEnable(void)
{// (CS) 引脚设置为低电平sFLASH_CS_LOW();//发送一个字节函数,定义了宏sFLASH_SendByte(sFLASH_CMD_WREN);// (CS) 引脚设置为低电平sFLASH_CS_HIGH();
}//读状态寄存器
//参考W25Q64中文10.2.6
void sFLASH_WaitForWriteEnd(void)
{uint8_t flashstatus = 0;/*!< 选择 Flash 存储器:将 Chip Select 置为低电平 */sFLASH_CS_LOW();/*!< 发送 "读取状态寄存器" 指令 */sFLASH_SendByte(sFLASH_CMD_RDSR);/*!< 循环,直到 Flash 存储器完成写入操作 */do{/*!< 发送一个空字节,以生成 Flash 所需的时钟,并将状态寄存器的值存入 flashstatus 变量 */flashstatus = sFLASH_SendByte(sFLASH_DUMMY_BYTE);}while ((flashstatus & sFLASH_WIP_FLAG) == SET);/* 写入进行中 *//*!< 取消选择 Flash 存储器:将 Chip Select 置为高电平 */sFLASH_CS_HIGH();
}//页编程 不支持跨页 
//参考W25Q64中文10.2.14
/*
参数1  待写入数据的首地址
参数2  写入到W25Q64中的地址
参数3  待写入的长度
*/
void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{/*!< 启用对 Flash 存储器的写访问权限 */sFLASH_WriteEnable();/*!< 选择 Flash 存储器:将 Chip Select 置为低电平 */sFLASH_CS_LOW();/*!< 发送 "写入内存" 指令 */sFLASH_SendByte(sFLASH_CMD_WRITE);/*!< Send WriteAddr high nibble address byte to write to *//*!< 发送写入地址的高字节 */sFLASH_SendByte((WriteAddr & 0xFF0000) >> 16);/*!< Send WriteAddr medium nibble address byte to write to *//*!< 发送写入地址的中间字节 */sFLASH_SendByte((WriteAddr & 0xFF00) >> 8);/*!< Send WriteAddr low nibble address byte to write to *//*!< 发送写入地址的低字节 */sFLASH_SendByte(WriteAddr & 0xFF);/*!< while there is data to be written on the FLASH *//*!< 当还有数据需要写入 Flash 时 */while (NumByteToWrite--){/*!< Send the current byte *//*!< 发送当前字节的数据 */sFLASH_SendByte(*pBuffer);/*!< Point on the next byte to be written *//*!< 移动到下一个字节 */pBuffer++;}/*!< Deselect the FLASH: Chip Select high *//*!< 取消选择 Flash 存储器:将 Chip Select 置为高电平 */sFLASH_CS_HIGH();/*!< Wait the end of Flash writing *//*!< 等待 Flash 完成写入操作 */sFLASH_WaitForWriteEnd();
}//扇区擦除
//参考W25Q64中文10.2.16
//参数  扇区的首地址
void sFLASH_EraseSector(uint32_t SectorAddr)
{/*!< 发送写使能指令 */sFLASH_WriteEnable();/*!< 扇区擦除 *//*!< 选择 Flash 存储器:将 Chip Select 置为低电平 */sFLASH_CS_LOW();/*!< 发送扇区擦除指令 */sFLASH_SendByte(sFLASH_CMD_SE);/*!< 发送扇区地址的高字节 */sFLASH_SendByte((SectorAddr & 0xFF0000) >> 16);/*!< 发送扇区地址的中间字节 */sFLASH_SendByte((SectorAddr & 0xFF00) >> 8);/*!< 发送扇区地址的低字节 */sFLASH_SendByte(SectorAddr & 0xFF);/*!< 取消选择 Flash 存储器:将 Chip Select 置为高电平 */sFLASH_CS_HIGH();/*!< 等待 Flash 完成写入操作 */sFLASH_WaitForWriteEnd();
}//读数据
//参考W25Q64中文10.2.8
/*
参数1  读取的数据存放的地址
参数2  读取W25Q64的地址
参数3  读取的长度
*/
void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{sFLASH_CS_LOW();sFLASH_SendByte(sFLASH_CMD_READ);sFLASH_SendByte((ReadAddr & 0xFF0000) >> 16);sFLASH_SendByte((ReadAddr& 0xFF00) >> 8);sFLASH_SendByte(ReadAddr & 0xFF);/*!< 当还有数据需要读取时 */while (NumByteToRead--) /*!< while there is data to be read */{/*!< Read a byte from the FLASH *//*!< 从 Flash 读取一个字节 */*pBuffer = sFLASH_SendByte(sFLASH_DUMMY_BYTE);/*!< Point to the next location where the byte read will be saved *//*!< 指向下一个存储读取字节的位置 */pBuffer++;}sFLASH_CS_HIGH();
}//跨页写
/*
参数1 待写入数据首地址
参数2 写到w25q64中的地址
参数3 待写入的长度
*/
void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;// 计算写入地址的偏移量Addr = WriteAddr % sFLASH_SPI_PAGESIZE;// 计算该页剩余空间count = sFLASH_SPI_PAGESIZE - Addr;// 计算需要写入的完整页面数NumOfPage =  NumByteToWrite / sFLASH_SPI_PAGESIZE;// 计算剩余不足一页的字节数NumOfSingle = NumByteToWrite % sFLASH_SPI_PAGESIZE;if (Addr == 0) /*!< WriteAddr 已对齐到 sFLASH_PAGESIZE */{if (NumOfPage == 0) /*!< NumByteToWrite 小于 sFLASH_PAGESIZE */{// 如果写入数据少于一页,直接写入sFLASH_WritePage(pBuffer, WriteAddr, NumByteToWrite);}else /*!< NumByteToWrite 大于 sFLASH_PAGESIZE */{while (NumOfPage--)// 写入多页数据{sFLASH_WritePage(pBuffer, WriteAddr, sFLASH_SPI_PAGESIZE);WriteAddr +=  sFLASH_SPI_PAGESIZE;pBuffer += sFLASH_SPI_PAGESIZE;}// 写入最后不足一页的数据sFLASH_WritePage(pBuffer, WriteAddr, NumOfSingle);}}else /*!< WriteAddr 未对齐到 sFLASH_PAGESIZE */{if (NumOfPage == 0) /*!< NumByteToWrite 小于 sFLASH_PAGESIZE */{if (NumOfSingle > count)  /*!< (NumByteToWrite + WriteAddr) 大于一页 */{temp = NumOfSingle - count;// 写入当前页剩余的数据sFLASH_WritePage(pBuffer, WriteAddr, count);WriteAddr +=  count;pBuffer += count;// 写入下一页的数据sFLASH_WritePage(pBuffer, WriteAddr, temp);}else{// 如果写入的数据不超过一页,直接写入sFLASH_WritePage(pBuffer, WriteAddr, NumByteToWrite);}}else /*!< NumByteToWrite 大于 sFLASH_PAGESIZE */{// 计算去除当前页后剩余的数据NumByteToWrite -= count;NumOfPage =  NumByteToWrite / sFLASH_SPI_PAGESIZE;NumOfSingle = NumByteToWrite % sFLASH_SPI_PAGESIZE;// 写入当前页剩余的数据sFLASH_WritePage(pBuffer, WriteAddr, count);WriteAddr +=  count;pBuffer += count;// 写入剩余的完整页面数据while (NumOfPage--){sFLASH_WritePage(pBuffer, WriteAddr, sFLASH_SPI_PAGESIZE);WriteAddr +=  sFLASH_SPI_PAGESIZE;pBuffer += sFLASH_SPI_PAGESIZE;}// 写入最后不足一页的数据if (NumOfSingle != 0){sFLASH_WritePage(pBuffer, WriteAddr, NumOfSingle);}}}
}

相关文章:

  • Gemini 的超长回复
  • CSS相关知识
  • 6个月Python学习计划 Day 4
  • 前端流行框架Vue3教程:26. 异步组件
  • 【25软考网工】第八章 (1)交换机基础
  • springboot 控制层调用业务逻辑层,注入报错,无法自动装配 解决办法
  • 在机器学习中,L2正则化为什么能够缓过拟合?为何正则化等机制能够使一个“过度拟合训练集”的模型展现出更优的泛化性能?正则化
  • c++总结-04-智能指针
  • 奈雪小程序任务脚本
  • Python与C++中浮点数的精度与计算误差(易忽略易错)
  • C++11(2):
  • 历年华东师范大学保研上机真题
  • 计算机病毒的发展历程及其分类
  • 审计报告附注救星!实现Word表格纵向求和+横向计算及其对应的智能校验
  • JavaScript 中的 structuredClone() 如何彻底改变你的对象复制方式
  • 制造业主要管理哪些主数据范围
  • 智能办公系统 — 审批管理模块 · 开发日志
  • 理解HTTP基本认证与表单登录认证
  • [创业之路-381]:企业战略管理案例分析-战略制定/设计-市场洞察“五看”:看宏观-经济-如何获得国家经济政策与愿景规划,以及技术发展趋势、技术成熟度
  • Windows 开始菜单快捷方式路径说明
  • 微信公众号文章怎么制作/石家庄网站建设seo公司
  • 在工商局网站怎么做清算/常州网站制作维护
  • 哪个找房网站好/软文推广案例大全
  • 深圳效果好的免费网站建设/seo资料网
  • 北京注册网站/市场推广
  • 以百度云做网站空间/产品推销