12.2. 四個層級

協定函式庫建構成四個層級的堆疊,每一層解決單一問題並建立在其下一層之上。本章其餘部分將由下而上走訪這個堆疊。

四個標記過的層級垂直堆疊:最底部是傳輸層(USB 或 UART), 其上是框架層(帶有 CRC 的封包標頭),再上面是可靠性層 (序號、ACK / NAK、重傳),最頂部是通道層(具名的邏輯 串流)。

12.2.1. 傳輸層

最底層是相機與主機之間的位元組管道。協定函式庫不在乎是哪一個來承載這些位元組:

  • 透過相機所插入之 USB 埠的 USB-CDC。這是每台相機的預設、也是頻寬最高的選項。

  • 透過相機上一對 GPIO 接腳、連接到主機端序列轉接器的 UART。在 USB 埠忙碌中或實體上無法存取的無頭(headless)部署中很有用。

傳輸層的唯一工作就是「位元組進去、位元組出來,依序」。此層之上的一切都假設傳輸會依寫入的順序傳遞位元組,但容許位元組本身可能損毀,或連結整個中斷。有損的突發(少了幾個位元組)和乾淨的中斷(整條連結消失一陣子,然後又回來)都由更上層處理。

12.2.2. 框架層

再上一層為位元組串流施加結構。每則訊息都成為一個封包——一個 10 位元組的標頭,後接一段酬載,再接一個 4 位元組的尾段。標頭攜帶:

  • 一個 2 位元組的同步字組(0xD5AA),讓接收端在失去同步後能重新找到封包的起始位置。

  • 一個 1 位元組的序號,供可靠性層使用。

  • 一個 1 位元組的通道 ID,標示封包所屬的邏輯串流。

  • 一個 1 位元組的旗標欄位,用於 ACK / NAK /片段/事件位元。

  • 一個 1 位元組的運算碼(opcode),用以區分協定命令、系統命令與通道命令。

  • 一個 2 位元組的酬載長度。

  • 一個針對前八個標頭位元組計算的 2 位元組 CRC。

接著是酬載,然後是一個針對酬載本身計算的 4 位元組 CRC。這兩個 CRC 各自獨立地捕捉損毀:標頭中翻轉的一個位元會使標頭 CRC 失效,接收端便可丟棄該封包,根本不需要讀取酬載。

12.2.3. 可靠性層

可靠性層將「可能會抵達的封包」轉變為「已經抵達的封包」。它追蹤標頭中的序號,要求對方為每個需要確認的封包送回確認(acknowledgement),並在確認未於逾時內抵達時進行重傳。預設情況下,重傳逾時從 500 毫秒開始,每次重試便加倍,在放棄前重試三次。

這些行為每一項都可在 protocol.init() 呼叫中設定:對於單向串流可以關閉 ACK,在完全乾淨的傳輸上可以略過 CRC 驗證,重傳參數也可針對緩慢或高延遲的連結進行調校。

12.2.4. 通道層

最頂層是應用程式碼所看到的。一條 通道 是由 0 到 31 的通道 ID 所識別的具名邏輯串流。一條傳輸上最多可共存 32 條通道;每一條都獨立於其他通道,並透過其在每個封包標頭中的 ID 來定址。相機開機時帶有四條內建通道——stdinstdoutstreamprofile——而應用程式碼透過呼叫 protocol.register() 並傳入一個 Python 類別來在其上註冊更多通道。

這四個層級不混淆各自的職責。框架層不知道通道;可靠性層不知道封包內容;通道層不知道位元組如何抵達。正是這種分離,讓傳輸的更換(例如從 USB 換到 UART)不會向上波及通道程式碼,也正是它讓本章其餘部分能夠一次一層地逐步走訪。