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通信设备连接图

STM32使用SPI协议主从通信_第1张图片

SPI框图

STM32使用SPI协议主从通信_第2张图片

通常 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协议层

数据时钟时序图
STM32使用SPI协议主从通信_第3张图片

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数据发送与接收过程

主模式通讯过程

STM32使用SPI协议主从通信_第4张图片

从模式通讯过程

STM32使用SPI协议主从通信_第5张图片

 

 

接收与发送缓冲器
在接收时,接收到的数据被存放在一个内部的接收缓冲器中;在发送时,在被发送之前,数据
将首先被存放在一个内部的发送缓冲器中。 对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);	
	}
   
} 

你可能感兴趣的:(stm32,单片机,物联网)