简介:面向移动设备高速接口开发者与硬件工程师的 MIPI C-PHY 规范文档,MIPI 联盟于 2021 年正式发布 2.1 版本,适用于图像传感器、显示模组等 CSI/DSI 场景的物理层设计与验证。压缩包共 1 个 PDF 文件,约 3.5MB,为可直接检索查阅的官方英文规格书,涵盖三级编码、多级数据传输、低功耗设计、电气特性、同步定时与错误检测等章节,便于随查随用。已有 4394 人学习下载,说明其在移动影像与显示接口领域具有较高的参考价值。读者可借此系统掌握 C-PHY 相比 D-PHY 在功耗与带宽上的取舍逻辑,核对电压摆幅、上升下降时间、最大数据速率等电气指标,并据此排查链路时序与时钟抖动问题;文档中的会员协议与知识产权声明也可作为合规实施的依据。适合需要对照规范完成寄存器配置、信号完整性评估或协议层联调的工程师长期留存备查。
1. 三根线一组的高速接口,为什么值得单独读一遍规范
调一个摄像头模组,D-PHY 跑到 2.5 Gbps 之后,走线、功耗、EMI 三件事往往同时开始咬人,方案就会被推到 C-PHY 上。手上这份 C-PHY 规范 v2.1 的 PDF 九百多页,目录摊开足够吓退人,但真正决定项目能不能点亮的,其实集中在第 5 到第 7 章那几十页里。它不是什么入门读物,而是三根线一组、时钟塞进数据里、相邻符号不许重复的一套物理层规则集。适合三类人翻:做 CSI-2/DSI 链路 bring-up 的硬件与固件工程师、写 PHY 数字控制逻辑的、以及要给互连做信号完整性和功耗评估的。下面按我自己的阅读顺序拆:先把符号怎么算讲透,再落到时序、校准、转向,最后是排错和自检脚本。
2. 三线一组怎么算:把 2.28 bit/symbol 和时钟恢复落到纸面
C-PHY 最反直觉的地方在于,它把“传多少数据”和“线怎么翻转”绑成了一件事。D-PHY 里差分对只管 0 和 1,时钟另走一对线;C-PHY 里三根线的高低顺序本身就是信息,时钟要从这些翻转里被恢复出来。不先把这层算术关系理清,后面看时序参数表全是天书。
2.1 为什么是三根线而不是两根
三根线组成一个 trio,任意时刻三根线的电平是“一高、一中、低”的分布,接收端判的是谁高谁低这个相对顺序,而不是某根线对地的绝对值。这么做的好处很直接:三根线同时参与驱动,切换时共模电流互相抵消,辐射和地弹都比单端或纯差分小;同时每个符号周期能塞进去超过 2 bit,同样工艺下有效吞吐比一对差分线高。
代价也很实在。三根线之间任何一点传播延迟差,都会直接吃掉采样裕量;接收端必须有CDR 从符号跳变里锁时钟,一旦出现长时间不翻转就会失锁;协议状态机从“一对线两个状态”变成“一组线多个状态加跳转约束”,控制逻辑复杂度上一个台阶。
| 维度 | D-PHY(一对差分) | C-PHY(三线 trio) |
|---|---|---|
| 时钟 | 独立时钟 lane | 嵌入式,靠 CDR 恢复 |
| 每符号承载 | 1 bit | 约 2.2857 bit |
| 线数(单向) | 2 数据 + 2 时钟 | 3 根一组 |
| 主要风险 | 时钟与数据偏斜 | 三线间偏斜、CDR 失锁、状态跳转错误 |
| 功耗特征 | 静态功耗随 lane 数线性增长 | 相同带宽下驱动级更少,翻转效率更高 |
这张表放在项目启动会上比任何 PPT 都好用,因为它把“要不要换”量化成了线数、每符号比特和风险项三列,而不是一句“C-PHY 更先进”。
2.2 wire state 与 16 bit 到 7 个符号的映射
设三根线为 A、B、C,它们的相对高低排列共有 3! = 6 种,业内叫 wire state:A>B>C、A>C>B、B>A>C、B>C>A、C>A>B、C>B>A。每个 wire state 还带一个极性方向,但真正约束编码的是“下一个符号不能停在与当前相同的排列上”——停住就没有跳变沿,CDR 立刻失去参考。所以从一个状态出发,下一步只有 5 个候选。
5 的 7 次方是 78125,2 的 16 次方是 65536。78125 刚好装得下 65536,于是 7 个符号正好编码 16 位数据,平均下来 16 ÷ 7 ≈ 2.2857 bit/symbol。这个数字不是设计出来的美感,是被“三根线、不许重复”这两个硬约束逼出来的唯一解附近的值。
实现上,把 16 位数据当作一个整数,按 5 进制展开成 7 个 0 到 4 的索引,每个索引表示“相对当前 wire state,跳到第几个候选状态”。
| 索引值 | 含义 | 备注 |
|---|---|---|
| 0 | 跳到候选表中的第 0 个 | 候选表排除当前状态后按固定顺序排列 |
| 1~3 | 依次对应第 1~3 个候选 | 顺序必须收发两侧完全一致 |
| 4 | 跳到候选表中的第 4 个 | 共 5 个候选,索引范围 0~4 |
提示:纯算术的 5 进制展开能得到同样的索引空间,但具体哪个索引映射到哪次跳转,必须以规范 6.1.3 节给出的映射与置换表为准。收发两端如果各自按“看起来合理”的顺序实现,链路会通,但误码率会在特定数据模式下突然飙高,这种问题极难查。
写数字逻辑的时候,我一般会把这张候选表做成组合逻辑加一级寄存器保存“当前 state”,而不是用除法器实时算——7 次除法在几百 MHz 的符号率下面积和功耗都不划算,查表更稳。
2.3 时钟到底从哪来:CDR 的边界条件
接收端的时钟恢复环路靠每个符号周期里的电平跳变来锁相。因为编码保证了相邻符号一定不同,所以每个 UI 内至少有一次可用的边沿。但“有边沿”和“边沿质量够好”是两回事。
第一个约束是跳变幅度。不同 wire state 之间的转换,三根线各自的电压变化量并不相同,有的转换三根线全动,有的只有两根动。CDR 环路如果按最坏情况设计带宽,就会在平均情况下过阻尼,锁得慢;按平均情况设计,遇到连续小幅度转换就容易抖。常见做法是按规范 6.1.2 节给出的位序与串行化规则,把不同跳转类型的最坏相位误差列出来,取包络做环路参数。
第二个约束是符号率本身。UI 就是一个符号的时间,2.5 Gsym/s 对应 UI = 400 ps,每个 trio 的有效数据率是 2.5 × 2.2857 ≈ 5.7 Gbps。CSI-2 常见的三 trio 配置下,聚合带宽到 17 Gbps 量级。到这个程度,PCB 上 1 mm 的走线长度差在 FR4 里大约是 6~7 ps 的延迟,三根线之间差 5 mm 就是 30 ps 以上,已经吃掉 400 ps 的 7%。
2.4 拿规范核对链路预算的具体步骤
每次新项目立项,我会按下面这个顺序把数字过一遍,避免做到 layout 阶段才发现速率对不上:
- 从传感器或桥接芯片的数据手册里查出每个 trio 支持的最大符号率和 trio 数量,注意区分“每 trio 符号率”和“聚合数据率”,这两个数经常被销售材料混着写。
- 用符号率乘以 2.2857 得到每 trio 的原始比特率,再乘以 trio 数得到聚合速率。协议开销要单独扣,别拿聚合速率直接当净荷带宽。
- 翻到规范 6.14 节,把项目要用到的时序参数按“只含 UI 值”“UI 与时间混合”“只含时间”“与速率无关”四类分开。这一步决定了后面哪些参数要随速率重算,哪些可以写死。
- 把所有绝对时间参数换算成 ps,与走线偏斜、连接器、FPC 的传播延迟放在同一张表里比较,看裕量还剩多少。
- 给温度漂移和工艺角留出至少 20% 的余量,再回头确认第 1 步里的符号率是否还站得住。
| 参数类别 | 换算方式 | 换速率时是否要重算 |
|---|---|---|
| 只含 UI 值 | 乘以当前 UI | 要 |
| UI 与时间混合 | 分别满足两个下限,取更严的 | 要 |
| 只含时间值 | 直接查表,与速率无关 | 不要 |
| 与速率无关的时间值 | 直接查表 | 不要 |
这张分类表看着枯燥,但它决定了寄存器配置表能不能复用。我见过不止一个项目,把上一代 1.5 Gsym/s 的配置原样搬到 2.5 Gsym/s 的链路上,前面几版板子怎么调都只能跑低速,最后发现是某个只含 UI 的参数没重算。
3. 从 LP 到 HS 再到 ALP:上电时序怎么照着第 6 章落地
规范第 6 章叫 Global Operation,是整份文档里最值得逐字读的一章。它把链路的生命周期拆成低功耗、高速、交替低功耗三种模式,每一种都有自己的进入条件、退出条件和超时保护。真正写代码的时候,这一章会变成状态机的迁移表。
3.1 三种模式的职责划分与切换顺序
LP 模式是链路的“默认态”,电平摆幅大、速率低,用来传控制命令、寄存器读写和 escape 序列。HS 模式是数据通道,摆幅小、速率高,只在有突发数据的时候打开。ALP 是 v2.1 里比较有存在感的一档,介于两者之间:它不像 HS 那样追求峰值速率,但比 LP 快得多,适合那些“有数据要传、但不需要全速”的场景。
正常的上电顺序是:初始化(6.11 节的 LP 初始化与 ALP 初始化)→ 停留在 LP → 检测到发送请求 → 走 SoT 序列进 HS → 传突发数据 → EoT 序列回 LP。中间任何一步超时或者序列错,都会落到第 7 章定义的那几种错误状态里。
这块最容易翻车的地方是初始化的时序假设。LP 初始化阶段双方都还没建立可靠的时钟参考,所有超时都得按最慢的工艺角算。我一般的做法是把初始化阶段的超时统一放大到标称值的 3 倍,等链路进过一次 HS 之后再切回正常值。
3.2 SoT 与 EoT:进高速和出高速的那几拍
SoT(Start-of-Transmission)不是发一个特定的“起始字节”,而是一段从 LP 电平过渡到 HS 电平的序列,接收端在这段序列里完成三件事:检测到链路要进 HS、把 CDR 拉到接近锁定、完成符号同步。EoT 是反向过程,发送端主动把链路拉回 LP 电平,接收端检测到之后关闭 HS 接收通路。
规范 6.4.2 和 6.4.3 分别定义了这两个序列的结构,6.9 节给出了对应的全局时序参数。落地的时候要盯住两类参数:
一类是“准备时间”,即从发出请求到真正开始传数据之间必须留出的间隔。这个值太短,接收端 CDR 还没稳住,前几个符号必然出错;太长,突发传输的效率被吃掉,尤其是图像传感器那种一行一行的小突发场景,开销占比会很难看。
另一类是“保持时间”,即序列中各个电平阶段各自要维持多久。这类参数通常以 UI 为单位给出,速率一变就要重算,不能直接抄。
LP 空闲 └─ 发送端拉起 SoT 请求 └─ 等待 T_prepare(按 UI 与绝对时间双下限取严) └─ 驱动 HS 前导序列 └─ CDR 锁定 + 符号同步 └─ 突发数据 └─ EoT 序列 └─ 回到 LP 空闲上面这个流程不是规范原文的图,是我把 6.4 节的文字描述压成的一条线。实际状态机比这复杂,还要处理“同步失败重试”“对端无响应”“超时回退”这些分支,但主干就是这五步。写 RTL 的时候,我建议把每一步做成一个独立的状态,而不是用计数器硬凑,否则后期加 ALP 分支会非常痛苦。
3.3 ALP 模式什么时候值得开
ALP 的价值在于“不用为了传一点点数据就把整条链路拉到全速”。它的符号率比 HS 低,驱动强度也小,功耗自然下来。但进出 ALP 本身有开销:要发特定的前导、要重新对齐、要等待确认。
判断要不要开,我一般算一笔账:把一次 ALP 进入加退出的总时间,乘以这段时间里 HS 能传的数据量,再和“直接走 HS 传同样数据”的总时间比。如果前者明显更短,就开;如果两者接近,就别开——多一条模式分支就多一组状态机路径,后期调试成本远大于省下的那点功耗。
适合开的典型场景:低帧率预览时的周期性小包、传感器寄存器的批量下发、链路空闲但需要保活的场合。不适合的场景:突发性强、平均包长很短、对延迟敏感的控制回路。
3.4 把时序参数表读成配置寄存器值
规范 6.9 节的参数表有三列最关键:参数名、单位、依赖关系。落地时我按下面这个流程处理:
- 先按 6.14 节把参数分成“随速率变”和“不随速率变”两组,分组结果直接决定寄存器表要不要做两份。
- 对随速率变的参数,写出“值 = 系数 × UI”的表达式,系数从表里查,UI 由符号率反算。
- 对不随速率变的参数,直接按标称值填,但要确认工艺角下的最小值和最大值都满足。
- 对同时含 UI 和时间两种成分的参数,分别算两个下限,取更严的那个,并在注释里写清楚取的是哪一个。
- 把所有结果整理成一张“参数名 → 计算式 → 标称值 → 最小值 → 最大值 → 对应寄存器”的五列表,随代码一起进版本库。
| 参数名(示例位) | 单位 | 依赖 | 落地动作 |
|---|---|---|---|
| 前导准备间隔 | UI | 随速率变 | 系数 × UI,向上取整 |
| 序列保持时间 | UI | 随速率变 | 同上,注意不同阶段系数不同 |
| 初始化超时 | ns | 不随速率变 | 直接查表,按最慢工艺角放大 |
| 转向保护间隔 | UI + ns | 混合 | 两个下限取严,注释标注 |
注意:规范里的参数表给的是“必须满足”的边界,不是“推荐值”。直接拿边界值去配寄存器,实验室常温下能跑,一到高低温循环就出事。我习惯在边界值上再放 15% 到 20% 的裕量,尤其是接收方向的超时类参数。
4. 校准与双向转向:第 6.12 与 6.5 节的两块硬骨头
如果说第 6 章前半部分是“正常流程”,那 6.5 和 6.12 就是“异常流程”和“物理补偿”。这两块内容在目录里只占几页,实际调试时却常常占掉一半时间。
4.1 校准前导格式解决的是什么问题
三根线在 PCB 和 FPC 上的传播延迟不可能完全一样。速率低的时候,几皮秒的差异无所谓;符号周期掉到 400 ps 以下,三根线之间的偏斜就能吃掉采样窗口的一大块。校准要做的,就是在正式传数据之前,让接收端测出每根线相对于参考的延迟,然后调整采样相位或者延迟线。
规范 6.12.1 节定义了校准前导的几种格式,区别在于测量什么、测多久、以及结果怎么反馈。落地时要先想清楚自己的链路属于哪种:单向链路只需要接收端自己测自己调;双向链路还要考虑对端能不能配合发前导。
这里有个常被忽略的点:校准测出来的是“当前温度、当前电压、当前走线”下的偏斜,它不会自动跟着环境变。所以校准的触发时机比校准算法本身更重要。我一般会在三个时机各做一次:上电初始化后、链路从深度低功耗唤醒后、以及芯片温度变化超过设定阈值后。
4.2 校准流程的步骤拆解
按 6.12.2 节的描述,一次完整的校准大致分五步:进入校准状态、发送校准前导、接收端测量并计算相位调整量、应用调整、退出并验证。每一步都有对应的失败模式。
| 步骤 | 成功标志 | 失败时的典型现象 |
|---|---|---|
| 进入校准状态 | 双方都停在约定的空闲电平 | 状态机卡死,链路完全无响应 |
| 发送前导 | 接收端检测到前导序列 | 检测不到,反复重试直到超时 |
| 测量与计算 | 得到落在允许范围内的相位值 | 结果超出范围,或三根线结果差异过大 |
| 应用调整 | 调整后回环测试误码率为零 | 调整量被截断,实际未生效 |
| 退出并验证 | 回到 LP 且能正常进 HS | 能进 HS 但误码率随温度漂移 |
第三行是最容易骗人的。测量结果落在允许范围内,不代表偏斜真的被补掉了——有可能三根线的调整量互相抵消,看起来总延迟没变,实际上采样点已经偏到眼图边缘。验证的时候不能只看“有没有报错”,要跑一段有代表性的数据模式,观察误码率随时间的稳定性。
4.3 双向转向:控制模式与快速转向的差别
双向 lane 的换向有两种做法。控制模式转向(6.5.1 节)走完整的 LP 序列,先停 HS、回 LP、协商方向、再重新进 HS。过程稳,但慢,一次换向的开销在微秒量级。快速转向(6.5.2 节)在 HS 状态下直接换向,省掉回 LP 的往返,开销能压到纳秒量级,代价是时序窗口非常窄,双方必须严格对齐。
选择原则很朴素:控制类命令、方向切换不频繁的场景用控制模式转向,代码简单、容错好;图像数据回传这种方向切换频繁、对延迟敏感的场景才考虑快速转向,而且要先把两端的时钟域对齐做扎实。
快速转向最容易出问题的地方是“保护间隔”。换向的瞬间,原来的发送端要停止驱动,原来的接收端要开始驱动,中间必须留出一段双方都不驱动的窗口。这段窗口太短会撞上争用检测(7.1 节),太长则吞吐下降。规范给了下限,实际取值要按收发两端的驱动使能延迟、走线延迟、以及时钟域同步误差叠加后算。
4.4 转向超时配成多少才不翻车
规范 7.3 节列了一组看门狗定时器,其中 7.3.5 专门讲转向错误。这类定时器的作用是防止一方在等对方响应时无限挂起。配置原则有两条:
第一,超时值必须大于“最坏情况下合法响应的耗时”,这个最坏情况要按最慢工艺角、最高温度、最长走线叠加算,不能按典型值。第二,超时值又不能大到让故障恢复变得不可接受,否则一次偶发错误会让整条链路停摆几十毫秒,上层协议栈可能直接判定设备离线。
我的做法是把超时值设成最坏合法耗时的 1.5 倍,同时在软件层加一个更长的兜底定时器,硬件超时负责快速恢复,软件兜底负责处理硬件状态机彻底卡死的情况。两层保护听起来冗余,但真到现场偶发故障的时候,这层冗余就是后悔药。
5. 排查:同步错、争用检测和误码率对不上时的五条记录
下面这五条都是实实在在踩过的,现象、原因、处理方式按顺序写,方便对照自己的板子。
第一条:链路能点亮,但每隔几分钟报一次 SoT Sync Error,重新插拔 FPC 就好一阵。
现象是错误计数缓慢增长,不插拔也能自己恢复,插拔后错误率明显下降。原因是 FPC 连接器的接触电阻随插拔次数变化,导致三根线之间的阻抗不匹配加剧,SoT 前导期间 CDR 锁不定。解决方式不是加软件重试,而是换连接器型号、在 FPC 上给三根线做等长、并把校准触发条件从“上电一次”改成“检测到连续同步错误后自动重校准”。软件重试只能掩盖问题,掩盖久了会变成现场批量返修。
第二条:双向链路一切换方向就报争用检测,日志里两个方向同时有驱动。
现象是 contention detection 标志置位,链路随后进入错误恢复。原因是快速转向的保护间隔按标称值配的,没有算上两端驱动使能电路的延迟差异。解决方式是把保护间隔按“发送端关断延迟最大值 + 接收端开启延迟最大值 + 走线偏斜”重新算一遍,取三者之和再放 20% 裕量。如果改完还是偶发,就要检查两端的时钟域是不是真的对齐了——很多争用问题的根子不在 PHY,在上一层的数据通路握手。
第三条:常温下误码率是零,一进高低温循环就开始出零星错误。
现象是误码率随温度单调上升,回常温又恢复。原因通常是 CDR 环路带宽没有覆盖整个工作温度范围内的参考时钟漂移,或者校准只在常温做过一次。解决方式是先确认参考时钟在整个温度范围内的偏差是否在规范允许的窗口内,再把校准触发条件加上温度阈值,最后才是调 CDR 环路参数。顺序不能反,先调环路会把真正的原因盖住。
第四条:ALP 模式进得去出不来,链路卡在低功耗状态直到看门狗复位。
现象是进入 ALP 之后,退出请求发出但状态机不迁移。原因多半是 escape 模式的超时参数配得太短,退出序列还没走完就被判定超时,状态机回退到一个既不是 ALP 也不是 LP 的中间态。解决方式是先把 7.3.3 和 7.3.4 定义的两个超时都放大一倍,确认能稳定退出之后再逐步收紧到合理值。直接按规范下限配,几乎必翻车。
第五条:寄存器表是从上一代 D-PHY 项目抄过来的,HS 超时差了一个数量级。
现象是高速传输偶尔整包丢失,错误日志显示 HS RX timeout。原因是 D-PHY 的超时按绝对时间给,C-PHY 的很多超时按 UI 给,速率提上去之后绝对时间缩短,抄过来的值要么太大要么太小。解决方式是把 6.14 节的参数分类表重新过一遍,把所有“随速率变”的参数标出来重算。这条坑最没有技术含量,但发生率最高,因为抄配置表太顺手了。
6. 把规范参数变成可核对的表:立项前我自己走一遍的脚本
规范读完之后,真正的风险不是“看不懂”,而是“看懂了但算错”。人眼在几百个参数里做单位换算,出错是必然的。所以我习惯在项目启动前先跑一个小脚本,把符号率、UI、聚合带宽和几个关键时序参数一次算出来,生成一张对照表,后面配寄存器直接查表。
# 按 C-PHY 的符号率反算 UI、聚合带宽与关键时序参数 SYMBOL_BITS = 16 / 7 # 每符号承载的比特数,约 2.2857 def link_budget(symbol_rate_gsps: float, trios: int, ui_multipliers: dict): ui_ps = 1e3 / symbol_rate_gsps # 1 / 符号率,换算成 ps per_trio_gbps = symbol_rate_gsps * SYMBOL_BITS total_gbps = per_trio_gbps * trios print(f"符号率 {symbol_rate_gsps} Gsym/s -> UI = {ui_ps:.1f} ps") print(f"每 trio 原始速率 {per_trio_gbps:.2f} Gbps,聚合 {total_gbps:.2f} Gbps") # ui_multipliers 里放的是规范中"以 UI 为单位"的参数系数 for name, k in ui_multipliers.items(): print(f"{name}: 系数 {k} UI -> {k * ui_ps:.1f} ps") return ui_ps, total_gbps # 示例:2.5 Gsym/s、3 个 trio,两个以 UI 为单位的时序参数 link_budget(2.5, 3, {"前导准备间隔": 4, "转向保护间隔": 6})这段脚本只做一件事:把“以 UI 为单位”的参数统一换算成 ps,和人眼能对比的走线延迟放在同一个量纲上。SYMBOL_BITS是常量,不随速率变;ui_multipliers里的系数要从规范 6.9 节的表里逐个抄,抄的时候顺手把章节号写在键名旁边,后面复查能省一半时间。输出里最该盯的是最后两行——如果转向保护间隔算出来只有几十 ps,而你的走线偏斜就有三十几 ps,那这条链路在快速转向模式下基本没戏,得回头改布局或者退回控制模式转向。
脚本跑完得到的表,我会贴进设计文档的第一页,后面每次有人提出“把速率再提一档”,先看这张表还剩多少裕量,再决定要不要改板。从那以后我每次立项评审都强制走一遍这个换算,哪怕项目再小、哪怕参数看起来“和上一代一样”。希望帮到你。
本文还有配套的精品资源,点击获取