STM32 SPI通信(软件)
一、SPI简介
- SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线
- 四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select)
- 同步,全双工
- 支持总线挂载多设备(一主多从)
SCK:提供时钟信号,数据位的输出和输入,都是在SCK的上升沿或下降沿进行的。
MOSI:主机输出从机输入,主机配置为输出。从机配置为输入。
MISO:主机输入从机输出
SS:从机选择线。有几个从机就有几条SS
二、硬件电路
- 所有SPI设备的SCK、MOSI、MISO分别连在一起
- 主机另外引出多条SS控制线,分别接到各从机的SS引脚
- 输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入
解释:
- 因为有三个从机,所以有三个SS线,一共就是6跟线。因为都是单端型号,所以所有的设备还需要共地。从机没有独立供电的话,主机还需要额外引出电源正极VCC,给从机供电。
- SS线是低电平有效,同一时间只能置一个SS为低电平,只能选中一个从机,同时未选中的从机的MISO引脚,为高阻态,否则三个输出同时进入主机的输入会产生冲突
- 对于输出,选择推挽输出,使得上升沿、下降沿非常迅速
上图是SPI内部移位示意图
步骤:SPI高位先行,每来一个时钟,移位寄存器都会向左进行移位。
假设现在主机要将10101010发给从机,从机要将01010101发给主机。
首先向左移位
移出去的数据会在输出数据寄存器
此时MOSI为高电平1,MISO为低电平0。这就是第一个时钟上升沿的结果 。那么在第一个时钟的下降沿,寄存器里的数据被分别采样输入到对应要去的最低位。
这就是第一个时钟结束后的现象。
三、SPI时序基本单元
- 起始条件:SS从高电平切换到低电平
- 终止条件:SS从低电平切换到高电平
住:这里一共有四种模式,由CPOL和CPHA变换1、0来选择。是为了适配更多的设备而设置的。传输流程看上面的硬件电路部分。
四、W25Q64简介
注:上面有横线的都是低电平有效
电路框图:
- (1)和(2)描述的是存储器规划示意图,(2)被划分为若干个块“Block”,其中每一块再划分为若干个扇区(1)“Sector”。对于每个扇区又可以分为很多页 “Page” 。
- 在(2)里面,以64KB为一个基本单元,分了128块(因为一共8MB)。
- 在(1)里面,是对于块的更细的划分,以4KB为一份,分了16份。
- 在写入数据时,还会有个更细的划分Page,256字节为一份。
- (7)为SPI控制逻辑,接收指令和数据等
- (8)状态寄存器,表示芯片是否处于忙状态、是否写使能、是否写保护
- (9)写控制逻辑,配合WP引脚实现硬件写保护
- (4)高电压生成器,配合Flash编程,Flash要实现掉电不丢失,就需要高电压来刺激。
- (5)页地址锁存/计数器、(6)字节地址锁存/计数器,用来指定地址,读写操作
- (3)256字节的RAM存储器,由于SPI写入频率非常高,所有写入数据先到RAM缓存区里,再写入Flash(2)里面。写入时会给状态寄存器(8)的BUSY位置1
五、Flash操作注意事项
写入操作时:
- 写入操作前,必须先进行写使能
- 每个数据位只能由1改写为0,不能由0改写为1
- (由于上一条)写入数据前必须先擦除,擦除后,所有数据位变为1
- 擦除必须按最小擦除单元进行(一个扇区)
- 连续写入多字节时,最多写入一页的数据(256字节),超过页尾位置的数据,会回到页首覆盖写入
- 写入操作结束后(写到RAM),芯片进入忙状态(当前在RAM转Flash,或者在擦除当中),不响应新的读写操作
读取操作时:
- 直接调用读取时序,无需使能,无需额外操作,没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取
六、代码部分
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~1* 返 回 值:无* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置MOSI为低电平,当BitValue为1时,需要置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次,依次交换每一位数据{/*两个!可以对数据进行两次逻辑取反,作用是把非0值统一转换为1,即:!!(0) = 0,!!(非0) = 1*/MySPI_W_MOSI(!!(ByteSend & (0x80 >> i))); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线MySPI_W_SCK(1); //拉高SCK,上升沿移出数据if (MySPI_R_MISO()){ByteReceive |= (0x80 >> i);} //读取MISO数据,并存储到Byte变量//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0MySPI_W_SCK(0); //拉低SCK,下降沿移入数据}return ByteReceive; //返回接收到的一个字节数据
}
W25Q64.c
#include "stm32f10x.h" // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"/*** 函 数:W25Q64初始化* 参 数:无* 返 回 值:无*/
void W25Q64_Init(void)
{MySPI_Init(); //先初始化底层的SPI
}/*** 函 数:W25Q64读取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终止
}
相关文章:

STM32 SPI通信(软件)
一、SPI简介 SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slav…...

每日刷题c++
快速幂 #include <iostream> using namespace std; #define int long long int power(int a, int b, int p) {int ans 1;while (b){if (b % 2){ans * a;ans % p; // 随时取模}a * a;a % p; // 随时取模b / 2;}return ans; } signed main() {int a, b, p;cin >> a …...
(自用)Java学习-5.19(地址管理,三级联动,预支付)
1. 地址管理模块 地址展示 前端:通过 showAddress() 发起 Ajax GET 请求,动态渲染地址列表表格,使用 #{tag}、#{name} 等占位符替换真实数据。 后端: 控制器层调用 AddressService,通过 AddressMapper 查询用户地址数…...
【容器】docker使用问题处理
问题一、systemctl start docker启动报 ERROR: ZONE_CONFLICT: docker0 already bound to a zone 处理方法 firewall-cmd --permanent --zonedocker --change-interfacedocker0 systemctl restart firewalld 问题二、启动容器报 ptables failed/iptables: No chain/target/…...

ChemDraw 2023|Win英文|化学结构编辑器|安装教程
软件下载 【名称】:ChemDraw 2023 【大小】:1.34G 【语言】:英文界面 【安装环境】:Win10/Win11 【夸克网盘下载链接】(务必手机注册): https://pan.quark.cn/s/320bcb67da80 【网站下载…...
Vue3实现提示文字组件
Vue3 实现一个文字提示组件(Tooltip) 文字提示(Tooltip)是前端开发中非常常见的组件,通常用于在用户悬停某个元素时显示额外的信息。 一、需求分析 我们要实现一个 Vue3 的文字提示组件,具备以下功能&…...
JAVA与C语言之间的差异(一)
一、代码习惯以及主函数 JAVA中{在使用的时候不要换行 public static void main(String[] args) {int[] array {1, 2, 3};for(int i 0; i < array.length; i){System.out.println(array[i] " ");}} 其次,以main函数为主函数: public …...
深入剖析 C 语言中的指针数组与数组指针
资料合集下载链接: https://pan.quark.cn/s/472bbdfcd014 在C语言中,指针是其强大和灵活性的核心。然而,围绕指针的概念有很多容易混淆的地方,其中“指针数组”和“数组指针”就是一对常见的“双胞胎”概念。它们名称相似,但含义和用法却大相径庭。 本文旨在清…...

4.1.1 Spark SQL概述
Spark SQL是Apache Spark的一个模块,专门用于处理结构化数据。它引入了DataFrame这一编程抽象,DataFrame是带有Schema信息的分布式数据集合,类似于关系型数据库中的表。用户可以通过SQL、DataFrames API和Datasets API三种方式操作结构化数据…...
【VSCode-Qt】Docker远程连接的项目UI文件在 VSCode 上无法预览
Docker远程连接的UI文件在 VSCode 上无法预览,通常是因为 VSCode 通过远程开发扩展(Remote - SSH/Docker)连接到 Docker 容器时,某些图形化功能未正确配置或支持。以下是可能原因和解决方案: 原因分析 X11 转发未配置…...

redis五种数据结构详解(java实现对应的案例)
一、简述 Redis是一款高性能的键值对存储数据库,它支持五种基本数据类型,分别是字符串(String)、列表(List)、哈希(Hash)、集合(Set)、有序集合(Sorted Set)。 二、五种基本数据类型 2.1 字符串(String) String是Redis最基本的类型,一个key对…...
Telnet 命令详解
Telnet 命令详解:从基础到实战应用 Telnet 是一种历史悠久的网络协议,广泛用于远程登录和管理设备。尽管如今更安全的 SSH 协议已逐渐取代其地位,但 Telnet 在特定场景下依然发挥着重要作用。本文将深入解析 Telnet 命令的参数、使用场景及注…...
深度解析新能源汽车结构与工作原理
一、核心系统架构 新能源汽车主要由三大核心系统构成: 电力驱动系统:包含永磁同步电机、电机控制器(MCU)及减速器,采用三合一集成设计实现轻量化。永磁同步电机通过电磁感应原理将电能转化为机械能,其效率可…...

React 生命周期与 Hook:从原理到实战全解析
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 持续学习,不断…...
OpenSSL 与 C++ 搭建一个支持 TLS 1.3 的服务器
好的,我们可以使用 OpenSSL 与 C 搭建一个支持 TLS 1.3 的服务器。下面是: ✅ 一、完整示例代码(基于 OpenSSL) 使用 C 和 OpenSSL 创建一个简单的 TCP TLS 服务器,支持 TLS 1.3。 ✅ 代码:tls_server.cp…...
HOW - 简历和求职面试宝典(六)
文章目录 1. 如何更好地认识自己?一、认清自己的实力二、明确求职方向三、认识求职岗位与自己的匹配度2. 如何判断公司是否合适自己?一、网站平台二、内部人员三、通过面试官1. 如何更好地认识自己? 一、认清自己的实力 我们经常会听到这样的话:我现在的工作做的好不开心…...

【机器学习基础】机器学习入门核心算法:逻辑回归(Logistic Regression)
机器学习入门核心算法:逻辑回归(Logistic Regression) 一、算法逻辑1.1 基本概念1.2 Sigmoid函数1.3 决策边界 二、算法原理与数学推导2.1 概率建模2.2 损失函数推导2.3 梯度下降优化2.4 正则化处理 三、模型评估3.1 常用评估指标3.2 ROC曲线…...
深入理解设计模式之命令模式
下面是一篇关于设计模式之命令模式(Command Pattern)的详细博客,并附有 Java 实现代码示例。 深入理解设计模式之:命令模式(Command Pattern) 一、什么是命令模式? 命令模式(Comma…...

智能仓储落地:机器人如何通过自动化减少仓库操作失误?
仓库作业的速度和准确性至关重要,尤其是在当前对无差错、高效作业的要求达到前所未有的环境下。每一个错误,无论是物品放错位置还是库存差异,都会在供应链中产生连锁反应,造成延误、增加成本,并最终影响客户满意度。 …...
Android 架构演进之路:从 MVC 到 MVI,拥抱单向数据流的革命
在移动应用开发的世界里,架构模式的演进从未停歇。从早期的 MVC 到后来的 MVP、MVVM,每一次变革都在尝试解决前一代架构的痛点。而今天,我们将探讨一种全新的架构模式 ——MVI(Model-View-Intent),它借鉴了…...

[低代码表单生成器设计基础]ElementUI中Layout布局属性Form表单属性详解
Layout 布局 ElementUI 的 Layout 布局系统基于 24 栏栅格设计,提供了灵活的响应式布局能力,适用于各种页面结构的构建。(CSDN) 📐 基础布局结构 ElementUI 的布局由 <el-row>(行)和 <el-col>࿰…...
数据结构7——二叉树
一、二叉树的定义与性质 1.定义 首先是树形结构,每个节点最多有2棵树,二叉树的子树有左右之分,不能颠倒。 2.性质 (1)二叉树的第i层,最多有2的(i-1)次幂。 (2)深度为k࿰…...

从“被动养老”到“主动健康管理”:平台如何重构代际关系?
在老龄化与数字化交织的背景下,代际关系的重构已成为破解养老难题的关键。 传统家庭养老模式中,代际互动多表现为单向的“赡养-被赡养”关系。 而智慧养老平台的介入,通过技术赋能、资源整合与情感连接,正在推动代际关系向“协作…...
Java 中的 synchronized 和 Lock:如何保证线程安全
Java 中的 synchronized 和 Lock:如何保证线程安全 引言 在 Java 多线程编程中,线程安全是一个核心问题。当多个线程同时访问共享资源时,可能会导致数据不一致或其他不可预期的结果。synchronized关键字和Lock接口是 Java 中实现线程同步的…...

贪心算法应用:最大匹配问题详解
Java中的贪心算法应用:最大匹配问题详解 贪心算法是一种在每一步选择中都采取当前状态下最优的选择,从而希望导致结果是全局最优的算法策略。在Java中,贪心算法可以应用于多种问题,其中最大匹配问题是一个经典的应用场景。下面我将从基础概念到具体实现,全面详细地讲解贪…...

爬虫IP代理效率优化:策略解析与实战案例
目录 一、代理池效率瓶颈的根源分析 二、六大核心优化策略 策略1:智能IP轮换矩阵 策略2:连接复用优化 策略3:动态指纹伪装 策略4:智能重试机制 三、典型场景实战案例 案例1:电商价格监控系统 案例2:…...

豆瓣电视剧数据工程实践:从爬虫到智能存储的技术演进(含完整代码)
通过网盘分享的文件:资料 链接: https://pan.baidu.com/s/1siOrGmM4n-m3jv95OCea9g?pwd4jir 提取码: 4jir 1. 引言 1.1 选题背景 在影视内容消费升级背景下,豆瓣电视剧榜单作为国内最具影响力的影视评价体系,其数据价值体现在:…...
【HW系列】—C2远控服务器(webshell链接工具, metasploit、cobaltstrike)的漏洞特征流量特征
文章目录 蚁剑、冰蝎、哥斯拉一、蚁剑(AntSword)流量特征二、冰蝎(Behinder)流量特征三、哥斯拉(Godzilla)流量特征 metasploit、cobaltstrike一、Metasploit流量特征二、CobaltStrike流量特征三、检测与防…...
5.28 孔老师 nlp讲座
本次讲座主要介绍了语言模型的起源、预训练模型以及大语言模型(需要闫老师后讲)等内容。首先,语言模型的起源可以追溯到语音识别中的统计语言模型,通过估计声学参数串产生文字串的概率来找到最大概率的文字串。然后,介…...

基于微信小程序的漫展系统的设计与实现
博主介绍:java高级开发,从事互联网行业六年,熟悉各种主流语言,精通java、python、php、爬虫、web开发,已经做了六年的毕业设计程序开发,开发过上千套毕业设计程序,没有什么华丽的语言࿰…...