技术文档

单片机spi从通信 SPI通信协议:单片机spi通信接口什么意思,spi接口干什么用的?

小编 2025-08-05 技术文档 23 0

SPI通信协议:单片机spi通信接口什么意思,spi接口干什么用的?

讲真,以前做开发的时候最怕就是调spi和iic。

因为公司没有逻辑分析仪,调起来全凭经验,一出问题找都找不到,只能仔细看代码盲调,看是不是哪个时序有问题。

说到这里,可能刚初学的小伙伴会问:单片机spi通信是什么鬼?spi接口是干什么用的?

一、单片机spi通信是什么鬼?

单片机是一个可编程的微控制器,和spi通信其实是两个独立的东西。

只不过我们用单片机可以去实现spi通信而已,但是实现spi通信不一定非要单片机。

我们人和人之间沟通,要靠说话,并且两个人语言要一样,比如说都用普通话。

那芯片和芯片沟通,它们又不会说话,怎么传递信息呢?

就是通过通信总线,通信总线有很多种,比如IIC、SPI、USART、CAN,它们的区别就像普通话、英语、韩语、俄语,都是用来传递信息用的,只是传递方式不同

但传递信息,有一个前提,就是语言要相同,也就是通信方式要相同。

所以说,spi是一种通信方式,是用来传递信息的

不同的通信方式,传递信息的方式不同。

Spi是一种全双工、高速的、同步 的通信总线。

简单来说,就是你们两个可以同时快速地说话,你能把你要说的告诉对方,同时也能听到对方说什么,这就是全双工

还有一种叫半双工,就是你们同时只能有一个人说,另一个听,说完了,另外一个才能说。

很明显,全双工传递信息的效率更高 吧。

同步的意思我给你们两个对话设定个条件,比如说我说开始你们才可以对话,我说停止就都不能说话。

二、spi接口是干什么用的?

我们人和人传递信息一般是靠耳朵和嘴巴对吧?

Spi通信传递信息是通过接口,来看下图:

spi接口有4根线,分别是CS、SCLK、MOSI、MISO

SCLK是同步信号 ,一般由主控来控制。

既然是通信,自然是需要有个”对象”,如上图A和B。

A是SPI Master,也就是主控,比如说单片机,一般就是主控的角色,主控来负责发送SCLK同步信号通知SPI Slave是否需要进行数据通信。

B是SPI Slave,也就是从机,比如说Flash芯片。

CS代表片选信号 ,为什么要用片选呢?

因为一个SPI Master可以跟不同的SPI Slave进行通信。

SPI Master就是通过CS来控制,具体跟哪个SPI Slave通讯,控制CS为低电平就代表选中该SPI Slave。

如果说只跟一个从设备通讯,也可以直接把CS串联电阻接地,省的每次都去控制它。

MOSI英文全称是Master Output Slave Input,这个一般接主控器件数据输出引脚,从机器件数据输入引脚。

MISO英文全称是Slave Input Master Output,这个一般接主控器件数据输入引脚,从机器件数据输出引脚。

除此以外,还有一种特殊的用法,就是只用SCLK和MOSI这两个引脚的SPI通信。

比如说我们无际单片机编程实战项目课程里有个OLED屏的应用。

片选引脚CS我直接硬件接地了,因为只有一个从设备。

我们主要是控制OLED屏去显示内容,并不需要从OLED读取数据,所以MISO也不需要。

这种情况是可以只用2根线的。

所以,这种协议要从本质上去理解它们,不需要死记硬背要接几个引脚,你学废了吗?

更多单片机开发实战干货可以到无际单片机编程。

还在苦恼单片机通讯吗?SPI双机通讯带你飞!

由于项目的需要,需要使用两个STM32的单片机进行双机通讯。以前使用SPI都是使用spi外挂类似FLASH或者显示屏之类的片子。不需要从机进行反数据。本来以为会很简单。没想到在实际调试中却发现了一些问题,耽搁了两天才调试完成。现在谈谈我的一些经过。也为有类似问题的同学提供一些参考。

我使用的是stm32f103单片机的SPI1。PA5,PA6,PA7三个引脚的硬件spi。下面我把程序拆分粘贴上来。谈谈自己调试中遇到的问题,跟解决方案。

首先要说明的一个就是硬件的接线。SPI接线不能跟串口一样交叉。这点一定要注意。

MISO---------MISO

MOSI---------MOSI

SCK------------SCK

主机片选IO可自己选----------从机NSS引脚PA4

首先在IO引脚这里的配置一定要注意。有些人直接都配置成AF_PP,这样在主机通讯的时候是没有问题的,但是在从机也这样配置就会出现错误。因为从机是不能发送时钟信号的需要接受主机产生的时钟,所以需要配置成浮空输入。具体程序如下

主机*******************************************************************************

//**********************配置GPIO管脚*********************************

void GPIO_Configuration(void)

{

GPIO_InitTypeDef GPIO_InitStructure;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //推挽复用

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7;//sck mosi

GPIO_Init(GPIOA,&GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;//miso

GPIO_Init(GPIOA,&GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;//cs

GPIO_Init(GPIOA,&GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//cs

GPIO_Init(GPIOB,&GPIO_InitStructure);

}

从机**********************************************************************************

//**********************配置GPIO管脚*********************************

void GPIO_Configuration(void)

{

GPIO_InitTypeDef GPIO_InitStructure;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7;//sck mosi

GPIO_Init(GPIOA,&GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;//miso

GPIO_Init(GPIOA,&GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;//cs

GPIO_Init(GPIOA,&GPIO_InitStructure);

}

配置完IO接下来就要配置spi的初始化函数了。这里我在网上看到有人说CPOL,CPHA的配置不能完全一样。说会产生数据位的错误。但是官方的数据手册说是应该配置成一样的。通过我自己试验发现配置成一样的并没有发生数据位错误的现象。也许是单片机的原因吧。反正我是配置成一样的 。

主机***************************************************************************************

//**********************SPI初始化函数*********************************

void SPI_Configuration(void)

{

SPI_InitTypeDef SPI_InitStructure;

SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;

SPI_InitStructure.SPI_Mode = SPI_Mode_Master;

SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;

SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;

SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;

SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;

SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;

SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//SPI_FirstBit_MSB;

SPI_InitStructure.SPI_CRCPolynomial = 7;

SPI_Init(SPI1, &SPI_InitStructure);

SPI_Cmd(SPI1, ENABLE);

}

从机**********************************************************************************************

//**********************SPI初始化函数*********************************

void SPI_Configuration(void)

{

SPI_InitTypeDef SPI_InitStructure;

SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;

SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;

SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;

SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;

SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;

SPI_InitStructure.SPI_NSS = SPI_NSS_Hard;

SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;

SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//SPI_FirstBit_MSB;

SPI_InitStructure.SPI_CRCPolynomial = 7;

SPI_Init(SPI1, &SPI_InitStructure);

SPI_Cmd(SPI1, ENABLE);

}

因为从机的片选需要用主机进行控制。(实际上我是6片单片机进行的SPI通讯。)所以 SPI_InitStructure.SPI_NSS = SPI_NSS_Hard; 这里从机一定要配置成hard硬件片选模式。只有这样才能用主机的IO才可以控制从机

接下来其实就是最关键的地方了也是我出现了很多问题的地方。我先说我最开始直接用官方的库函数出现的问题。主机从机的代码这里是一样的。我先发一个有问题的。

u8 SPI_Send_Byte(u8 byte)

{

while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET); //判断是否忙,不忙的时候才发送数据

SPI_I2S_SendData(SPI1, byte); //发送数据

while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)==RESET); //判断接收标志

return SPI_I2S_ReceiveData(SPI1); //返回接收数据

}

这里使用这个函数用主机给从机发送数据的时候是完全没有问题的。数据可以很稳定的发送和接收。但是当从机给主机发送的时候就会发生数据错乱的问题。所以从机反数据的时候我浪费了很长的时间去测试。包括使用示波器观察。因为spi是全双工通信的。所以无论你怎样工作数据收发都是同步进行的。而芯片内部寄存器实际上只有一个来存储这些数据。如果没有及时的清空就会对数据产生影响。所以用这个函数就会发生紊乱。

那么要解决这个函数的问题,实际上就要把数据分开发送,就是一个字节一个字节的发送。发一个的同时接受一个。一发一收永远同步进行。因此我这里套用了一个别人的代码。不过当时这个代码还是有问题的。一会再说。先发一个最原始的代码。

网上的原始代码*************************************************************************************************

u8 SPI_Send_Byte(u8 byte)

{

while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET); //判断是否忙,不忙的时候才发送数据

SPI_I2S_SendData(SPI1, byte); //发送数据

while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)==RESET); //判断接收标志

return SPI_I2S_ReceiveData(SPI1); //返回接收数据

}

u8 SPI1_ReadWriteByte(u8 TxData) //这个代码是抄的原子大哥的,我就不BB了

{

u16 retry=0;

u8 data;

while((SPI1->SR&1<<1)==0)//等待发送区空

{

retry++;

if(retry>0XFFFE)return 0;

}

SPI1->DR=TxData; //发送一个byte

retry=0;

while((SPI1->SR&1<<0)==0) //等待接收完一个byte

{

retry++;

if(retry>0XFFFE)return 0;

}

data=SPI1->DR;

return SPI1->DR; //返回收到的数据

}

这段代码表面看起来天衣无缝。并且使用主机发送的时候也是没有问题的。当我用从机返回数据的时候我还是出现了之前数据错误的问题。这下我纠结了。理论上是不应该有问题。因此还是只能从程序上找问题。其实这个问题就是最后一句话

return SPI1->DR; //返回收到的数据

因为从机需要及时的把数据发送出去,如果还是返回SPI1->DR; 这个寄存器的数据其实已经不是刚刚的数据了,因为只有一个寄存器,而且全双工一直在工作。这时的寄存器已经是一个不稳定状态了,数据当然会发生紊乱。所以这里需要直接返回的是这个变量的值。return SPI1->DR; 改成return data; 这样就可以了,因为变量的值是不会被刷新的。最后实际测试也是完全正确的。从机也可以很稳定的发送数据。

下面我发送一下我的主函数。测试程序。

主机***************************************************************************************

#include "pbdata.h"

u8 a;

u8 b;

int main(void)

{

RCC_Configuration();

GPIO_Configuration();

SPI_Configuration();

USART_Configration();

NVIC_Configuration(); //中断优先级配置

// GPIO_ResetBits(GPIOB,GPIO_Pin_5);//从机开始工作

// GPIO_SetBits(GPIOB,GPIO_Pin_5);//从机停止工作

while(1)

{

if(a==1)

{

GPIO_ResetBits(GPIOB,GPIO_Pin_5);//从机开始工作

if(b==1)

{

// while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE)==RESET); //判断是否忙,不忙的时候才发送数据

// // SPI1->DR=spisend1;//发送

// SPI_I2S_SendData(SPI1, spisend1); //发送数据

// while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE)==RESET); //判断接收标志

// // spivalue1=SPI1->DR;//接收

// spivalue1=SPI_I2S_ReceiveData(SPI1);

u8 a=8;

while(a--)

{

spivalue1=SPI1_ReadWriteByte( spisend1);

}

}

}

if(a==0)

{

GPIO_SetBits(GPIOB,GPIO_Pin_5);//从机停止工作

}

delay_ms(2);

}

}

这里的a,b是为了在线调试的时候对片选和收发开关进行控制。

从机***********************************************************************************************

#include "pbdata.h"

u8 a;

int main(void)

{

RCC_Configuration();

GPIO_Configuration();

SPI_Configuration();

USART_Configration();

NVIC_Configuration(); //中断优先级配置

while(1)

{ u8 a=8;

if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)==0)

{

while(a--)

{

spivalue2= SPI1_ReadWriteByte(spisend2); //这个代码是抄的原子大哥的,我就不BB了

}

}

}

}

while(a--)这个是因为我发送的数据是8帧的。所以分8次发送。

spisend和spivalue是两个全局变量。在线调试的时候直接观察自己收发的数据。

spi一般用作片间通讯较多。很少使用板间通讯。因此我也在担心不稳定的问题。所以我特地亲自只做了3根长度2米5左右的线进行了测试。没有添加上拉电阻等原件。实际测试发现数据还是很稳定的。因此可以判定在这个距离以内是没有必要去担心的。另外提醒大家在做这个实验的时候一定要记得两个电路板要共地不要忽略了这个。

相关问答

飞思卡尔两个 单片机 如何进行 SPI通信 ?

飞思卡尔S12系列的单片机基本上都有SPI接口,而且一般可以切换复用的,比如XS128就可以用PS4-7或者PM口进行通信,2个单片机进行SPI通信的时候,普通模式下需要4...

器件怎样与 单片机 通讯?

器件与单片机可以通过spi或者i2c通讯,目前很多传感器或者存储芯片通过这些通讯方式与单片机进行通讯。采用这些通讯方式的好处是,第一可以节省硬件资源,一般...

传感器 单片机通信 原理?

传感器单片机通信是指通过传感器将感应到的物理量转变为数字信号后,通过串口、I2C、SPI等通信协议传输到单片机,然后对信号进行采集、处理和分析。其中,传感...

51 单片机 spi 协议吗?

有很多型号的51单片机具备SPI接口,内部通过特殊功能寄存器方式实现SPI通讯协议,例如STC系列51单片机几乎都有SPI接口。对于没有SPI接口的51单片机,也可以通过...

光电耦合器和 单片机 通信 方式?

光电耦合器和单片机之间的通信方式可以通过串口通信实现。光电耦合器将光信号转换为电信号,然后通过串口发送给单片机。单片机接收到电信号后,可以解析并处理...

单片机SPI 编程?

1.定义三个gpio:p0-sclk,p1-sdi,p2-sdo;p0用于模拟spi的clock,p1用于接收数据,p2用于f发送数据;硬件上单片机A的p0接单片机B的p0,A的p1接B的p2,...

如何利用 单片机 实现无线 通信 ?

可以采用nordic公司的无线收发芯片nR905,其数据传输率可以达到78.5kbps,可以实现高速半全双工通信。nR905通过SPI接口和MCU进行数据传送,在配合几个简单控制...

单片机 中 ADC 是如何进行采集的?

摘要:本文设计并实现了基于2.4GHzISM频段射频收发芯片nRF2401的计算机短距离无线数据采集系统。该系统采用PC作为系统控制中心,以C8051F021单片机...

单片机 中 ADC 是如何进行采集的?

摘要:本文设计并实现了基于2.4GHzISM频段射频收发芯片nRF2401的计算机短距离无线数据采集系统。该系统采用PC作为系统控制中心,以C8051F021单片机...

单片机 和外设(或外部系统)的 通信 方式除了串口 通信 之外还有那...

1-line单总线,IIC总线通信I平方C,SMBUS,SPI.SPI,I2C总线都可以阿

猜你喜欢