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;	//速度50MHzGPIO_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;	//速度50MHzGPIO_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;	//速度50MHzGPIO_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;	//速度50MHzGPIO_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;	//速度50MHzGPIO_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 / 128SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;	//时钟极性,这里选择的是模式0,空闲时低电平SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;	//时钟相位,这里选择第一个边沿采样,这个和SPI的四种模式有关SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;	//这里选择软件NSSSPI_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);//发送和接收是同步的,到接收完成的时候也就代表发送移位完成了,接收移位完成时,会收到一个字节数据,这时会置标志位RXNEwhile (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标志位,所以按照上面程序的步骤就不用手动清除标志位了

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

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

相关文章

FLINK-窗口算子

参考资料 官方文档- WindowFlink中的时间和窗口之窗口 窗口 在流处理中&#xff0c;我们往往需要面对的是连续不断、无休无止的无界流&#xff0c;不可能等到所有所有数据都到齐了才开始处理。所以聚合计算其实只能针对当前已有的数据——之后再有数据到来&#xff0c;就需要继…

chat4-Server端保存聊天消息到mysql

本文档描述了Server端接收到Client的消息并转发给所有客户端或私发给某个客户端 同时将聊天消息保存到mysql 服务端为当前客户端创建一个线程&#xff0c;此线程接收当前客户端的消息并转发给所有客户端或私发给某个客户端同时将聊天消息保存到mysql 本文档主要总结了将聊天…

JavaWeb_SpringBootWeb基础

先通过一个小练习简单了解以下SpringBootWeb。 小练习&#xff1a; 需求&#xff1a;使用SpringBoot开发一个Web应用&#xff0c;浏览器发起请求/hello后&#xff0c;给浏览器返回字符串"Hello World~"。 步骤&#xff1a; 1.创建SpringBoot项目&#xff0c;勾选We…

epoll模型下的简易版code

epoll模型下的简易版code c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/epoll.h> #include <fcntl.h>#define MAX_EVENTS 10 #define NUM_DESCRIPTORS 5 // 模拟多个文件描述符// …

工具类解决事务和过滤器解决事务

事务的四大特性ACID 原子性&#xff1a;强调事务的不可分割.多条语句要么都成功&#xff0c;要么都失败。 一致性&#xff1a;强调的是事务的执行的前后&#xff0c;数据要保持一致 隔离性&#xff1a;并发访问数据库时,一个事务的执行不应该受到其他事务的干扰. 持久性&#…

测试:ollama加载羊驼版本llama-3中文大模型

找了一个晚上各种模型&#xff0c;像极了当初找各种操作系统的镜像&#xff0c;雨林木风&#xff0c;深蓝、老毛桃…… 主要是官方的默认7B版本回答好多英文&#xff0c;而且回复的很慢&#xff0c;所以我是在ollama上搜索"chinese"找到了这个羊驼版本的&#xff0c…

crossover软件是干什么的 crossover软件安装使用教程 crossover软件如何使用

CrossOver 以其出色的跨平台兼容性&#xff0c;让用户在Mac设备上轻松运行各种Windows软件&#xff0c;无需复杂的设置或额外的配置&#xff0c;支持多种语言&#xff0c;满足不同国家和地区用户的需求。 CrossOver 软件是干嘛的 使用CrossOver 不必购买Windows 授权&#xf…

Winform ListView 嵌入组合框、布尔、图片等复杂控件

一、Winform ListView 显示复杂控件示例 以下展示了两种实现思路方案。最后修改日期 2024-05 surfsky 1.1 方案一&#xff1a;ListView 结合组合框进行模拟编辑 基本思路 在界面上放置一个lisview和一个combobox&#xff0c;combobox平时是隐藏的。点击listview&#xff0c…

521源码-源码论坛-宝塔面板操作日志是存放在哪里的? 如何删除部分日志记录

我们帮别人搭建或者登录了&#xff08;不是自己权属的宝塔面板&#xff09;&#xff0c;会留下登录及操作的日志&#xff0c;我们不想留下这些操作日志&#xff0c;可以通过下面的方法处理掉&#xff0c;以达到无痕迹访问操作的目的&#xff1a; 如图所示的面板操作日志&#…

Python-3.12.0文档解读-内置函数sum()详细说明+记忆策略+常用场景+巧妙用法+综合技巧

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 详细说明 sum(iterable, /, start0) 参数&#xff1a; 返回值&#xff1a; 注意事…

排序方法大汇总

以下所有排序方法均以排升序为例 一.插入排序 1.直接插入排序 1>方法介绍&#xff1a;假定前n个数据有序&#xff0c;将第n1个数据依次与前n个数据相比&#xff0c;若比第i个数据小且比第i-1个数据大则插入两者之间 2>时间复杂度&#xff1a;O(N^2) 空间复杂度&#…

BUUCTF中的密码题目解密

BUUCTF 1.MD5 题目名称就是MD5&#xff0c;这个题目肯定和md5密码有关&#xff0c;下载题目&#xff0c;打开后发现这确实是一个md5加密的密文 Md5在线解密网站&#xff1a;md5在线解密破解,md5解密加密 经过MD5在线解密网站解密后&#xff0c;获取到flag为&#xff1a;flag{…

网络编程TCP

White graces&#xff1a;个人主页 &#x1f649;专栏推荐:Java入门知识&#x1f649; &#x1f649; 内容推荐:Java网络编程(下)&#x1f649; &#x1f439;今日诗词: 壮士当唱大风哥, 宵小之徒能几何&#xff1f;&#x1f439; ⛳️点赞 ☀️收藏⭐️关注&#x1f4ac;卑微…

CentOS7单用户模式,救援模式操作记录

CentOS7单用户模式&#xff0c;救援模式操作记录 1. 单用户模式 单用户模式进入不需要密码&#xff0c;无网络连接&#xff0c;拥有root权限&#xff0c;禁止远程登陆。一般用于用于系统维护&#xff0c;例如忘记root密码后可以通过进入单用户模式进行重置。 开机启动&#…

数据结构 实验 1

题目一&#xff1a;用线性表实现文具店的货品管理问题 问题描述&#xff1a;在文具店的日常经营过程中&#xff0c;存在对各种文具的管理问题。当库存文具不足或缺货时&#xff0c;需要进货。日常销售时需要出库。当盘点货物时&#xff0c;需要查询货物信息。请根据这些要求编…

使用低代码系统的意义与价值主要体现在哪里?

使用低代码系统的意义与价值主要体现在以下几个方面&#xff0c;这些观点基于驰骋低代码设计者的专业洞察和行业经验&#xff1a; 快速原型创建&#xff1a; 低代码平台通过提供图形化界面和预构建的模块&#xff0c;极大地加速了系统原型的创建过程。这意味着企业能够更快地验…

60 关于 SegmentFault 的一些场景 (1)

前言 呵呵 此问题主要是来自于 帖子 月经结贴 -- 《Segmentation Fault in Linux》 这里主要也是 结合了作者的相关 case, 来做的一些 调试分享 当然 很多的情况还是 蛮有意思 本文主要问题如下 1. 访问可执行文件中的 只读数据 2. 访问不存在的虚拟地址 3. 访问内核地址…

嵌入式工程师人生提质的十大成长型思维分享

大家好,作为一名嵌入式开发者,很多时候,需要考虑个人未来的发展,人生旅途复杂多变,时常面临各种各样的挑战。如何在这个复杂多变的社会中稳步向前,不断成长,成为每个人都应该思考的问题。实际上,思维方式的差异决定我们应对挑战的能力与成长的速度。 第一:寻找自我坐…

HNCTF2022 REVERSE

[HNCTF 2022 WEEK2]esy_flower 简单花指令 Nop掉 然后整段u c p然后就反汇编 可能反编译的不太对&#xff0c;&#xff0c;看了别人的wp就是ida反编译的有问题 #include<stdio.h> #include<string.h> int main() {int i,j;char ch[]"c~scvdzKCEoDEZ[^roDICU…

微软远程连接工具:Microsoft Remote Desktop for Mac 中文版

Microsoft Remote Desktop 是一款由微软开发的远程桌面连接软件&#xff0c;它允许用户从远程地点连接到远程计算机或虚拟机&#xff0c;并在远程计算机上使用桌面应用程序和文件。 下载地址&#xff1a;https://www.macz.com/mac/5458.html?idOTI2NjQ5Jl8mMjcuMTg2LjEyNi4yMz…