STM32实现软件SPI对W25Q64内存芯片实现读写操作

先看看本次实验的成果吧:

这么简单的一个程序,我学习了一个星期左右,终于把所有的关节都打通了。所有代码都能什么都不看背着敲出来了。为了使自己的记忆更为清晰,特意总结了一个思维导图,感觉自己即便是日后忘记了看一遍思维导图也就知道怎么写了。特此展示一下吧!

STM32内部集成了硬件SPI收发电路,

可以由硬件自动执行时钟生成、数据收发等功能,

减轻CPU的负担 可配置8位/16位数据帧、高位先行/低位先行

时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256) 支持多主机模型、主或从操作

可精简为半双工/单工通信 支持DMA 兼容I2S协议

STM32F103C8T6 硬件SPI资源:SPI1、SPI2

这个图应该是从右往左看才对的,我个人感觉是这个样子的,因为我写程序是这个顺序的,下面我就把所有的程序展示一下吧:

根据时序图会更好更快的理解程序的逻辑,一切都是要最后芯片的时序来编程,否则就不能和芯片通讯了。

首先是SPI.c的文件:

#include "stm32f10x.h"                  // Device header#define GPIOX                      GPIOA                  //宏定义GPIOA,需要更改端口时只更改这里就好了
#define RCC_APB2Periph_GPIOX       RCC_APB2Periph_GPIOA   //宏定义时钟开启端口
#define MySPI_CS                   GPIO_Pin_4             //宏定义CS片选信号
#define MySPI_MOSI                 GPIO_Pin_7             //宏定义MOSI主机输出信号
#define MySPI_CLK                  GPIO_Pin_5             //宏定义时钟信号
#define MySPI_MISO                 GPIO_Pin_6             //宏定义主机输入信号void MySPI_W_CS(uint8_t BitValue)                                   //位操作片选信号
{GPIO_WriteBit(GPIOX, MySPI_CS, (BitAction)BitValue);
}void MySPI_W_MOSI(uint8_t BitValue)                                 //位操作主机输出信号
{GPIO_WriteBit(GPIOX, MySPI_MOSI, (BitAction)BitValue);
}void MySPI_W_CLK(uint8_t BitValue)                                  //位操作时钟信号
{GPIO_WriteBit(GPIOX, MySPI_CLK, (BitAction)BitValue);
}uint8_t MySPI_Read_MISO(void)                                        //读取主机输入信号
{return GPIO_ReadInputDataBit(GPIOX, MySPI_MISO);
}void MySPI_Init(void)                                                     //SPI初始化
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOX, ENABLE);                   //开启时钟GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStruct.GPIO_Pin = MySPI_CS | MySPI_MOSI | MySPI_CLK;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOX, &GPIO_InitStruct);                                   //初始化片选,主机输出,时钟三个信号为推挽输出模式GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStruct.GPIO_Pin = MySPI_MISO;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOX, &GPIO_InitStruct);                                //初始化主机输入信号为上拉输入模式MySPI_W_CS(1);                                                   //片选信号高电平,没有选中从机MySPI_W_CLK(0);                                                  //时钟低电平
}void MySPI_Start(void)                              //SPI开始函数
{MySPI_W_CS(0);  // 片选信号低电平,选中从机
}void MySPI_Stop(void)                              //SPI结束函数
{MySPI_W_CS(1);  //片选信号高电平,没有选中从机
}uint8_t MySPI_RW_Byte(uint8_t SendByte)           //SPI读写一个字节函数
{uint8_t i;for(i = 0; i < 8; i ++)                         //循环8次,一个字节8位,每次操作1位{MySPI_W_MOSI(SendByte & 0x80);                  // 主机输出信号写:发送数据的最高位SendByte <<= 1;                                 // 发送信号右移1位,下次循环到来时还是发送最高位MySPI_W_CLK(1);                                 //时钟信号高电平if(MySPI_Read_MISO() == 1){SendByte |= 0x01;}   // 如果接收到的从机信号为1:发送数据的最低位就写入1MySPI_W_CLK(0);                                 //时钟信号低电平}return SendByte;     //返回发送的数据:此时经过了八次从最低位往最高位写入读入的位值,此时就是收到的新的一个字节数据。
}

接着是SPI.h的文件:

#ifndef __MYSPI_H
#define __MYSPI_Hvoid MySPI_Init(void);                        //SPI初始化void MySPI_Start(void);                       //SPI开始void MySPI_Stop(void);                        //SPI结束uint8_t MySPI_RW_Byte(uint8_t SendByte);      //SPI读写一个字节#endif

写入操作时:

写入操作前,必须先进行写使能 每个数据位只能由1改写为0,不能由0改写为1

写入数据前必须先擦除,擦除后,所有数据位变为1 擦除必须按最小擦除单元进行

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

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

读取操作时:

直接调用读取时序,无需使能,无需额外操作,没有页的限制,

读取操作结束后不会进入忙状态,但不能在忙状态时读取

接着是W25Q64.c的文件:

#include "stm32f10x.h"                  // Device header
#include "MySPI.h" 
#include "W25Q64_Int.h" void W25Q64_Init(void)                                               //W25Q64初始化
{MySPI_Init();                                                               //SPI初始化
}void W25Q64_Read_ID(uint8_t *MID, uint16_t *DID)                     //W25Q64 读取ID号
{MySPI_Start();                                   //SPI开始MySPI_RW_Byte(W25Q64_JEDEC_ID);                  //SPI写命令*MID = MySPI_RW_Byte(W25Q64_DUMMY_BYTE);         //SPI读取从机发过来的第一个字节就是MID号*DID = MySPI_RW_Byte(W25Q64_DUMMY_BYTE);         //SPI读取从机发过来的第二个字节就是DID的高8位*DID <<= 8;                                      //上面读到的是高8位所以向右移动8位就是高8位了*DID |= MySPI_RW_Byte(W25Q64_DUMMY_BYTE);        //低8位再写入从机发过来的第三个字节MySPI_Stop();                                    //SPI停止
}void W25Q64_WaitBusy(void)                                     //W25Q64 等待忙函数
{uint32_t Timeout = 100000;                                          //定义一个超时变量MySPI_Start();                                                      //SPI开始MySPI_RW_Byte(W25Q64_READ_STATUS_REGISTER_1);                       //读取忙标志位命令while((MySPI_RW_Byte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)            //如果读取到的标志位最后1为是1就循环{Timeout --;                                                         //超时变量自减if(Timeout == 0)                                                    //如果超时变量为0了,while循环还没有结束{MySPI_Stop();                                                   //SPI停止break;                                                          //退出while循环(防止在While循环中出不去)}}MySPI_Stop();                                                       //SPI停止
}void W25Q64_WriteENABLE(void)                                                  //W25Q64写使能
{MySPI_Start();                                                                  //SPI开始MySPI_RW_Byte(W25Q64_WRITE_ENABLE);                                             //W25Q64写使能命令MySPI_Stop();                                                                   //SPI停止
}void W25Q64_Write_Data(uint32_t Address, uint8_t *DataArry, uint16_t Len)    //W25Q64写数据(参数:32位的地址,8位的数组,要写入数据的长度)
{uint16_t i;                                            //定义16位的i,循环用,一页可以写256个字节,8位的画最大255,差一个不够,所以用16位的W25Q64_WriteENABLE();                                  //W25Q64写使能(W25Q64需要写入数据前的必须操作)MySPI_Start();                                          //SPI开始MySPI_RW_Byte(W25Q64_PAGE_PROGRAM);                     //SPI写入操作页的命令MySPI_RW_Byte(Address >> 16);                           //SPI写入24位地址的高8位MySPI_RW_Byte(Address >> 8);                            //SPI写入24位地址的中间8位MySPI_RW_Byte(Address);                                 //SPI写入24位地址的低8位for(i = 0; i < Len; i ++)                               //循环 写入数据的长度 次{MySPI_RW_Byte(DataArry[i]);                             //SPI写入 写入数组的(从低到高)单个字节     }MySPI_Stop();                                           //SPI停止W25Q64_WaitBusy();                                      //W25Q64等待忙完
}void W25Q64_SectorErase(uint32_t Address)                        // W25Q64擦除数据
{W25Q64_WriteENABLE();                                       //W25Q64写使能(W25Q64需要写入数据前的必须操作)MySPI_Start();                                                    //SPI开始MySPI_RW_Byte(W25Q64_SECTOR_ERASE_4KB);                           //SPI写入擦除页的命令MySPI_RW_Byte(Address >> 16);                                     //SPI写入24位地址的高8位MySPI_RW_Byte(Address >> 8);                                      //SPI写入24位地址的中间8位MySPI_RW_Byte(Address);                                           //SPI写入24位地址的低8位MySPI_Stop();                                                     //SPI停止W25Q64_WaitBusy();                                                //W25Q64等待忙完
}void W25Q64_ReadData(uint32_t Address, uint8_t *DataArry, uint32_t Len)   // W25Q64读取数据
{uint32_t i;                                            // 定义读取数据的长度变量(读取不受页的空间限制,读多少地址都会自增) 所以i的容量要大   MySPI_Start();                                          //SPI开始    MySPI_RW_Byte(W25Q64_READ_DATA);                        //SPI写入读取的命令MySPI_RW_Byte(Address >> 16);                           //SPI写入24位地址的高8位MySPI_RW_Byte(Address >> 8);                            //SPI写入24位地址的中间8位 MySPI_RW_Byte(Address);                                 //SPI写入24位地址的低8位for(i = 0; i < Len; i ++)                               //循环  要读取的字节数  次{DataArry[i] = MySPI_RW_Byte(W25Q64_DUMMY_BYTE);        //数组的第i位赋值为 W25Q64传过来的字节}MySPI_Stop();                                             //SPI停止 
}                                                             //由于读完自动清除忙标志位,所以读完不用等待忙完

然后是W25Q64.h的文件:

#ifndef __W25Q64_H
#define __W25Q64_Hvoid W25Q64_Init(void);void W25Q64_Read_ID(uint8_t *MID, uint16_t *DID);void W25Q64_Write_Data(uint32_t Address, uint8_t *DataArry, uint16_t Len);void W25Q64_SectorErase(uint32_t Address);void W25Q64_ReadData(uint32_t Address, uint8_t *DataArry, uint16_t Len);#endif

最后就是main.c的主函数文件了:

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "W25Q64.h"uint8_t MID;
uint16_t DID;uint8_t DataW[] = {0x55, 0x66, 0x77, 0x88};
uint8_t DataR[4];int main(void)
{OLED_Init();       //oled  屏幕初始化W25Q64_Init();     //W25Q64初始化OLED_ShowString(1,1, "MID:   DID:"); //OLED第一行第一列显示字符串W25Q64_Read_ID(&MID, &DID);           // W25Q64 读IDOLED_ShowHexNum(1,5, MID, 2);        //OLED显示MIDOLED_ShowHexNum(1,13, DID, 4);       //OLED显示DIDOLED_ShowString(2,1, "W:");          // OLED显示要写入的数据OLED_ShowString(3,1, "R:");          // OLED显示读出的数据W25Q64_SectorErase(0x000100);          //W25Q64擦除地址为0x000100开始的一页的数据W25Q64_Write_Data(0x000100, DataW, 4); //W25Q64从地址为0x000100的地方开始写入数据,共写入4个字节W25Q64_ReadData(0x000100, DataR, 4);   //W25Q64从地址为0x000100的地方开始读数据,共读出4个字节的数据OLED_ShowHexNum(3,3, DataR[0], 2);      //OLED显示读出的数据0OLED_ShowHexNum(3,6, DataR[1], 2);      //OLED显示读出的数据1OLED_ShowHexNum(3,9, DataR[2], 2);      //OLED显示读出的数据2OLED_ShowHexNum(3,12, DataR[3], 2);     //OLED显示读出的数据3OLED_ShowHexNum(2,3, DataW[0], 2);      //OLED显示要写入的数据0OLED_ShowHexNum(2,6, DataW[1], 2);      //OLED显示要写入的数据1OLED_ShowHexNum(2,9, DataW[2], 2);      //OLED显示要写入的数据2OLED_ShowHexNum(2,12, DataW[3], 2);     //OLED显示要写入的数据3while(1){}
}

还有一个W25Q64芯片的命令文件W25Q64_Int.h:里面放的是写好的命令和值的对应关系都做好了宏定义:

#ifndef __W25Q64_INT_H
#define __W25Q64_INT_H#define W25Q64_WRITE_ENABLE							0x06   //写使能命令值
#define W25Q64_WRITE_DISABLE						0x04   //写失能命令值
#define W25Q64_READ_STATUS_REGISTER_1				0x05   //读芯片忙状态寄存器,最后一位为1,代表忙
#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   //擦除4KB命令
#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    //读ID命令
#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

然后根据我的文件架构来布置代码编译后就能实现对W25Q64存储芯片的读写了:

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

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

相关文章

机器学习的15个概念

机器学习 有监督学习 有监督学习是利用训练数据集进行预测的机器学习任务。有监督学习可以分为分类和回归。回归用于预测“价格”“温度”或“距离”等连续值&#xff0c;而分类用于预测“是”或“否”、“垃圾邮件”或“非垃圾邮件”、“恶性”或“良性”等类别。 分类包含…

如何保护IP地址?安全匿名上网的方法

当互联网成为每个家庭的重要组成部分后&#xff0c;IP地址就成了你的虚拟地址。您的请求从该地址开始&#xff0c;然后 Internet 将消息发送回该地址。那么&#xff0c;您担心您的地址被泄露吗&#xff1f; 对于安全意识高或者某些业务需求的用户&#xff0c;如果您正在寻找保护…

C++ 静态库与动态库的生成和使用:基于 VS Studio 生成 newmat 矩阵库的静态库与动态库

文章目录 Part.I IntroductionChap.I 预备知识Chap.II 静态库与动态库区分 Part.II 静态库的生成与使用 (newmat)Chap.I 生成静态库Chap.II 使用静态库 Part.III 动态库的生成与使用 (newmat)Chap.I 生成动态库Chap.II 使用动态库 Part.IV 文件内容Chap.I test.cpp (静态库)Cha…

Hadoop Yarn

首先先从Yarn开始讲起&#xff0c;Yarn是Hadoop架构的资源管理器&#xff0c;可以管理mapreduce程序的资源分配和任务调度。 Yarn主要有ResourceManager、NodeManage、ApplicationMaster&#xff0c;Container ResourceMange负责管理全局的资源 NodeManage&#xff08;NM&a…

九河云:在AWS上实现跨region VPC互联

如何跨region实现不同VPC之间的对等链接&#xff1f;九河云为您介绍AWS跨region连接方案。 说明&#xff1a;VPC-A位于弗吉尼亚region&#xff0c;VPC-B位于俄勒冈region 本文将在同一账户的弗吉尼亚和俄勒冈VPC中各启用一台EC2&#xff08;本文已提前创建好VPC、EC2等资源&am…

Spring Boot中前端通过请求接口下载后端存放的Excel模板

导出工具类 package com.yutu.garden.utils;import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; import org.apache.commons.io.IOUtils; import org.apache.poi.hssf.util.HSSFColor; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.slf4j.Logger;…

云计算的安全需求

目录 一、概述 二、云安全服务基本能力要求 三、信息安全服务&#xff08;云计算安全类&#xff09;资质要求 3.1 概述 3.2 资质要求内容 3.2.1 组织与管理要求 3.2.2 技术能力要求 四、云安全主要合规要求 4.1 安全管理机构部门的建立 4.2 安全管理规范计划的编制 4…

C++ //练习 11.3 编写你自己的单词计数程序。

C Primer&#xff08;第5版&#xff09; 练习 11.3 练习 11.3 编写你自己的单词计数程序。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 /*************************************************************************> …

2024最新AI创作系统ChatGPT源码+Ai绘画网站源码,GPTs应用、AI换脸、插件系统、GPT文档分析、GPT语音对话一站式解决方案

一、前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持GPT…

记 log4j-over-slf4j.jar AND bound slf4j-log4j12.jar jar包冲突问题

报错信息如下 SLF4J: Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the class path, preempting StackOverflowError. SLF4J: See also http://www.slf4j.org/codes.html#log4jDelegationLoop for more details. Exception in thread “main” java.lan…

NineData云原生智能数据管理平台新功能发布|2024年3月版

数据库 DevOps - 大功能升级 SQL 开发早期主要提供 SQL 窗口&#xff08;IDE&#xff09;功能&#xff0c;在产品经过将近两年时间的打磨&#xff0c;新增了大量的企业级功能&#xff0c;已经服务了上万开发者&#xff0c;覆盖了数据库设计、开发、测试、变更等生命周期的功能…

神经网络与深度学习(二)

一、深度学习平台 张量&#xff08;Tensor&#xff09; 是一个物理量&#xff0c;对高维(维数 ≥ 2) 的物理量进行“量纲分析” 的一种工具。简单的可以理解为&#xff1a;一维数组称为矢量&#xff0c;二维数组为二阶张量&#xff0c;三维数组为三阶张量 计算图 用“结点”…

调用飞书获取用户Id接口成功,但是没有返回相应数据

原因&#xff1a; 该自建应用没有开放相应的数据权限。 解决办法&#xff1a; 在此处配置即可。

DETR【Transformer+目标检测】

End-to-End Object Detection with Transformers 2024 NVIDIA GTC&#xff0c;发布了地表最强的GPU B200&#xff0c;同时&#xff0c;黄仁勋对谈《Attention is All You Need》论文其中的7位作者&#xff0c;座谈的目的无非就是诉说&#xff0c;Transformer才是今天人工智能成…

【环境变量】命令行参数 | 概念 | 理解 | 命令行参数表 | bash进程

目录 四组概念 命令行参数概念&理解 查看命令函参数 命令行字符串&命令行参数表 命令行参数存在的意义 谁形成的命令行参数 父进程&子进程&数据段 bash进程 最近有点小忙&#xff0c;可能更新比较慢。 四组概念 竞争性: 系统进程数目众多&#xff0c…

构建企业级微服务平台:实现可扩展性、弹性和高效性

在软件开发的快速发展领域中&#xff0c;企业不断努力构建健壮、可扩展和高效的系统。随着微服务架构的出现&#xff0c;再加上云原生技术的应用&#xff0c;创建敏捷且具有弹性的平台的可能性是无限的。在本指南中&#xff0c;我们将深入探讨使用强大的工具和技术组合&#xf…

Python基于深度学习的人脸识别项目源码+演示视频,利用OpenCV进行人脸检测与识别 preview

​ 一、原理介绍 该人脸识别实例是一个基于深度学习和计算机视觉技术的应用&#xff0c;主要利用OpenCV和Python作为开发工具。系统采用了一系列算法和技术&#xff0c;其中包括以下几个关键步骤&#xff1a; 图像预处理&#xff1a;首先&#xff0c;对输入图像进行预处理&am…

.Net Core/.Net6/.Net8 ,启动配置/Program.cs 配置

.Net Core/.Net6/.Net8 &#xff0c;启动配置/Program.cs 配置 没有废话&#xff0c;直接上代码调用 没有废话&#xff0c;直接上代码 /// <summary>/// 启动类/// </summary>public static class Mains{static IServiceCollection _services;static IMvcBuilder _…

Debian linux版本下运行的openmediavault网盘 千兆网卡升级万兆

一、适用场景 1、使用vmware ESXi虚拟化平台运行多种不同应用服务器时&#xff0c;其中网盘服务器采用开源的openmediavault搭建&#xff1b; 2、将老专业服务器升级千兆网为万兆网&#xff1b; 3、需要转移的数据量大的企业或用户&#xff1b; 4、从服务器到服务器的数据转移…

【群晖】NASTOOL-自动化处理影音视频工具

【群晖】NASTOOL-自动化处理影音视频 本文主要从获取、部署、使用、配置等方面进行手把手教学如何使用nastool工具进行影音视频自动化处理。从此靠别繁琐的网上各个网址找资源-下载-复制-改名-刮削等操作。 准备 DSM 7.1 &#xff08;我使用的是群晖 7.1 系统&#xff0c;不管…