问题描述
最近在进行两个stm32芯片spi全双工通信时发现网上对于这种通信的解答不是很全面,大部分都是基于中断处理去进行数据的收发,在查询了手册后发现可以使用轮询的方式查询标志位去进行数据的收发处理于是有了下文。
基本介绍
本例子基于STM32F407
芯片,实现两个芯片间SPI3与SPI3的主从通信。其中SPI3_!配置为主机,SPI3_2配置为从机,均配置为全双工模式。硬件连接图:
其中,我们需要注意的是,SPI的从机不能主动发送数据,只能应答数据。
所以主机使用查询方式发送数据给从机。从机也使用轮询的方式接收并返回自定义的数据,把接收到的数据保存到自定义的接收数据的数组中然后再将自定义的数据发送给主机。从机总是在收到主机的数据时,才会发送数据给从机。即从机被动发送数据,也即主机主动申请数据。
最终效果
下面是实现的效果:
配置和讲解
我的spi配置是自己配置的不是cubmax,当然如果要用cubmax进行配置也不是很难,参考就行
/**
* @brief SPI3初始化代码
* @note 主机模式,8位数据,禁止硬件片选
* @param 无
* @retval 无
*/
void spi3_init(void)
{
SPI3_SPI_CLK_ENABLE(); /* SPI3时钟使能 */
g_spi3_handler.Instance = SPI3_SPI; /* SPI3 */
g_spi3_handler.Init.Mode = SPI_MODE_MASTER; /* 设置SPI工作模式,设置为主模式 */
g_spi3_handler.Init.Direction = SPI_DIRECTION_2LINES; /* 设置SPI单向或者双向的数据模式:SPI设置为双线模式 */
g_spi3_handler.Init.DataSize = SPI_DATASIZE_8BIT; /* 设置SPI的数据大小:SPI发送接收16位帧结构 */
g_spi3_handler.Init.CLKPolarity = SPI_POLARITY_HIGH; /* 串行同步时钟的空闲状态为高电平 */
g_spi3_handler.Init.CLKPhase = SPI_PHASE_1EDGE; /* 串行同步时钟的第二个跳变沿(上升或下降)数据被采样 */
g_spi3_handler.Init.NSS = SPI_NSS_SOFT; /* NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制 */
g_spi3_handler.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; /* 定义波特率预分频的值:波特率预分频值为256 */
g_spi3_handler.Init.FirstBit = SPI_FIRSTBIT_MSB; /* 指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始 */
g_spi3_handler.Init.TIMode = SPI_TIMODE_DISABLE; /* 关闭TI模式 */
g_spi3_handler.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; /* 关闭硬件CRC校验 */
g_spi3_handler.Init.CRCPolynomial = 7; /* CRC值计算的多项式 */
HAL_SPI_Init(&g_spi3_handler); /* 初始化 */
__HAL_SPI_ENABLE(&g_spi3_handler); /* 使能SPI3 */
spi3_read_write_byte(0XFF); /* 启动传输, 实际上就是产生8个时钟脉冲, 达到清空DR的作用, 非必需 */
}
怕有些小白不是很懂配置我就稍微讲讲一些比较重要的配置:
- 首先配置的是spi的主从模式,我这里配置的是主机的所以mode选项我们选择master同理如果是从机配置为slaver即可
- 配置spi的单向双向模式,这里因为我要进行两边的数据交互所以这里选择双线模式
- 数据大小很好理解,你要传送接收的数据是十六位还是八位
- 因为spi时钟是由主机发出,因此此处的配置我们其实任意选择都行,无论是high还是low都可以只是触发是上升触发还是下降触发的区别而已
- 然后看边沿触发是第一个还是第二个触发进行数据交换,也比较简单理解
- 注意ti的模式我这里是关闭的也就是不用中断的方式
基础配置好了之后我们要理一下spi收发数据的逻辑
首先我们的主机要将片选信号打开 那么我这里配置的是 SPI3_CS(0); //打开片选
然后我们调用一个hal库里spi比较常用的一个函数
HAL_SPI_TransmitReceive(&g_spi3_handler, txdata, rxdata, len, 2000);//发送接收数据
去将数据进行收发处理,注意此处有个很重要的地方,如果只是单纯的打开片选后两边都调用这个函数进行数据交互会出现乱码等各种意想不到的问题,当时项目就是这里卡了一会,后来看了一下spi的寄存器配置发现其实解决办法很简单,我们后面说。那我们讲到寄存器了那我们先来看看spi的寄存器配置
这个图在手册里面很好找到,我们只要看sr寄存器的TXE和RXNE标志即可,详细的意思我们可以打开hal库内的说明就知道了
#define SPI_FLAG_RXNE SPI_SR_RXNE /* SPI status flag: Rx buffer not empty flag */
#define SPI_FLAG_TXE SPI_SR_TXE /* SPI status flag: Tx buffer empty flag */
那么我们再通过hal库内的标志位获取函数去进行标志位的判断将数据缓存区内的数据进行逻辑判断和处理就能将时序理清了
这里我就简单贴一下主机的程序
/*----------------------------------------------spi主机自定义函数------------------------------------------------- */
/**
* @brief SPI3主机发送同时接收一组数据
* @param rxdata : 接收数据的数组的指针
* @param txdata : 要发送的数据数组指针
* @param len : 发送接收数据的长度
* @retval None
* @说明:本函数是spi主机发送接收数据函数,具备全双工功能
打开片选功能后进行主从通信,通过标志位进行数据缓存的判断与交互
*/
void SPI3_Master_TransmitReceive_Byte(uint8_t *txdata,uint8_t *rxdata, uint8_t len)
{
SPI3_CS(0); //打开片选
HAL_SPI_TransmitReceive(&SPI3_Handler, txdata, rxdata, len, 2000);//发送接收数据
if(__HAL_SPI_GET_FLAG(&g_spi3_handler, SPI_FLAG_TXE)) //发送缓存区为空
{
if(__HAL_SPI_GET_FLAG(&g_spi3_handler, SPI_FLAG_RXNE)) //接收缓存区不为空时
{
/* 当接收缓存区不为空时的操作 */
// HAL_SPI_Receive(&g_spi3_handler, rxdata, 4, 2000); // 通过SPI3接收数据
}
else
{
SPI3_CS(1); //关闭片选
printf("%s%s",txdata,rxdata); //将发送和接收的数据打印
}
}
}
里面都有比较详细的注释这里就不做讲解
从机的代码这里就不贴出来了,实现也是比较简单的
需要注意的是我这里主机是主动发送数据从机才会触发,因此笔者在主函数里面使用了外部触发的方式将自定义的数据通过mosi发送至从机从机收到数据同步发送回传数据到主机。还有个注意的地方由于数据传输并不是简单的直接互传,而是数据交换,因此如果要从从机拿到数据主机就要发一个数据过去才可以,就算是空的也行。
由于笔者自身能力也有限,如果有其他问题也可以直接联系笔者进行交流^^