12.1. 為何需要協定函式庫

只要一對纜線加上一個鮑率,就足以把位元組從相機傳送到主機 PC。USB-CDC 與 UART 都會給相機程式提供一個串流,其中 write 把位元組塞入一端,而 read 從另一端取出。那麼,協定函式庫 在這之上又多提供了什麼呢?

如果你想直接以原始位元組打造一個真正可用的相機對主機通道,每次都得自行撰寫以下三件事:

封包切分(Framing)。 位元組串流本身沒有任何固有結構。相機寫入 temp=42,而主機讀到的卻是 temp= 加上稍後抵達的一個中斷,接著是 42 以及下一則以 humid=... 起頭的訊息,兩者已經混在一起。位元組沒有邊界。每一個非平凡的主機連結最後都得自行發明某種標記——訊息之間的 \n、長度前綴標頭、二進位酬載的跳脫序列——好讓接收端知道一則訊息在哪裡結束、下一則從哪裡開始。協定函式庫提供你一個統一的封包格式,內含同步字(sync word)與長度欄位,接收端再也不必猜測。

可靠性(Reliability)。 USB-CDC 在正常運作下不會無聲地遺失位元組,但 UART 會(當主機未能夠快地服務該連接埠時),而序列纜線拔除後再重新插上,可能讓其中一端留下一個不完整的封包。正確的做法是偵測損毀、要求對方重傳,並且只把完整抵達的訊息交給應用程式碼。協定函式庫透過 CRC 與逐封包的確認(acknowledgement)為每個封包做到這一點——預設啟用;應用程式看不到那些重試。

多工(Multiplexing)。 相機與主機之間恰好只有一個 USB-CDC 連接埠。如果相機正在串流一張影像,同時 主機正在傳送組態給它,而且 IDE 正在讀取 stdout 以取得 print 的輸出,這三個交換就都必須共用那一條位元組串流。協定函式庫給每個獨立串流一個 通道(channel) 編號,讓相機為每個通道註冊一個 Python 類別,並避免主機在各通道上的讀取彼此干擾。從應用程式碼的角度來看,每個通道看起來都像是自己的私有連結。

12.1.1. 為何不自己寫就好

你當然可以。要在一條序列線上把這三件事全部做對,得花上好幾週;而要讓封包切分能乾淨地處理熱插拔復原、讓重傳不致在往返上浪費能量、並讓多工器在部分讀取下仍能存活而不把某個通道的位元組混入另一個通道,又得再花上好幾週。

協定函式庫早已完成了那些工作,已在每一款支援的相機上通過驗證,並在主機端備有採用相同線上格式(wire format)的對應函式庫。使用它,意味著相機端的程式碼就是每個通道一個小類別,而主機端的程式碼則是一個帶有 channel_readchannel_write 方法的 Camera 物件。省下來的腦力空間,就能全部投入應用程式真正要做的事情上。

本章從頭開始教導這個協定:線上格式、封包切分規則、可靠性機制、通道模型,最後是兩端的 Python 類別。讀完之後,讀者就能打造一個與相機對話的主機 GUI、一個把感測器資料從相機串流到筆電的指令碼,以及像 openmv-projects/tools/ 中所附那種互動式校正工具。