stm32 SPI主从通信总结及解决办法

stm32 SPI主从通信总结

前言

由于项目需求,需要做一个stm32的SPI从机模式,之前都是主机模式,没搞过从机,
研究了3天,目前通信可以说是正常,写文章记录一下。基本的配置和协议我就不说了,只说我遇到的主要问题。

验证环境

1.硬件环境
主机使用stm32F405 从机使用stm32F103。
F4系列和F1系列SPI配置大致一样,注意GPIO的设置就行了。
主机无中断,从机接受中断。
2.通信过程
主机发送提前约定的协议,发送命令后,读取数据。读取的数据可以是多个,不过要提前定义好。

遇到的问题

1.数据移位问题:主机第一次读都是00,后面读取的数据都是前一次应该读取的数据;
2.正常通信后,主机重启,从机不重启,数据通信异常。

这些问题其实都是因为SPI是全双工通信,没有等待的时间,难以控制,所以通信过程必须密切配合,从机处理的速度必须要快,否则主机读取的数据就不是从机后来放入的数据。

解决方法

这里的解决方法我不保证绝对正确,至少在我的环境里验证是正确的,仅供大家参考,可以交流。
问题1:在主机读取的时候添加延时,这样做我不知道合不合理,但是确实解决了。代码见下。
问题2:这是因为片选的原因。主机配置成软件模式,从机配置成硬件模式。代码见下。

前提是基本配置正确,除了SPI工作模式和NSS模式,其他的比如时钟状态和数据捕获,主从机都要一致。

相关代码

主机SPI主要配置:

SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置 SPI 全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置 SPI 工作模式:主 SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置 SPI 的数据大小: 8 位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//串行同步时钟的空闲状态为高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //数据捕获于第二个时钟沿
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS 信号由软件管理
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; //预分频 2
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从 MSB 位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC 值计算的多项式

从机SPI主要配置

SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置 SPI 全双工
SPI_InitStructure.SPI_Mode = SPI_Mode_Slave; //设置 SPI 工作模式:从SPI
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置 SPI 的数据大小: 8 位帧结构
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;//串行同步时钟的空闲状态为高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //数据捕获于第二个时钟沿
SPI_InitStructure.SPI_NSS = SPI_NSS_Hard; //NSS 信号由硬件管理
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; //预分频 2
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从 MSB 位开始
SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC 值计算的多项式

中断比较好配置,我就不写了,就是打开接受中断,配置下NVIC。
主机发送函数:

int hwd_spi_master_send(SPI_CB *dev,u8 *pData)
{
	uint8_t* point = pData;
    u8 ret = 0;
	if(point == NULL)
	{
		HWD_LOG(LOGEVENT, "spi_write error, send pData NULL");
		return HWD_STATUS_FAILURE;
	}
	while(SPI_I2S_GetFlagStatus(dev->SPIx, SPI_I2S_FLAG_TXE) == RESET);
	SPI_I2S_SendData(dev->SPIx, *point); 
	while (SPI_I2S_GetFlagStatus(dev->SPIx, SPI_I2S_FLAG_RXNE) == RESET); 
	
	ret = SPI_I2S_ReceiveData(dev->SPIx); //无效
    //printf("\r\nret = 0x%x",ret);

	return HWD_STATUS_OK;
}

代码跟网上的都一样。
主机接收函数:

int hwd_spi_master_read(SPI_CB *dev,u8 *pData)
{
	uint8_t* point = pData;
	
	if(point == NULL)
	{
		HWD_LOG(LOGEVENT, "spi_read error, read pData NULL");
		return HWD_STATUS_FAILURE;
	}
	while (SPI_I2S_GetFlagStatus(dev->SPIx, SPI_I2S_FLAG_TXE) == RESET);

	hwd_DelayMS(5);//延时
	SPI_I2S_SendData(dev->SPIx, 0xFF);	  //发送与应用不相关的数据,为后续的读数据提供时钟信号
	while (SPI_I2S_GetFlagStatus(dev->SPIx, SPI_I2S_FLAG_RXNE) == RESET); 
	*point = SPI_I2S_ReceiveData(dev->SPIx); 

	return HWD_STATUS_OK;
}

发送函数跟网上的都一样,但是我加了一步延时,如果没有这一步,或者换位置,数据就会异常,可能出错,也可能移位,跟从机的处理速度有关。之前就是我在从机的发送函数里加了几个打印,出现数据移位,我以为没有办法解决,然后调试完成去掉打印后,通信正常,对了,前提是主机要加上延时。我不确定这里加延时是否合理,我的理解就是主机等待上一次发送的数据完成后,延时一段时间等待从机那边把需要的数据放入到DR寄存器中,然后主机再读取。如果不合理请指正。

从机中断处理:

void hwd_SPI_IrqHandler(int SpiIndex)
{
    portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
    char cChar = 0;

    cChar = SPI_I2S_ReceiveData(g_SpiCb[SpiIndex].SPIx);
    if(g_SpiCb[SpiIndex].xRxedChars != serINVALID_QUEUE)
        xQueueSendFromISR(g_SpiCb[SpiIndex].xRxedChars, 
            &cChar, &xHigherPriorityTaskWoken);

    //上下文切换
    portEND_SWITCHING_ISR(xHigherPriorityTaskWoken);  
}

这里不用纠结函数名的问题,我在外面加了一层函数,大家看函数内部就行了。
因为使用了freeRtos,所以使用的队列函数,这里不使用直接接收处理应该也是可以的,我没试过。中断直接处理我担心处理的速度问题,因为在我这里收到数据直接发送给队列后继续等待,处理函数在另一个地方。不会占用太多中断函数处理的时间,如果直接处理可能会有问题。大家可以试下。
从机发送函数:

int hwd_spi_slave_send(SPI_CB *dev,u8 *pData)
{
    uint8_t* point = pData;

	if(point == NULL)
	{
		HWD_LOG(LOGEVENT, "spi_write error, send pData NULL");
		return HWD_STATUS_FAILURE;
	}

	while(SPI_I2S_GetFlagStatus(dev->SPIx, SPI_I2S_FLAG_TXE) == RESET);
	SPI_I2S_SendData(dev->SPIx, *point); 
	//while(SPI_I2S_GetFlagStatus(dev->SPIx, SPI_I2S_FLAG_RXNE) == RESET);
	//SPI_I2S_ReceiveData(dev->SPIx); //无效
	
    return HWD_STATUS_OK;
}

从机发送函数跟主机不太一样,从机发送不需要再读取。

总结

结果我就不发了,通信都是正常的,我这里主要就是两点:
1.主机NSS要配成软件模式,从机要配成硬件模式。
2.主机读取加延时函数。

SPI主从通信问题很多,需要多试试,我的解决办法都是我试了很多次才成功的,大家可能用了我这些也还是有问题,自己多试试。

补充一下:ST官方手册里写的很清楚,数据移位是正常的,如果没有必要,那就不用强制把它搞成数据同步的。
stm32 SPI主从通信总结及解决办法_第1张图片

你可能感兴趣的:(stm32 SPI主从通信总结及解决办法)