项目中两个芯片之间用串口进行通信,由于传输格式中有校验位,在数据量很大的时候总是校验失败。于是花了很长的时间最终解决了这个问题。
首先串口丢数据有两种情况(明显排除发送端发送的数据不对),第一种是信道也就是串口线或者连接口不行,无法承受很高的波特率(我使用的波特率是921600),第二种就是接收端由于某种原因丢数据。通过观察我排除了第一种情况,因为如果是信道承受不了太高的波特率的话那平时的小段小段的数据也可能会丢包,而明显我的情况是只有在一次传输大量数据(大概512字节以上)时才会丢数据。所以问题出在接收端的处理流程。
于是借此机会也了解了一般linux中串口接收数据的处理流程。我们都知道应用层通过select和read来及时的读取串口的数据,而读取的数据其实是内存里的FIFO缓冲区的数据。而串口模块的数据从RXD最终到内存里的FIFO会经过几个流程,大概如下图所示(这里我们仅分析串口接收端即RXD的处理流程):
图1 串口接收端处理流程
如图所示,串并转换模块将来自于RXD传输线上的数据转换成对应的一段数据,比如8位数据位一位停止位无校验位的情况下,就是转换成9bit的数据。然后将其中传输的数据部分写入到模块自带的硬件FIFO中(有了硬件FIFO,cpu就不用每接收到一个字节触发一次中断了)。当硬件FIFO达到设定的阈值时触发串口中断,串口中断处理程序通过配置DMA来搬运存在硬FIFO里的数据到内存里开辟的软FIFO里。应用层通过read()等接口读取内存中FIFO的数据。
接收端发生了丢失串口数据的情况,由上图可知有两种情况。第一可能是内存中的软FIFO由于是定长的,应用层读取频次太低导致该FIFO溢出从而导致数据丢失。第二种可能就是该串口模块自身的硬件FIFO(也是定长的)溢出导致数据丢失。
对于第一种情况我在应用层调用了ioctl接口设置软FIFO大小为1M,如下代码所示(虽然还是没解决,不过写在这里提供参考):
#include
#include
#include
#include
#include
#include
int UartBuffSizeSet(char *dev_path,int size) {
int ret;
int fd = open(dev_path, O_RDWR | O_NOCTTY | O_NONBLOCK);
if(fd < 0){
return -1;
}
struct serial_struct serial;
ret = ioctl(fd, TIOCGSERIAL, &serial);
if (ret != 0) {
close(fd);
return -2;
}+
serial.xmit_fifo_size = 1024*1024; //1M
ret = ioctl(fd, TIOCSSERIAL, &serial);
if(ret != 0) {
close(fd);
return -3;
}
close(fd);
return 0;
}
我设置完之后发现丢数据问题还是很明显,大概可以确认是硬件FIFO溢出,后来发现不丢数据总是在发送端一次发送64字节以下的时候,一旦一次传输超过太多比如我这里的512字节就丢数据,而我的设备串口硬FIFO的大小正好是64字节,所以可以确认是硬件FIFO溢出了。
由上面的图1可知串口硬FIFO达到阈值之后会触发中断,然后DMA会把硬FIFO里的数据搬运到内存中。如果FIFO溢出了那么就是DMA没有把数据搬运到内存中,也就是由于串口触发中断时CPU在处理其他中断暂时屏蔽了所有中断,导致没有响应串口的中断,这时串口硬FIFO依然持续增长直到溢出。
由于我的运行环境有摄像头、图像处理、图像输出等单元。CPU可能在这些的中断里停留时间较长。有一个解决的方法就是优化当前系统中的各个中断处理流程。显然这工作量不小而且可能提升不了多少,我的解决方法非常简单,但需要各位和我一样是多处理器的平台。我将串口的中断绑定到了CPU1上就ok了,CPU0默认处理了很多中断。
首先通过指令: cat /proc/interrupts 查看系统中各个设备的中断号:
#cat /proc/interrupts
CPU0 CPU1
29: 991442 4917 GIC 29 arch_timer
30: 0 0 GIC 30 arch_timer
36: 232252 0 GIC 36 uart-pl011
38: 0 453209 GIC 38
41: 0 0 GIC 41 pl022
42: 0 0 GIC 42 pl022
43: 0 0 GIC 43 pl022
44: 0 0 GIC 44 pl022
45: 0 0 GIC 45 himci
51: 0 0 GIC 51 ehci_hcd:usb3
52: 1 0 GIC 52 ohci_hcd:usb4
54: 0 0 GIC 54 xhci-hcd:usb1
55: 1689 0 GIC 55 himci
56: 52 0 GIC 56 himci
57: 253785 0 GIC 57 10050000.ethernet
59: 294156 0 GIC 59
60: 0 0 GIC 60 mipi0_int
61: 0 0 GIC 61 mipi1_int
62: 588957 0 GIC 62 ISP
63: 0 0 GIC 63 ISP
64: 294465 0 GIC 64
66: 19617 0 GIC 66 tde_osr_isr
67: 513076 0 GIC 67
68: 0 0 GIC 68 AIO Interrupt
69: 489697 0 GIC 69
70: 588293 0 GIC 70
71: 588277 0 GIC 71
96: 0 143945 GIC 96 timer
IPI0: 0 0 CPU wakeup interrupts
IPI1: 0 0 Timer broadcast interrupts
IPI2: 413363 612 Rescheduling interrupts
IPI3: 0 0 Function call interrupts
IPI4: 1 2 Single function call interrupts
IPI5: 0 0 CPU stop interrupts
IPI6: 0 0 IRQ work interrupts
IPI7: 0 0 completion interrupts
IPI8: 0 0 CPU backtrace
Err: 0
可以看到我的串口设备中断号是36。
使用如下指令将中断号36唯一绑定到CPU1上:
echo "2" >> /proc/irq/38/smp_affinity
echo 输入的数字的各个bit为1代表使用对应的CPU,比如bit0为1代表使用CPU0,可同时绑定多个CPU。
至此我遇到的串口数据丢失问题就得到了解决,虽然最后解决只是一句指令的事情,但是前面的分析查找原因花了不少功夫。而且如果我的运行平台是单核CPU那还得费脑筋解决,对于单核的CPU我大概想了一下在我这种原因(硬FIFO溢出)导致的串口丢数据的问题的解决方法:
1.尽量优化中断处理流程,拆分一次传输的size。
2.降低串口波特率。
3.找出执行时间较长的中断处理程序,视情况来决定在它屏蔽中断时不屏蔽串口的中断。
4.芯片外接带较大FIFO的串口模块,降低串口中断触发周期。(我自己猜想的,不知道有没有这样的模块)。