記一次linux下串口數據丟包解決過程

項目中兩個芯片之間用串口進行通信,由於傳輸格式中有校驗位,在數據量很大的時候總是校驗失敗。於是花了很長的時間最終解決了這個問題。

首先串口丟數據有兩種情況(明顯排除發送端發送的數據不對),第一種是信道也就是串口線或者連接口不行,無法承受很高的波特率(我使用的波特率是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的串口模塊,降低串口中斷觸發週期。(我自己猜想的,不知道有沒有這樣的模塊)。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章