XVF3800 的 I²C 双身份:Master 与 Slave 到底能不能切换
上一篇讲的是 USB 与 I²C 的分工。这一篇聚焦 I²C 自身:同一颗芯片,为什么既要做从机被 MCU 控制,又要做主机去控制 DAC 和扩展器?两者在什么条件下互斥、什么条件下可以并存?
一句话结论:XVF3800 的 I²C 只有一对引脚(Pin 44 / Pin 45),数据手册明确写着这对线「接收 I²C 主机的控制命令,或控制 DAC——视配置而定」。所以 Master 与 Slave 不是运行期可以随时切换的两种模式,而是由固件配置在编译期指派的一种身份。官方固件没有提供两者同时在线的镜像,要并存就得改总线拓扑或改固件。
一、从引脚表读出的关键事实
很多人以为 Slave 用一对引脚、Master 另用一对,实际并非如此。XVF3800 的数据手册引脚表中,这两条线的描述是并列的:
Pin 44 X0D37 I2C_SCL:I2C serial clock line for receiving control command from I2C host or controlling DAC (depending on config)
Pin 45 X0D38 I2C_SDA:I2C serial data line for receiving control command from I2C host or controlling DAC (depending on config)
这一句「depending on config」就是全部答案的起点:一对物理引脚,两种逻辑身份。做 Slave 时它监听外部主机(地址 0x2C);做 Master 时它去写 DAC、IO 扩展器和 LED 控制器。身份随固件配置变化,不能同时成立——因为一条 I²C 总线的 SCL 在任一时刻只能有一个驱动者。

[图 1 同一对引脚(44/45)的两种身份:被控的 Slave 与主控外设的 Master]
二、Master 模式在干什么:三个具体任务
XVF3800 做主机,不是为了对外通信,而是为了照料自己板上的外设。官方固件里能确认的用途有三个:
DAC 上电初始化。开发套件用 TLV320DAC3101 作线路输出。上电流程是先通过 GPO(Pin 40,CODEC_RST_N)复位,再由
dac3101_init()通过 I²C 写入一串寄存器。这是 Master 角色最刚性、无法回避的用途——没有它,模拟输出根本不出声。IO 扩展器。打开
io-exp构建选项后,固件会驱动挂在总线上的 PCAL6416A(地址 0x21),把 4 个额外按键读进来、把 1 路 LED 写出去,并通过 HID 上报给主机。LED 控制器。同一块扩展板上还有 IS31FL3193(地址 0x68),用于环形灯效。Ring 事件走快闪、Hold 事件走慢闪,靠的就是写不同的闪烁模式寄存器。
实现上很值得借鉴:XVF3800 没有把 I²C 读写塞进控制逻辑,而是拆成「服务者 + 定时任务」两层。GPO 命令先到 io_expander_gpo_servicer,只更新共享内存里的目标状态;真正的总线操作由 io_expander_task 按固定周期(IO_EXP_POLL_TIME_MS)统一执行,读按键、写 LED 都在这一个循环里完成。

[图 2 Master 侧的任务结构:控制命令与 I²C 物理读写解耦,由定时任务统一收发]
三、Slave 模式:对外的那张脸
Slave 的角色比较单纯——它是 XVF3800 被外部主控(MCU、SoC、树莓派)操作的入口:固定地址 0x2C、100 kbps、单次读写不超过 60 字节,命令走 ResID + CmdID。前提是固件采用了 INT 配置并且控制协议选了 I²C(-i2c)。在 UA 配置下,控制走 USB EP0,这对引脚就不会以 Slave 身份工作。
于是两种身份的使用场景天然错开:要么被上位机管,要么去管板上的器件。这也是为什么官方构建里几乎看不到两者同时出现——不是因为做不到,而是因为大多数形态只需要一个。
四、官方构建里,两种角色分别怎么出现
| 构建配置 | I²C 身份 | 总线主控方 | 典型用途 |
|---|---|---|---|
| UA | Master | XVF3800(控 DAC) | USB 会议设备,控制走 EP0 |
| UA + io-exp | Master | XVF3800(DAC + 扩展器 + LED) | 需要额外按键与灯效的终端 |
| INT-Device + -i2c | Slave 0x2C | 外部 MCU / SoC | 面板、speakerphone 的本地主控 |
| INT-Device + -spi | 不使用 I²C | 外部 MCU(SPI) | 主机接口资源紧张时 |
| inthost + i2c(定制) | Slave 0x2C | 外部 MCU 独占,codec 也归它 | 纯语音前端(如 Seeed 方案) |
表格里有个反直觉的地方:支持 IO 扩展器的那套构建属于 UA 变体,也就是说它一边通过 USB 做音频和控制,一边还要当 I²C 主机去轮询扩展器。这说明「Master 与 USB 并存」完全没问题——真正互斥的是 Master 与 Slave 同时出现在同一对引脚上。
五、为什么不能「运行期切换」
xcore.ai 上的 I²C 并不是一个独立硬件外设,而是用 1-bit 端口软件位操作实现的(lib_i2c)。这带来两个直接后果:
身份由任务与端口在编译期绑定。Slave 任务需要持续监听 START、随时准备应答;Master 任务则在需要时主动拉低 SCL。两者对同一对端口的驱动方式相反,无法在同一个任务树里共存。
切换意味着重配端口与重启任务。技术上端口可以重配,但要付出「切换窗口内总线不可用」的代价;如果总线上还有别的主机,重配期间的电气状态还可能与对方冲突。
所以标准固件的做法很干脆:编译期定死一种身份,运行期不做切换。这也是官方文档反复强调「choice is set in the firmware and cannot be changed during operation」的原因。
六、确实需要并存时,三条路线

[图 3 总线分离、主机接管外设、分时复用——三条路线的代价与边界]
| 路线 | 改什么 | 代价 | 适用 |
|---|---|---|---|
| 1 总线分离 | 固件把 master 映射到其它 1-bit 端口,硬件走两组总线 | 改板 + 改配置,周期最长 | 新设计、对可靠性要求高 |
| 2 主机接管外设 | XVF3800 只做 Slave,DAC/LED 由 MCU 初始化与驱动 | MCU 侧软件工作量增加 | 沿用现有硬件,最快落地 |
| 3 分时复用 | 上电先用 Master 初始化外设,再重配为 Slave | 切换窗口丢命令、时序与仲裁风险 | 熟练固件团队、可接受风险 |
业界的常见选择是路线 2:把总线的唯一主机交给 ESP32——XMOS 只做 0x2C 从机,codec 由主机侧初始化。这样不需要改 XMOS 固件,也不会出现两个主机抢总线,代价是 MCU 必须承担外设初始化逻辑。
七、落地时的四个细节
上拉与电压域。SCL/SDA 的上拉必须接到与 XVF3800 相同的 3.3 V 数字域;跨域上拉会导致应答偶发失败,且在 Slave 模式下表现为「能扫描到 0x2C 但读回乱码」。
DAC 复位时序。CODEC_RST_N(Pin 40)是 GPO,必须先复位再写寄存器;顺序搞反会出现初始化成功但无输出的怪现象。
扩展器轮询周期。按键扫描是定时轮询,周期太短会挤占 I²C 带宽(只有 100 kbps),太长则手感迟滞;官方用 IO_EXP_POLL_TIME_MS 这个宏统一控制,按 10–20 ms 调比较稳妥。
多主机不是不能做,而是不该顺手做。I²C 支持多主仲裁,但需要每个主机都能检测冲突并重发;在语音设备里引入这种不确定性,收益远小于风险——把主机数量控制为 1,是更划算的工程决策。
八、结论
XVF3800 的 I²C Master 与 Slave 不是两种可以随时拨动的模式,而是同一对引脚上的两种身份,由固件在编译期指派。Master 用于照料板载外设(DAC、IO 扩展器、LED 控制器),Slave 用于接受外部主控的指令;官方每个构建只启用其中之一,Master 与 USB 音频并存则毫无问题。
需要两者兼顾时,优先考虑让总线只有一个主机:要么外部 MCU 全接管外设,XMOS 纯做从机(路线 2,最快);要么物理上分成两条总线(路线 1,最稳)。分时复用虽然在技术上成立,但要把「命令丢失窗口」和「端口重配时序」写进设计文档,只建议成熟团队尝试。
一句话记法:Slave 是「听谁的」,Master 是「管谁的」,同一时刻只能选一个。