软件SPI读写W25Q64硬件SPI读写W25Q64

news2024/10/6 20:29:46

目录

软件SPI读写W25Q64 

MySPI

W25Q64

主函数

硬件SPI读写W25Q64


软件SPI读写W25Q64 

  • 程序整体框架:

SPI模块包含通信引脚封装,初始化,SPI三个基本时序单元(起始,终止,交换一个字节)

W25Q64通过调用SPI基本时序完成整个时序(写使能,页编程,擦除,读数据)

主函数中调用W25Q64的硬件驱动层函数时序功能

MySPI

#include "stm32f10x.h"                  // Device header

void MySPI_W_CS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);
}
void MySPI_W_CLK(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)BitValue);
}
void MySPI_W_MOSI(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA,GPIO_Pin_7,(BitAction)BitValue);
}
uint8_t MySPI_R_MISO(void)
{
	return GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6);
}

void MySPI_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_Init_Structure;
	//要求配置为推挽输出,浮空或上拉输入
	GPIO_Init_Structure.GPIO_Mode=GPIO_Mode_IPU;
	GPIO_Init_Structure.GPIO_Pin=GPIO_Pin_6;
	GPIO_Init_Structure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_Init_Structure);
	
	GPIO_Init_Structure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_Init_Structure.GPIO_Pin=GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_7;
	GPIO_Init_Structure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_Init_Structure);

	MySPI_W_CS(1);
	MySPI_W_CLK(0);
}
//三个基本时序:起始,交换数据,终止
void MySPI_Start(void)
{
	MySPI_W_CS(0);
}
void MySPI_Stop(void)
{
	MySPI_W_CS(1);
}
//通过掩码,依次挑出每一位操作,优点是保留了原数据的值
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	uint8_t i,ByteReceive = 0x00;
	for(i = 0;i < 8;i ++)
	{
		MySPI_W_MOSI(ByteSend &(0x80>>i));
		MySPI_W_CLK(1);
		if(MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}
		MySPI_W_CLK(0);
	}
	return ByteReceive;
}

//还可以按照移位示意图中的方式交换数据,优点是效率高,但不能保存原数据值
//uint8_t MySPI_SwapByte(uint8_t ByteSend)
//{
//	 uint8_t i;
//   for(i=0;i<8;i ++)
//    {
//    	MySPI_W_MOSI(ByteSend &0x80);
//		ByteSend <<=1;			//最高位移出,最后补0
//    	MySPI_W_CLK(1);
//    	if(MySPI_R_MISO() == 1) {ByteSend |= 0x01;}	 //输入的数据放在最低位
//    	MySPI_W_CLK(0);
//    }
//    return ByteSend;
//}

W25Q64

在注意事项中:写入操作后,芯片进入忙状态。这里通过定义的函数进行事前等待和事后等待。

事前等待:1、写入前先等待,等不忙了再写入   2、效率高。  3、在写入和读取操作之前都要等待。

事后等待:1、写入后立刻等待,不忙了退出。  2、这样最保险,函数结束后,函数之外的地方芯片肯定不忙。  3、只需在写入后等待。

#include "stm32f10x.h"                  // Device header
#include "MySPI.H"
#include "W25Q64_INS.H"

void W25Q64_Init(void)
{
	MySPI_Init();
}
//用指针的方式来获取多个函数的值!!!
void W25Q64_ReadID(uint8_t *MID,uint16_t *DID)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_JEDEC_ID);
	*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	*DID <<= 8;
	*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	MySPI_Stop();
}
void W25Q64_WiteEnable(void)
{
	MySPI_Start();
	MySPI_SwapByte(W25Q64_WRITE_ENABLE);
	MySPI_Stop();
}
/**
  * @brief 直到BUSY清零后结束
  * @param  
  * @retval 
  */
void W25Q64_WaitBusy(void)
{
	uint32_t Timeout=100000;  //为了防止卡死
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
	
	//直到busy不为1
	while((MySPI_SwapByte(0xFF) & 0X01) == 0X01)
	{
		Timeout--;
		if(Timeout == 0)
		{
			break;
		}
	}
	MySPI_Stop();
}

//页编程写入,注意页编程写入一页的范围
void W25Q64_PageProgram(uint32_t Address,uint8_t *DataArray,uint16_t count)	
{
	//没有24位,通过数组可以传多个字节。所以用32位,写入数据的数量范围0-256,所以用uint16不用uint8
	W25Q64_WiteEnable();
	
	uint16_t i;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);	//高两位会丢弃
	MySPI_SwapByte(Address);
	for(i=0;i < count ;i++)
	{
		MySPI_SwapByte(DataArray[i]);
	}
	MySPI_Stop();
	
	W25Q64_WaitBusy();
}

void W25Q64_SectorErase(uint32_t Address)	
{
	//写使能仅对之后跟随的一条时序有效,结束之后会失能,所以每个函数加入这个就不用再写失能
	W25Q64_WiteEnable();
	
	MySPI_Start();
	MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);	
	MySPI_SwapByte(Address);
	MySPI_Stop();

	W25Q64_WaitBusy();
}

void W25Q64_ReadData(uint32_t Address,uint8_t *DataArray,uint32_t count)	//改为32位
{	
	uint32_t i;
	MySPI_Start();
	MySPI_SwapByte(W25Q64_READ_DATA);
	MySPI_SwapByte(Address >> 16);
	MySPI_SwapByte(Address >> 8);	
	MySPI_SwapByte(Address);
	for(i=0;i < count ;i++)
	{
		//调用交换读取之后,内部指针自动自增
		DataArray[i]=MySPI_SwapByte(W25Q64_DUMMY_BYTE);
	}
	MySPI_Stop();
}

主函数

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

uint8_t MID;
uint16_t DID;

uint8_t ArrayWrite[]={0x55,0x66,0x77,0x88};
uint8_t ArrayRead[4];
int main(void)
{
	OLED_Init();
	W25Q64_Init();
	OLED_ShowString(1,1,"MID:   DID:");
	OLED_ShowString(2,1,"W:");
	OLED_ShowString(3,1,"R:");

	W25Q64_ReadID(&MID,&DID);
	OLED_ShowHexNum(1,5,MID,2);
	OLED_ShowHexNum(1,12,DID,4);
	
	//只擦除不写入,可以验证flash擦除之后变为ff
	//不擦除直接改写,可以测试不能由0到1,只能1到0

    //写之前先擦除。xxx000-xxffff
	W25Q64_SectorErase(0x000000); 	
	
	//页地址范围xxxx00-xxxxff
	W25Q64_PageProgram(0X000000,ArrayWrite,4); 
	
	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)
	{

	}
}
  • 验证不能跨页写入时,

W25Q64_PageProgram(0X0000ff,ArrayWrite,4); 
W25Q64_ReadData(0X000000,ArrayRead,4);

因为读可以跨页,此时读到的结果为 55 FF FF FF

改为这句W25Q64_ReadData(0X000000,ArrayRead,4);结果如下。说明不能跨页存储,超过范围会从页开始存储。

硬件SPI读写W25Q64

只需要修改底层MySPI文件,将软件实现改为硬件实现,之后基于通信层的业务代码不用改变

转移到移位寄存器后会自动生成波形,硬件自动生成不需要像软件那样置高低电平

#include "stm32f10x.h"                  // Device header

void MySPI_W_CS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);
}

void MySPI_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);

	GPIO_InitTypeDef GPIO_Init_Structure;
	//硬件外设控制的输出MOSI,SCK;用复用推挽输出
	GPIO_Init_Structure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_Init_Structure.GPIO_Pin=GPIO_Pin_5 | GPIO_Pin_7;
	GPIO_Init_Structure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_Init_Structure);
	//软件控制的输出信号ss,通用推挽输出
	GPIO_Init_Structure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_Init_Structure.GPIO_Pin=GPIO_Pin_4;
	GPIO_Init_Structure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_Init_Structure);
	//MISO
	GPIO_Init_Structure.GPIO_Mode=GPIO_Mode_IPU;
	GPIO_Init_Structure.GPIO_Pin=GPIO_Pin_6;
	GPIO_Init_Structure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_Init_Structure);
	
	SPI_InitTypeDef SPI_InitStrcture;
	SPI_InitStrcture.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_128;
	//配置模式
	SPI_InitStrcture.SPI_CPHA=SPI_CPHA_1Edge;		//第一个边沿采样
	SPI_InitStrcture.SPI_CPOL=SPI_CPOL_Low; 
	SPI_InitStrcture.SPI_CRCPolynomial=7;
	
	SPI_InitStrcture.SPI_DataSize=SPI_DataSize_8b;
	SPI_InitStrcture.SPI_Direction=SPI_Direction_2Lines_FullDuplex;
	
	SPI_InitStrcture.SPI_FirstBit=SPI_FirstBit_MSB;    //高位先行
	SPI_InitStrcture.SPI_Mode=SPI_Mode_Master;		//主机模式
	SPI_InitStrcture.SPI_NSS=SPI_NSS_Soft;
	
	SPI_Init(SPI1,&SPI_InitStrcture);
	
	//不要忘记开使能
	SPI_Cmd(SPI1,ENABLE);
	
	//默认不选择从机
	MySPI_W_CS(1);

}
//三个基本时序:起始,交换数据,终止
void MySPI_Start(void)
{
	MySPI_W_CS(0);
}
void MySPI_Stop(void)
{
	MySPI_W_CS(1);
}

//四步
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);
	
}

TXE RXNE标志位不用手动清除,中断标志位进中断后必须手动清除 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/679931.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

matlab实现语音信号的频域分析及应用

1.语音信号本质上是非平稳信号。但我们可以假设语音信号在一个短时间内是平稳的&#xff0c;这样我们用稳态分析方法处理非平稳信号。应用在傅立叶分析就是短时傅立叶变换。 语音的频域分析&#xff1a;包括语音信号的频谱、功率谱、倒频谱、频谱包络等. 常用频域分析方法&am…

排序算法之堆排序_20230624

排序算法之堆排序 前言 堆排序是基于比较排序的一类算法&#xff0c;算法重复利用堆(Binary heap)的特性&#xff0c;最大&#xff08;最小&#xff09;元素一定位于堆顶的位置&#xff0c;那么就可以提取堆顶元素&#xff0c;放置在数组的尾部位置&#xff0c;后再把剩余的元…

设计模式之状态模式笔记

设计模式之状态模式笔记 说明State(状态)目录状态模式示例类图抽象状态类环境角色类电梯开启状态类电梯关闭状态类电梯运行状态类电梯停止状态类测试类 说明 记录下学习设计模式-状态模式的写法。JDK使用版本为1.8版本。 State(状态) 意图:允许一个对象在其内部状态改变时改…

从零搭建一台基于ROS的自动驾驶车-----4.定位

系列文章目录 北科天绘 16线3维激光雷达开发教程 基于Rplidar二维雷达使用Hector_SLAM算法在ROS中建图 Nvidia Jetson Nano学习笔记–串口通信 Nvidia Jetson Nano学习笔记–使用C语言实现GPIO 输入输出 Autolabor ROS机器人教程 从零搭建一台基于ROS的自动驾驶车-----1.整体介…

【Leetcode60天带刷】day25回溯算法——216.组合总和III,17.电话号码的字母组合

​ 题目&#xff1a; 216. 组合总和 III 找出所有相加之和为 n 的 k 个数的组合&#xff0c;且满足下列条件&#xff1a; 只使用数字1到9每个数字 最多使用一次 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次&#xff0c;组合可以以任何顺序返回。 示例 1…

读书笔记--数据治理之术

继延续上一篇文章&#xff0c;对数据治理之术进行学习思考&#xff0c;这部分内容是本书整体内容的核心细节&#xff0c;内容比较多比较杂&#xff0c;通读了好长时间才动手总结整理&#xff0c;因此更新的慢了一些。数据治理之术是操作层面的技术或方法&#xff0c;数据治理相…

linux系统如何添加硬盘设备

前言&#xff1a; 今天记录一下硬盘方面的知识&#xff0c;主要讲一下分区、挂载方面的知识&#xff0c;心情太郁闷了&#xff0c;假期的最后一天。 1、硬盘的命名规则 现在的硬盘设备一般都会以“/dev/sd”开头&#xff0c;而一台主机上可以有多块硬盘设备&#xff0c;因此系…

6.24全球央行鹰派立场重现,下周黄金是否会继续下跌?

近期有哪些消息面影响黄金走势&#xff1f;下周黄金多空该如何研判&#xff1f; ​黄金消息面解析&#xff1a;周五&#xff08;6月23日&#xff09;美市尾盘&#xff0c;现货黄金收报1920.44美元/盎司&#xff0c;上升6.58美元或0.34%&#xff0c;日内最高触及1937.46美元/盎…

Linux基础服务3——samba

文章目录 一、基本了解1.1 服务安装1.2 服务进程和端口1.3 samba用户1.4 配置文件1.4.1 主配置文件1.4.2 配置文件参数 1.5 安全级别 二、访问samba2.1 参数测试2.2 交互式访问2.3 挂载访问2.3.1 临时挂载2.3.2 永久挂载 2.4 配置用户认证共享2.5 匿名共享 一、基本了解 什么是…

VS Code基于服务器中的docker的开发环境配置

VS Code基于服务器中的docker的开发环境配置 基于Dev Containers插件基于Jump Machine&#xff08;跳板机&#xff09;服务器通过ssh连接docker容器VS Code配置ssh config文件连接docker容器 基于Dev Containers插件 当然可以在vscode中直接下载Dev Containers插件&#xff0c…

表上作业法一般流程(最小元素法、闭合回路法、位势法)

目录 一、列出物资调运平衡表和运价表 二、编制初始调运方案 三、初始方案的检验与调整 1&#xff09;闭合回路法 2&#xff09;位势法 3&#xff09;调整调运方案 表上作业法一般步骤&#xff1a; ①列出调运物资的供需(产销)平衡表及运价表&#xff1b; ②按最小元素…

mediapipe 谷歌高效ML框架-图像识别、人脸检测、人体关键点检测、手部关键点检测

参考&#xff1a; https://github.com/google/mediapipe https://developers.google.com/mediapipe/solutions/guide 框架也支持cv、nlp、audio等项目&#xff0c;速度很快&#xff1a; 1、图形识别 参考&#xff1a;https://developers.google.com/mediapipe/solutions/vi…

05.内存管理:动态申请和释放内存

动态分配内存&#xff0c;进行内存管理 参考: 伙伴算法原理简介 linux 0.11源码 本文主要针对Linux0.11的malloc和free进行分析。是一种类似伙伴系统的内存管理方法&#xff0c;不过伙伴系统的内存通常是申请大于一页的内存&#xff0c;但是在该内核版本的内存管理&#xff0c…

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先 / LeetCode 235. 二叉搜索树的最近公共祖先(二叉搜索树性质,搜索与回溯)

题目&#xff1a; 链接&#xff1a;剑指 Offer 68 - I. 二叉搜索树的最近公共祖先&#xff1b;LeetCode 235. 二叉搜索树的最近公共祖先 难度&#xff1a;中等 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对…

redis存储原理与数据模型学习笔记

目录 1 redis线程模型1.1 线程组成1.2 redis命令处理是单线程 2 redis db 存储分析2.1 先了解代码server.hdict.h 2.2 从kv存储分析2.3 负载因子2.4 渐进式rehash机制数据访问scan 3 数据模型分析以zset为例跳表 1 redis线程模型 1.1 线程组成 redis-server 命令处理 网络事件…

Nerf论文前后介绍以及今后方向(2020年各个方向工作论文分析) NEURAL VOLUME RENDERING:NERF AND BEYOND

你好&#xff01; 这里是“出门吃三碗饭”本人&#xff0c; 本文章接下来将介绍2020年对Nerf工作的一篇总结论文NEURAL VOLUME RENDERING:NERF AND BEYOND&#xff0c;论文作者是佐治亚理工学院的Frank Dellaert同学和 MIT的Lin Yen-Chen同学&#xff0c;非常感谢两位大佬的总结…

序列化和反序列化的认识【protobuf、json、xml】

1. 什么是序列化与反序列化&#xff1f; 程序员在编写应用程序的时候往往需要将程序的某些数据存储在连续的内存中&#xff0c;然后将其写入文件或是将其传输到网络中的另一台计算机上以实现通讯。这个将程序数据转换成能被存储并传输的格式的过程被称为序列化&#xff08;seri…

C++ array (STL array) 序列容器

文章目录 1 类模板2 begin()/end() 和 cbegin()/cend()3 cbegin() 和 cend()4 rbegin()/rend() 和 crbegin()/crend()5 访问array容器中单个元素参考 1 类模板 array 容器以类模板的形式定义在 头文件&#xff0c;并位于命名空间 std 中&#xff0c;如下所示&#xff1a; nam…

一、约束编程简介(Constraints Programming)

文章目录 1、约束编程求解器2、值域传播器THE END 1、约束编程求解器 \qquad 约束编程是一种编程范式&#xff0c;旨在解决那些需要满足一系列约束条件的问题。在约束编程中&#xff0c;问题被定义为一组变量和约束条件&#xff0c;而不是指定解决问题的算法步骤。通过定义变量…

XXE漏洞复现步骤

0X00XXE注入定义 XXE注入&#xff0c;即XML External Entity&#xff0c;XML外部实体注入。通过 XML 实体&#xff0c;”SYSTEM”关键词导致 XML 解析器可以从本地文件或者远程 URI 中读取数据。所以攻击者可以通过 XML 实体传递自己构造的恶意值&#xff0c;是处理程序解析它…