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,一经查实,立即删除!

相关文章

网络安全法中的网络安全规定和措施

《中华人民共和国网络安全法》是中国首部全面规范网络空间安全管理的基础性法律&#xff0c;旨在加强网络安全&#xff0c;保障国家安全和社会公共利益&#xff0c;保护公民、法人和其他组织的合法权益&#xff0c;促进互联网的健康发展。以下是该法律中关于网络安全的一些核心…

Linux线程--线程创建、等待及退出

1. pthread_create 功能 创建一个新的线程。 原型 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);参数 thread: 指向pthread_t类型变量的指针&#xff0c;用于存储新创建线程的标识符。attr: 线程属性&…

采用bat结合zabbix监控sqlserver数据库表的信息

采用bat结合zabbix监控sqlserver数据库表的信息&#xff0c;当表插入某个特定的值的时候就发出告警。 监控需求&#xff1a;数据库存在某个表&#xff0c;该表不是一个固定的表名字&#xff0c;而且根据当前的日期生成表&#xff0c;比如tab20240501,tab20240502,需要查询当天…

安卓init进程详解

目录 一、概述1.1 Init进程如何被启动&#xff1f;1.2 Init进程启动后&#xff0c;做了哪些事&#xff1f; 二、kernel启动init进程2.1 kernel_init2.2 do_basic_setup 三、Init 进程启动源码分析3.1 Init 进程入口3.2 ueventd_main3.3 init 进程启动第一阶段3.4 加载SELinux规…

FLINK-窗口算子

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

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

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

多项目的.net core解决方案(项目间引用)如何使用Docker部署

解决方案内部项目之间引用很正常&#xff0c;但我docker不是很熟&#xff0c;对一些基础命令含义还理解不深入&#xff0c;部署引用其他项目的项目总不成功。搜到了一篇非常适合初学者&#xff0c;从dockerfile命令讲解&#xff0c;到解决引用其他项目时如何docker部署的文章。…

JavaWeb_SpringBootWeb基础

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

3-EMMC命令使用

在调试emmc的过程&#xff0c;我们需要用到命令读写emmc&#xff0c;烧录&#xff0c;查看emmc寄存器&#xff0c;设置寄存器等功能&#xff0c;所以uboot和linux下都有各自的命令可以使用。 1、 uboot下mmc命令 1.1、mmc信息 查看mmc信息&#xff1a;mmc info 描述了emmc的速…

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 // 模拟多个文件描述符// …

【代码随想录训练营】【Day 38】【贪心-5】| Leetcode 435, 763, 56

【代码随想录训练营】【Day 38】【贪心-5】| Leetcode 435, 763, 56 需强化知识点 重叠区间系列 题&#xff0c; 763&#xff0c; 435 题目 435. 无重叠区间 左起点排序&#xff0c;记录重叠区间个数&#xff0c;总数相减即为结果&#xff0c;过程中维护右边界注意&#x…

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

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

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

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

使用javacv对摄像头视频转码并实现播放

要实现Java接受RTSP流解码&#xff0c;并推送给前端实现播放实时流&#xff0c;可以使用一些流媒体处理库&#xff0c;比如JavaCV或者FFmpeg等。以下是一个简单的示例代码&#xff1a; 1.控制层方面的 根据视频rtsp流链接打开转换&#xff0c;通过响应写出流到前台使用flvjs播…

go语言初学04

Go 语言近年来发展迅速&#xff0c;并且出现了许多优秀的开发框架和组件来支持各种不同的开发需求。以下是一些常用的 Go 语言开发框架和组件&#xff1a; Web 框架 Gin&#xff1a; URL: Gin简单、高效、易用&#xff0c;适合构建高性能的 Web 应用。 Echo&#xff1a; URL: …

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…

ArrayList源码讲解

ArrayList 底层采用的是数组队列&#xff0c;相当于动态数组。 ArrayList内部使用一个可重新分配的Object数组来存储元素&#xff0c;这个数组会随着元素的添加自动增长以容纳更多的元素&#xff0c;这就是所谓的“动态数组”。 1.实现了RandomAccess接口&#xff0c;可以随机…

rust嵌入式开发之总结

我们用rust开发的新版产品刚刚交付&#xff0c;已经在海上安装测试完毕并顺利投产。终于松了口气&#xff0c;同时也有时间和精力来做个全面的总结了。 这个产品&#xff0c;目前差不多有三版&#xff1a; 第一个版本是用crt-thread写的&#xff0c;投产后出了一个内存泄露的…

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

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