STM32F1之SPI通信·软件SPI代码编写

目录
1. 简介
2. 硬件电路
移位示意图
3. SPI时序基本单元
3.1 起始条件
3.2 终止条件
3.3 交换一个字节(模式0)
3.4 交换一个字节(模式1)
3.5 交换一个字节(模式2)
3.6 交换一个字节(模式3)
4. 代码编写
4.1 引脚初始化
4.2 引脚置高低电平封装
4.2.1 SPI写SS引脚电平
4.2.2 SPI写SCK引脚电平
4.2.3 SPI写MOSI引脚电平
4.2.4 I2C读MISO引脚电平
4.3 SPI起始
4.4 SPI终止
4.5 SPI交换传输一个字节
4.5.1 模式0
4.5.2 模式1
4.5.2 模式2
4.5.2 模式3
1. 简介
SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线。同步,全双工。支持总线挂载多设备(一主多从)。
四根通信线:SCK(Serial Clock)串行时钟线;
MOSI(Master Output Slave Input)主机输出从机输入;
MISO(Master Input Slave Output)主机输入从机输出;
SS(Slave Select)从机选择(若是有多个从机,有几个从机就有几条SS线,可见硬件电路中的连接图)。
2. 硬件电路
所有SPI设备的SCK、MOSI、MISO分别连在一起;
主机另外引出多条SS控制线,分别接到各从机的SS引脚;
输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入。

移位示意图
工作原理,假如主机想要发送一个字节给从机,从机也想发送一个字节给主机,开始,当SCK处于上升沿移位寄存器最左边数据移出,例如SPI主机中移位寄存器最左边“1”,移出到MOSI引脚上,而SPI从机的移位寄存器的最左边的数据“0”,移出到MISO引脚上,当SCK处于下降沿,MOSI上的数据进入到SPI从机的移位寄存器最右边,MISO上的数据进入到SPI主机的移位寄存器最右边。往复八次经过时钟的上升沿和下降沿,即可完成相互发送一个字节数据。

当多个从机输出连在一起,如果同时开启输出,会造成冲突,解决方法是,当SS未被选中的状态,从机的MISO引脚必须关断输出,即配置为高阻态。
3. SPI时序基本单元
3.1 起始条件
SS从高电平切换到低电平

3.2 终止条件
SS从低电平切换到高电平

3.3 交换一个字节(模式0)
CPOL=0:空闲状态时,SCK为低电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据

3.4 交换一个字节(模式1)
CPOL=0:空闲状态时,SCK为低电平
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

3.5 交换一个字节(模式2)
CPOL=1:空闲状态时,SCK为高电平
CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据

3.6 交换一个字节(模式3)
CPOL=1:空闲状态时,SCK为高电平
CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

4. 代码编写
4.1 引脚初始化
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默认低电平
}
其中,MySPI_W_SS(1); 和 MySPI_W_SCK(0);为封装函数,可以参照下一条。
4.2 引脚置高低电平封装
为了后续代码的编写方便,我们可以将,初始化的引脚进行封装。
4.2.1 SPI写SS引脚电平
此函数需要用户实现内容,当BitValue为0时,需要置SS为低电平,当BitValue为1时,需要置SS为高电平。
void MySPI_W_SS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); //根据BitValue,设置SS引脚的电平
}
4.2.2 SPI写SCK引脚电平
此函数需要用户实现内容,当BitValue为0时,需要置SCK为低电平,当BitValue为1时,需要置SCK为高电平。
void MySPI_W_SCK(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue); //根据BitValue,设置SCK引脚的电平
}
4.2.3 SPI写MOSI引脚电平
此函数需要用户实现内容,当BitValue为0时,需要置MOSI为低电平,当BitValue非0时,需要置MOSI为高电平。
void MySPI_W_MOSI(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue); //根据BitValue,设置MOSI引脚的电平,BitValue要实现非0即1的特性
}
4.2.4 I2C读MISO引脚电平
此函数需要用户实现内容,当前MISO为低电平时,返回0,当前MISO为高电平时,返回1。
uint8_t MySPI_R_MISO(void)
{return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6); //读取MISO电平并返回
}
也可以参考:
STM32F1之I2C通信·软件I2C代码编写-CSDN博客
进行其他方法也能实现同样功能。
4.3 SPI起始
根据3.1我们可以看出SPI起始只需要将SS拉低就可以开始时序。
void MySPI_Start(void)
{MySPI_W_SS(0); //拉低SS,开始时序
}
4.4 SPI终止
同理,根据3.2我们可以看出SPI起始只需要将SS拉高就可以结束时序。
void MySPI_Stop(void)
{MySPI_W_SS(1); //拉高SS,终止时序
}
4.5 SPI交换传输一个字节
4.5.1 模式0
这里需要注意一下,在SPI中对于硬件SPI来说,由于使用了硬件的移位寄存器电路,所以下图中黄色部分几乎是同时发生的,但是对于软件SPI来说程序执行需要一条一条执行,有一个先后顺序,因此我们可以将这里看成一个先后执行的逻辑:

因此我们可以将其传送一位数据的流程如下,先SS下降,再移出数据,在SCK上升沿,在移入数据,在SCK下降沿,再移出数据。

uint8_t MySPI_SwapByte(uint8_t ByteSend)
{uint8_t ByteReceive = 0x00; //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到MySPI_W_MOSI(ByteSend & 0x80); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线MySPI_W_SCK(1); //拉高SCK,上升沿移出数据if (MySPI_R_MISO() == 1){ByteReceive |= 0x80;} //读取MISO数据,并存储到Byte变量//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0MySPI_W_SCK(0); //拉低SCK,下降沿移入数据MySPI_W_MOSI(ByteSend & 0x40); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线MySPI_W_SCK(1); //拉高SCK,上升沿移出数据if (MySPI_R_MISO() == 1){ByteReceive |= 0x40;} //读取MISO数据,并存储到Byte变量//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0MySPI_W_SCK(0); MySPI_W_MOSI(ByteSend & 0x20); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线MySPI_W_SCK(1); //拉高SCK,上升沿移出数据if (MySPI_R_MISO() == 1){ByteReceive |= 0x20;} //读取MISO数据,并存储到Byte变量//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0MySPI_W_SCK(0); MySPI_W_MOSI(ByteSend & 0x10); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线MySPI_W_SCK(1); //拉高SCK,上升沿移出数据if (MySPI_R_MISO() == 1){ByteReceive |= 0x10;} //读取MISO数据,并存储到Byte变量//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0MySPI_W_SCK(0); MySPI_W_MOSI(ByteSend & 0x08); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线MySPI_W_SCK(1); //拉高SCK,上升沿移出数据if (MySPI_R_MISO() == 1){ByteReceive |= 0x08;} //读取MISO数据,并存储到Byte变量//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0MySPI_W_SCK(0); //......一直移出到第八位return ByteReceive; //返回接收到的一个字节数据
}
以上代码太过冗余,我们可以使用for循环来进行实现:
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时,不做处理,指定位为默认的初值0MySPI_W_SCK(0); //拉低SCK,下降沿移入数据}return ByteReceive; //返回接收到的一个字节数据
}
我们还可以根据“2.硬件电路”中的移位示意图中的数据进行操作,编写代码:
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{uint8_t i; for (i = 0; i < 8; i ++) //循环8次,依次交换每一位数据{MySPI_W_MOSI(ByteSend & 0x80);ByteSend <<=1;MySPI_W_SCK(1); //拉高SCK,上升沿移出数据if (MySPI_R_MISO() == 1){ByteReceive |= 0x01;}MySPI_W_SCK(0); //拉低SCK,下降沿移入数据}return ByteReceive; //返回接收到的一个字节数据
}
这种方式相较于上一种代码效率更高,但是原始数据ByteSend会发生改变,因为这种方法是用移位数据本身进行操作的,效率跟高,但是原始数据ByteSend会在移位过程中发生改变,对于上一种方式编写的代码是还有掩码一次提取数据每一位,不会改变参数本身,两种方法皆可使用。
4.5.2 模式1

我们也可以将其传送一位数据的流程描述如下,先SS下降之后,在SCK上升沿,再移出数据,在SCK下降沿,在移入数据。
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{uint8_t i, ByteReceive = 0x00; //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到for (i = 0; i < 8; i ++) //循环8次,依次交换每一位数据{MySPI_W_SCK(1); MySPI_W_MOSI(ByteSend & (0x80 >> i)); //使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线MySPI_W_SCK(0); if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);} //读取MISO数据,并存储到Byte变量//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0}return ByteReceive; //返回接收到的一个字节数据
}
4.5.2 模式2

可以对比模式0,可以发现只是SCK极性相反,只需要将模式0的代码中出现SCK的地方“1”改为“0”,“0”改为“1”即可,将极性翻转一下。(注意初始化中的极性也要进行修改)
4.5.2 模式3

同理,可以对比模式1,可以发现只是SCK极性相反,只需要将模式0的代码中出现SCK的地方“1”改为“0”,“0”改为“1”即可,将极性翻转一下。(注意初始化中的极性也要进行修改)

相关文章:
STM32F1之SPI通信·软件SPI代码编写
目录 1. 简介 2. 硬件电路 移位示意图 3. SPI时序基本单元 3.1 起始条件 3.2 终止条件 3.3 交换一个字节(模式0) 3.4 交换一个字节(模式1) 3.5 交换一个字节(模式2) 3.6 交换一个字节&a…...
实战:生成个性化词云的Python实践【7个案例】
文本挖掘与可视化:生成个性化词云的Python实践【7个案例】 词云(Word Cloud),又称为文字云或标签云,是一种用于文本数据可视化的技术,通过不同大小、颜色和字体展示文本中单词的出现频率或重要性。在词云中…...
云存储与云计算详解
1. 云存储与云计算概述 1.1 云存储 云存储(Cloud Storage)是指通过互联网将数据存储在远程服务器上,用户可以随时随地访问和管理这些数据。云存储的优点包括高可扩展性、灵活性和成本效益。 1.2 云计算 云计算(Cloud Computin…...
【飞舞的花瓣】飞舞的花瓣代码||樱花代码||表白代码(完整代码)
关注微信公众号「ClassmateJie」有完整代码以及更多惊喜等待你的发现。 简介/效果展示 这段代码是一个HTML页面,其中包含一个canvas元素和相关的JavaScript代码。这个页面创建了一个飘落花瓣的动画效果。 代码【获取完整代码关注微信公众号「ClassmateJie」回复“…...
网络安全的重要组成部分:数据库审计
数据库审计(简称DBAudit)以安全事件为中心,以全面审计和精确审计为基础,实时记录网络上的数据库活动,对数据库操作进行细粒度审计的合规性管理,对数据库遭受到的风险行为进行实时告警。它通过对用户访问数据…...
gc和gccgo编译器
Go 语言有两个主要的编译器,分别是 Go 编译器(通常简称为 gc)和 GCCGO。它们之间有一些重要的异同点: gc 编译器: gc 是 Go 语言的官方编译器,由 Go 语言的开发团队维护。它是 Go 语言最常用的编译器&#…...
开放重定向漏洞
开放重定向漏洞 1.开放重定向漏洞概述2.攻击场景:开放重定向上传 svg 文件3.常见的注入参数 1.开放重定向漏洞概述 开放重定向漏洞(Open Redirect)是指Web应用程序接受用户提供的输入(通常是URL参数),并将…...
基于YoloV4汽车多目标跟踪计数
欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 随着城市交通的快速发展,交通流量和车辆密度的不断增加,对交通管理和控…...
交叉编译程序,提示 incomplete type “struct sigaction“ is not allowed
问题描述 incomplete type "struct sigaction" is not allowed解决办法 在代码的最顶端添加如下代码即可 #define _XOPEN_SOURCE此定义不是简单的宏定义,是使程序符合系统环境的不可缺少的部分 _XOPEN_SOURCE为了实现XPG:The X/Open Porta…...
叶面积指数(LAI)数据、NPP数据、GPP数据、植被覆盖度数据获取
引言 多种卫星遥感数据反演叶面积指数(LAI)产品是地理遥感生态网推出的生态环境类数据产品之一。产品包括2000-2009年逐8天数据,值域是-100-689之间,数据类型为32bit整型。该产品经过遥感数据获取、计算归一化植被指数、解译植被类…...
光环P3O不错的一个讲座
光环P3O不错的一个讲座,地址:https://apphfuydjku5721.h5.xiaoeknow.com/v2/course/alive/l_663dc840e4b0694c62c32d1d?app_idapphfuydJkU5721&share_fromu_5c987304d8515_wH2E5HgCgx&share_type5&share_user_idu_5c987304d8515_wH2E5HgCgx…...
Typescnipt 学习笔记
TypeScript 学习笔记 一、什么是 TypeScript TypeScript 是一种由微软开发的开源编程语言,它是 JavaScript 的一个超集。它添加了静态类型和面向对象的特性,并提供了更强大的工具和功能,以增强 JavaScript 的开发体验。 二、为什么要学习 …...
如何在 Ubuntu 24.04 (桌面版) 上配置静态IP地址 ?
如果你想在你的 Ubuntu 24.04 桌面有一个持久的 IP 地址,那么你必须配置一个静态 IP 地址。当我们安装 Ubuntu 时,默认情况下 DHCP 是启用的,如果网络上可用,它会尝试从 DHCP 服务器获取 IP 地址。 在本文中,我们将向…...
小恐龙跳一跳源码
小恐龙跳一跳源码是前两年就火爆过一次的小游戏源码,不知怎么了今年有火爆了,所以今天就吧这个源码分享出来了!有喜欢的直接下载就行,可以本地单机直接点击index.html进行运行,又或者放在虚拟机或者服务器上与朋友进行…...
快手二面准备【面试准备】
快手二面准备【面试准备】 前言版权快手二面准备秋招一面中的问题实习一面中的问题计算机网络和操作系统论坛项目登录注册ThreadLocal代替session存储用户秒杀项目登录注册->阿里验证码->rpcsession为什么改为token实现,redis存储用户信息由binlog的用法->…...
贪心算法2(c++)
最大子矩阵 描述 已知矩阵的大小定义为矩阵中所有元素的和。给定一个矩阵,你的任务是找到最大的非空(大小至少是1*1)子矩阵。 比如,如下4*4的矩阵 0- 2 -7 0 9 2 -6 2 -4 1 -4 1 -1 8 0-2 的最大子矩阵是 9 2 -4 1 -18 这个子矩阵的大小是15。 输入…...
Bugku Crypto 部分题目简单题解(四)
目录 python_jail 简单的rsa 托马斯.杰斐逊 这不是md5 进制转换 affine Crack it rsa python_jail 启动场景 使用虚拟机nc进行连接 输入print(flag) 发现报错,经过测试只能传入10个字符多了就会报错 利用python中help()函数,借报错信息带出flag变…...
软考备考三
操作系统 操作系统概述 功能:组织和管理软件,硬件资源以及计算机系统中的工作流程,控制程序的执行,向用户提供接口。 分类: 1.批处理操作系统 单道批 多道批(宏观上并行,微观上串行)…...
Fortran: select type
Fortran: select type 实现类似C的template函数功能 module M_reduceuse mpi_f08interface reducemodule procedure reduce_scalar,reduce_arrayend interface reducecontains!!https://docs.open-mpi.org/en/v5.0.x/man-openmpi/man3/MPI_Reduce.3.htmlsubroutine reduce_ar…...
QEMU启动Linux内核
在QEMU环境下启动linux内核命令如下: QEMU_AUDIO_DRVnone qemu-system-arm -m 256M -nographic -M versatilepb -kernel /home/yukeyang/myfile/linux-6.6.30/arch/arm/boot/zImage -append "consolettyAMA0 rdinit/bin/sh" -dtb arch/arm/boot/dts/arm/…...
DISMTools企业部署:在组织中大规模应用的最佳实践
DISMTools企业部署:在组织中大规模应用的最佳实践 【免费下载链接】DISMTools The connected place for Windows system administration 项目地址: https://gitcode.com/GitHub_Trending/di/DISMTools DISMTools是一款专为Windows系统管理设计的连接平台&…...
STM32单片机学习(28) —— STM32的SPI外设
文章目录概述SPI通信的移位机制(以bit为单位)SPI外设框图第一部分:数据通路SPI通信的数据帧格式SPI外设移位机制(以字节为单位)第二部分:主机时钟生成器SPI通信时钟频率与传输速率第三部分:主从…...
机器学习与深度学习在地球物理勘探中的应用:基于电阻率数据预测极化率模型
1. 项目概述与核心价值在花岗岩这类地质条件复杂的地区搞勘探,最头疼的就是地下情况“看不清”。传统的电阻率(ERT)和激发极化(IP)联合反演,就像用一把刻度模糊的尺子去量一块表面坑洼不平的石头——面对高…...
别再乱用npm install了!手把手教你用npx only-allow为项目指定包管理器(支持pnpm/yarn/npm)
用only-allow统一团队包管理器:从配置到CI的全流程指南 你是否曾经在拉取一个新项目后,面对npm install、yarn还是pnpm i的抉择感到困惑?或者更糟的是,团队成员混用不同包管理器导致node_modules结构不一致,引发各种诡…...
户外实用|艾迪欧 R6000 测评 —— 户外 / 自驾 / 露营的通讯好搭档
户外出行,通讯工具的核心是稳定、清晰、耐用、续航久、功能全。艾迪欧 R6000 作为一款兼顾专业与户外的 DMR 对讲机,全频段覆盖、双模通讯、自定义功能、长续航,完美适配自驾、露营、登山、越野等户外场景,是户外爱好者的靠谱通讯…...
AI 如何改变软件工程:Martin Fowler 视角 + 实战洞见
AI 如何改变软件工程:Martin Fowler 视角 实战洞见 AI(尤其是 LLM)是软件工程自高级语言(从汇编到 C/Fortran)以来最大的转变。它引入了非确定性(Non-deterministic)编程,改变了从编…...
基于ESP8266的可穿戴Wi-Fi设备:从硬件设计到ESPHome智能控制
1. 项目概述:一个可穿戴的Wi-Fi智能小玩意最近在捣鼓智能家居和可穿戴电子,总想把手边的小物件变得更“聪明”一点。于是,我设计并制作了一个基于ESP8266的可穿戴Wi-Fi设备。它的核心思路很简单:把一块功能强大的Wi-Fi微控制器&am…...
[特殊字符] 高效统计排序数组中目标元素的出现次数
给定一个已排序的数组和一个目标值,如何快速统计该目标值在数组中出现的次数?这是面试中非常经典的一道题,今天就来聊聊两种解法:线性搜索和二分搜索。 问题描述 假设有一个已排序的数组 arr[] 和一个整数 target,需…...
FModel完整部署指南:UE5资源提取与逆向解析实战
1. 为什么FModel不是“另一个UE资源查看器”,而是虚幻项目逆向分析的起点FModel虚幻引擎资源提取工具完整部署指南——这标题里藏着三个被多数人忽略的关键信号:“FModel”不是泛指,“虚幻引擎”特指UE4/UE5原生资产体系,“完整部…...
5个高效技巧:重新定义你的Chrome书签管理体验
5个高效技巧:重新定义你的Chrome书签管理体验 【免费下载链接】neat-bookmarks A neat bookmarks tree popup extension for Chrome [DISCONTINUED] 项目地址: https://gitcode.com/gh_mirrors/ne/neat-bookmarks 你是否曾花费数分钟在混乱的书签海洋中寻找那…...
