简介:这份PDF资料围绕TYPE-C接口中的CC(Configuration Channel)功能展开,面向硬件工程师、嵌入式开发者及对USB Type-C协议感兴趣的进阶学习者,帮助厘清正反插识别、接口配置、PD供电协商与Alternate Mode切换等核心机制。资源包共1个PDF文件,大小约878KB,内容以图文结合方式呈现,涵盖CC1/CC2引脚检测原理、DFP与UFP连接示意、MUX数据切换、VL160典型电路框图、Rp/Rd电阻配置、PD2.0/PD3.0/PD3.1功率档位、VCONN与Emarker线缆供电,以及DisplayPort Alt Mode的两种模式与激活流程。目前已有6685人学习下载,适合作为接口协议入门与电路设计参考,便于快速建立CC功能整体认知并对照实际方案排查配置问题。
1. Type-C CC 功能到底在管什么:从一根线插反了说起
很多人第一次接触 Type-C,是从「终于不用看正反面」开始的。但真正让 Type-C 区别于老 USB 的,不是接口形状,而是藏在连接器里那根不起眼的 CC 线。CC 是 Configuration Channel 的缩写,它承担了插入检测、正反插识别、角色协商、供电能力广播这一整套「插上之后到底谁给谁供电、走多大电流、谁当主机」的决策。没有 CC,Type-C 就只是一根长得对称的线,插上以后两端大眼瞪小眼,谁也不知道对方是谁。
我做过一个双 Type-C 口的小板子,最初图省事只把 VBUS、GND、D+/D- 拉出来,CC 悬空。结果插上充电器毫无反应,插上电脑也认不到设备。后来补上 CC 下拉电阻,充电器才肯输出。这件事让我彻底记住:Type-C 的「智能」几乎全压在 CC 上。这篇笔记就围绕 CC 的电气行为、角色协商、供电广播和实际调试展开,适合正在画 Type-C 电路、写 PD 协议栈,或者被「插上没反应」折磨过的硬件和嵌入式从业者。
2. CC 的电气角色:上拉、下拉与 Rd/Rp 的取值逻辑
2.1 为什么 CC 上必须有电阻
Type-C 规范里,CC 引脚不是随便接的。主机侧(Source,供电方)要在 CC 上挂上拉电阻 Rp,设备侧(Sink,受电方)要挂下拉电阻 Rd。当两根线插到一起,Rp 和 Rd 形成分压,Source 检测到 CC 电压被拉低,就知道「有设备插进来了」;Sink 检测到 CC 电压被抬高,就知道「有电源接上了」。这个分压值还直接编码了供电能力,是后续 PD 协商的起点。
如果两端都不挂电阻,CC 就是浮空,谁也不知道线插没插。如果两端都挂同向电阻,分压结果落在无效区间,协议栈会判定为异常。我见过最典型的翻车,是两块自己画的板子对接,两边都按 Sink 设计挂了 Rd,结果谁都不供电,插上以后安静得像没插。
2.2 Rd 和 Rp 的阻值与对应关系
标准里 Rd 的典型值是 5.1 kΩ。Rp 则不是一个固定值,而是用不同阻值表示不同的默认供电能力。下面这张表是我调试时贴在工位上的速查表,数值来自 Type-C 规范里 Source 端 Rp 的定义。
| Source 端 Rp | 广播的默认电流能力 | 典型场景 |
|---|---|---|
| 56 kΩ | 500 mA(USB 2.0 默认) | 普通 USB 口、低功耗适配器 |
| 22 kΩ | 1.5 A | 中等功率充电器 |
| 10 kΩ | 3.0 A | 大功率充电器、笔记本适配器 |
Sink 端统一用 5.1 kΩ 下拉。Source 通过测量 CC 上的电压,反推自己挂的 Rp 和对方 Rd 的分压比,从而知道该按哪一档电流供电。这里有个容易忽略的点:Rp 是 Source 自己挂的,它测的是「自己挂的 Rp 和对方 Rd 分压后的电压」,所以同一块 Source 板子换不同 Rp,广播能力就变了。
2.3 正反插识别是怎么靠 CC 完成的
Type-C 接口上下两排引脚是对称的,CC1 和 CC2 分别位于两侧。线缆内部只把其中一根 CC 连到插头,另一根悬空。设备插入后,Source 检测 CC1 和 CC2 哪一根有下拉,就能判断插入方向,然后通过多路开关把 D+/D- 映射到正确的引脚对上。这就是「正反插」的电气实现,不靠机械结构,靠 CC 的哪根被拉低。
调试时如果发现「只有一面能识别,翻过来就不行」,八成是 CC1 或 CC2 有一路虚焊或者多路开关没切过去。用万用表量两根 CC 对地电阻,正常应该是一根 5.1 kΩ、另一根开路(对 Sink 而言),如果两根都开路或者都短路,方向识别必然出问题。
3. 用 MCU 读 CC 电压:从 ADC 采样到角色判定
3.1 最小检测电路与分压计算
如果你用 MCU 做 Type-C 设备,常见做法是用两个电阻分压后接 ADC,分别监测 CC1 和 CC2。下面是一个我常用的检测电路参数:CC 线先串一个 100 kΩ 限流电阻,再并一个 100 kΩ 到地,ADC 取中间点。这样即使 CC 上出现异常高压,也不会直接打穿 MCU 引脚。
// cc_detect.c // 假设 ADC 为 12 位,参考电压 3.3V // CC 分压:CC -- 100k --+-- 100k -- GND // | // ADC #define ADC_MAX 4095 #define VREF_MV 3300 #define DIVIDER_RATIO 2 // 两个 100k 等分 int read_cc_mv(int adc_raw) { int adc_mv = (adc_raw * VREF_MV) / ADC_MAX; // 还原到 CC 线上的实际电压 return adc_mv * DIVIDER_RATIO; } // 根据 CC 电压粗判角色 // Source 挂 Rp,Sink 挂 Rd,分压后电压落在不同区间 const char* judge_role(int cc_mv) { if (cc_mv < 200) return "Sink attached (Rd detected)"; if (cc_mv > 800) return "Source attached (Rp detected)"; return "Invalid / floating"; }这段代码的逻辑很直白:先把 ADC 原始值换算成引脚电压,再乘分压比还原到 CC 线电压。参数上,100 kΩ 分压电阻不能太小,否则会干扰正常的 Rp/Rd 分压;也不能太大,否则 ADC 输入阻抗影响精度。我一般用 100 kΩ 这一档,实测误差在 ±5% 以内,足够做角色粗判。注意这里只是粗判,真正的供电能力协商要靠 PD 协议,不能只靠 ADC 电压。
3.2 采样时序与去抖
CC 上的电压在插入瞬间会有抖动,尤其是线缆接触弹跳。如果 MCU 一检测到电压变化就立刻判定角色,很容易误判。我的做法是连续采样 5 次,间隔 2 ms,取中位数,再和阈值比较。下面是一个简单的去抖实现。
// 连续采样取中位数,避免接触抖动导致误判 int sample_cc_stable(int (*read_fn)(void)) { int buf[5]; for (int i = 0; i < 5; i++) { buf[i] = read_fn(); delay_ms(2); } // 简单冒泡排序取中位 for (int i = 0; i < 4; i++) for (int j = 0; j < 4 - i; j++) if (buf[j] > buf[j+1]) { int t = buf[j]; buf[j] = buf[j+1]; buf[j+1] = t; } return buf[2]; }参数说明:采样间隔 2 ms 是经验值,太短去抖效果差,太长影响插入响应速度。5 次采样覆盖 10 ms 窗口,能滤掉大部分机械弹跳。如果你的应用对插入响应要求高,可以降到 3 次采样、1 ms 间隔,但误判率会上升。这个取舍要根据实际线缆质量来调,没有万能值。
3.3 角色判定的边界情况
实际调试中,角色判定会遇到几种边界。第一种是「都挂 Rd」,两块 Sink 板对接,CC 电压接近 0,判定为无效,这是对的,但用户会以为坏了。第二种是「都挂 Rp」,两块 Source 对接,CC 电压接近 VREF,同样无效。第三种是 CC 线对地短路,电压恒为 0,和 Sink 插入很像,但此时如果 VBUS 没有正常输出,就要怀疑短路。
我一般会在固件里加一个状态机:先判 CC 电压区间,再结合 VBUS 是否出现、D+/D- 是否有枚举,三者交叉验证。单看 CC 电压就下结论,翻车概率不低。
4. PD 协商里的 CC 报文:BMC 编码与 SOP 序列
4.1 CC 上的数据是怎么调制的
当 CC 完成角色判定后,如果双方都支持 PD,就会在 CC 线上跑 BMC(Biphase Mark Coding)编码的报文。BMC 的特点是每个 bit 周期内必有一次电平翻转,用翻转的位置表示 0 或 1。这种编码自带时钟恢复能力,接收端不需要单独的时钟线。CC 线此时既是检测线,又是数据线,还是供电广播线,一根线三用。
BMC 的 bit rate 是 300 kbps ± 10%,接收端用边沿检测恢复时钟。调试时如果 PD 协商失败,用示波器看 CC 上有没有 300 kbps 的翻转波形,是最快的判断方法。没有波形,说明根本没进 PD 状态;有波形但协商失败,就要看报文内容。
4.2 SOP 序列与报文类型
PD 报文按 SOP(Start of Packet)序列区分对象。SOP 是发给线缆另一端的端口伙伴,SOP' 是发给线缆里的 e-Marker 芯片,SOP'' 是发给主动线缆的。下面这张表是我整理的三类 SOP 的用途。
| SOP 类型 | 目标 | 典型用途 |
|---|---|---|
| SOP | 端口伙伴 | Source Capabilities、Request、Accept |
| SOP' | 线缆 e-Marker | 读取线缆电流能力、线缆类型 |
| SOP'' | 主动线缆 | 主动线缆的特定配置 |
Source 先发 Source Capabilities,广播自己能提供的电压电流档位;Sink 从中选一档,发 Request;Source 回 Accept,然后调整 VBUS,再发 PS_RDY。这一套走完,供电才算真正建立。任何一步超时或 CRC 错误,协商就会重来或失败。
4.3 用日志判断协商卡在哪一步
我调试 PD 时习惯在固件里把每个发出的报文类型和收到的报文类型打出来。下面是一段伪代码,展示怎么记录协商状态。
// pd_log.c // 记录 PD 协商关键节点,便于定位卡点 void pd_trace(const char* dir, const char* msg_type, int sop_type) { // dir: "TX" 或 "RX" // msg_type: "SourceCap" / "Request" / "Accept" / "PS_RDY" // sop_type: 0=SOP, 1=SOP', 2=SOP'' printf("[PD] %s %s SOP%d\n", dir, msg_type, sop_type); } // 在状态机里调用 void pd_state_machine(void) { switch (state) { case SEND_SRC_CAP: pd_trace("TX", "SourceCap", 0); send_source_cap(); state = WAIT_REQUEST; break; case WAIT_REQUEST: if (recv_request()) { pd_trace("RX", "Request", 0); state = SEND_ACCEPT; } break; // ... 其余状态 } }这段日志的价值在于:协商失败时,你能一眼看出是 SourceCap 没发出去,还是 Request 没收到,还是 Accept 之后 PS_RDY 没来。参数上,日志要带时间戳,否则分不清先后。我一般还会把 CC 电压和 VBUS 电压一起打出来,交叉验证。
5. 避坑与排查:CC 调试里最常见的 5 个翻车现场
5.1 插上没反应,CC 电压正常但 VBUS 不出
现象:CC 电压测出来是正常的分压值,说明角色判定没问题,但 VBUS 始终 0V。原因通常是 Source 端的 VBUS 使能逻辑没写对,或者 VBUS 开关管驱动电路有问题。解决:先量 VBUS 开关管的栅极有没有驱动信号,再查固件里使能 VBUS 的条件是不是依赖了某个没满足的状态。我遇到过一回,是 PD 状态机卡在 WAIT_REQUEST,根本没走到开 VBUS 那一步。
5.2 只有一面能识别,翻面就失效
现象:Type-C 插一面正常,翻过来就不识别。原因基本锁定在 CC1 或 CC2 有一路没接好,或者多路开关切换逻辑写反了。解决:量两根 CC 对地电阻,正常应该一根 5.1 kΩ、另一根开路;如果两根都开路,查焊接;如果两根都 5.1 kΩ,查是不是多路开关没切。还有一种可能是线缆本身只连了一根 CC,换根线试试。
5.3 PD 协商反复重试,日志里全是 SourceCap
现象:日志显示 SourceCap 一直在发,但收不到 Request。原因可能是 Sink 端没正确解析 SourceCap,或者 CC 上的 BMC 信号质量太差,Sink 解不出来。解决:用示波器看 CC 波形,确认 300 kbps 翻转是否干净;再查 Sink 端 PD 解析代码的 CRC 校验和超时设置。我踩过一次,是 Sink 端把 SOP 和 SOP' 搞混了,把发给线缆的报文当成了发给自己的。
5.4 大电流档位申请失败,只能跑 500 mA
现象:Source 广播了 3A 档,但 Sink 申请后只拿到 500 mA。原因通常是线缆没有 e-Marker,或者 e-Marker 读取失败,Source 出于安全只给默认电流。解决:换一根带 e-Marker 的线缆,或者在固件里确认 SOP' 报文有没有正常收发。注意,不带 e-Marker 的线缆按规范只能跑 3A 以下,这是硬约束,不是 bug。
5.5 CC 对地短路,误判为 Sink 插入
现象:CC 电压恒为 0,固件判定为 Sink 插入,但实际没有设备。原因是 CC 线对地短路,和 Rd 下拉的表现一样。解决:在判定为 Sink 之后,再检查 VBUS 是否有正常输出、D+/D- 是否有枚举行为。如果 VBUS 一直不出,或者 D+/D- 毫无动静,就要怀疑短路。我一般会在固件里加一个「CC 低但 VBUS 无输出」的异常状态,提示硬件排查。
6. 进阶技巧:用 e-Marker 读取和 VBUS 斜率验证 CC 链路
6.1 读 e-Marker 确认线缆能力
e-Marker 是线缆里的一颗小芯片,通过 SOP' 报文告诉 Source 这根线能跑多大电流、支持什么速率。如果你做的是 Source 设备,读 e-Marker 是判断线缆是否合格的最直接手段。下面是一段读取 e-Marker 的伪代码框架。
// emarker.c // 通过 SOP' 读取线缆 e-Marker 信息 typedef struct { int max_current_a; // 线缆支持的最大电流 int cable_speed; // 线缆速率等级 int is_active; // 是否主动线缆 } emarker_info_t; int read_emarker(emarker_info_t *info) { // 发送 Discover Identity,SOP' 目标 if (pd_send_discover_identity(SOP_PRIME) != 0) { return -1; // 线缆无 e-Marker 或通信失败 } // 等待 Discover Identity ACK if (pd_wait_ack(SOP_PRIME, 100) != 0) { return -2; // 超时 } // 解析返回的 ID Header 和 VDO info->max_current_a = parse_max_current(); info->cable_speed = parse_cable_speed(); info->is_active = parse_active_flag(); return 0; }参数说明:超时 100 ms 是经验值,e-Marker 响应通常很快,超过这个时间基本可以判定通信失败。解析出来的 max_current_a 如果小于你想要的档位,Source 就应该拒绝大电流请求,这是安全逻辑。我一般会在产品里把 e-Marker 读取失败作为降级条件,只给 500 mA,避免线缆过热。
6.2 用 VBUS 斜率验证 CC 协商是否真正生效
CC 协商成功的一个间接证据,是 VBUS 的上升斜率。Source 在收到 PS_RDY 后才会打开 VBUS,而且不同档位的 VBUS 上升时间有差异。用示波器抓 VBUS 从 0 到 5V 的上升沿,如果斜率很缓或者有台阶,说明 VBUS 开关管或软启动电路有问题。我习惯把 CC 波形和 VBUS 波形双通道一起抓,CC 上出现 PS_RDY 报文后,VBUS 应该在几十毫秒内建立。如果 CC 上报文走完了,VBUS 迟迟不动,那问题就在 VBUS 使能路径,不在 CC。
6.3 一个我坚持了很久的习惯
每次画完 Type-C 电路,我都会先不焊主芯片,只焊 CC 电阻和 VBUS 开关,用外部电源模拟 Source 和 Sink,手动量 CC 分压和 VBUS 通断。这一步能提前暴露 80% 的 CC 相关问题,比焊完整个板子再调试省事得多。CC 这东西,玄学不多,但细节极密,电阻错一个数量级,行为就完全变了。希望帮到你。
本文还有配套的精品资源,点击获取