STM32_SPI

1、SPI简介

1.1 什么是SPI

        SPI,即Serial Peripheral Interface,串行外设接口。SPI是一种高速的、全双工、同步的串行通信总线;SPI采用主从方式工作,一般有一个主设备和一个或多个从设备;SPI需要至少4根线,分别是MISO(主设备输入,从设备输出)、MOSI(主设备输出,从设备输入)、SCK(时钟)、SS(从机选择)。

        相较于IIC而言,SPI的传输速度更快,SPI协议并没有严格规定最大传输速度,这个最大传输速度一般取决于芯片厂商的设计需求;而IIC由于上拉电阻的原因,电平由低转高时耗时更多,一般速度最快在400KHz。

        所有设备的SCK、MOSI、MISO分别连接在一起;主机另外引出多条SS控制线,分别连接各从机的SS引脚;输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入。

1.2 SPI外设

        TM32内部集成了硬件SPI收发电路,可以由硬件自动执行时钟生成、数据收发等功能,减轻CPU的负担。

        可配置8位/16位数据帧、高位先行/低位先行,一般都是8位数据帧、高位先行

        时钟频率: fPCLK / (2, 4, 8, 16, 32, 64, 128, 256)

        支持多主机模型、主或从操作

        可精简为半双工/单工通信

        支持DMA

        兼容I2S协议(音频传输协议)

        STM32F103C8T6 硬件SPI资源:SPI1、SPI2

1.3 极性和相位

        SPI总线有四种不同的工作模式,取决于极性(CPOL)和相位(CPHL)这两个因素。

        CPOL表示CLK空闲时的状态:CPOL=0,空闲时SCK为低电平;反之则为高电平。

        CPHL表示采样时刻:CPHL=0,每个周期的第一个时钟沿采样;CPHL=1,每个周期的第二个时钟沿采样。

2、SPI结构图

        以下结构图来自STM32F103xxx

        这里发送缓冲区就是TDR,接收缓冲区就是RDR,和串口那里一样,TDR和RDR占用同一个地址,统一叫做DR。如果我们需要连续发送一批数据,第一个数据写入到TDR,当移位寄存器没有数据移位时,TDR的数据会立刻转入移位寄存器,开始移位,这个转入时刻,会置状态寄存器的TXE为1,表示发送寄存器空,当我们检查TXE置1后,紧跟着,下一个数据,就可以提前写入到TDR里候着了,一旦上一个数据发完,下一个数据就可以立刻跟进,实现不间断的连续传输。然后移位寄存器这里,一旦有数据过来了,它就会自动产生时钟,将数据移出去,在数据移出的过程中,MISO的数据也会移入,一旦数据移出完成,数据移入也就完成了,这时移入的数据就会整体的从移位寄存器转入到接收缓冲区RDR,这个时刻会置状态寄存器的RXNE为1,表示接收寄存器非空,当我们检查RXNE置1后,就要尽快把数据从RDR读出来,在下一个数据到来之前,读出RDR,就可以实现连续接收。

        基本结构

SPI移位示意图

        从图中可以看出,SPI的数据收发,都是基于字节交换这个基本单元来进行的。当主机需要发送一个字节,并且同时需要接收一个字节时,就可以执行一下字节交换的时序,这要主机要发送的数据就跑到了从机,主机要从从机接收的数据,就跑到了主机,这就完成了发送同时接收的目的。

3、SPI时序

3.1 基本时序单元

        起始条件:SS从高电平切换到低电平

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

        SS低电平选中,高电平代表未选中,那么在低电平期间就代表正在通信,下降沿是通信的开始,上升沿是通信的结束。

        交换一个字节(模式0):

        CPOL=0:空闲状态时,SCK为低电平

        CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据

        交换一个字节(模式1):

        CPOL=0:空闲状态时,SCK为低电平

        CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

        交换一个字节(模式2):

        CPOL=1:空闲状态时,SCK为高电平

        CPHA=0:SCK第一个边沿移入数据,第二个边沿移出数据

        交换一个字节(模式3):

        CPOL=1:空闲状态时,SCK为高电平

        CPHA=1:SCK第一个边沿移出数据,第二个边沿移入数据

3.2 SPI时序

        发送指令:向SS指定的设备,发送指令(0x06)

         指定地址写:向SS指定的设备,发送写指令(0x02),随后在指定地址(Address[23:0])下,写入指定数据(Data)

        指定地址读:向SS指定的设备,发送读指令(0x03),随后在指定地址(Address[23:0])下,读取从机数据(Data)

4、示例代码

4.1 软件实现SPI

#include "stm32f10x.h"                  // Device header


//对SPI四个引脚的封装
//从机选择,写SS的引脚
void MySPI_W_SS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}

void MySPI_W_SCK(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_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;	//速度50MHz
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;	//上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;	
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//速度50MHz
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	//引脚初始化之后的默认电平
	MySPI_W_SS(1);	//默认使用高电平,不选择从机
	MySPI_W_SCK(0);	//计划使用SPI模式0,所以默认是低电平
	
}

//写SPI的三个基本时序单元
//起始信号
void MySPI_Start(void)
{
	MySPI_W_SS(0);	//根据时序图将SS置低电平
}

//终止信号
void MySPI_Stop(void)
{
	MySPI_W_SS(1);	//根据时序图将SS置高电平
}

//交换一个字节
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	//定义一个字节,用于接收
	uint8_t i, ByteReceive = 0x00;
	
	for (i = 0; i < 8; i++)
	{
		//在SS下降沿之后开始交换字节,先有下降沿再有数据移出的动作
		MySPI_W_MOSI(ByteSend & (0x80 >> i));	//这里相当于通过掩码的模式来获取想要的位
		MySPI_W_SCK(1);	//写SCK为1,产生上升沿,上升沿时,从机会自动把MOSI的数据读走,主机的任务就是把从机刚才放到MISO的数据位读进来
		if (MySPI_R_MISO() == 1)
		{
			ByteReceive |= (0x80 >> i);	//这样就把最高位存在ByteReceive中了
		}
		MySPI_W_SCK(0);	//写SCK为0,产生下降沿,下降沿时,主机和从机移出下一位
		
		//该for循环中的内容还可以进行优化,用掩码的方式提取,好处是不会改变ByteSend本身,后面有需要还可以使用
		//用移位数据本身来操作,好处是提高了效率,但是ByteSend这个数据在移位过程中改变了

	}
	
	return ByteReceive;
}

4.2 硬件SPI读写

#include "stm32f10x.h"                  // Device header


//对SPI四个引脚的封装
//从机选择,写SS的引脚
//这里还是使用软件模拟
void MySPI_W_SS(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}
	
	//以上代码替换成SPI外设的初始化
	//第一步,开启时钟和GPIO的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
	
	//第二步,初始化GPIO口,其中SCK和MOSI是由硬件外设控制的输出信号,所以配置为复用推挽输出,MISo是硬件外设的输入信号,设置为上拉拉输入,
		//因为输入设备可以有多个,所以不存在复用输入这个东西,SS是软件控制的输出信号,所以配置为通用推挽输出
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	//推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//速度50MHz
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//速度50MHz
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;	//上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//速度50MHz
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	//第三步,配置SPI外设
	SPI_InitTypeDef SPI_InitStructure;
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;	//选择SPI的模式,决定当前设备是SPI的主机还是从机
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;	//这里选择的是双线全双工
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;	//这里选择的是8位数据位
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//这里选择的是高位先行
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;	//波特率预分频器,这里SCK的时钟频率就是72MHz / 128
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;	//时钟极性,这里选择的是模式0,空闲时低电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;	//时钟相位,这里选择第一个边沿采样,这个和SPI的四种模式有关
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;	//这里选择软件NSS
	SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC校验的多项式,这里填多少都可以,反正我们不用
	SPI_Init(SPI1, &SPI_InitStructure);
	
	//第四步,开关控制,调用SPI_Cmd,给SPI使能即可
	SPI_Cmd(SPI1, ENABLE);
	
	//开启spi之后,还要调用一下MySPI_W_SS,默认给SS输出高电平,默认是不选中从机的
	MySPI_W_SS(1);
}

//写SPI的三个基本时序单元
//起始信号
void MySPI_Start(void)
{
	MySPI_W_SS(0);	//根据时序图将SS置低电平
}

//终止信号
void MySPI_Stop(void)
{
	MySPI_W_SS(1);	//根据时序图将SS置高电平
}

//交换一个字节
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
	//当调用这个交换字节的函数时,硬件的SPI外设就要自动控制SCK、MOSI、MISO这三个引脚来生成时序了
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);	//等待TXE的标志位为1
	//写入数据至SPD的DR,就是TDR,要发送的数据
	//ByteSend传入到TDR中,之后ByteSend会自动转入移位寄存器,一旦移位寄存器有数据了,就会自动产生时序波形
	SPI_I2S_SendData(SPI1, ByteSend);
	//发送和接收是同步的,到接收完成的时候也就代表发送移位完成了,接收移位完成时,会收到一个字节数据,这时会置标志位RXNE
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);	//等待RXNE的标志位为1,表示收到一个字节,同时也表示发送时序产生完成了
	//读取DR,从RDR中,把交换接收的数据读出来
	return SPI_I2S_ReceiveData(SPI1);
	
}
//注意事项1:这里的硬件SPI,必须是发送,同时接收,要想接收必须得先发送,因为只有你给TDR写数据,才会触发时序的生成
	//如果不发送只接收,那时序是不会动的
//注意事项2:TXE和RXNE标志位,在写入SPI_DR时,TXE标志被清除,在读SPI数据寄存器可以清除RXNE标志位,所以按照上面程序的步骤就不用手动清除标志位了

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【操作与配置】VS2017与MFC环境配置

【操作与配置】VS2017与MFC环境配置 概述 Visual Studio 是一款强大且多功能的集成开发环境&#xff08;IDE&#xff09;&#xff0c;适用于软件开发人员和团队。使用此应用程序&#xff0c;您可以构建和调试现代Web应用程序&#xff0c;并利用扩展帮助探索几乎任何编程语言。…

springboot高校网上选课系统-计算机毕业设计源码85583

摘 要 本论文主要论述了如何使用JAVA语言开发一个高校网上选课系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论述高校网上选课系统的当前背景以及系统开发的目…

重复文件怎么查找并清理?电脑重复文件清理工具分享:4个

在日常使用电脑的过程中&#xff0c;我们不可避免地会遇到各种重复文件的问题。这些重复文件不仅占据了宝贵的存储空间&#xff0c;还可能导致系统性能下降&#xff0c;甚至引发一些不必要的问题。因此&#xff0c;如何有效地查找并清理这些重复文件成为了许多用户关注的焦点。…

牛客BM85 验证IP地址【中等 字符串 Java/Go/PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/55fb3c68d08d46119f76ae2df7566880 https://www.lintcode.com/problem/1222/description 思路 直接模拟&#xff0c;注意IPv4,ipv6的条件Java代码 import java.util.*;public class Solution {/*** 验证IP地址…

docker基础,docker安装mysql,docker安装Nginx,docker安装mq,docker基础命令

核心功能操作镜像 Docker安装mysql docker run -d --name mysql -p 3306:3306 -e TZAsia/Shanghai -e MYSQL_ROOT_PASSWORDlcl15604007179 mysql docker的基本操作 docker rm 容器名称即可 docker ps 查看当前运行的容器 docker rm 干掉当前容器 docker logs 查看容器命令日…

Day 41 NGINX详解

Nginx详解 一、HTTP协议 HTTP协议是Hyper Text Transfer Protocol&#xff08;超文本传输协议&#xff09;的缩写,是用于从万维网&#xff08;WWW:World Wide Web &#xff09;服务器传输超文本到本地浏览器的传送协议。 1、HTTP 工作原理 HTTP协议通信流程 WEB Server&…

数组基础-笔记

数组是非常基础的数据结构&#xff0c;实现运用和理解是两回事 数组是存放在连续内存空间上的相同类型的数据的集合 可以方便的通过下表索引的方式获取到下标下对应的数据。 举一个字符数组的例子&#xff1a; 注意两点&#xff1a; 数组下标从0开始 数组内存空间的地址是连…

AOP案例

黑马程序员JavaWeb开发教程 文章目录 一、案例1.1 案例1.2 步骤1.2.1 准备1.2.2 编码 一、案例 1.1 案例 将之前案例中增、删、改相关节后的操作日志记录到数据库表中。 操作日志&#xff1a;日志信息包含&#xff1a;操作人、操作时间、执行方法的全类名、执行方法名、方法…

pytest框架用例命名规则详解

pytest 测试用例的命名规则是为了确保 pytest 能够正确地识别和执行测试用例。 以下是关于 pytest 测试用例命名规则的详细解释&#xff1a; 1 编写单个测试文件 单个测试文件须以‘test_’开头或者以‘_test’结尾 比如我们创建test_case1.py case2_test.py文件。 2 在单个…

【Mac】Lightroom Classic 2024(LrC 2024中文版) v13.1安装教程

软件介绍 Lightroom Classic 2024 for Mac是一款功能强大的照片编辑和组织软件&#xff0c;专为专业摄影师和爱好者设计。它提供了一系列工具和功能来增强和管理您的数码照片。Lightroom Classic 2024在照片组织和管理方面进行了重大改进。它新增了一个智能化的“发现”面板&a…

电容的电路应用

电容的电路应用 1、陶瓷电容应用于滤波 电源电路&#xff0c;负载电流较小时&#xff0c;可以使用陶瓷电容进行滤波。 C18电容起到滤波作用&#xff0c;因为负载电流比较小&#xff0c;所以可以用小容量的电容&#xff0c;比如经典的10uF、1uF、4.7uF都是可以的 滤波过程&am…

名下企业查询,清晰明了;在线操作,方便快捷

在现代社会&#xff0c;越来越多的人开始涉足创业和投资&#xff0c;拥有自己的企业成为一种时尚。然而&#xff0c;随之而来的是繁琐的企业注册流程和复杂的信息查询。为了解决这个问题&#xff0c;挖数据平台推出了一项名下企业查询接口&#xff0c;提供了一种方便快捷的方式…

easy-captcha生成验证码

引入依赖 <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>…

【Docker】学习笔记(超万字图文整理)

前言 再此感谢黑马程序员提供的Docker课程&#xff01; 什么是Docker&#xff1f;看这一篇干货文章就够了&#xff01; UPD: 补充更新微服务集群、Docker镜像仓库部分内容 所有笔记、生活分享首发于个人博客 想要获得最佳的阅读体验&#xff08;无广告且清爽&#xff09;&#…

低代码开发系统是什么?它有那些部分组成?

低代码开发系统是什么&#xff1f;它有那些部分组成&#xff1f; 一、引言 在当今快速变化的商业环境中&#xff0c;企业对于快速响应市场需求、降低开发成本和提高开发效率的需求日益增强。低代码开发系统&#xff08;Low-Code Development Platform&#xff09;应运而生&am…

视频监控平台AS1000:通过网络SDK接入松下视频监控设备(Panasonic监控摄像机) 的源代码的函数和功能介绍及分享

目录 一、视频监控平台介绍 1、概述 2、视频接入能力介绍 3、功能介绍 二、PANASONIC网络摄像机 1、产品种类与定位 2、规格参数 3、功能特点 4、环境适应性 5、网络功能 6、其他特性 三、代码和解释 1、代码和注释 2、函数功能说明 &#xff08;1&#xff09;处…

SpringBoot项目启动时提示程序包不存在和找不到符号

一、前言 最近接手同事开发的一个Springboot工作项目&#xff0c;从svn上整体拉取下来后&#xff0c;构建完成后&#xff0c;启动的时候遇到了程序包找不到的情况&#xff0c;记录一下处理过程&#xff1b; 二、项目问题 1、报错信息&#xff1a;启动后报 java: 程序包org.sp…

MySql 查询缓存

前言 MySQL的查询缓存&#xff08;Query Cache&#xff09;是一个在内存中存储SELECT语句及其结果集的机制&#xff0c;目的是避免对相同的查询进行重复的解析、编译和执行&#xff0c;从而提高数据库性能。 Mysql 结构图如下&#xff1a; 查询缓存的工作流程大致如下&#…

UE5 读取本地图片并转换为base64字符串

调试网址&#xff1a;在线图像转Base64 - 码工具 (matools.com) 注意要加&#xff08;data:image/png;base64,&#xff09; FString UBasicFuncLib::LoadImageToBase64(const FString& ImagePath) {TArray<uint8> ImageData;// Step 1: 读取图片文件到字节数组if (!…

1、pyton环境的安装-windows系统下

python官网 https://www.python.org/ 点击黄色的按钮&#xff0c;下载完成&#xff0c;如下&#xff1a; 双击安装&#xff0c;我现在以3.10.4版本进行安装说明&#xff1a; 一定要勾选上下边的to path&#xff0c;然后选择自定义安装 全选&#xff0c;点击next 选择要安装的路…