之前调试fx10的时候遇到了很多困难,走了不少弯路。看了很多的文档和代码,后来才发现要想调试好这颗芯片。最重要的还是要了解好固件架构,知道什么该做,什么不该做,什么是我们应该关注的,什么是我们不应该关注的,下面就分享一下我的心得。
首先我们来看一下这颗芯片固件的分层结构:
芯片分为以下几个层次,这几个层次不是在物理意义上真正存在的。只是为了好开:而人为的分了这么几个层次。I
USB Device contriller: 芯片物理层,就是由晶体管构成的那个。
USB Controller Abstraction Later (CAL)USB 控制抽象层代码,把硬件的各个寄存器控制实现为一个个的API, 这样上层再次实现某个功能的时候,不需要直接操作寄存器了
USDB layer:实现具体的协议的部分,比如实现USB的复杂的协议,实现 和FPGA通信接口的LINK协议具体部分,具体功能比如有:
自动应答标准请求 (GET_DESCRIPTOR等)
管理端点 & 数据通道
速率协商、描述符切换
APP Layer:用户层,用户具体实现自己的业务
为什么要分这么多层级呢?原因是对于芯片开发来说这么多层级好处多多。
抽象化和模块化:每个层次可以处理特定的功能或任务,使得各层之间的耦合度降低。这样可以更容易地替换某一层的实现而不影响其他层。例如,USB协议层可以在不改变硬件抽象层的情况下进行更新或优化。
易于移植:通过将硬件抽象成独立的层(如USB控制器抽象层CAL),可以更容易地在不同的硬件平台之间迁移代码。不同的硬件平台可能只需要重新实现硬件相关层,而无需修改整个应用程序或协议层的代码。
代码重用:在不同的项目中可以重用已经开发好的层次,减少重复劳动。例如,USB设备控制器的驱动可以在多个应用中共用,只需要为特定硬件编写底层的硬件接口部分。
层级隔离:每一层只负责自己的职责,避免不同模块之间的复杂交互,降低错误发生的概率。这种隔离使得每一层可以单独进行测试和调试。
开发效率:分层结构帮助开发人员聚焦在特定的功能上,而不需要同时处理硬件、协议等多个复杂问题。
我只是用起来这个芯片,因此只需要关心用户层和 USBD 层级就好了,再往下的不去探究了
这一点非常重要,因为我们在看固件代码的时候,他会套好几层,一层一层的,如果我们一直往下看,那么这个代码看好几年也看不完。因此我们只需要关心到USBd这一层,再往下是驱动工程师做的事情,里面涉及到的都是一些芯片寄存器操作了,我们没有寄存器手册,看了也没用。
在用户代码这一层,我们需要关心的功能由以下几个类型的函数构成:(后续的文章也是根据这几部分挨个记录)
1 freeRtos 任务:这个芯片是有实时操作系统的。这个操作系统实现了很多个任务:
A :打印日志task
打印日志的函数会把数据加入到一个缓存中,然后这个任务会把缓存中的信息定期执行
B:Cy_USBD_TaskHandler任务
实现USB协议的task "UsbdTask"
C:Cy_HBDma_Mgr_TaskHandler任务
实现数据DMA的task,HbDmaTask
这个任务内部由高带宽DMA消息队列处理任务
所有高带宽DMA套接字接收的中断通过此消息队列传递给HBDma管理器任务。
D:Cy_UVC_AppDeviceTaskHandler任务是
实现 用户功能的task,这里是UVC功能
UvcDeviceTask ,在Cy_USB_AppInit函数中创建
这个任务内部有 USB设备堆栈消息队列:从USBHS接收的中断以及USBSS IP块通过该消息队列传递给USB堆栈任务
E:reertos 内核创建的task:
以下任务我们无需关注。只是了解有这个就可以了。
• Idle task created by FreeRTOS Kernel:
Idle task 是 FreeRTOS 的“兜底任务”,当系统没有其它任务运行时,它接管 CPU,做资源回收、低功耗、Idle Hook 调用等后台工作。
• Timer task created by FreeRTOS Kernel: 定制器任务,总管一切软件定制器
主要作用:
统一调度软件定时器
所有通过 xTimerCreate()、xTimerStart() 等 API 创建的 FreeRTOS 软件定时器,不会自己生成单独的任务,而是将超时事件放入一个定时器命令队列中,由 Timer task 轮询处理并调用用户注册的回调函数。
集中管理,减少任务开销
软件定时器共享一个 Timer task,避免每个定时功能都开独立任务,降低了任务切换开销。
代码中有以下两个timer:
usbdTimer:
- 在 USB 链路从低功耗恢复到活跃状态后,延迟 10ms(USB 2.x)或 50ms(USB 3.x)才允许再次进入低功耗模式,确保数据传输完成。
usb3ConnTimer:
- 检测到 USB 2.x 总线复位后,延迟 400ms 再尝试 USB 3.x 重连,保证主机控制器的增强模式就绪。
这些定时器的回调最终都是在 Timer task 里被调用的。
执行流程举例说明这个:
- 硬件 SysTick 每 1ms 触发一次 tick 中断
- FreeRTOS tick handler 检测到 500ms 到期 → 向 Timer task 队列发送“执行 LedToggleCallback”命令
- Timer task 从队列取出命令 → 调用 LedToggleCallback() → 切换 LED 状态
2 回调函数
所有回调函数都是中断回调实现的,这里中断实现的回调函数分为两类:
A 中断回调函数当场执行,也就是说直接在中断中解决了,主要是一些LVDS DMA的中断需要及时处理,这类对实时性要求非常高的。
B中断后只是把需要执行的消息通过freertos发送给任务,让任务后续再慢慢执行,一些比较耗时的任务在这里执行。
通过这些任务和回调函数,可以实现我们需要的各种功能,我们要设置的一些功能包括:(后续的文章也是根据这几部分挨个记录)
1 HBDMA:
实现Fpga的数据到了fx10以后。这些数据传输到USB的端点上的路径应该如何配置,内部有一些socket,thread,描述符等等:
- Thread
-
- LVDS 物理链路和 DMA Adapter 之间的专用硬件连接
- 可以理解为一个数据通道(比如一根数据线)
- DMA Adapter
-
- 把 Thread 上的数据搬到 DMA Buffer(或反向)
- 类似数据格式转换和搬运的“接驳器”
- Socket
-
- 外设和 DMA Buffer 之间的接口
- 每个外设通过 socket 与 DMA 交互
- Descriptor
-
- 描述当前和下一个 DMA Buffer 区域的结构体
- 用于 DMA 控制器知道下一步要搬运哪一段数据(支持链式传输)
Thread → Adapter → Socket 关系:LVDS/LVCMOS数据通道有2个DMA Adapter,对应4个thread,每个thread 有8个socket,
socket是抽象好的最小的数据传输线路,代码中给每个socket编了号,不同的socket就是对应不同的硬件数据传输路线,比如地址为17的socket是第三个thread,对应DMA adapter1
每个 Socket 有唯一的 全局编号,这样一来,代码里给 Socket 编号,其实就是在选用哪一条硬件传输通道。
这些大概看看,先记录下手册提到的,有个映像。
2 USB 协议
实现USB的各个协议功能,比如说set up的解析自动回复。Cc检测等
3 LVDS/LVCMOS接口
Fpga的数据如何输入到fx10,LVds,LVCMOS接口如何训练等,数据发送的时序是什么等等
4 APP 应用业务
自己的一些业务,比如说uVC协议中的一些规定可以在行任务中做实现,又或者说自己的一些特定功能。
这些功能大部分demo都改好了,我们只需要修改一部分。重点其实还是APP自己的业务那块。
后面的笔记记录挨个记录下提到的这些功能如何实现的,尤其是如何对接FPGA的。