STM32主从通信:使用SPI协议实现

目录

前言

一、理论部分     

SPI简介  

SPI特征

SPI物理层

SPI协议层

SPI配置过程

SPI数据发送与接收过程

二、代码部分

主机代码

从机代码


前言

这是一篇学习笔记,记录自己学习SPI通信。方便之后运用的时候回顾。参考《STM32中文参考手册》

一、理论部分     

SPI简介  

         SPI 协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设  

备接口,是一种高速全双工的通信总线。它被广泛地使用在
ADC

LCD
等设备与
MCU
间,

要求通讯速率较高的场合。   

SPI特征


3
线全双工同步传输

● 带或不带第三根双向数据线的双线单工同步传输


8

16
位传输帧格式选择

● 主或从操作

● 支持多主模式


8
个主模式波特率预分频系数
(
最大为
f
PCLK
/2)

● 从模式频率
(
最大为
f
PCLK
/2)

● 主模式和从模式的快速通信

● 主模式和从模式下均可以由软件或硬件进行
NSS
管理:主
/
从操作模式的动态改变

● 可编程的时钟极性和相位

● 可编程的数据顺序,
MSB
在前或
LSB
在前

● 可触发中断的专用发送和接收标志


SPI
总线忙状态标志

● 支持可靠通信的硬件
CRC

        ─ 在发送模式下,
CRC
值可以被作为最后一个字节发送

        ─ 在全双工模式中对接收到的最后一个字节自动进行
CRC
校验

● 可触发中断的主模式故障、过载以及
CRC
错误标志

● 支持
DMA
功能的
1
字节发送和接收缓冲器:产生发送和接受请求

SPI物理层

 SPI通信设备连接图

SPI框图

通常
SPI
通过
4
个引脚与外部器件相连:


MISO
:主设备输入
/
从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。


MOSI
:主设备输出
/
从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。


SCK
:串口时钟,作为主设备的输出,从设备的输入


NSS
:从设备选择。这是一个可选的引脚,用来选择主
/
从设备。它的功能是用来作为“片

选引脚”,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。从设备的
NSS

引脚可以由主设备的一个标准
I/O
引脚来驱动。一旦被使能
(SSOE

)

NSS
引脚也可以作为

输出引脚,并在
SPI
处于主模式时拉低;此时,所有的
SPI
设备,如果它们的
NSS
引脚连接

到主设备的
NSS
引脚,则会检测到低电平,如果它们被设置为
NSS
硬件模式,就会自动进

入从设备状态。当配置为主设备、
NSS
配置为输入引脚
(MSTR=1

SSOE=0)
时,如果
NSS

被拉低,则这个
SPI
设备进入主模式失败状态:即
MSTR
位被自动清除,此设备进入从模式

SPI协议层

数据时钟时序图

SPI配置过程

 SPI主模式配置

1. 通过SPI_CR1寄存器的BR[2:0]位定义串行时钟波特率。
2. 选择CPOL和CPHA位,定义数据传输和串行时钟间的相位关系。
3. 设置DFF位来定义8位或16位数据帧格式。
4. 配置SPI_CR1寄存器的LSBFIRST位定义帧格式。
5. 如果需要NSS引脚工作在输入模式,硬件模式下,在整个数据帧传输期间应把NSS脚连接到高电平;在软件模式下,需设置SPI_CR1寄存器的SSM位和SSI位。如果NSS引脚工作在输出模式,则只需设置SSOE位。
6. 必须设置MSTR位和SPE位(只当NSS脚被连到高电平,这些位才能保持置位)。 在这个配置中,MOSI引脚是数据输出,而MISO引脚是数据输入。

SPI从模式配置

1.
设置
DFF
位以定义数据帧格式为
8
位或
16
位。

2.
选择
CPOL

CPHA
位来定义数据传输和串行时钟之间的相位关系。
为保证正确 的数据传输,从设备和主设备的CPOL

CPHA
位必须配置成相同的方式。

3.
帧格式
(SPI_CR1
寄存器中的
LSBFIRST
位定义的
”MSB
在前

还是
”LSB
在前
”)
必须与主设备相同。

4.
硬件模式下
(
参考从选择
(NSS)
脚管理部分
)
,在完整的数据帧
(8
位或
16

)
传输过程中,NSS引脚必须为低电平。在
NSS
软件模式下,设置
SPI_CR1
寄存器中的
SSM
位并清除
SSI 位。

5.
清除
MSTR
位、设置
SPE

(SPI_CR1
寄存器
)
,使相应引脚工作于
SPI
模式下。 在这个配置中,MOSI
引脚是数据输入,
MISO
引脚是数据输出。

SPI数据发送与接收过程

主模式通讯过程

从模式通讯过程

 

 

接收与发送缓冲器

在接收时,接收到的数据被存放在一个内部的接收缓冲器中;在发送时,在被发送之前,数据

将首先被存放在一个内部的发送缓冲器中。 对SPI_DR
寄存器的读操作,将返回接收缓冲器的内容;写入
SPI_DR
寄存器的数据将被写入发 送缓冲器中。

主模式下开始传输

● 全双工模式

当写入数据到
SPI_DR
寄存器
(
发送缓冲器
)
后,传输开始;


在传送第一位数据的同时,数据被并行地从发送缓冲器传送到
8
位的移位寄存器中,

然后按顺序被串行地移位送到
MOSI
引脚上;


与此同时,在
MISO
引脚上接收到的数据,按顺序被串行地移位进入
8
位的移位寄存器

中,然后被并行地传送到
SPI_DR
寄存器
(
接收缓冲器
)
中。

从模式下开始传输

● 全双工模式

当从设备接收到时钟信号并且第一个数据位出现在它的
MOSI
时,数据传输开始,随

后的数据位依次移动进入移位寄存器;


与此同时,在传输第一个数据位时,发送缓冲器中的数据被并行地传送到
8
位的移位

寄存器,随后被串行地发送到
MISO
引脚上。软件必须保证在
SPI
主设备开始数据传

输之前在发送寄存器中写入要发送的数据。

主或从模式下
全双工发送和接收过程模式

软件必须遵循下述过程,发送和接收数据

1.
设置
SPE
位为
’1’
,使能
SPI
模块;

2.

SPI_DR
寄存器中写入第一个要发送的数据,这个操作会清除
TXE
标志;

3.
等待
TXE=1
,然后写入第二个要发送的数据。等待
RXNE=1
,然后读出
SPI_DR
寄存器并获得第一个接收到的数据,读SPI_DR
的同时清除了
RXNE
位。重复这些操作,发送后续的数据同时接n-1
个数据;

4.
等待
RXNE=1
,然后接收最后一个数据;

5.
等待
TXE=1
,在
BSY=0
之后关闭
SPI
模块。

也可以在响应
RXNE

TXE
标志的上升沿产生的中断的处理程序中实现这个过程。

二、代码部分

    我的想法是在串口助手上发送给主机什么,主机就发什么给从机。之后从机把接收的数据再返回给主机,最终再串口助手上打印出来。验证SPI的全双工通讯。主机和从机都采用中断的方式发送和接收。

主机代码

SPI配置代码

void SPI2_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	SPI_InitTypeDef  SPI_InitStructure;
	
	/* SPI的IO口和SPI外设打开时钟 */
    RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOB, ENABLE );
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;        //PB12推挽输出
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  //
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	/* SPI的IO口设置 */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //PB13/14/15复用推挽输出 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);

	GPIO_SetBits(GPIOB,GPIO_Pin_12);
	
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		//设置SPI工作模式:设置为主SPI
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;		//设置SPI的数据大小:SPI发送接收8位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;		//串行同步时钟的空闲状态为高电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;	//串行同步时钟的第二个跳变沿(上升或下降)数据被采样
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;		//定义波特率预分频的值: 
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
	SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC值计算的多项式
	SPI_Init(SPI2, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
	SPI_Cmd(SPI2, ENABLE); //使能SPI外设
	
	
}

串口中断部分

void USART1_IRQHandler(void)                	//串口1中断服务程序
{
	u16 r,i;
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断
	{
		GPIO_ResetBits(GPIOB,GPIO_Pin_12);
		r =USART_ReceiveData(USART1);//(USART1->DR);	//读取接收到的数据
		while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);//等待发送区空 
		SPI_I2S_SendData(SPI2, r);
		while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET); //等待接收完一个byte	
		i = SPI_I2S_ReceiveData(SPI2);	
		
		GPIO_SetBits(GPIOB,GPIO_Pin_12);
		USART_senddate(USART1,i) ;
	} 
} 	  

从机代码

void SPI2_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	SPI_InitTypeDef  SPI_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	/* SPI的IO口和SPI外设打开时钟 */
    RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOB, ENABLE );
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;        //PB12推挽输出
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //下拉输入
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	/* SPI的IO口设置 */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //PB13/14/15复用推挽输出 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);

	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
	SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;		//设置SPI工作模式:设置为主SPI
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;		//设置SPI的数据大小:SPI发送接收8位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;		//串行同步时钟的空闲状态为高电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;	//串行同步时钟的第二个跳变沿(上升或下降)数据被采样
	SPI_InitStructure.SPI_NSS = SPI_NSS_Hard;		//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;		//定义波特率预分频的值:波特率预分频值为256
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
	SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC值计算的多项式
	SPI_Init(SPI2, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
	
	SPI_Cmd(SPI2, ENABLE); //使能SPI外设
	
	
	SPI_I2S_ITConfig(SPI2,SPI_I2S_IT_RXNE,ENABLE);//开启相关中断
	//Usart1 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = SPI2_IRQn;//SPI2中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;//抢占优先级0
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =2;		//子优先级2
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器、	
	
}
u16 i;
void SPI2_IRQHandler(void) 
{ 
	//接收数据 
	if(SPI_I2S_GetITStatus(SPI2,SPI_I2S_IT_RXNE) != RESET)
	{
		i = SPI_I2S_ReceiveData(SPI2);
		while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);
		SPI_I2S_SendData(SPI2,i);		
		USART_senddate(USART1, i);	
	}
   
} 

物联沃分享整理
物联沃-IOTWORD物联网 » STM32主从通信:使用SPI协议实现

发表评论