首页 > 其他分享 >02_STM32软件+硬件SPI读写W25Q64(HAL库)

02_STM32软件+硬件SPI读写W25Q64(HAL库)

时间:2024-03-19 19:29:19浏览次数:14  
标签:02 HAL W25Q64 MySPI SPI SwapByte Address GPIO

目录

1、SPI简介

2、SPI时序单元

2.1 模式0(应用最多)

2.2 模式1

2.3 模式2

2.4 模式3

3、SPI移位示意图

4、简单软件SPI代码(HAL库)

5、简单硬件SPI读写W25Q64(HAL库)

6、例程下载

1、SPI简介

2、SPI时序单元

2.1 模式0(应用最多)

2.2 模式1

2.3 模式2

模式2与模式0类似,区别在于CPOL=1,SCK初始为高电平,其他完全一致

2.4 模式3

模式3与模式1类似,区别在于CPOL=1,SCK初始为高电平,其他完全一致

3、SPI移位示意图

4、简单软件SPI代码(HAL库)

①软件SPI

#include "stm32f1xx_hal.h"                  // Device header
#include "MySPI.h"
/*引脚配置层*/
#define SPI_SS_PORT GPIOB
#define SPI_SS_PIN GPIO_PIN_9

#define SPI_SCK_PORT GPIOA
#define SPI_SCK_PIN GPIO_PIN_5

#define SPI_MOSI_PORT GPIOA
#define SPI_MOSI_PIN GPIO_PIN_7

#define SPI_MISO_PORT GPIOA
#define SPI_MISO_PIN GPIO_PIN_6

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

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

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

/**
  * 函    数:I2C读MISO引脚电平
  * 参    数:无
  * 返 回 值:协议层需要得到的当前MISO的电平,范围0~1
  * 注意事项:此函数需要用户实现内容,当前MISO为低电平时,返回0,当前MISO为高电平时,返回1
  */
GPIO_PinState MySPI_R_MISO(void)
{
	return HAL_GPIO_ReadPin(SPI_MISO_PORT, 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;								//返回接收到的一个字节数据
}

 ②软件SPI读写W25Q64

#include "stm32f1xx_hal.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、简单硬件SPI读写W25Q64(HAL库)

#include "stm32f1xx_hal.h"                  // Device header
#include "W25Q64_Ins.h"
#include "W25Q64.h"
void SPI_Start(void)
{
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET);				//拉低SS,开始时序
}

void SPI_Stop(void)
{
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET);				//拉高SS,结束时序
}
										  
/**
  * 函    数:MPU6050读取ID号
  * 参    数:MID 工厂ID,使用输出参数的形式返回
  * 参    数:DID 设备ID,使用输出参数的形式返回
  * 返 回 值:无
  */
void W25Q64_ReadID(uint8_t* ID)
{
	uint8_t cmd[1] = {W25Q64_JEDEC_ID};
	SPI_Start();								//SPI起始
	//MySPI_SwapByte(W25Q64_JEDEC_ID);			//交换发送读取ID的指令
	HAL_SPI_Transmit(&hspi1, cmd, 1, 1000);
	//*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位,通过输出参数返回
	HAL_SPI_Receive(&hspi1, ID, 3, 1000);
	SPI_Stop();								//SPI终止
}


/**
  * 函    数:W25Q64写使能
  * 参    数:无
  * 返 回 值:无
  */
void W25Q64_WriteEnable(void)
{
	uint8_t cmd[1] = {W25Q64_WRITE_ENABLE};
	SPI_Start();								//SPI起始
	//MySPI_SwapByte(W25Q64_WRITE_ENABLE);		//交换发送写使能的指令
	HAL_SPI_Transmit(&hspi1, cmd, 1, 1000);
	SPI_Stop();								//SPI终止
}

/**
  * 函    数:W25Q64等待忙
  * 参    数:无
  * 返 回 值:无
  */
void W25Q64_WaitBusy(void)
{
	//uint32_t Timeout;
	uint8_t ret = 0;
	uint32_t Timeout = 10000;
	uint8_t cmd[1] = {W25Q64_READ_STATUS_REGISTER_1};
	SPI_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;								//跳出等待,不等了
	//	}
	//}
	HAL_SPI_Transmit(&hspi1, cmd, 1, 1000);
	do
	{
		HAL_SPI_Receive(&hspi1, &ret, 1, 1000);
		Timeout--;
		if(Timeout == 0)break;
	}while((ret & 0x01) == 0x01);
	SPI_Stop();								//SPI终止	
}

/**
  * 函    数:W25Q64页编程
  * 参    数:Address 页编程的起始地址,范围:0x000000~0x7FFFFF
  * 参    数:DataArray	用于写入数据的数组
  * 参    数:Count 要写入数据的数量,范围:0~256
  * 返 回 值:无
  * 注意事项:写入的地址范围不能跨页
  */
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
	uint8_t cmd[4] = {W25Q64_PAGE_PROGRAM, (uint8_t)Address>>16, (uint8_t)Address>>8, (uint8_t)Address};
	W25Q64_WriteEnable();						//写使能
	
	SPI_Start();								//SPI起始
	//MySPI_SwapByte(W25Q64_PAGE_PROGRAM);		//交换发送页编程的指令
	//MySPI_SwapByte(Address >> 16);				//交换发送地址23~16位
	//MySPI_SwapByte(Address >> 8);				//交换发送地址15~8位
	//MySPI_SwapByte(Address);					//交换发送地址7~0位
	HAL_SPI_Transmit(&hspi1, cmd, 4, 1000);
	//for (i = 0; i < Count; i ++)				//循环Count次
	//{
	//	MySPI_SwapByte(DataArray[i]);			//依次在起始地址后写入数据
	//}							//SPI终止
	HAL_SPI_Transmit(&hspi1, DataArray, Count, 1000);
	/* 嗯,我是彩笔,在这犯了个错
	下面两行的顺序不能反过来,因为等待忙和发送指令不能属于同一个SPI时序
	必须在等待忙之前停掉上一个SPI时序*/
	SPI_Stop();
	W25Q64_WaitBusy();							//等待忙
}

/**
  * 函    数:W25Q64扇区擦除(4KB)
  * 参    数:Address 指定扇区的地址,范围:0x000000~0x7FFFFF
  * 返 回 值:无
  */
void W25Q64_SectorErase(uint32_t Address)
{
	uint8_t cmd[4] = {W25Q64_SECTOR_ERASE_4KB, (uint8_t)Address>>16, (uint8_t)Address>>8, (uint8_t)Address};
	W25Q64_WriteEnable();						//写使能
	SPI_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位
	HAL_SPI_Transmit(&hspi1, cmd, 4, 1000);
	SPI_Stop();								//SPI终止
	W25Q64_WaitBusy();							//等待忙
}

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

6、例程下载

链接:https://pan.baidu.com/s/19sAyLrj8iq2VotlKLyIXSA?pwd=1145 
提取码:1145

标签:02,HAL,W25Q64,MySPI,SPI,SwapByte,Address,GPIO
From: https://blog.csdn.net/qq_62262213/article/details/136725863

相关文章

  • GTC 2024 开幕,英伟达发布新一代 GPU 架构;Apple ID 或将淘汰丨 RTE 开发者日报 Vol.168
       开发者朋友们大家好: 这里是「RTE开发者日报」,每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享RTE(RealTimeEngagement)领域内「有话题的新闻」、「有态度的观点」、「有意思的数据」、「有思考的文章」、「有看点的会议」,但内容仅代表编辑的个人......
  • GEE高阶应用——如何绘制2000-2022年土地利用变化轨迹时序图
    简介土地利用变化是指在一定时间范围内,土地利用类型和结构发生的变化。时序变化是指这种变化随时间的推移而发生的序列变化。土地利用变化轨迹的时序变化具体介绍如下:首先,土地利用变化轨迹的时序变化体现在土地利用类型的演变上。在过去的几十年里,随着人口的增加、经济的发展......
  • L2-028 秀恩爱分得快
    测试点会出现-0这种,直接导致无法使用int类型接收编号,因为无从判断性别,因此只能用string类型。并且需要使用sex来记录当前是男性还是女性,只有对方是异性的时候,亲密度才有意义。#include<bits/stdc++.h>usingnamespacestd;intp[1010][510],k[1010],sex[1010];doublereco......
  • 2024-3-19
    多任务级联通过级联(即顺序连接)不同的任务来改善整体模型性能。这种方法通常涉及将几个相关的任务组织成一个流水线,其中每个任务的输出都作为下一个任务的输入。多任务级联的核心思想是利用不同任务之间的内在联系和互补信息,以此来增强模型的泛化能力和提高特定任务的精度。......
  • 02-【K210】FPIO的API介绍和简单应用
    目录01概述02特性03接口04api接口验证01概述Fpioa(FieldProgrammableI/OArray)AllowsTheUserToMap256InternalFunctionsTo48FreeI/OsOnTheChip02特性•SupportForI/O’SProgrammableFunctionSelection•8DrivingCapabilityOptionsForI/OOutp......
  • 2024.03.19【文字排版】作为设计师 这三个功能不用还是尽量别用
    第一个功能-黑色加粗的“B”它是Bold的简写,可以通过这一功能将字体给加粗加大一号。可是实际上这个功能并不是把字体变成大一号,而是单纯的给字体加上一个外轮廓,这样不仅破坏了设计师原本的字形轮廓设计,可选粗细单一而且会使得字体变形,看着不美观也不自然所以大部分字库都会......
  • 020_若依框架集成MQTT
    目录什么是MQTT什么是EMQMQTTX下载使用下载使用若依集成MQTT拉取若依单应用版本pom.xml导入mqtt依赖yml文件配置mqtt拷贝mqtt相关代码MqttConfigPushCallbackMqttPushClient测试订阅接收消息测试发布消息订阅接收消息存入数据库什么是MQTTMQTT_百度百科什么是EMQEMQX_百度......
  • 记一次 HalconControl 无法正常显示埋下的坑 关于Shown,Load,警钟长鸣
    最近在写一个视觉软件demo(基于Halcon) 根据MEF框架下进行 后台代码的解耦。首推这一款框架,在自动化,运动控制,视觉领域可运用范围极广。首先简单介绍一下什么是MEF,MEF,全称ManagedExtensibilityFramework(托管可扩展框架)。单从名字我们不难发现:MEF是专门致力于解决扩展性问题......
  • 020_Windows快速搭建FTP服务器
    目录搭建FTP服务器,匿名访问安装FTP服务添加FTP站点测试本机上传下载测试上传下载局域网上传下载测试测试网络关闭防火墙上传浏览器测试搭建FTP服务器,用户访问安装FTP服务创建登录FTP服务器的用户名和密码添加FTP站点测试FTP服务ftp客户端工具配置FTP防火墙入站规则搭建FTP服务器......
  • 【专题】2024年中国物流地产市场趋势及展望报告合集PDF分享(附原数据表)
    原文链接:https://tecdat.cn/?p=35388原文出处:拓端数据部落公众号2023年,中国物流地产市场在压力之下呈现出波动的复苏态势,市场需求展现出结构性的变化特点。展望未来,物流地产市场将逐渐走向恢复,但不同区域市场之间的表现可能会更加分化。经济的新业态和新动能将为物流地产市场带......