当前位置:   article > 正文

STM32使用SPI协议主从通信_stm32 spi中断使用

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配置代码

  1. void SPI2_Init(void)
  2. {
  3. GPIO_InitTypeDef GPIO_InitStructure;
  4. SPI_InitTypeDef SPI_InitStructure;
  5. /* SPI的IO口和SPI外设打开时钟 */
  6. RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );
  7. RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
  8. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //PB12推挽输出
  9. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //
  10. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  11. GPIO_Init(GPIOB, &GPIO_InitStructure);
  12. /* SPI的IO口设置 */
  13. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
  14. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15复用推挽输出
  15. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  16. GPIO_Init(GPIOB, &GPIO_InitStructure);
  17. GPIO_SetBits(GPIOB,GPIO_Pin_12);
  18. SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
  19. SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
  20. SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b; //设置SPI的数据大小:SPI发送接收8位帧结构
  21. SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步时钟的空闲状态为高电平
  22. SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
  23. SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
  24. SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //定义波特率预分频的值:
  25. SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
  26. SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
  27. SPI_Init(SPI2, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
  28. SPI_Cmd(SPI2, ENABLE); //使能SPI外设
  29. }

串口中断部分

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

从机代码

  1. void SPI2_Init(void)
  2. {
  3. GPIO_InitTypeDef GPIO_InitStructure;
  4. SPI_InitTypeDef SPI_InitStructure;
  5. NVIC_InitTypeDef NVIC_InitStructure;
  6. /* SPI的IO口和SPI外设打开时钟 */
  7. RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );
  8. RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
  9. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //PB12推挽输出
  10. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //下拉输入
  11. GPIO_Init(GPIOB, &GPIO_InitStructure);
  12. /* SPI的IO口设置 */
  13. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
  14. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //PB13/14/15复用推挽输出
  15. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  16. GPIO_Init(GPIOB, &GPIO_InitStructure);
  17. SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
  18. SPI_InitStructure.SPI_Mode = SPI_Mode_Slave; //设置SPI工作模式:设置为主SPI
  19. SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b; //设置SPI的数据大小:SPI发送接收8位帧结构
  20. SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步时钟的空闲状态为高电平
  21. SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
  22. SPI_InitStructure.SPI_NSS = SPI_NSS_Hard; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
  23. SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //定义波特率预分频的值:波特率预分频值为256
  24. SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
  25. SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
  26. SPI_Init(SPI2, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
  27. SPI_Cmd(SPI2, ENABLE); //使能SPI外设
  28. SPI_I2S_ITConfig(SPI2,SPI_I2S_IT_RXNE,ENABLE);//开启相关中断
  29. //Usart1 NVIC 配置
  30. NVIC_InitStructure.NVIC_IRQChannel = SPI2_IRQn;//SPI2中断通道
  31. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;//抢占优先级0
  32. NVIC_InitStructure.NVIC_IRQChannelSubPriority =2; //子优先级2
  33. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
  34. NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、
  35. }
  36. u16 i;
  37. void SPI2_IRQHandler(void)
  38. {
  39. //接收数据
  40. if(SPI_I2S_GetITStatus(SPI2,SPI_I2S_IT_RXNE) != RESET)
  41. {
  42. i = SPI_I2S_ReceiveData(SPI2);
  43. while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);
  44. SPI_I2S_SendData(SPI2,i);
  45. USART_senddate(USART1, i);
  46. }
  47. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/50837
推荐阅读
相关标签
  

闽ICP备14008679号