首页 > 其他分享 >stm32 SPI通信协议&W25Q64(软件SPI读写W25Q64)

stm32 SPI通信协议&W25Q64(软件SPI读写W25Q64)

时间:2024-09-12 21:24:07浏览次数:12  
标签:SCK void 通信协议 W25Q64 MySPI SPI GPIO

理论

SPI

1.SPI通信

SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线

四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select)

同步,全双工

支持总线挂载多设备(一主多从)

SCK:时钟线

MOSI:主机输出,从机输入

MISO:主机输入,从机输出

SS:从机选择

2.硬件电路

所有SPI设备的SCK、MOSI、MISO分别连在一起

主机另外引出多条SS控制线,分别接到各从机的SS引脚

输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入

3.移位示意图

高位先行

4.SPI时序基本单元

(1)起始条件

起始条件:SS从高电平切换到低电平 

(2)终止条件

终止条件:SS从低电平切换到高电平

(3)交换一个字节
模式0

CPOL(SCK)=0:空闲状态时,SCK为低电平

CPHA(移出数据加快半个节点)=0:SCK第一个边沿移入数据,第二个边沿移出数据

模式1

CPOL=0:空闲状态时,SCK为低电平

CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

模式2

CPOL=1:空闲状态时,SCK为高电平

CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据

模式3

CPOL=1:空闲状态时,SCK为高电平

CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

5.SPI时序

(1)发送指令

向SS指定的设备,发送指令(0x06)

(2)指定地址写

向SS指定的设备,发送写指令(0x02),      随后在指定地址(Address[23:0])下,写入指定数据(Data)

(3)指定地址读

向SS指定的设备,发送读指令(0x03),      随后在指定地址(Address[23:0])下,读取从机数据(Data)

 W25Q64

1.W25Q64简介

W25Qxx系列是一种低成本、小型化、使用简单的非易失性存储器,常应用于数据存储、字库存储、固件程序存储等场景

存储介质:Nor Flash(闪存)

时钟频率:80MHz / 160MHz (Dual SPI) / 320MHz (Quad SPI)

存储容量(24位地址):

    W25Q40:4Mbit / 512KByte

    W25Q80: 8Mbit / 1MByte

    W25Q16: 16Mbit / 2MByte

    W25Q32:32Mbit / 4MByte

    W25Q64:64Mbit / 8MByte

    W25Q128:128Mbit / 16MByte

    W25Q256:256Mbit / 32MByte

2.硬件电路

DI(IO):MOSI

CLK:SCK

DO(IO):MISO 

/CS:SS

3.W25Q64框图

存储器:块区(64KB)->扇区(4KB)

缓存区:最大是256Byte/页

4.Flash操作注意事项

写入操作时: 写入操作前,必须先进行写使能(一般在写入数据时&&擦除数据时  因为擦除数据本质是写入0xFF)

每个数据位只能由1改写为0,不能由0改写为1(如果想修改数据就必须将对应位置的块区或扇区擦除)

写入数据前必须先擦除,擦除后,所有数据位变为1

擦除必须按最小擦除单元进行

连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入

写入操作结束后,芯片进入忙状态,不响应新的读写操作

读取操作时:

直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取

代码

软件SPI读写W25Q64

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"

uint8_t MID;							//定义用于存放MID号的变量
uint16_t DID;							//定义用于存放DID号的变量

uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04};	//定义要写入数据的测试数组
uint8_t ArrayRead[4];								//定义要读取数据的测试数组

int main(void)
{
	/*模块初始化*/
	OLED_Init();						//OLED初始化
	W25Q64_Init();						//W25Q64初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "MID:   DID:");
	OLED_ShowString(2, 1, "W:");
	OLED_ShowString(3, 1, "R:");
	
	/*显示ID号*/
	W25Q64_ReadID(&MID, &DID);			//获取W25Q64的ID号
	OLED_ShowHexNum(1, 5, MID, 2);		//显示MID
	OLED_ShowHexNum(1, 12, DID, 4);		//显示DID
	
	/*W25Q64功能函数测试*/
	W25Q64_SectorErase(0x000000);					//扇区擦除
	W25Q64_PageProgram(0x000000, ArrayWrite, 4);	//将写入数据的测试数组写入到W25Q64中
	
	W25Q64_ReadData(0x000000, ArrayRead, 4);		//读取刚写入的测试数据到读取数据的测试数组中
	
	/*显示数据*/
	OLED_ShowHexNum(2, 3, ArrayWrite[0], 2);		//显示写入数据的测试数组
	OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);
	OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);
	OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);
	
	OLED_ShowHexNum(3, 3, ArrayRead[0], 2);			//显示读取数据的测试数组
	OLED_ShowHexNum(3, 6, ArrayRead[1], 2);
	OLED_ShowHexNum(3, 9, ArrayRead[2], 2);
	OLED_ShowHexNum(3, 12, ArrayRead[3], 2);
	
	while (1)
	{
		
	}
}

 MySPI.c

#include "stm32f10x.h"                  // Device header

/*引脚配置层*/

/**
  * 函    数:SPI写SS引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SS的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SS为低电平,当BitValue为1时,需要置SS为高电平
  */
void MySPI_W_SS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);		//根据BitValue,设置SS引脚的电平
}

/**
  * 函    数:SPI写SCK引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SCK的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCK为低电平,当BitValue为1时,需要置SCK为高电平
  */
void MySPI_W_SCK(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);		//根据BitValue,设置SCK引脚的电平
}

/**
  * 函    数:SPI写MOSI引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入MOSI的电平,范围0~0xFF
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置MOSI为低电平,当BitValue非0时,需要置MOSI为高电平
  */
void MySPI_W_MOSI(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);		//根据BitValue,设置MOSI引脚的电平,BitValue要实现非0即1的特性
}

/**
  * 函    数:I2C读MISO引脚电平
  * 参    数:无
  * 返 回 值:协议层需要得到的当前MISO的电平,范围0~1
  * 注意事项:此函数需要用户实现内容,当前MISO为低电平时,返回0,当前MISO为高电平时,返回1
  */
uint8_t MySPI_R_MISO(void)
{
	return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);			//读取MISO电平并返回
}

/**
  * 函    数:SPI初始化
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,实现SS、SCK、MOSI和MISO引脚的初始化
  */
void MySPI_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA4、PA5和PA7引脚初始化为推挽输出
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA6引脚初始化为上拉输入
	
	/*设置默认电平*/
	MySPI_W_SS(1);											//SS默认高电平
	MySPI_W_SCK(0);											//SCK默认低电平
}

/*协议层*/

/**
  * 函    数:SPI起始
  * 参    数:无
  * 返 回 值:无
  */
void MySPI_Start(void)
{
	MySPI_W_SS(0);				//拉低SS,开始时序
}

/**
  * 函    数:SPI终止
  * 参    数:无
  * 返 回 值:无
  */
void MySPI_Stop(void)
{
	MySPI_W_SS(1);				//拉高SS,终止时序
}

/**
  * 函    数:SPI交换传输一个字节,使用SPI模式0
  * 参    数:ByteSend 要发送的一个字节
  * 返 回 值:接收的一个字节
  */
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	uint8_t i, ByteReceive = 0x00;					//定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
	
	for (i = 0; i < 8; i ++)						//循环8次,依次交换每一位数据
	{
		MySPI_W_MOSI(ByteSend & (0x80 >> i));		//使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线
		MySPI_W_SCK(1);								//拉高SCK,上升沿移出数据
		if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}	//读取MISO数据,并存储到Byte变量
																//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0
		MySPI_W_SCK(0);								//拉低SCK,下降沿移入数据
	}
	
	return ByteReceive;								//返回接收到的一个字节数据
}

MySPI.h

#ifndef __MYSPI_H
#define __MYSPI_H

void MySPI_Init(void);
void MySPI_Start(void);
void MySPI_Stop(void);
uint8_t MySPI_SwapByte(uint8_t ByteSend);

#endif

W25Q64.c

#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"

/**
  * 函    数:W25Q64初始化
  * 参    数:无
  * 返 回 值:无
  */
void W25Q64_Init(void)
{
	MySPI_Init();					//先初始化底层的SPI
}

/**
  * 函    数:MPU6050读取ID号
  * 参    数:MID 工厂ID,使用输出参数的形式返回
  * 参    数:DID 设备ID,使用输出参数的形式返回
  * 返 回 值:无
  */
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q64_JEDEC_ID);			//交换发送读取ID的指令
	*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);	//交换接收MID,通过输出参数返回
	*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);	//交换接收DID高8位
	*DID <<= 8;									//高8位移到高位
	*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);	//或上交换接收DID的低8位,通过输出参数返回
	MySPI_Stop();								//SPI终止
}

/**
  * 函    数:W25Q64写使能
  * 参    数:无
  * 返 回 值:无
  */
void W25Q64_WriteEnable(void)
{
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q64_WRITE_ENABLE);		//交换发送写使能的指令
	MySPI_Stop();								//SPI终止
}

/**
  * 函    数:W25Q64等待忙
  * 参    数:无
  * 返 回 值:无
  */
void W25Q64_WaitBusy(void)
{
	uint32_t Timeout;
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);				//交换发送读状态寄存器1的指令
	Timeout = 100000;							//给定超时计数时间
	while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)	//循环等待忙标志位
	{
		Timeout --;								//等待时,计数值自减
		if (Timeout == 0)						//自减到0后,等待超时
		{
			/*超时的错误处理代码,可以添加到此处*/
			break;								//跳出等待,不等了
		}
	}
	MySPI_Stop();								//SPI终止
}

/**
  * 函    数:W25Q64页编程
  * 参    数:Address 页编程的起始地址,范围:0x000000~0x7FFFFF
  * 参    数:DataArray	用于写入数据的数组
  * 参    数:Count 要写入数据的数量,范围:0~256
  * 返 回 值:无
  * 注意事项:写入的地址范围不能跨页
  */
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
	uint16_t i;
	
	W25Q64_WriteEnable();						//写使能
	
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);		//交换发送页编程的指令
	MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位
	MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位
	MySPI_SwapByte(Address);					//交换发送地址7~0位
	for (i = 0; i < Count; i ++)				//循环Count次
	{
		MySPI_SwapByte(DataArray[i]);			//依次在起始地址后写入数据
	}
	MySPI_Stop();								//SPI终止
	
	W25Q64_WaitBusy();							//等待忙
}

/**
  * 函    数:W25Q64扇区擦除(4KB)
  * 参    数:Address 指定扇区的地址,范围:0x000000~0x7FFFFF
  * 返 回 值:无
  */
void W25Q64_SectorErase(uint32_t Address)
{
	W25Q64_WriteEnable();						//写使能
	
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);	//交换发送扇区擦除的指令
	MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位
	MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位
	MySPI_SwapByte(Address);					//交换发送地址7~0位
	MySPI_Stop();								//SPI终止
	
	W25Q64_WaitBusy();							//等待忙
}

/**
  * 函    数:W25Q64读取数据
  * 参    数:Address 读取数据的起始地址,范围:0x000000~0x7FFFFF
  * 参    数:DataArray 用于接收读取数据的数组,通过输出参数返回
  * 参    数:Count 要读取数据的数量,范围:0~0x800000
  * 返 回 值:无
  */
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
	uint32_t i;
	MySPI_Start();								//SPI起始
	MySPI_SwapByte(W25Q64_READ_DATA);			//交换发送读取数据的指令
	MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位
	MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位
	MySPI_SwapByte(Address);					//交换发送地址7~0位
	for (i = 0; i < Count; i ++)				//循环Count次
	{
		DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);	//依次在起始地址后读取数据
	}
	MySPI_Stop();								//SPI终止
}

W25Q64.h

#ifndef __W25Q64_H
#define __W25Q64_H

void W25Q64_Init(void);
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID);
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count);
void W25Q64_SectorErase(uint32_t Address);
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count);

#endif

接线图

标签:SCK,void,通信协议,W25Q64,MySPI,SPI,GPIO
From: https://blog.csdn.net/2302_79504723/article/details/142185579

相关文章

  • [20240912]记录使用tnsping遇到的问题.txt
    [20240912]记录使用tnsping遇到的问题.txt--//tnsping用来检测数据库是否连接存在许多局限性,记录自己在使用tnsping遇到的问题.1.环境:--//关闭数据库开启监听.SYS@book>shutdownimmediate;Databaseclosed.Databasedismounted.ORACLEinstanceshutdown.--//服务端监听配置......
  • USB转串口设备在不同通信协议下的性能表现如何?
    USB转串口设备在不同通信协议下的性能表现主要取决于其硬件设计和驱动程序的支持。以下是对几种常见通信协议下USB转串口设备性能的分析:RS-232:这是一种传统的串行通信标准,使用非平衡信号传输,通常用于短距离通信。USB转串口设备在RS-232协议下通常能够提供最高115200bps的波特率,但由......
  • SPiT:超像素驱动的非规则ViT标记化,实现更真实的图像理解 | ECCV 2024
    VisionTransformer(ViT)架构传统上采用基于网格的方法进行标记化,而不考虑图像的语义内容。论文提出了一种模块化的超像素非规则标记化策略,该策略将标记化和特征提取解耦,与当前将两者视为不可分割整体的方法形成了对比。通过使用在线内容感知标记化以及尺度和形状不变的位置嵌入......
  • 较好的SPI 接口的 OLED入门文章
    参考文章:1.基于STM32+0.96寸OLED--7脚SPI接线显示+代码解析链接:https://blog.csdn.net/qq_59527512/article/details/139471311推荐理由:很好地讲解了OLED的基础认知:7脚OLED模块的引脚和5种接线方式(DS0/1/2)   我们的OLED屏幕选择的是SSD1306Z的芯片,是一个128*64大小......
  • 五、STM32标准库硬件SPI驱动OLED(基于SSD1106)
    1、驱动芯片及MCU介绍        SSD1106是一款常用于嵌入式设备中的小型屏幕OLED(有机发光二极管)显示器驱动芯片,被广泛应用于各种嵌入式设备中,本示例程序基于SSD1106驱动芯片的OLED显示屏。        本次示例采用STM32F103系列MCU,使用标准库硬件SPI驱动OLED......
  • SPIE独立出版。遥感征稿中--2024年遥感与数字地球国际学术会议(RSDE 2024)
    ​〔成都,遥感主题,稳定EI检索〕2024年遥感与数字地球国际学术会议(RSDE2024)2024InternationalConferenceonRemoteSensingandDigitalEarth  大会官网:www.ic-rsde.org   大会时间:2024年11月8-10日大会地点:中国-成都截稿日期:2024年10月7日(分轮截稿)收录检索:EIC......
  • 【连续多年EI检索,去年EI检索只需2.5个月!学生易中稿,性价比超高!SPIE出版 (ISSN号: 0277-
    由重庆理工大学、重庆市特种设备检测研究院和重庆市电子学会联合主办,重庆邮电大学、韩国仁荷大学、重庆工程学院、重庆能源职业学院协办,光纤传感与光电检测重庆市重点实验室、现代光电检技术与仪器重庆市高校重点实验室、西部复杂环境机电设备安全国家市场监管重点实验室、智能......
  • 树莓派raspiOS换源
    新装了个树莓派,更新时候发现连不上网,遂换国内源。由于近期AI用习惯了,就没找官方说明,结果......果然踩雷了。所以还是得看官方提供的说明。清华开源镜像站Raspbian软件仓库1、查看系统架构$uname-maarch64提示aarch64用户直接参考Debian帮助Debian软件源大部分Debia......
  • 一文讲清,常用通信协议IIC,SPI,串口,基于STM32
    目录一、通讯的基本概念1.串行通讯2.并行通讯3.传输模式(单工、半双工、全双工)二、常见通讯协议(串口、IIC、SPI)1.串口(1)UART和USART的区别是什么?(2)UART(TTL、RS232、RS485)(3)基于STM32的HAL库的串口配置2.IIC(1)物理层(2)协议层(3)软件模拟IIC通讯代码(4)有关IIC面试的问题(5)硬......
  • Spire.Doc for Java Version:12.9
    Spire.DocforJavaisaprofessionalWordAPIthatempowersJavaapplicationstocreate,convert,manipulateandprintWorddocumentswithoutdependencyonMicrosoftWord.Byusingthismultifunctionallibrary,developersareabletoprocesscopioustasks......