首页 > 其他分享 >STM32通过SPI硬件读写W25Q64

STM32通过SPI硬件读写W25Q64

时间:2024-06-22 18:30:08浏览次数:12  
标签:void MySPI W25Q64 STM32 SPI GPIO define

文章目录

1. W25Q64

2. 硬件电路

3. 软件/硬件波形对比

4. STM32中的SPI外设

5. 代码实现

5.1 MyI2C.c

5.2 MyI2C.h

5.3 W25Q64.c

5.4 W25Q64.h

5.5 W25Q64_Ins.h

5.6 main.c


1. W25Q64

对于SPI通信和W25Q64的详细解析可以看下面这篇文章

STM32单片机SPI通信详解-CSDN博客

对于STM32通过SPI软件读写W25Q64的代码,可以看下面这篇文章

STM32通过SPI软件读写W25Q64-CSDN博客

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

地址设计

  • 地址位数:指用于寻址的二进制位数。在计算机系统中,每个内存单元都有一个唯一的地址,通过地址可以访问和引用内存中的数据或指令。
  • 地址总线:用于地址传输的总线。W25Q64 的 24 位地址总线意味着它可以访问 2^24 个地址,即 16,777,216 个字节(16MB)的空间。
  • 地址位数与存储容量:地址位数越多,能寻址的存储空间越大。例如,8 位地址可以寻址 256 个字节,16 位地址可以寻址 65,536 个字节(64KB)。

W25Q64 的存储空间

  • 存储容量:W25Q64 具体的存储容量为 64Mbit,即 8MB,但其地址总线的设计可以支持更大的寻址空间。
  • 数据组织:存储器通常按字节组织,每个字节有唯一的地址。W25Q64 可以通过 24 位地址总线访问每个字节,这使得数据读写操作更加灵活和高效。

2. 硬件电路

引脚功能

VCC、GND

电源(2.7~3.6V)

CS(SS)

SPI片选

CLK(SCK)

SPI时钟

DI(MOSI)

SPI主机输出从机输入

DO(MISO)

SPI主机输入从机输出

WP

写保护

HOLD

数据保持

WP(Write Protect):写保护

WP 引脚用于实现硬件写保护功能。WP 引脚为低电平时,写保护有效,无法进行写操作;WP 引脚为高电平时,可以进行写操作。

HOLD:数据保持

HOLD 引脚为低电平时,芯片进入保持状态。当在进行正常的读写操作时,如果需要中断 SPI 通信以操作其他设备,可以将 HOLD 引脚置为低电平。此时,芯片会保持当前状态但释放总线控制权。这样可以在不中断当前操作的前提下,使用 SPI 总线与其他设备通信。操作完毕后,将 HOLD 引脚置为高电平,芯片将恢复并继续之前的操作。这个功能允许在不终止总线操作的情况下,实现 SPI 总线的中断处理。

3. 软件/硬件波形对比

硬件数据波形变化紧贴SCK边沿 软件数据变化在边沿后有些延迟。

I2C:SCL低电平期间数据变化,高电平期间数据采样 SPI:SCK下降沿数据移出,上升沿数据移入。 两者最终波形的表现形式都是一样的,无论是下降沿变化还是低电平期间变化,它们都 是一个意思,都可以作为数据变化的时刻。

4. STM32中的SPI外设

STM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担,可配置8位/16位数据帧、高位先行/低位先行

时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)

支持多主机模型、主或从操作

可精简为半双工/单工通信

支持DMA

兼容I2S协议

STM32F103C8T6 硬件SPI资源:SPI1、SPI2

5. 代码实现

硬件SPI读写W25Q64

硬件SPI配置步骤

在软件读写I2C的基础上进行改写,以适应硬件读写SPI的需求。以下是详细步骤:

  1. 保留SS引脚的GPIO软件模拟:使用GPIO软件模拟保留SS(Slave Select)引脚的功能。
  2. 初始化SPI外设:配置SPI外设的相关参数。
  3. 交换一个字节的操作流程:具体操作流程如下:
    • 开启SPI和GPIO时钟:确保SPI外设和GPIO端口的时钟已开启。
    • 初始化GPIO端口
      • SCK和MOSI:这些是由硬件外设控制的输出信号,需要配置为复用推挽输出模式。
      • MISO:这是硬件外设的输入信号,需要配置为上拉输入模式。由于输入设备可以有多个,因此不存在复用输入的情况。普通GPIO口和外设都可以进行输入操作。
      • SS引脚:这是由软件控制的输出信号,需要配置为通用推挽输出模式。
    • 配置SPI外设:使用结构体对SPI外设进行配置,设定相应的参数。
    • 开关控制:使用SPI_Cmd函数来开启或关闭SPI外设。

5.1 MyI2C.c

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:SPI写SS引脚电平,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初始化
void MySPI_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);	//开启SPI1的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA4引脚初始化为推挽输出
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将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引脚初始化为上拉输入
	
	/*SPI初始化*/
	SPI_InitTypeDef SPI_InitStructure;						//定义结构体变量
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;			//模式,选择为SPI主模式
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;	//方向,选择2线全双工
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//数据宽度,选择为8位
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;		//先行位,选择高位先行
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;	//波特率分频,选择128分频
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;				//SPI极性,选择低极性
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;			//SPI相位,选择第一个时钟边沿采样,极性和相位决定选择SPI模式0
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;				//NSS,选择由软件控制
	SPI_InitStructure.SPI_CRCPolynomial = 7;				//CRC多项式,暂时用不到,给默认值7
	SPI_Init(SPI1, &SPI_InitStructure);						//将结构体变量交给SPI_Init,配置SPI1
	
	/*SPI使能*/
	SPI_Cmd(SPI1, ENABLE);									//使能SPI1,开始运行
	
	/*设置默认电平*/
	MySPI_W_SS(1);											//SS默认高电平
}

//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)
{
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);	//等待发送数据寄存器空
	
	SPI_I2S_SendData(SPI1, ByteSend);								//写入数据到发送数据寄存器,开始产生时序
	
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);	//等待接收数据寄存器非空
	
	return SPI_I2S_ReceiveData(SPI1);								//读取接收到的数据并返回
}

5.2 MyI2C.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

5.3 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终止
}

5.4 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

5.5 W25Q64_Ins.h

#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H

#define W25Q64_WRITE_ENABLE							0x06
#define W25Q64_WRITE_DISABLE						0x04
#define W25Q64_READ_STATUS_REGISTER_1				0x05
#define W25Q64_READ_STATUS_REGISTER_2				0x35
#define W25Q64_WRITE_STATUS_REGISTER				0x01
#define W25Q64_PAGE_PROGRAM							0x02
#define W25Q64_QUAD_PAGE_PROGRAM					0x32
#define W25Q64_BLOCK_ERASE_64KB						0xD8
#define W25Q64_BLOCK_ERASE_32KB						0x52
#define W25Q64_SECTOR_ERASE_4KB						0x20
#define W25Q64_CHIP_ERASE							0xC7
#define W25Q64_ERASE_SUSPEND						0x75
#define W25Q64_ERASE_RESUME							0x7A
#define W25Q64_POWER_DOWN							0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE				0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET			0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID		0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID				0x90
#define W25Q64_READ_UNIQUE_ID						0x4B
#define W25Q64_JEDEC_ID								0x9F
#define W25Q64_READ_DATA							0x03
#define W25Q64_FAST_READ							0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT				0x3B
#define W25Q64_FAST_READ_DUAL_IO					0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT				0x6B
#define W25Q64_FAST_READ_QUAD_IO					0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO				0xE3

#define W25Q64_DUMMY_BYTE							0xFF

#endif

5.6 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)
	{
		
	}
}

标签:void,MySPI,W25Q64,STM32,SPI,GPIO,define
From: https://blog.csdn.net/TENET123/article/details/139758478

相关文章

  • STM32通过SPI软件读写W25Q64
    文章目录1.W25Q642.硬件电路3. W25Q64框架图4. 软件/硬件波形对比5.代码实现5.1MyI2C.c5.2 MyI2C.h5.3W25Q64.c5.4 W25Q64.h5.5 W25Q64_Ins.h5.6main.c1.W25Q64对于SPI通信和W25Q64的详细解析可以看下面这篇文章STM32单片机SPI通信详解-CSDN博客......
  • STM32项目分享:智能家居语音系统(升级版)
    目录一、前言二、项目简介1.功能详解2.主要器件三、原理图设计四、PCB硬件设计1.PCB图 ​​编辑​​编辑2.PCB板打样焊接图五、程序设计 六、实验效果 七、资料内容项目分享一、前言项目成品图片:哔哩哔哩视频链接:https://www.bilibili.com/video/BV1wi42......
  • STM32互补PWM模拟(带有Dead Time)
    目录前言一、时基单元基本概念二、时基单元CNT计数模式三、互补PWM模拟1.中央对其计数模式2.引脚模式选择3.输出结果总结前言本文介绍了互补PWM的概念,并且能够使用通用定时器和高级定时器模拟出互补PWM的波形,并且能够调控输出波形占空比和死区时间(DeadTime)。一......
  • 通讯协议大全(UART,RS485,SPI,IIC)
    参考自: 常见的通讯协议总结(USART、IIC、SPI、485、CAN)-CSDN博客UART那么好用,为什么单片机还需要I2C和SPI?_哔哩哔哩_bilibili5分钟看懂!串口RS232RS485最本质的区别!_哔哩哔哩_bilibili喜欢几位博主老师老师的还请看原贴/原视频数据通信 数据通信是指通过某种传......
  • stm32 sai 接口调试总结
    一概念STM32的新片子的音频外设叫SAI了,S就是Serial,A就是Audio,I就是Interface,还是挺好记的.究竟新在哪里,就是支持音频各种了,但是实际上用到的,基本上还都是I2S居多。这里给出I2S的stm32配置做一个总结。二参数配置1.接口和时钟配置图: 2.源码解析......
  • 基于STM32单片机的智能垃圾桶垃圾分类语音识别控制系统(实物+所有资料)
        随着社会科学技术的飞速发展,人们的生活质量和速度也在不断提高。大多数传统的家用垃圾桶已经过时且缺乏新颖性,并且缺乏人性化设计。使用起来既不方便也不卫生,并且所有的生活垃圾和废物垃圾都被均匀地装载,没有经过仔细的分类。随之而来的是,清洁工的任务量正以几何速......
  • [题解]AT_abc236_f [ABC236F] Spices
    思路首先对所有的\(c\)从小到大排序,然后对于每一个值如果之前能凑出就不选,否则就选。这样做显然是对的。令\(p_1,p_2,\dots,p_{2^n-1}\)表示将\(c\)排序之后,对应原来的下标;\(S\)表示选出数的集合;\(S'\)表示最终选出数的集合。可以证明两个问题:如果\(p_i\)可以被已选......
  • 【单片机毕业设计选题24019】-基于STM32的安防监测灭火系统
    系统功能:1.水泵喷水灭火功能:当火焰传感器监测到火焰时,蜂鸣器报警,水泵工作实现灭火。2.风扇功能:当烟雾传感器检测到CO或温度传感器检测到温度超过阈值时,蜂鸣器报警,启动风扇进行驱散烟雾或降温。3.防盗功能介绍:采用红外传感器,当有人靠近时就会报警。4.通过蓝牙实现在......
  • 【单片机毕业设计选题24018】-基于STM32和阿里云的农业大棚系统
    系统功能:系统分为手动和自动模式,上电默认为自动模式,自动模式下系统根据采集到的传感器值自动控制,温度过低后自动开启加热,湿度过高后自动开启通风,光照过低后自动开启补光,水位过低后自动开启水泵补水。手动模式下可以手动控制加热/除湿/补光/补水。主要功能模块原理图:......
  • STM32三种调试工具CMSIS-DAP、J-Link和ST-Link
    一.概述CMSIS-DAP、J-Link和ST-Link均是嵌入式处理器的开发调试工具。CMSIS-DAP是一种轻量级调试接口,旨在实现开源的开发调试。它的优点是使用方便、通用性好、成本低,还支持固件的在线升级。J-Link是一款由德国公司SEGGERMicrocontroller开发的高性能调试工具。但是价格较......