【嵌入式】STM32实现SPI双机通信的一些细节(1)

【嵌入式】STM32实现SPI双机通信的一些细节(1)

  • 背景介绍
    • 双机通信需要考虑哪些内容
    • SPI简介
    • 主从机通信构思

背景介绍

实现STM32F405双机通信,拥有明确的主从机关系,通信由主机控制,可以向从机写入数据或者读取数据。目前的需求只涉及一主一从,对于多主多从的情况,了解了基本原理以后我觉得不是很难。实现双机通信花费了我大量的时间,非常痛苦,STM32参考手册以及博客上很多内容都讲得非常含糊。对于硬件片选的内容我至今摸得也不是很清晰,希望如果有朋友了解的话能够分享一下。

双机通信需要考虑哪些内容

这是我第一次接触SPI,先前有请教接触过SPI的朋友,但是他们使用的场景大多是对传感器的读取,传感器本身已经封装了指令和数据更新等功能,主机要做的就是根据传感器手册输入相应的指令然后读取数据即可。
那么,想要实现主从机的读取需要做哪些方面的工作呢?
1.主从机SPI的底层配置;这里面歪门邪道一大堆
2.通信协议的设计;相对比较简单,根据自己的需求设计数据帧格式
3.主从机整体框架搭建;主要是关于中断

SPI简介

这里我就不详细写了,网络上随便一搜就有一大堆,回想起我最开始看SPI方面的内容时,是通过《STM32中文参考手册》以及“正点原子SPI实验”视频和源码进行学习的。网络上的博客可以起到一定的补充作用,最好是在已经大致看过SPI后再去看,感受一下别人的视角。这里放上一个我觉得还可以的博客地址:https://blog.csdn.net/maliangwen_12138/article/details/79199258

主从机通信构思

主从机是完全独立的两个系统,主机与从机之间,是读是写,读写哪里的数据,读写多少数据,这些是从机完全不知道的。所以与读取传感器数据类似,主机必须要先向从机发送指令,让从机知道上述信息,配合主机完成数据交换工作。
了解SPI的同学应该知道,对于全双工SPI通信,主从机之间的收发本质上是两个移位寄存器的数据交换。这里需要注意主机读的情况:主机想读多少数据就要向从机写多少数据,理解了这个就很好办了。

通信协议:由于具体的实现涉及到一些隐私,所以我这里就使用一个简单的例子作为设计通信协议的基础:主从机一定是都知道彼此的数据存储结构的,简单理解就是,主机知道自己写的数据是什么,也知道写到从机的什么地方,从机接收的时候也知道主机写入的数据是希望保存在哪里;主机读也是类似的。最简单的情况就是,主从机双方都有一个相同的结构体(可能会涉及到“共用体”概念,很常用)主从机都明确数据交换是交换的这两个结构体中的数据。那么主机写数据时,提供偏移地址和数据长度,以及实际数据,从机就可以存入结构体对应的位置。具体的协议也是基于这个背景进行设计的。

帧头 0x24 ‘$’ 1byte
主从 0x35 主机发从机收 0x53 主机收从机发 1byte
功能码 自己任意设计,标识当前是主机读还是主机写 1byte
数据长度 实际将要读写的数据长度 1byte
偏移地址 相对结构体首地址的偏移地址 2byte
实际数据 主机读时不包含实际数据,主机写是包含需要写的数据
校验和 1byte

主机端:每次读写时根据实际需要构建一帧数据,通过SPI发送出去。主机写:帧中就已经包含了实际写的数据;主机读:先发送一帧包含目标数据偏移地址、目标数据大小的指令帧,然后再发送无意义的数据将目标数据逐字节地读回来;

从机端:对接收到的帧进行解包,如果当前帧有效做出相应的处理。主机写:当解包得出当前数据帧有效时,就直接将数据存入相应的位置;主机读:从机解包出当前帧有效后,准备实际的数据并组成一帧,由主机控制逐字节地发送回去。

中断机制:正点原子和网络上的一些例子都没有用到SPI的中断,都是使用的ReadWriteByte的方式,即:等待直到发送缓冲区为空,发送一个字节的数据,然后等待接收缓冲区非空,读取交换的数据,我不太喜欢这种过于死板的方式,虽然总听说中断开销大,但是对于我的使用场景暂时还没有考虑缩减这个开销的必要,毕竟我不希望遇到意外情况函数就死循环了,比如我的从机突然掉电,主机就一直等着了。
【嵌入式】STM32实现SPI双机通信的一些细节(1)_第1张图片

这里我原本没有花费太多心思在里面,但是仔细想了一下发现这对我后续,使用中断进行数据收发的整个框架设计都至关重要:
首先先介绍一下SPI全双工收发一个字节的流程:
【嵌入式】STM32实现SPI双机通信的一些细节(1)_第2张图片
SPI两个主要中断的介绍:

中断 触发时刻
SPI发送中断 当发送缓冲区为空时触发
SPI接收中断 当接收缓冲区非空时触发

说明

  • 单个字节发送时,一定是先触发发送中断然后再触发接收中断,这对于后续的框架设计至关重要,这意味着,在接收到实际数据进行处理前总会先触发发送中断向发送缓冲区中写入一个字节,例如:主机读取从机数据,在从机接收到主机帧数据最后一个字节之前,发送缓冲区里已经放入了一个无效字节。只有这个无效字节放入移位寄存器后,从机才能向发送缓冲区内放入主机实际想要读取的数据(从机同样是按照通信协议构造了一个完整帧),也就是说,主机在发送完指令帧之后,需要读取一个额外的无效字节而后才能接收到从机的帧数据;
  • 主机在初次发送数据前,发送缓冲区就是空的,会在初始化SPI后立即触发中断,这样是不合理的。因此发送中断的使能应当在准备好实际要发送的数据后。从机则不存在这个问题,反而从机需要在初始化后立即向发送缓冲区中放入一个无效字节等待主机的交换;
  • 这里还有一个隐含的比较重要的时序问题:发送一个字节是先放入缓冲区,从缓冲区传入移位寄存器时触发发送中断用来向发送缓冲区放入下一个字节,移位寄存器完成数据交换后放入接收缓冲区触发接收中断。那么:数据从移位寄存器传入到接收缓冲区触发接收中断发送缓冲区中的数据放入移位寄存器触发下一个发送中断谁先谁后,这里关乎到我能否根据接收到的实际数据去影响下一个发送的字节。《STM32中文参考手册》中的一张图给出了答案【嵌入式】STM32实现SPI双机通信的一些细节(1)_第3张图片
// SPI中断函数框架
void SPI1_IRQHandler(void)
{
  if(SPI_I2S_GetFlagStatus(User_SPI, SPI_I2S_FLAG_TXE) != RESET)	//发送缓冲区为空
	{}
	if(SPI_I2S_GetFlagStatus(User_SPI, SPI_I2S_FLAG_RXNE) != RESET)	//接收缓冲区非空
	{}
}

你可能感兴趣的:(嵌入式)