当前位置:网站首页>STM32(十)------- SPI通信
STM32(十)------- SPI通信
2022-07-31 14:44:00 【跋扈洋】
引言
在万物互联时代,“通信”对物联网尤为重要。在单片机或嵌入式开发领域,几乎很少有一个硬件单独就能实现所有功能的,即使是单片机裸机开发,往往也需要与传感器进行通信。
SPI可以说是我们在嵌入式开发中,最常用的通信协议之一,本文将来介绍什么是SPI协议l,SPI协议的特点是什么,怎么用SPI协议。
介绍
SPI 简介
SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,它被广泛地使用在 ADC、LCD 等设备与 MCU 间,要求通讯速率较高的场合。并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,越来越多的芯片集成了这种通信协议。
SPI接口是全双工三线同步串行外围接口,采用主从模式架构;支持多slave模式应用,一般仅支持单Master.时钟由Master控制,在时钟移位脉冲下,数据按位传输,高位在前,低位在后;SPI接口有两根单向数据线,为全双工通信,目前数据速率可达几Mbps的水平,速率较高。
SPI特点
- 可以同时发出和接收串行数据;
- 可以当作主机或从机工作;
- 提供频率可编程时钟;
- 发送结束中断标志;
- 写冲突保护;
- 总线竞争保护;
- 传输速度快
SPI 物理层
SPI总线是一种4线总线,因其硬件功能很强,所以与SPI有关的软件就相当简单,使中央处理器有更多的时间处理其他事务。
- MOSI:主器件输出,从器件数据输入;这条线上数据的方向为主机到从机。
- MISO主器件数据输入,从器件数据输出,即在这条线上数据的方向为从机到主机。
- SCLK:时钟信号,用于通讯数据同步。它由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,如 STM32 的 SPI 时钟频率最大为fpclk/2,两个设备之间通讯时,通讯速率受限于低速设备。
- /SS:从器件使能信号,由主器件控制(片选)。而 SPI 协议中没有设备地址,它使用 NSS 信号线来寻址,当主机要选择从设备时,把该从设备的 NSS 信号线设置为低电平,该从
设备即被选中,即片选有效,接着主机开始与被选中的从设备进行 SPI 通讯。所以SPI 通讯以 NSS 线置低电平为开始信号,以 NSS 线被拉高作为结束信号。
SPI 协议层
SPI 协议定义了通讯的起始和停止信号、数据有效性、时钟同步等环节。NSS 信号线由高变低,是 SPI 通讯的起始信号。NSS 是每个从机各自独占的信号线,当从机在自己的 NSS 线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。NSS 信号由低变高,是 SPI 通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。SPI 使用 MOSI 及 MISO 信号线来传输数据,使用 SCK 信号线进行数据同步。MOSI及 MISO 数据线在 SCK 的每个时钟周期传输一位数据,且数据输入输出是同时进行的。数据传输时,MSB 先行或 LSB 先行并没有作硬性规定,但要保证两个 SPI 通讯设备之间使用同样的协定。MOSI 及 MISO 的数据在 SCK 的上升沿期间变化输出,
在 SCK 的下降沿时被采样。即在 SCK 的下降沿时刻,MOSI 及 MISO 的数据有效,高电平时表示数据“1”,为低电平时表示数据“0”。在其它时刻,数据无效,MOSI 及 MISO为下一次表示数据做准备。SPI 每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制。
STM32 的 SPI 特性及架构
STM32F1 的 SPI 功能很强大,SPI 时钟最高可以到 18Mhz,支持 DMA,可以配置为 SPI协议或者 I2S 协议。
STM32 的主模式配置步骤如下:
- 配置相关引脚的复用功能,使能 SPI2 时钟。
我们要用 SPI2,第一步就要使能 SPI2 的时钟,SPI2 的时钟通过 APB1ENR 的第 14 位来设置。其次要设置 SPI2 的相关引脚为复用输出,这样才会连接到 SPI2 上否则这些 IO 口还是默认的状态,也就是标准输入输出口。这里我们使用的是 PB13、14、15 这 3 个(SCK.、MISO、MOSI,CS 使用软件管理方式),所以设置这三个为复用功能 IO。
使能 SPI2 时钟的方法为:
__HAL_RCC_SPI2_CLK_ENABLE(); //使能 SPI2 时钟
复用 PB13,14,15 为 SPI2 引脚通过 HAL_GPIO_Init 函数实现,代码如下:
GPIO_Initure.Pin=GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH; //快速
HAL_GPIO_Init(GPIOB,&GPIO_Initure);
- 设置 SPI2 工作模式
这一步全部是通过 SPI2_CR1 来设置,我们设置 SPI2 为主机模式,设置数据格式为 8 位,然后通过 CPOL 和 CPHA 位来设置 SCK 时钟极性及采样方式。并设置 SPI2 的时钟频率(最大18Mhz),以及数据的格式(MSB 在前还是 LSB 在前)。在 HAL 库中初始化 SPI 的函数为:
HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi);
SPI 初始化实例代码如下:
SPI1_Handler.Instance= SPI2; // SPI2
SPI1_Handler.Init.Mode=SPI_MODE_MASTER; //设置 SPI 工作模式,设置为主模式
SPI1_Handler.Init.Direction=SPI_DIRECTION_2LINES;
//设置 SPI 单向或者双向的数据模式:SPI 设置为双线模式
SPI1_Handler.Init.DataSize=SPI_DATASIZE_8BIT;
//设置 SPI 的数据大小:SPI 发送接收 8 位帧结构
SPI1_Handler.Init.CLKPolarity=SPI_POLARITY_HIGH;
//串行同步时钟的空闲状态为高电平
SPI1_Handler.Init.CLKPhase=SPI_PHASE_2EDGE;
//串行同步时钟的第二个跳变沿(上升或下降)数据被采样
SPI1_Handler.Init.NSS=SPI_NSS_SOFT; //NSS 信号由硬件(NSS 管脚)还是软件
//(使用 SSI 位)管理:内部 NSS 信号有 SSI 位控制
SPI1_Handler.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256;
//定义波特率预分频的值:波特率预分频值为 256
SPI1_Handler.Init.FirstBit=SPI_FIRSTBIT_MSB;
//指定数据传输从 MSB 位还是 LSB 位开始:数据传输从 MSB 位开始
SPI1_Handler.Init.TIMode=SPI_TIMODE_DISABLE; //关闭 TI 模式
SPI1_Handler.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE;
//关闭硬件 CRC 校验
SPI1_Handler.Init.CRCPolynomial=7; //CRC 值计算的多项式
HAL_SPI_Init(&SPI2_Handler);//初始化
同样,HAL 库也提供了 SPI 初始化 MSP 回调函数 HAL_SPI_MspInit,定义如下:
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi);
- 使能 SPI2
__HAL_SPI_ENABLE(&SPI2_Handler); //使能 SPI2
- SPI 传输数据
通信接口当然需要有发送数据和接受数据的函数,HAL 库提供的发送数据函数原型为:
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData,
uint16_t Size, uint32_t Timeout);
这个函数很好理解,往 SPIx 数据寄存器写入数据 Data,从而实现发送。
HAL 库提供的接受数据函数原型为:
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData,
uint16_t Size, uint32_t Timeout);
这个函数也不难理解,从 SPIx 数据寄存器读出接受到的数据。
前面我们讲解了 SPI 通信的原理,因为 SPI 是全双工,发送一个字节的同时接受一个字节,发送和接收同时完成,所以 HAL 也提供了一个发送接收统一函数:
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData,
uint8_t *pRxData, uint16_t Size, uint32_t Timeout);
该函数发送一个字节的同时负责接收一个字节。
设计实现
- SPI1的初始化
以下是SPI模块的初始化代码,配置成主机模式。
void SPI2_Init(void)
{
SPI2_Handler.Instance=SPI2; //SPI2
SPI2_Handler.Init.Mode=SPI_MODE_MASTER; //设置SPI工作模式,设置为主模式
SPI2_Handler.Init.Direction=SPI_DIRECTION_2LINES; //设置SPI单向或者双向的数据模式:SPI设置为双线模式
SPI2_Handler.Init.DataSize=SPI_DATASIZE_8BIT; //设置SPI的数据大小:SPI发送接收8位帧结构
SPI2_Handler.Init.CLKPolarity=SPI_POLARITY_HIGH; //串行同步时钟的空闲状态为高电平
SPI2_Handler.Init.CLKPhase=SPI_PHASE_2EDGE; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
SPI2_Handler.Init.NSS=SPI_NSS_SOFT; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
SPI2_Handler.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256;//定义波特率预分频的值:波特率预分频值为256
SPI2_Handler.Init.FirstBit=SPI_FIRSTBIT_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
SPI2_Handler.Init.TIMode=SPI_TIMODE_DISABLE; //关闭TI模式
SPI2_Handler.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE;//关闭硬件CRC校验
SPI2_Handler.Init.CRCPolynomial=7; //CRC值计算的多项式
HAL_SPI_Init(&SPI2_Handler);//初始化
__HAL_SPI_ENABLE(&SPI2_Handler); //使能SPI2
SPI2_ReadWriteByte(0Xff); //启动传输
}
- 底层驱动,时钟使能,引脚配置
void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOB_CLK_ENABLE(); //使能GPIOB时钟
__HAL_RCC_SPI2_CLK_ENABLE(); //使能SPI2时钟
//PB13,14,15
GPIO_Initure.Pin=GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH; //快速
HAL_GPIO_Init(GPIOB,&GPIO_Initure);
}
- SPI速度设置函数
void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)
{
assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判断有效性
__HAL_SPI_DISABLE(&SPI2_Handler); //关闭SPI
SPI2_Handler.Instance->CR1&=0XFFC7; //位3-5清零,用来设置波特率
SPI2_Handler.Instance->CR1|=SPI_BaudRatePrescaler;//设置SPI速度
__HAL_SPI_ENABLE(&SPI2_Handler); //使能SPI
}
- 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI2_ReadWriteByte(u8 TxData)
{
u8 Rxdata;
HAL_SPI_TransmitReceive(&SPI2_Handler,&TxData,&Rxdata,1, 1000);
return Rxdata; //返回收到的数据
}
后续
如果想了解更多物联网、智能家居项目知识,可以关注我的项目实战专栏和软硬结合专栏。
欢迎关注公众号了解更多。
编写不易,感谢支持。
边栏推荐
- Redis 】 【 publish and subscribe message
- For enterprises in the digital age, data governance is difficult, but it should be done
- Groupid(artifact id)
- 基于极限学习机(ELM)进行多变量用电量预测(Matlab代码实现)
- 谷歌CTS测试(cta测试)
- Comparison of Optical Motion Capture and UWB Positioning Technology in Multi-agent Cooperative Control Research
- 五个维度着手MySQL的优化
- 蔚来杯2022牛客暑期多校训练营4
- jvm 一之 类加载器
- sentinel与nacos持久化
猜你喜欢

为什么要分库分表?

The 232-layer 3D flash memory chip is here: the single-chip capacity is 2TB, and the transmission speed is increased by 50%

Sentinel安装与部署

“听我说谢谢你”还能用古诗来说?清华搞了个“据意查句”神器,一键搜索你想要的名言警句...

对数字化时代的企业来说,数据治理难做,但应该去做

组合系列--有排列就有组合

Essential Learning for Getting Started with Unity Shader - Transparency Effect

Shang Silicon Valley-JVM-Memory and Garbage Collection (P1~P203)

消息队列消息数据存储MySQL表设计

Asynchronous processing business using CompletableFuture
随机推荐
The use of thread pool two
OpenShift 4 - 用 Operator 部署 Redis 集群
LeetCode二叉树系列——110.平衡二叉树
Spark学习(2)-Spark环境搭建-Local
Sentinel流量控制
自适应控制——仿真实验三 用超稳定性理论设计模型参考自适应系统
[Pytorch] torch.argmax() usage
leetcode:485.最大连续 1 的个数
/etc/profile、/etc/bashrc、~/.bash_profile、~/.bashrc 文件的作用
Resnet&API
Nuget打包并上传教程
MySQL【聚合函数】
OAuth2:四种授权方式
Uniapp WeChat small application reference standard components
MANIFEST.MF文件(PDB文件)
Five dimensions to start MySQL optimization
UnityShader入门学习(三)——Unity的Shader
An article makes it clear!What is the difference and connection between database and data warehouse?
三角恒等变换公式
Spark学习(3)-Spark环境搭建-Standalone