最近在调一块多传感器板子,按老规矩先做一轮 USB 转 I2C 的总线扫描,顺便在 100KHz 速率档位上把时序基线测一遍,结果直接归档进 Excel。标题里这行 “USB TO I2C_(Excel)_Scan —— 100KHz总线速率测试_A”,就是我们内部测试记录里的编号,A 代表首轮常温批次。别看这名字长得像文件名拼接,它其实覆盖了一整套可复用的 I2C 总线健康检查流程:用 USB 转 I2C 适配器把电脑接到目标板,以标准模式 100KHz 跑一遍地址扫描,确认哪些从设备在线,再把实测时序、应答情况、环境参数写进 Excel 留档。
这套流程最适合三类人:一是嵌入式工程师,新板或新物料回来要快速确认 I2C 地址没冲突、器件能正常通信;二是产线测试或硬件验收的同学,需要把总线检查变成可追溯的记录,而不是靠肉眼和示波器截图;三是刚接触 I2C、想搞明白“扫描到底在扫什么”的学习者。看完这篇文章,你可以直接照着搭一套自己的 I2C 总线基线测试方案。
1. 任务拆解与方案选型
1.1 标题背后到底藏了哪些需求
先把标题拆开看。“USB TO I2C”定义的是接入方式,也就是用 PC 通过 USB 转 I2C 适配器去控制目标总线;“Excel”定义了结果呈现方式,扫描结果要落到表格里,方便存档和对比;“Scan”是核心动作,遍历 I2C 地址空间,找出哪些地址有设备应答;“100KHz总线速率测试”定义了测试条件,SCL 时钟设定在标准模式最高的 100kHz,并验证时序是否达标;最后的 “_A” 则是我们内部约定俗成的批次后缀,代表这次测试属于首轮常温组,后面可能还有 _B、_C 对应不同板卡或不同温度条件。
这个任务要解决的实际上有两个问题。第一个是“有没有设备在线、地址是多少”,也就是扫描。第二个是“总线在 100KHz 下是不是真的稳”,这就涉及 SCL 频率、上升下降沿、建立保持时间这些时序参数。很多人做完扫描就收工了,其实漏了后半段:扫描只能证明“某个地址回应了 ACK”,不能证明“这个地址在 100KHz 下能稳定工作”。有些板子低速下一切正常,一提到 100KHz 就丢 ACK、卡总线,所以速率测试必须和扫描绑在一起做。
1.2 为什么选 USB 转 I2C、100KHz 和 Excel 这个组合
先说接入方式。MCU 原生 I2C 调试当然可以,但每次改代码、烧固件、看日志太绕。USB 转 I2C 适配器把 I2C 总线直接暴露给 PC 端脚本,扫描一个地址只要几毫秒,遍历完整个地址空间也就一两秒,改频率、改探测方式都特别快。调试阶段我基本不用 MCU 自己扫,因为固件里写扫描代码还得考虑 Flash 占用、打印缓冲、看门狗超时这些问题,实在没必要。
再说 100KHz。I2C 标准模式的上限就是 100kHz,所有符合规范的从设备都必须在 100KHz 下工作。先在这个档位做基线测试,是因为它兼容性最广、时序容限最大,一旦 100KHz 都跑不稳,那 400KHz 快速模式想都不要想。另一个原因是排查问题时有余量,示波器或逻辑分析仪抓波形,100KHz 的周期是 10us,哪怕采样率不高也能看得比较清楚。新板、新物料、换 PCB 批次后,我最先跑的就是 100KHz 基线。
Excel 则是为了把“感觉没问题”变成“数据证明没问题”。纯文本日志也能记录,但你要对比二十块板卡的扫描结果,或者统计这批板子有几个地址异常,Excel 的筛选、条件格式、透视表就方便太多了。热词里还有人提到 markdown 表格转换 Excel,说明大家确实有“先把结果整理成文本表、再转成 Excel”的习惯。我的建议是别走这步弯路,脚本直接生成 xlsx,字段格式一次定好,后面批次直接复用模板。
2. 测试环境搭建与基础准备
2.1 硬件选型与连接要点
USB 转 I2C 适配器市面上方案很多,我实际用下来主要看三个东西:频率精度、软件生态、是否支持协议级抓包。点名几个常见方案:
| 芯片/方案 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| FT232H | 频率可控、pyftdi 库成熟、支持多协议 | 价格稍高 | 自动化脚本、批量测试 |
| CH341A | 便宜、工具多 | Windows 驱动签名麻烦、频率精度一般 | 临时验证、简单读写 |
| CP2112 | HID 免驱、官方有 Excel 插件 | 可定制性弱 | 快速扫描、记录到表格 |
| 总线分析仪 | 抓包深度强、时序测量准 | 贵 | 疑难问题定位 |
我这次用的是 FT232H 方案,主要看中 pyftdi 库可以直接在 Python 里配置频率和探测地址,方便后面生成 Excel。如果你是第一次玩,CH341A 也能用,但如果你打算把流程固化下来反复跑,建议直接上 FT232H 或同级别方案,省下的时间比差价值钱。
连接上要注意四根线:SCL、SDA、GND,还有一个电源轨。I2C 是开漏结构,SCL 和 SDA 必须接上拉电阻到电源,不能只接两根信号线就指望通信。上拉电阻阻值的选择有个简单估算:以 3.3V 系统、总线电容约 100pF 为例,上升时间约等于 0.85 乘以电阻再乘以电容,要保证 100KHz 下上升沿不超过 1us,电阻取 4.7k 左右是安全值。总线长了或挂的设备多了,电容变大,电阻就适当往小了选,比如 2.2k。还有一个容易忽略的点:如果你的适配器输出电平是 5V,而板子是 3.3V 器件,最好确认适配器有没有电平转换,或者用外部电平转换模块,别直接硬怼。
还有一个反直觉的经验:SCL 和 SDA 两根信号线尽量短、尽量等长。杜邦线超过 20cm 在 400KHz 下基本会出问题,100KHz 虽然容限大,但线太长会让边沿变缓,导致适配器采样到错误的电平。我测试时习惯把杜邦线控制在 10cm 以内,实在要延长就换双绞线或者加总线缓冲器。
2.2 软件工具链与驱动准备
先说一个常见的坑:USB 转 I2C 和 USB 转串口并不是一回事。FT231X 是 USB 转 UART 的芯片,驱动名字里带 UART;而 FT232H 这类支持 I2C/SPI/JTAG 的是靠 MPSSE 引擎,功能完全不同。别看到 USB 相关就去找 UART 驱动,很多新手卡在这一步。
Windows 下用 FT232H,需要装 FTDI 的 D2XX 驱动或 VCP 驱动,推荐 D2XX。Linux 下一般不用额外装驱动,内核自带 ftdi_sio,配合 pyftdi 直接访问即可。需要提醒的是,如果你在虚拟机里调试,USB 设备透传经常会出幺蛾子,比如设备枚举不稳定、驱动加载失败。我的习惯是物理机上跑调试脚本,虚拟机只用来写文档。
软件这边我主要用 Python 加两个库:pyftdi 负责 USB 转 I2C 通信,openpyxl 负责生成 Excel 报告。安装很简单:
pip install pyftdi openpyxl装完先跑一个最简单的连接测试,确认适配器能被系统识别:
from pyftdi.ftdi import Ftdi print(Ftdi().list_devices())能看到类似ftdi://ftdi:232h/1的设备路径,说明环境已经通了。这个步骤一定要先做,不然后面所有代码都会卡在设备识别上,你还以为是接线问题。
3. 100KHz 速率时序验证:参数规格与实测方法
3.1 标准模式时序参数速查
说 100KHz 测试,不能只把频率设成 100000 就完事。I2C 标准模式除了 SCL 频率上限,还规定了一堆时序参数,我在测试记录里固定了几个必须看的:
| 参数 | 标准模式要求 | 含义 |
|---|---|---|
| fSCL | 最高 100kHz | SCL 时钟频率 |
| tHIGH | 最小 4.0us | SCL 高电平时间 |
| tLOW | 最小 4.7us | SCL 低电平时间 |
| tr | 最大 1us | SCL/SDA 上升沿时间 |
| tf | 最大 0.3us | SCL/SDA 下降沿时间 |
| tSU;DAT | 最小 250ns | SDA 数据建立时间 |
| tHD;DAT | 最小 0ns | SDA 数据保持时间 |
| tSU;STO | 最小 4.0us | 停止条件建立时间 |
很多适配器标称 100kHz,实测可能只有 96kHz,或者高电平时间偏短。频率差几个 kHz 通常不影响通信,但如果 SCL 高电平时间不够 4us,那就是硬性违规,某些要求严格的从设备会直接不响应。所以速率测试不只是把频率参数填上,还要用逻辑分析仪或示波器实际抓波形,逐项对照这张表。
3.2 用逻辑分析仪实测 SCL 频率与边沿
抓波形时,逻辑分析仪采样率建议至少 25M,最好 50M。采样率太低会看到很多假毛刺,影响判断。接好通道后,让脚本对一个已知设备重复读写,比如我常用 0x50 地址的 EEPROM 做目标,循环读它的状态寄存器。抓到的波形上,重点看三件事:SCL 实际频率是多少、高电平低电平分别多长、上升沿有没有明显变缓。
实测记录里一个重要心得:SCL 频率是从一个上升沿到下一个上升沿算周期,再换算成频率,不要只看一个脉冲的宽度。占空比也要留意,标准模式没规定占空比要 50%,但如果看到明显不对称,比如高电平只有 2us、低电平有 8us,说明适配器要么是软件模拟时序,要么频率配置有问题。
边沿缓是最容易被忽略的问题。上升沿如果超过 1us,在长总线、大电容的条件下很容易出现,表现是设备偶发不应答、或者某个地址扫描时有时无。如果你抓波形看到梯形波而不是方波,优先怀疑上拉电阻太大或总线上电容太大,而不是怀疑适配器。
3.3 时序参数不合格怎么调
实测发现时序不合格,排查顺序很固定。频率整体偏低:先看适配器配置是不是真的生效了,pyftdi 里 frequency 参数设置后,可以用工具读回,有些型号标称和实际差 10%,换一个品牌适配器往往就好了。高电平时间不够:通常是适配器时序引擎的问题,比较多见于软件模拟 I2C 的方案,解决方法是换带硬件 I2C 控制器的适配器芯片,或者降低目标频率到 90kHz。上升沿太缓:这是硬件问题,减上拉电阻阻值、缩短线缆、减少挂载设备数量,三选一或三选二。下降沿缓:少见,通常是被测板上的地线阻抗问题,检查共地是否可靠。
我的习惯是每调一次参数,就重新抓一次波形,并把前后两组数据并排写进 Excel。这样调完心里有数:到底是频率配置的偏差,还是板子布线的问题,查记录一眼就能看出来。
4. 地址扫描与 Excel 记录实现
4.1 I2C 地址扫描的原理与扫描算法
扫描的原理说白了就是“试探”。I2C 通信的第一步是主控发出起始条件,然后发送 7 位从设备地址加 1 位读写标志。如果总线上有设备匹配这个地址,它会在第 9 个时钟周期把 SDA 拉低,也就是回应 ACK;如果没人认领这个地址,SDA 保持高电平,即 NACK。扫描就是遍历所有合法地址,看哪个地址收到 ACK。
合法地址范围要注意一下。7 位地址理论上有 128 个,但 0x00 到 0x07 和 0x78 到 0x7F 是保留地址,分别用于广播、起始字节、HS 模式前缀、10 位寻址扩展等,普通扫描直接跳过。实用范围是 0x03 到 0x77,共 117 个地址。
还有一个细节:有的从设备只对读方向应答,有的只对写方向应答。比如某些传感器,你发“地址+写位”它不鸟你,发“地址+读位”才应答。所以完整的扫描要对每个地址都做一次读探测和一次写探测。这也是为什么 I2C 扫描工具的菜单里通常有 Read mode 和 Write mode 的选项。
4.2 用 Python 快速实现 100KHz 扫描
基于 pyftdi,完整的扫描脚本可以这样写:
from pyftdi.i2c import I2cController, I2cIOError CTRL = I2cController() CTRL.configure('ftdi://ftdi:232h/1', frequency=100_000) hits = [] for addr in range(0x03, 0x78): for direction, read_flag in (('R', True), ('W', False)): try: ack = CTRL.probe(addr, read=read_flag, write=not read_flag) if ack: print(f'0x{addr:02X} {direction} ACK') hits.append((addr, direction)) except I2cIOError: continue print(f'total ACK: {len(hits)}')说明几个要点。frequency 参数直接设 100000,pyftdi 会按这个目标频率初始化。probe 方法内部实现了完整的起始、地址发送、等待 ACK、停止流程,比自己用 exchange 拼字节省心。循环里读和写分开探测,避免漏掉单方向应答的设备。每个地址探测之间会有微秒级延时,整体遍历 117 个地址、两个方向,大概两三秒。
跑完扫描,你不仅知道哪些地址在线,还能初步判断有没有地址冲突。正常情况下,总线上 0x50 有设备就只该出现 0x50,如果 0x50 和 0x51 都响应,而且你明明只挂了一片 EEPROM,那就要怀疑地址引脚配置错了或者芯片本身有问题。
4.3 Excel 记录格式设计与多批次对比
扫描结果如果只是打印到屏幕,那就失去了一半价值。我的 Excel 表结构固定成这样:
| 字段 | 示例 | 说明 |
|---|---|---|
| 批次 | A | 对应标题里的 _A |
| 测试时间 | 2025-01-18 14:30 | 精确到分钟 |
| 板卡编号 | BRD-003 | 被测板号 |
| 适配器型号 | FT232H | 记录测试环境 |
| 设定频率 | 100000 Hz | 脚本参数 |
| 实测SCL频率 | 98.5 kHz | 逻辑分析仪读数 |
| SCL高电平 | 4.3 us | 逻辑分析仪读数 |
| SCL低电平 | 5.1 us | 逻辑分析仪读数 |
| 上拉电阻 | 4.7 kOhm | 板上实测值 |
| 供电电压 | 3.3 V | 测量点电压 |
| 应答地址列表 | 0x50(W),0x50(R) | 扫描结果汇总 |
| 异常说明 | 无 | 异常才填 |
| 判定 | PASS | 条件格式判断 |
写入 Excel 用 openpyxl,核心思路是先把所有结果整理成列表,再一次性写入,避免逐格操作浪费时间。顺带做一个条件格式:判定为 FAIL 的行自动标红,没有应答设备时把应答地址列表标黄。这样后面堆了几十行数据,扫一眼颜色就能发现问题。具体代码可以这样:
from openpyxl import Workbook from openpyxl.styles import PatternFill RED = PatternFill('solid', start_color='FFC7CE') YELLOW = PatternFill('solid', start_color='FFEB9C') wb = Workbook() ws = wb.active ws.append(['批次', '测试时间', '板卡编号', '应答地址列表', '判定']) # 假设 scan_data 是已整理好的字典列表 for row in scan_data: ws.append(list(row.values())) for r in ws.iter_rows(min_row=2): if r[4].value == 'FAIL': for cell in r: cell.fill = RED elif r[3].value == '': r[3].fill = YELLOW wb.save('i2c_scan_100k_A.xlsx')一个容易踩的坑是地址列表写入 Excel 后变成科学计数法或数字格式,比如 0x50 被解释成 80。解决办法是地址以字符串形式写入,比如"0x50"而不是0x50这个整数,openpyxl 写入字符串就不会被转换。
5. 常见问题与排查经验实录
5.1 全扫无应答的排查顺序
这是最打击人的情况:代码没问题,适配器也识别了,但扫描结果一个 ACK 都没有。排查顺序我建议固定下来,能省大量时间。第一步量静态电平,用万用表量 SCL 和 SDA 对地电压,正常空闲状态应该都是高电平,接近电源电压。如果 SDA 是低电平,大概率是总线上某个设备把总线拉死了,常见原因是设备地址冲突或者从机锁死。如果两根线都是低电平,大概率是没接上拉,或者上拉接到了没有供电的电源轨。
第二步检查接线顺序,SCL 和 SDA 接反在杜邦线场景下极其常见。我踩过一次坑,两根线颜色太接近,插反之后扫了十分钟全空,最后量电平才发现问题。后来习惯用固定配色,SDA 用橙色、SCL 用黄色,写进测试规范里。
第三步确认共地。适配器的 GND 和板子的 GND 必须连在一起,否则 I2C 信号参考地不一致,什么诡异现象都可能出。最后再怀疑驱动和设备枚举,用前面提到的Ftdi().list_devices()确认设备路径还在,有时候 USB 线接触不良导致设备掉线,脚本报错提示会让人误以为是总线问题。
5.2 扫描结果时有时无与数据粘连问题
扫描地址列表和上一次不一样,或者同一个地址这次能扫到、下次又没了,这类问题最烦。先说软件层面的原因:如果适配器是软件模拟 I2C,系统调度抖动会导致时序不稳定,某个地址正好卡在 ACK 判定边缘,就会时有时无。解决方法是换硬件 I2C 引擎的适配器,比如 FT232H 的 MPSSE 就是硬件状态机,时序稳定很多。
再说硬件层面的原因:总线过长或者上拉电阻偏大,导致上升沿太缓,从设备发出的 ACK 信号还没抬到高电平阈值,适配器就采样了,于是误判成 NACK。这种问题在 100KHz 下也能出现,只是概率低一些。排查时用逻辑分析仪抓那个“时有时无”的地址,观察 ACK 位的波形,如果 SDA 在 ACK 窗口内呈现缓慢上升的形态,就可以确定是边沿问题。
还有一种情况是 SDA 数据粘连。现象是所有地址都显示 ACK,密密麻麻一大片。这通常是探针逻辑写得不严谨,设备 NACK 之后没有正确释放总线,或者适配器驱动在异常后没有做总线恢复。做法很简单:每个地址探测之间加一个小的总线空闲延时,另外设置总超时时间,超时就强制停止条件。若某次扫描发现 0x00 或者 0x7F 这种保留地址也出现 ACK,先检查扫描范围,再检查是不是总线被异常拉低了。
5.3 与驱动、线缆相关的隐性坑
驱动问题上,常见的是用户把 USB 转 UART 驱动和 USB 转 I2C 驱动搞混。FT231X 是单路 UART 芯片,FT232H 是带 MPSSE 的多协议芯片,两者驱动不通用。装错驱动之后设备管理器里能看到设备名,但 pyftdi 打开时会报设备忙或功能不支持,这时候先别怀疑代码,去看看驱动对不对。
线缆方面,USB 延长线太长会导致适配器供电不足,适配器工作不稳定。我测试时如果用了 USB 延长线,会优先插在主板的背板 USB 口而不是前置面板,前置口供电质量和主板口差不少。还有一次问题是 USB 线本身是充电线,只接了电源线没接数据线,设备管理器里压根看不到设备。这种低级错误现在回想起来都好笑,但确实会浪费半小时。
虚拟机透传 USB 设备也是一类坑点。Windows 主机里跑 VirtualBox 或 VMware,把 FT232H 透传给虚拟机系统,常见现象是透传后设备正常,但过几分钟设备掉了,或者说 Adapter busy。这不是适配器问题,是虚拟机的 USB 控制器 interrupt 处理不及时。调试 I2C 这类对实时性有一定要求的场景,我不建议在虚拟机里跑。
6. 这套流程的扩展场景与个人体会
6.1 从单板调试延伸到产线批量测试
这个 Excel 扫描流程如果只是自己调试用,其实有点浪费。我后来把它扩展成了产线工装:每个板卡插上之后,脚本自动跑扫描、自动测 SCL 频率、自动判定 PASS/FAIL,结果按板卡编号写入 Excel,月底汇总一下就能看出这批物料的一致性。因为是基线测试,我不需要每个板卡都做复杂读写,只需要确认设备在线、地址对、时序达标,这三项能过滤掉绝大多数虚焊、贴错料、地址引脚搭锡的问题。
批量测试时,Excel 的价值会更明显。我可以按批次筛选,看某一批板卡的应答地址分布,如果正常板子都是 0x50、0x68 两个地址,某一批突然多出一个 0x51,那就说明 EEPROM 的地址引脚有异常。没这种表格,你只能一块块板翻日志。
6.2 给新人的三点实操建议
如果这篇文章你只记住三件事,我希望是这三条。第一条,动手前先量电平,再谈扫描。SCL 和 SDA 空闲必须为高,这是判断接线和上拉是否正确的最快方式,别一上来就跑代码,排错半天才发现线没接对。第二条,设置完 100KHz 之后,一定要用逻辑分析仪实测一遍,不要信“我设了 100K”这句话。我测过不少适配器,标称 100K 实际只有 70K 的都有,这种系统性偏差平时不明显,一旦到了协议边界就会暴露。第三条,地址列表统一用两位十六进制字符串记录,不要裸存整数,不然到 Excel 里就变成十进制数字了,看起来极不直观。
最后再分享一个小技巧。我每次拿到新板子,第一件事就是跑一遍 Scan 加 100KHz 基线,把通过的那次结果单独存成一个“黄金模板”。之后每批板子扫描完,脚本自动和黄金模板做对比,只要应答地址列表有差异,哪怕差一个地址,都标记为 FAIL。这样做的效果是,你不需要记住每块板子上该有哪些器件,也不需要每次对着原理图数地址,异常会在对比的瞬间自己跳出来。这套流程跑顺之后,I2C 总线检查从“抓瞎式调试”变成了“填表式验收”,省下来的时间足够你多调几个真问题。