Come Together !

从 USB 理解嵌入式软件分层

USB 只是一个例子。真正需要掌握的,不是把某一种通信协议孤立地背下来,而是学会面对一个复杂系统时,如何划分软件层次、确定每一层负责什么,以及判断问题应该到哪一层排查。

先把同一个问题拆成不同层次

以“设备无法正常通信”为例,可以从三个层面观察:

  1. 物理层:信号是否真的能够在硬件之间传输,例如电源、连线、引脚复用、电平和信号质量。
  2. 协议层:双方是否按照同一套规则交换数据,例如数据如何打包、设备如何寻址、谁先发起通信,以及如何校验和处理错误。
  3. 接口层:设备之间通过什么标准连接,例如连接器、供电能力和对外暴露的接口形式。

这三个层面相互关联,但不能混为一谈。Type-C 是接口形式,不等于某一个具体协议;看到总线上有电平变化,也不等于协议解析一定正确;应用层收到错误数据,也不一定是应用代码本身造成的。

从 USB 抽象出软件开发的通用分层

在嵌入式开发中,可以把硬件和软件关系简化为下面三层:

软件层次主要职责不应承担的职责
应用层处理字节、数据结构、帧头、长度、校验和业务逻辑直接操作寄存器,关心具体引脚来自哪一种芯片
驱动层配置外设、处理中断和 DMA,收发数据,并向上提供稳定接口解释业务含义,决定一帧数据代表什么业务动作
硬件层提供供电、时钟、引脚连接和实际信号通路代替软件完成协议解析和业务决策

这种分层的核心不是把代码机械地分成几个文件,而是让每一层只依赖自己应该知道的内容:

  • 应用层只关心“收到了哪些数据、数据是否有效、业务该如何处理”;
  • 驱动层只关心“如何让控制器可靠地收发数据”;
  • 硬件层只关心“信号是否有正确的物理通路”。

因此,同一套应用层的数据解析逻辑,理论上可以复用在 USB、UART、RS232、RS485 甚至蓝牙之上。底层传输方式不同,但上层仍然可以统一为“接收数据、缓存数据、解析帧、校验、执行业务”的流程。

为什么要做这种抽象

1. 降低认知负担

学习一个新外设时,不必同时理解芯片模拟电路、控制器寄存器、协议格式和业务逻辑。先明确当前问题属于哪一层,再只研究这一层需要的知识。

2. 方便替换底层实现

如果应用代码只依赖“发送数据”和“接收数据”这类抽象接口,那么把 UART 换成 USB CDC 时,通常只需要替换驱动适配部分,不需要重写整个业务模块。

3. 缩小故障范围

排查“没有收到正确数据”时,可以按顺序验证:

  1. 硬件是否连通、供电是否正常、引脚配置是否正确;
  2. 驱动是否初始化成功、是否收到中断或数据;
  3. 缓冲区和协议解析是否正确;
  4. 业务逻辑是否正确处理了解析结果。

每一层都通过可观察证据验证,而不是看到现象后直接修改应用代码。

USB 作为案例

USB 的通信由主机发起,设备响应。设备需要经过枚举,主机才能识别设备并使用相应的端点和传输方式。对于嵌入式开发者来说,可以把 USB 看成一条由底层驱动提供的数据通道:

  • USB 控制器和 HAL 负责初始化、端点配置、传输和中断;
  • USB 协议栈负责枚举、描述符和传输规则;
  • 应用代码负责处理收到的有效数据及其业务含义。

这样理解 USB 的目的,不是忽略底层细节,而是知道什么时候需要进入底层。只有当上层证据表明问题来自枚举、端点、传输或硬件信号时,才继续深入对应层次。


本文是个人博客的第一篇文章,作为测试

标签: none

添加新评论

  • 上一篇: 没有了
  • 下一篇: 没有了