項目中兩個芯片之間用串口進行通信,由於傳輸格式中有校驗位,在數據量很大的時候總是校驗失敗。於是花了很長的時間最終解決了這個問題。
首先串口丟數據有兩種情況(明顯排除發送端發送的數據不對),第一種是信道也就是串口線或者連接口不行,無法承受很高的波特率(我使用的波特率是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 <asm-generic/ioctl.h>
#include <termios.h>
#include <linux/serial.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
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的串口模塊,降低串口中斷觸發週期。(我自己猜想的,不知道有沒有這樣的模塊)。