最近项目上在使用Zynq开发,也是第一次使用,期间会遇到各种各样的问题,属于Zynq本身问题的我会更新到我的另一篇博客《Zynq开发调试踩坑指南》中,这个版块将会陆续记录我自身在程序开发中的问题。
这次的问题是串口收数据问题,众所周知,我在一个月前就已经把串口调通了,收发试着也没啥问题。现在正在加一个通过串口远程升级的功能,也就是发的数据量大了,几十毫秒一帧数据,每一帧都有个一百多字节,然后就发现偶尔收的数据CRC校验对不上,仔细核对发现是少收了一个字节了。
我一开始是怀疑收数据的软件层驱动不对。毕竟整个软件层都是我自己写的,也没大量验证过,于是直接看串口收的memory中数据确实是少一个字节。
于是就怀疑串口线的问题,因为我现在跑的波特率是2Mbps,就单独拿串口线自发自收,倒是没有问题,说明硬件链路是没问题的。
这时候我就开始合理推测了,我看到丢字节的数组位置大概在第64这个位置,心里一个不成熟的想法就浮出水面。
我的Zynq串口收数据是使用FIFO溢出中断+超时中断的方式来获取数据的。超时中断大家都懂,stm32单片机里有这个概念。就是单个字节发完后多长时间内如果没有新的比特进来,就认为是超时了,这时候你想要的数据就都已经在缓存区里了。不同于stm32超时中断需要配置DMA,Zynq本身有FIFO,中断后直接读FIFO就可以了。但是这个FIFO是有空间限制的,收发缓存都只有64字节的大小。如果一次收到的数据不足64字节,倒是可以直接读完一帧,那如果收到的数据比64字节多呢?
这时候就需要FIFO溢出中断和超时中断一起配合使用了。我开辟一个空间,FIFO溢出后我把FIFO里的数据全部读走,再来数据在之前的空间上叠加数据,当产生超时中断时,则认为数据全部收完了。收完这些数据,再把数据放进环形缓存区,供再上一层软件调用。
以下是我串口中断的配置方法,注意,其中有一处是需要改动的,下面再提
/**
* 配置超时中断。如果设置为N,则超时时间为N*4个比特时钟。
* 例如115200波特率,每比特时钟为0.00000868s,即8.68us。
* 如果N=8,则8.68us * 8 *4 = 277.76us
*/
XUartPs_SetRecvTimeout(&Uart0Ps, 40);
/*设置FIFO触发条件 */
/**
* FIFO最大值64,此处的值是FIFO溢出的中断,也即RXOVR中断
* 设置为最大值,和timout配合,如果接受字节小于64则在TOUT
* 中断里读走数据。如果接受字节大于64字节,则首先由RXOVR
* 中断接收前64字节数据,再由TOUT中断接受剩下的字节,两者配合。
*/
XUartPs_SetFifoThreshold(&Uart0Ps, 64 - 1) ;
/* 设置中断触发类型 */
XUartPs_SetInterruptMask(&Uart0Ps, XUARTPS_IXR_RXOVR | XUARTPS_IXR_TOUT);
可以看到上面我把缓存溢出的阈值设置为了64,也就是Zynq本身硬件上的最大串口缓存大小。
下面是我串口中断函数里做的操作,注意,recv函数我传递的参数是100,实际这个数填大点都没事,他只会返回缓存区有的数据长度,并不会溢出。
//接收fifo满
if (UartSrValue & XUARTPS_IXR_RXOVR) /* check if receiver FIFO trigger */
{
u32 Len = XUartPs_Recv(UartInstPtr, Usart1DataRcevSture.RecvOffsetPtr, 100);
Usart1DataRcevSture.RecvOffsetPtr += Len;
/* clear trigger interrupt */
UART_PS_INTR_CLEAR(UartInstPtr->Config.BaseAddress, XUARTPS_IXR_RXOVR);
}
//空闲中断
else if (UartSrValue & XUARTPS_IXR_TOUT)
{
gpio_toggle(PL_LED_PIN);
u32 Len = XUartPs_Recv(UartInstPtr, Usart1DataRcevSture.RecvOffsetPtr, 100);
Usart1DataRcevSture.RecvOffsetPtr += Len;
Usart1DataRcevSture.RecvDataLen = Usart1DataRcevSture.RecvOffsetPtr - &Usart1DataRcevSture.RecvBuffer[0];
Usart1DataRcevSture.RecvOffsetPtr = &Usart1DataRcevSture.RecvBuffer[0];
Usart1DataRcevSture.RecvFlg |= 0x01;
/* clear trigger interrupt */
UART_PS_INTR_CLEAR(UartInstPtr->Config.BaseAddress, XUARTPS_IXR_TOUT) ;
}
这样的配置和接收方法本身没什么问题,但是当像我这样高速率通信,高频率发帧时候就会出现我上面说的,丢字节的情况。为什么会丢呢?恰恰就是因为我们把缓存区的大小设置的和硬件允许的缓存一样大,当我们产生FIFO溢出中断时,如果中断没来得及把所有数据读走,这时候再来一个新数据,他就没有地方可存放了,就会丢失字节。当然,这种事情发生的概率取决于硬件对中断的响应速度和程序执行中断的时间。
我现在波特率太高,则会更容易出现这样的问题。
XUartPs_SetFifoThreshold(&Uart0Ps, 50 - 1) ;
所以我们只需要更改这里,把FIFO溢出的阈值设置稍小一点,就可以给硬件留出一定的裕量了。
改完之后测试就再没出现过问题。
|