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官方手册里写的很清楚,数据移位是正常的,如果没有必要,那就不用强制把它搞成数据同步的。
在这里插入图片描述

  • 4
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
STM32F103是一款基于ARM Cortex-M3内核的微控制器,它具有丰富的外设和通信接口,包括SPI(串行外设接口)。SPI是一种同步串行通信协议,可以用于主从设备之间的通信。 在STM32F103中,你可以使用SPI来实现主从通信。主设备负责控制通信的时序和数据传输,从设备则根据主设备的控制进行响应。以下是一个简单的SPI主从通信的步骤: 1. 配置引脚:首先,你需要配置相应的引脚作为SPI的时钟、数据输入和输出线。在STM32F103中,SPI引脚的功能可以通过GPIO Alternate Function来设置。 2. 初始化SPI:使用STM32Cube软件包或者手动编写代码来初始化SPI。你需要设置SPI的工作模式(主模式或从模式)、数据位宽、时钟分频等参数。 3. 发送数据(主设备):主设备通过向SPI数据寄存器写入要发送的数据来发送信息。发送完成后,等待SPI传输完成的标志位。 4. 接收数据(从设备):从设备通过读取SPI数据寄存器来接收主设备发送的数据。接收完成后,从设备可以进行相应的处理。 5. 响应数据(从设备):从设备可以根据接收到的数据进行相应的处理,并将结果发送回主设备。 以上是一个简单的SPI主从通信的流程。当然,在实际应用中,你可能还需要考虑中断处理、错误处理、数据校验等方面的内容。具体操作可以参考STM32F103的相关文档和例程。 希望这些信息对你有所帮助!如果你还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值