12.1. 为什么需要一个协议库¶
一对电缆加上一个波特率就足以把字节从摄像头传到主机 PC。USB-CDC 和 UART 都为摄像头程序提供了一个流,其中 write 把字节送入一端,read 从另一端取出。那么协议库在此之上又增加了什么呢?
如果你尝试直接在原始字节之上构建一个真正可用的摄像头到主机的通道,那么有三样东西你每次都得自己动手编写:
分帧。 字节流本身没有任何固有结构。摄像头写入 temp=42,而主机先读到 temp= 加上随后到达的一个中断,接着读到 42 加上下一条以 humid=... 开头、已经混入其中的消息。字节之间没有边界。每一个非平凡的主机链路最终都会发明某种标记——消息之间的 \n、长度前缀报头、用于二进制载荷的转义序列——以便接收方知道一条消息在哪里结束、下一条从哪里开始。协议库为你提供了统一的数据包格式,带有同步字和长度字段,接收方再也不必猜测。
可靠性。 USB-CDC 在正常工作时不会悄无声息地丢弃字节,但 UART 会(当主机不够快地处理端口时),而且串行电缆拔下再插上可能会让一端留下一个不完整的数据包。正确的做法是检测出损坏,请求对方重传,并且只把完整到达的消息交给应用代码。协议库借助 CRC 和逐包确认对每一个数据包都做到了这一点——默认开启;应用看不到这些重试。
多路复用。 摄像头与主机之间只有一个 USB-CDC 端口。如果摄像头正在传输一幅图像,同时主机正在向它发送配置,同时 IDE 正在读取 stdout 以获取 print 的输出,那么这三路交换都必须共享那一条字节流。协议库为每一条独立的流赋予一个通道号,让摄像头为每一条流注册一个 Python 类,并防止主机在各通道上的读取彼此干扰。从应用代码的角度看,每一条通道都像是它自己的私有链路。
12.1.1. 为什么不自己来写¶
你可以自己写。要在一条串行线上把这三样东西都做对需要几周的工作,再花上几周才能让分帧干净地处理热插拔恢复、让重传在不浪费往返能耗的情况下正常工作、并让多路复用器在部分读取下也能存活而不把一个通道的字节窜入另一个通道。
协议库已经完成了这项工作,已经在每一款受支持的摄像头上得到验证,并且在主机一侧有讲同样线缆格式的配套库。使用它意味着摄像头一侧的代码是每条通道一个小类,而主机一侧的代码是一个带有 channel_read 和 channel_write 方法的 Camera 对象。省下来的脑力都用在应用真正要做的事情上。
本章从头讲起这个协议:线缆格式、分帧规则、可靠性机制、通道模型,最后是两端的 Python 类。读完之后,读者就能构建一个与摄像头对话的主机 GUI、一个把传感器数据从摄像头流式传到笔记本电脑的脚本,以及那种随 openmv-projects/tools/ 一起发布的交互式标定工具。