前言
关于如何简单的写一个稳定的SPI主从机通讯,思路很简单
1、SPI高速传输的时候很容易出现错位之类的问题,CRC的校验首先是必要的。在STM32中SPI使用DMA通讯可以自动执行CRC的校验,只需要在CubeMX设置SPI的CRC和DMA
2、从机的NSS用硬件模式不是很好用,使用外部中断可以提供更多灵活的编写方式
3、有个很坑的点,如果中间出现断连等问题,很容易出现数据错位,而且错了一次会一直错下去,所以检测到CRC错误后要自己将SPI模块的时钟重置
20250107更新,我终于改出一版完全可用的多主从机方案了,总结以下
关于从机
1、Normal的DMA方案可以让整个程序变得更加简单,且circular模式不支持CRC循环
2、CRC是必须的,SPI通讯中出现传输错误是非常致命的
3、硬件NSS用起来不错,很值得使用
4、核心的回调使用HAL_SPI_TxRxCpltCallback和HAL_SPI_ErrorCallback,可以处理掉所有问题
CubeMX设置
SPI设置
推荐勾选CRC和硬件的NSS,CRC校验方式要和主机相同
这里Mode非常不推荐使用Circular,官方文件里明确写了Circular模式不支持CRC,不带CRC校验很难避免传输错误
我选的16bit传输,具体可以根据自己的硬件需求再改
SPI从机代码
放在哪里都行
//数据接收处理
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi == &hspi1)
{
if(HAL_SPI_GetError(&hspi1) == HAL_SPI_ERROR_NONE)//我个人觉得这个也不是很需要
{
memcpy(tgt_buf, spi1_rx_buf, 8);
HAL_SPI_TransmitReceive_DMA(&hspi1, (uint8_t*)spi1_tx_buf, (uint8_t*)spi1_rx_buf, 4);//开了normal所以要重新开启
}
}
}
//通讯错误处理
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
if (hspi == &hspi1)
{
HAL_SPI_DeInit(&hspi1);
HAL_SPI_Init(&hspi1);
HAL_SPI_TransmitReceive_DMA(&hspi1, (uint8_t*)spi1_tx_buf, (uint8_t*)spi1_rx_buf, 4);
}
}
//系统初始化之后记得增加一句
HAL_SPI_TransmitReceive_DMA(&hspi1, (uint8_t*)spi1_tx_buf, (uint8_t*)spi1_rx_buf, 4);
SPI主机代码
主机直接定时跑就够了
HAL_GPIO_WritePin(GPIOX, GPIO_PIN_X, GPIO_PIN_RESET);
HAL_SPI_TransmitReceive_DMA(&hspi1, (uint8_t*)spi1_tx_buf, (uint8_t*)spi1_rx_buf, 4);
HAL_GPIO_WritePin(GPIOX, GPIO_PIN_X, GPIO_PIN_SET);
CRC校验会自动在DMA传输完后自动再传输
总结讨论
网上讨论关于SPI从机的方案有很多,实测很多使用方法都有些问题
1、关于CRC是否需要让size+1,原文这里写的是pRxData要size+1,我个人理解是说Rx的缓冲区要比预期的大1,而不是说最后的size要写5。实际测试的时候,如果主机发4,从机接5,最后一位接到的是CRC的校验值,并且SPI模块自动的CRC会出错,所以我个人倾向是数据传输多少size就写多少
多次测试主机也是,比如你计划发送4,DMA传输中CRC会自动在第四位之后发送
此外关于pRxData是否需要+1我觉得比较可疑,实机运行过程中并不会观察到pRxData第五位内容的变化,保险起见可以在创建缓冲区的时候设计大一点
2、关于HAL_SPI_TxRxCpltCallback和HAL_SPI_ErrorCallback的回调触发,根据stm32g0xx_hal_spi.c文件中,任何通讯中的错误都会触发HAL_SPI_ErrorCallback,而只要触发HAL_SPI_ErrorCallback就不会触发HAL_SPI_TxRxCpltCallback,所以我个人觉得在HAL_SPI_TxRxCpltCallback中拿到的pRxData应该就是完全正确的,其实也并不需要额外检测下是否有通讯错误(包括CRC也会在其中校验出)。
另外关于关于通讯出错也是件相当胃疼的事情,在之前的测试中,通讯时序错位等等等都会导致通讯出问题,而且就像很多博主所说的连着后边的通讯统一错位(类似于3412的循环错误)。目前测试在使用硬件NSS方案中,在回调HAL_SPI_ErrorCallback的时候对spi模块进行重新初始化
我暂时没遇到时序错位的情况,如果有,可以重置下时钟
__HAL_RCC_SPI1_FORCE_RESET();
__HAL_RCC_SPI1_RELEASE_RESET();
3、实测过程中,在不使用硬件NSS的时候,多从机相互之间干扰也很严重,所以我推测使用软件NSS会导致非片选阶段NSS也会触发SPI的DMA通讯继续,后来使用硬件NSS就没这个问题了。
4、如果使用CRC校验的话,注意DMA别选circular,这俩是冲突的,没了CRC通讯非常容易跑飞,更建议在每次通讯完成后手动再设计新的传输