问题描述
中位机使用TCP方式接收数据时可能出现数据错误,出错概率极高,大约接收300字节内必出。错误如下:
最终结论
中位机使用ST厂商自带的HAL库,该源代码在关闭网络接收的DCache(数据缓存)功能时有bug,可能导致部分区域的DCache未被关闭,从而导致TCP接收时可能从不正确的数据来源区拷贝数据,从而出错。
软件环境
HAL库版本号: STM32H7xx HAL V1.9.0
STM32CubeMX版本: 6.1.0
中位机MCU: STM32H743
LWIP版本: 2.1.2
分析过程
1 思路
出于对网络通信可靠性的极度信任,首先怀疑自己收到数据后的解析与处理有问题。我司通信协议为自定义,较为繁杂,代码中有需要操作数据指针的地方,存在修改接收到数据的可能性。
验证
屏蔽掉数据解析与处理函数后测试,错误依旧,由此定位问题出在网络接收流程;
2 思路
本次使用的LWIP版本为2.1.2,而本人之前用过的成熟版本为1.4.1,虽说理论上LWIP应该不会存在这么大的坑,但不管怎样先试试。
验证
开启LWIP_DEBUG及相关调试宏,结果发现错误依旧,且LWIP协议栈一切正常,无任何警告输出;
3思路
有无可能是TCP接收存储区不够大、从而导致数据接收溢出?
验证
分析其底层网络接收机制,发现其接收机制为循环队列,队列容量为4,每块大小为1524字节。
- 修改lwipopt.h中相关宏(ETH_RX_BUFFER_SIZE),将其由1524字节增大为2000,错误依旧;
- 增大队列容量,将队列容量增大为8,错误依旧;
4 思路
TCP接收顶层函数是一个Task,其优先级为HIGH,已然高于其它任何用户Task。考虑到本工程使用了LWIP,其内部会自行创建几个网络Task,有无可能是TCP接收Task优先级不够高、被其它线程抢占?
验证
1)修改TCP接收任务的优先级为最高(osPriorityRealtime),错误依旧;
2)增大TCP接收任务的堆栈,错误依旧;
5 思路
网上有同行说可能是CPU处理能力不够,每接收一包后sleep一下。
验证
修改后错误依旧;
6 思路
网络接收通常会使用DMA,且STM32H743芯片有1级DCache功能。有无可能是DCache未及时刷新导致的数据一致性问题?
验证
1)查找网络接收数组的分配位置,固定为0x30040200;
2)查找代码中对该存储区域的RAM属性设置,发现未禁止DCache,即:使用DCache;
为保证DMA与Cache不产生一致性的问题,STM32官方提供了一系列关闭或清空Cache的库函数。按个人经验,为保险起见,在底层接收函数中应该有规避一致性问题的动作。查询底层接收代码,果然有所发现:
按字面来看,应该是OK的。继续往里追查SCB_InvalidateDCache_by_Addr()函数,又有所发现:
其中SCB_InvalidateDCache_by_Addr()是ST官方提供的函数。
查数据手册得知,STM32H7平台的Cache操作是以Cache Line作为最小单位的,一个Cache Line是32字节。
正常情况下,该函数的第1个参数addr操作的地址应该要求32字节对齐,第2个参数dsize应该是32字节的整数倍。这样才能保证对Cache的操作是真正以Cache Line为单位。否则一旦传入的addr不按32字节对齐,则将出现问题。果断追查TCP接收循环队列的首地址,果然存在不对齐情况。手动修改之,经测试,完美通过!
反思
32字节对齐其实既可以在调用时执行,也可以放在SCB_InvalidateDCache_by_Addr()函数内执行。我个人倾向后者,毕竟调用者究竟是否理解该机制是不敢保证的。