DP83849ID以太网PHY中断配置与寄存器详解:从原理到实战调试

1. 项目概述

搞嵌入式网络开发,尤其是用到以太网PHY芯片的时候,寄存器配置和中断处理绝对是绕不开的核心环节。很多新手朋友拿到芯片手册,看到动辄几十页的寄存器描述,往往感觉无从下手,特别是中断部分,配置起来总觉得云里雾里,出了问题也不知道从哪里查起。今天,我就以TI(原国家半导体)的经典工业级PHY芯片DP83849ID为例,结合我这些年踩过的坑和积累的经验,把它的中断控制与关键寄存器配置掰开揉碎了讲清楚。

DP83849ID是一款支持10/100Mbps的以太网物理层收发器,在工控、交换机、网关等对稳定性和实时性要求苛刻的领域应用非常广泛。它的强大之处不仅在于物理信号处理,更在于其通过MII管理接口(MDC/MDIO)暴露出的丰富可配置寄存器。其中,中断控制机制是实现高效、低延迟网络事件响应的关键。不同于简单的轮询查询状态,合理配置中断可以让你的主控MCU或处理器从频繁的“查岗”中解放出来,只在链路断开、速度改变、自协商完成或错误计数过半等真正重要的事件发生时才被“叫醒”,这对于降低系统功耗、提升实时响应能力至关重要。

本文将深入解析DP83849ID的中断控制寄存器组(MICR和MISR)以及与之紧密相关的扩展功能寄存器。我会从寄存器每一位的含义讲起,然后给出典型的配置流程、代码片段示例,并重点分享在实际调试中如何排查中断不触发、状态位不清除等常见问题。无论你是正在评估这款芯片,还是已经在项目中遇到了相关难题,希望这篇详尽的指南都能为你提供清晰的路径和实用的解决方案。

2. 核心寄存器深度解析

要玩转DP83849ID的中断,必须首先理解其寄存器架构。它采用了分层使能的设计思路,并非简单地打开一个总开关。我们需要关注三个核心寄存器:MII中断控制寄存器(MICR)MII中断状态与杂项控制寄存器(MISR),以及作为门户的页选择寄存器(PAGESEL)。此外,一些高级诊断和配置功能藏在扩展寄存器页中,也需要通过PAGESEL来访问。

2.1 寄存器访问基础与页选择机制

在深入细节之前,我们必须先打通访问这些寄存器的“道路”。DP83849ID的寄存器空间是分页的,这有点像一本书的目录和不同章节。

页选择寄存器(PAGESEL, 地址 0x13)这个寄存器是访问扩展功能的大门。它的低两位(PAGE_SEL[1:0])决定了后续访问的寄存器地址(0x14 到 0x1F)映射到哪个“页面”。

位域名称默认值描述
15:2RESERVED0保留位,写操作被忽略,读为0。
1:0PAGE_SEL0页面选择位
00:选择扩展寄存器页0(Extended Registers - Page 0)。这是我们配置中断、LED、RMII模式等主要功能的地方。
01:保留。
10:选择链路诊断寄存器页2(Link Diagnostics Registers - Page 2)。这里包含了电缆长度检测、时域反射计(TDR)、信号质量评估等高级诊断工具。

实操心得一:页面切换的时序在通过MDIO写PAGESEL寄存器切换页面后,必须等待一小段时间(通常至少几个MDC时钟周期),再进行下一页寄存器的读写。我遇到过因为切换后立即读写导致操作失败的情况。一个稳妥的做法是,在写PAGESEL后,插入一个微秒级的延时,或者先进行一次无关的MDIO读操作(例如读一个状态寄存器),确保芯片内部页面切换稳定完成。

2.2 MII中断控制寄存器(MICR)

MICR寄存器(地址 0x11)是中断系统的“总闸门”和“输出引脚配置器”。它不关心具体是什么事件,只负责中断的全局使能和硬件引脚映射。

表:MICR寄存器位定义详解

名称默认读写描述与配置要点
15:3RESERVED0RO保留位。写操作无效,读始终为0。
2TINT0RW测试中断。这是一个非常有用的调试功能。写1会强制PHY从PWRDOWN_INT引脚产生一个中断信号,用于测试你的MCU中断线连接和中断服务程序(ISR)是否正常工作。注意:只要此位保持为1,中断就会持续产生。测试完毕后务必将其清零。
1INTEN0RW中断使能。这是中断系统的总开关。必须将此位置1,才能使能基于MISR中事件的中断。即使MISR里某个事件使能位打开了,如果INTEN=0,也不会产生任何中断。
0INT_OE0RW中断输出使能。此位控制PWRDOWN_INT引脚的功能。这是一个复用引脚。
INT_OE = 0:该引脚作为电源关断输入(Power Down Input)。拉低此引脚会使PHY进入低功耗模式。
INT_OE = 1:该引脚作为中断输出(Interrupt Output)。当有使能的中断事件发生时,PHY会通过此引脚向MCU发出低电平有效(通常,需查手册确认极性)的中断信号。

配置逻辑解析

  1. 引脚复用PWRDOWN_INT引脚默认是掉电功能。如果你计划使用中断,第一步就是在硬件设计上确认此引脚已连接至MCU的外部中断输入引脚,并且在软件初始化时,必须将INT_OE位置1,将其配置为中断输出模式。否则,即使内部有中断事件,信号也送不出去。
  2. 全局使能INTEN是第二道开关。它控制内部中断逻辑是否能够触发PWRDOWN_INT引脚的动作。
  3. 测试模式TINT位用于硬件连线测试。在系统初始化后期,可以短暂置位TINT,然后在MCU端检查是否能收到中断,这是验证从PHY到MCU整个中断通路是否畅通的最直接方法。

2.3 MII中断状态与杂项控制寄存器(MISR)

MISR寄存器(地址 0x12)是中断系统的“事件管理员”。它包含了8个具体的中断事件状态位和对应的使能位。其设计非常经典:状态位(高8位)用于指示事件是否发生,使能位(低8位)用于决定哪些事件可以触发中断。

表:MISR寄存器位定义详解(高8位 - 状态位)

名称默认读写描述与操作要点
15LQ_INT0RO/COR链路质量中断。当链路质量监控器(Link Quality Monitor)检测到参数超出阈值时置位。COR (Clear-On-Read):读此寄存器后自动清零。
14ED_INT0RO/COR能量检测中断。当能量检测状态发生变化时置位(例如,网线插拔)。COR
13LINK_INT0RO/COR链路状态变化中断。当链路(Link)状态发生改变(通断)时置位。这是最常用的中断之一。COR
12SPD_INT0RO/COR速度状态变化中断。当连接速度(10M/100M)发生变化时置位。COR
11DUP_INT0RO/COR双工状态变化中断。当双工模式(全双工/半双工)发生变化时置位。COR
10ANC_INT0RO/COR自协商完成中断。当自动协商过程完成时置位。COR
9FHF_INT0RO/COR假载波计数器半满中断。当假载波事件计数器达到半满时置位。用于监控网络噪声水平。COR
8RHF_INT0RO/COR接收错误计数器半满中断。当接收错误计数器达到半满时置位。用于监控链路质量。COR

表:MISR寄存器位定义详解(低8位 - 使能位)

名称默认读写描述
7LQ_INT_EN0RW链路质量中断使能。置1使能LQ_INT事件触发中断。
6ED_INT_EN0RW能量检测中断使能。置1使能ED_INT事件触发中断。
5LINK_INT_EN0RW链路状态变化中断使能。置1使能LINK_INT事件触发中断。
4SPD_INT_EN0RW速度状态变化中断使能。置1使能SPD_INT事件触发中断。
3DUP_INT_EN0RW双工状态变化中断使能。置1使能DUP_INT事件触发中断。
2ANC_INT_EN0RW自协商完成中断使能。置1使能ANC_INT事件触发中断。
1FHF_INT_EN0RW假载波计数器半满中断使能。置1使能FHF_INT事件触发中断。
0RHF_INT_EN0RW接收错误计数器半满中断使能。置1使能RHF_INT事件触发中断。

核心工作机制与“COR”特性

  1. 状态与使能分离:某个事件(如链路断开)发生时,无论对应的*_INT_EN使能位是否打开,其状态位*_INT都会被硬件自动置1。这意味着你可以通过轮询MISR寄存器来查询事件,而不依赖中断。
  2. 中断产生条件:只有当MICR.INTEN=1MICR.INT_OE=1MISR.*_INT_EN=1对应事件发生时,PHY才会在PWRDOWN_INT引脚产生中断信号。
  3. 清除机制(COR):所有高8位的状态位都是“读清零”(Clear-On-Read)。这是最容易出错的地方。当你进入中断服务程序(ISR)后,为了判断是哪个事件触发的中断,你必须读取MISR寄存器。而读取操作本身,就会把所有已置位的状态位清零。因此,务必在ISR中先将MISR的值保存到一个变量中,然后再基于这个保存的值进行判断和处理。如果你先判断再读,或者读了两遍,可能会导致状态丢失。
  4. 多个事件同时发生:如果多个使能的事件同时发生,它们的状态位会同时置位。一次读取MISR会获取所有 pending 的状态,并在读取后全部清零。你的ISR需要能处理这种复合事件的情况。

3. 中断配置与处理实战流程

理解了寄存器原理,我们来搭建一个完整的中断处理框架。假设我们的应用场景是:监测网络链路的通断和速度变化,并在链路质量不佳(接收错误过多)时获得通知。

3.1 硬件与软件初始化步骤

步骤一:硬件连接确认确保DP83849ID的PWRDOWN_INT引脚(通常为第28脚)已连接到MCU的一个支持外部中断的GPIO引脚。查阅MCU数据手册,配置该GPIO为输入模式,并使能下降沿或低电平触发(根据PHY中断输出极性确定,通常为低电平有效)。

步骤二:PHY基础初始化在配置中断前,PHY需要完成基础初始化,通常包括软复位、自协商启动等。这里假设使用MDIO接口进行读写。

// 伪代码示例,需根据你的MDIO驱动调整 void phy_basic_init(uint8_t phy_addr) { // 1. 软复位(可选,但建议在完整初始化前进行一次) mdio_write(phy_addr, 0x00, 0x8000); // BMCR寄存器, bit15为软复位 delay_ms(100); // 等待复位完成,时间参考手册 // 2. 配置自协商(根据需求) mdio_write(phy_addr, 0x00, 0x1200); // BMCR: 自协商使能,重启自协商 // 3. 等待自协商完成(可以通过轮询BMSR寄存器bit5) uint16_t bmsr; do { bmsr = mdio_read(phy_addr, 0x01); } while (!(bmsr & 0x0020)); // 等待自协商完成位(bit5)置位 }

步骤三:配置中断系统这是核心步骤,我们按照“由外到内,由总到分”的顺序配置。

void phy_interrupt_init(uint8_t phy_addr) { uint16_t reg_val; // 1. 配置MICR:打开总开关,并设置引脚为中断输出 reg_val = 0; reg_val |= (1 << 1); // INTEN = 1, 使能中断 reg_val |= (1 << 0); // INT_OE = 1, PWRDOWN_INT引脚作为中断输出 // TINT保持为0,正常模式 mdio_write(phy_addr, 0x11, reg_val); // 写入MICR (地址0x11) // 2. 配置MISR:使能我们关心的事件 reg_val = 0; // 设置低8位使能位 reg_val |= (1 << 5); // LINK_INT_EN = 1, 使能链路状态变化中断 reg_val |= (1 << 4); // SPD_INT_EN = 1, 使能速度状态变化中断 reg_val |= (1 << 0); // RHF_INT_EN = 1, 使能接收错误半满中断(监控链路质量) // 高8位状态位是只读的,我们不用写 mdio_write(phy_addr, 0x12, reg_val); // 写入MISR (地址0x12) // 3. (可选)清除可能已存在的 pending 中断状态 // 读取MISR寄存器,其COR特性会自动清除所有状态位 reg_val = mdio_read(phy_addr, 0x12); // 这里读取的值可以记录下来,作为初始状态,但通常直接丢弃 }

注意事项:配置顺序的重要性我建议先配置MICR(打开总闸和输出),再配置MISR(选择具体事件)。如果反过来,在MICR.INTEN=0时,即使事件发生并置位了MISR状态位,也不会产生中断信号。但当你随后打开INTEN时,如果那些状态位仍然为1,可能会立即触发一个中断。这可能导致你的系统在初始化未完成时就误入中断服务程序。先开总闸,再设事件,逻辑上更清晰安全。

3.2 中断服务程序(ISR)实现要点

MCU端的中断服务程序需要高效、准确地处理来自PHY的事件。

// 假设MCU的GPIO中断服务程序框架 void EXTI_IRQHandler(void) { if(EXTI_GetITStatus(ETH_PHY_INT_EXTI_LINE) != RESET) { // 1. 清除MCU端的中断标志 EXTI_ClearITPendingBit(ETH_PHY_INT_EXTI_LINE); // 2. 读取并保存PHY的MISR寄存器值(关键步骤!) uint16_t misr_status = mdio_read(PHY_ADDR, 0x12); // 此读操作会清除PHY内部状态位 // 3. 根据保存的状态位判断事件来源 if (misr_status & (1 << 13)) { // 检查LINK_INT位 handle_link_change(); } if (misr_status & (1 << 12)) { // 检查SPD_INT位 handle_speed_change(); } if (misr_status & (1 << 8)) { // 检查RHF_INT位 handle_rx_error_half_full(); } // ... 检查其他使能了的事件 // 4. (可选)如果使能了能量检测中断,可能还需要读取PHYSTS等寄存器获取更详细的状态 } } void handle_link_change(void) { // 读取PHY状态寄存器(如PHYSTS或BMSR)确认当前链路状态 uint16_t physts = mdio_read(PHY_ADDR, 0x10); // 假设读PHYSTS if (physts & (1 << 2)) { // 假设bit2为链路状态位 printf("Link Up!\n"); // 进行链路恢复后的操作,如重启DMA、通知网络栈等 } else { printf("Link Down!\n"); // 进行链路断开后的操作,如停止发送、进入低功耗等 } } void handle_rx_error_half_full(void) { printf("RX Error Counter half full! Link quality may be poor.\n"); // 可以读取具体的错误计数器寄存器(如RECR)查看错误数量 // 甚至可以采取激进措施,如短暂关闭端口再重新自协商 }

实操心得二:中断服务程序中的“快进快出”原则PHY的中断通常用于通知状态变化,而不是处理大量数据。因此,ISR应该尽可能短小精悍。常见的做法是:

  1. 快速响应:在ISR中只做最必要的操作——读取状态、清除标志、设置一个软件标志位(flag)或向任务队列发送一个消息。
  2. 延迟处理:将耗时的操作,如打印日志、复杂的状态机切换、网络栈通知等,放到主循环或一个专用的低优先级任务中去处理。这能避免长时间占用中断,导致其他中断被延迟响应或丢失。
  3. 共享资源保护:如果你的MDIO读写函数不是可重入的(即不能在中断和主程序中被同时调用),需要在ISR中使用信号量或关中断等机制进行保护,但要注意这会增加中断延迟。更好的架构是设计一个线程安全的MDIO访问层。

4. 高级功能与诊断寄存器应用

DP83849ID的中断系统不仅可以响应简单的链路事件,还能与一系列强大的诊断功能联动,实现预测性维护和深度故障排查。这些功能大多位于扩展寄存器页0和链路诊断页2。

4.1 错误计数器与半满中断的应用

MISR中的FHF_INT(假载波半满)和RHF_INT(接收错误半满)中断,其源头是两个8位计数器:假载波感知计数器(FCSCR, 0x14)接收错误计数器(RECR, 0x15)。它们都是“读清零”的。

配置与使用场景: 假设我们想监控链路质量,当接收错误计数达到128(半满)时产生中断报警。

  1. 使能中断:如前所述,在MISR中设置RHF_INT_EN=1
  2. 中断处理:当RHF_INT中断触发时,在ISR中:
    • 读取MISR清除状态。
    • 读取RECR寄存器获取错误计数值。这个读取操作会将计数器清零,为下一次计数做准备。
    • 根据错误计数和网络流量情况,评估链路质量。持续的高错误率可能指示电缆损坏、连接器不良或电磁干扰。
// 在ISR中处理接收错误半满中断 if (misr_status & (1 << 8)) { // RHF_INT uint16_t error_count = mdio_read(PHY_ADDR, 0x15) & 0x00FF; // 读取RECR,低8位有效 printf("RX Error Interrupt! Count: %d\n", error_count); // 错误计数器会在本次读操作后清零 }

注意事项:计数器的“粘滞”特性手册中提到,这些计数器在达到最大值(0xFF)时会“粘住”(stick)。这意味着如果错误持续发生,计数器会停在255,而不会翻转归零。RHF_INT中断只会在计数器从127增加到128时触发一次。之后即使计数器停在255,也不会再次触发半满中断,除非你手动读取RECR将其清零。因此,你的监控逻辑需要考虑到这种情况,不能单纯依赖中断次数来判断错误频率。

4.2 链路质量监控器(LQM)的配置

这是DP83849ID的一个高级功能,位于页2。它允许你对DSP的内部参数(如频率偏移、均衡器系数)设置阈值,当参数超出范围时触发LQ_INT中断。这对于检测潜在的、尚未导致链路中断的性能劣化非常有用。

访问链路诊断页

// 切换到页2以访问LQM寄存器 mdio_write(PHY_ADDR, 0x13, 0x0002); // 设置PAGESEL为2 delay_us(10); // 等待页面切换稳定 // 现在可以访问地址0x14-0x1F,它们映射到页2的寄存器 // 例如,使能链路质量监控 mdio_write(PHY_ADDR, 0x1D, 0x8000); // 设置LQMR的LQM_ENABLE位(bit15) // ... 配置其他LQM阈值寄存器(需要访问LQDR等,流程较复杂) // 操作完成后,如果需要回到标准寄存器页 mdio_write(PHY_ADDR, 0x13, 0x0000); // 设置PAGESEL为0 delay_us(10);

配置要点

  1. 使能:在LQMR寄存器中设置LQM_ENABLE=1
  2. 阈值设置:通过LQDR等寄存器设置各个参数(如FREQ_HI/LOW_THR)的上下限。这些寄存器也位于页2,需要仔细查阅手册中关于LQDR索引机制的描述,这是一个间接寻址的寄存器组。
  3. 中断使能:别忘了在MISR寄存器中使能LQ_INT_EN位。
  4. 中断处理:当LQ_INT触发时,读取LQMR寄存器,其低8位(C1_LO_WARNFC_HI_WARN)会指示具体是哪个参数超出了阈值。读取操作会清除这些警告位。

4.3 时域反射计(TDR)功能简介

TDR是另一个强大的诊断工具,也位于页2。它可以向电缆发送脉冲并分析反射信号,用于估算电缆长度定位故障点(如开路、短路)。这对于现场安装和维护的网络设备是极有价值的功能。

基本使用流程

  1. 切换到页2。
  2. 配置TDR_CTRL寄存器:选择发送/接收通道、脉冲宽度、检测模式(Min/Max)、阈值等。
  3. 配置TDR_WIN寄存器:设置采样窗口的起始和结束时间,以屏蔽掉初始的发送脉冲,只观察反射。
  4. TDR_CTRL寄存器的SEND_TDR位写1,发送脉冲。
  5. 轮询TDR_CTRL[11]SEND_TDR位)或等待一个合理时间,直到它自动清零,表示捕获完成。
  6. 读取TDR_PEAKTDR_THR寄存器,获取峰值幅度、到达时间以及首次超过阈值的时间。
  7. 根据脉冲在电缆中的传播速度(约为光速的0.65倍),将时间转换为距离,从而估算故障点位置或电缆长度。

实操心得三:诊断功能的使用时机像LQM和TDR这类诊断功能,会干扰或影响正常的网络通信。切忌在设备正常传输数据时频繁使用。通常的实践是:

  • 上电自检时:运行一次TDR,获取基础电缆长度信息,并检查有无重大故障。
  • 链路中断时:如果链路意外断开,可以在尝试恢复前,启动TDR或检查LQM状态,辅助判断是远端设备问题还是物理链路问题。
  • 定期维护或手动触发:通过设备的管理接口(如SNMP、Web界面)提供手动触发诊断的按钮,由管理员在需要时执行。
  • 后台静默监控:使能LQM中断,但将阈值设置得比较宽松,只有在参数显著恶化时才报警,避免不必要的干扰。

5. 常见问题排查与调试技巧

即使按照手册配置,在实际项目中仍会遇到各种问题。下面是我总结的几个典型场景和排查思路。

5.1 问题一:中断完全不触发

现象:已经配置了所有寄存器,但插拔网线或改变连接状态时,MCU收不到任何中断。

排查步骤

  1. 检查硬件连接:用万用表或示波器确认PWRDOWN_INT引脚与MCU中断引脚的连接是否可靠,有无虚焊。测量该引脚在事件发生时的电平变化。
  2. 验证引脚模式:确认MICR寄存器的INT_OE位已被正确设置为1(中断输出模式)。如果此位为0,该引脚是掉电输入,不会输出中断信号。
  3. 验证全局使能:确认MICR寄存器的INTEN位为1。
  4. 验证事件使能:读取MISR寄存器,确认你关心的那些事件(如LINK_INT_EN)的使能位确实被写入了1。
  5. 使用测试中断:这是一个黄金调试手段。在初始化完成后,手动将MICR寄存器的TINT位写1。此时,无论链路状态如何,PHY都应该在PWRDOWN_INT引脚产生一个中断脉冲。用示波器观察该引脚,或者看MCU是否能进入中断服务程序。如果测试中断能触发,说明从PHY中断输出到MCU中断响应的整个硬件路径是通的,问题可能出在特定事件没有发生或状态位没置位。如果测试中断都不能触发,那么问题一定在硬件连接或MICR的基础配置上。
  6. 检查事件状态:在认为应该触发中断的条件下(如链路断开),直接读取MISR寄存器的高8位(状态位)。看看对应的事件状态位(如LINK_INT)是否为1。如果状态位是1但没中断,回头检查INTENINT_OE;如果状态位是0,说明PHY根本没有检测到这个事件,需要排查PHY的基础链路状态检测是否正常(检查PHYSTSBMSR寄存器)。

5.2 问题二:中断只触发一次,后续事件无中断

现象:第一次链路变化能触发中断,但再次插拔网线就没反应了。

排查步骤

  1. 检查中断清除逻辑:这是最常见的原因。回顾你的中断服务程序,是否在ISR中读取了MISR寄存器?记住,MISR的高8位状态位是“读清零”(COR)。如果你的ISR没有读取MISR,或者读取时机不对(比如在其他地方误读了),那么状态位会一直保持为1。对于PHY来说,中断是在事件发生、状态位从0变1的边沿触发的。如果状态位已经是1,即使新事件发生,也不会产生新的中断边沿。
  2. 确保ISR读取MISR:必须在ISR中尽早读取MISR,并将值保存下来用于后续判断。这个读取操作会清除所有pending的状态位,为接收下一次中断做好准备。
  3. 检查MCU中断标志清除:确认在MCU的ISR中,清除了对应的外部中断标志。如果MCU端的中断标志没有清除,会导致中断被锁死,无法响应新的边沿。

5.3 问题三:中断频繁误触发

现象:没有明显的网络事件,但中断频繁发生。

排查步骤

  1. 检查使能了哪些中断:读取MISR寄存器,确认低8位的使能位。是否不小心使能了ED_INT_EN(能量检测)?在嘈杂的电气环境中,微小的能量波动可能会被误认为是事件。是否使能了FHF_INT_ENRHF_INT_EN?如果网络中有背景噪声或错误,计数器可能很快达到半满。
  2. 读取状态位判断来源:在ISR中,打印或记录保存下来的MISR状态值,看具体是哪个事件位被置1。这能直接定位问题源头。
  3. 针对能量检测中断:能量检测功能(由EDCR寄存器控制)非常敏感,常用于节能模式。如果你不需要此功能,确保EDCR.ED_EN为0(禁用能量检测)。如果需要使用,可能需要调整ED_DATA_COUNTED_ERR_COUNT阈值,过滤掉噪声。
  4. 硬件干扰:检查PCB布局,PWRDOWN_INT走线是否靠近噪声源(如开关电源、时钟线)。考虑增加一个上拉电阻(如果PHY内部是开漏输出)和一个小电容(如10-100pF)到地,以滤除毛刺。

5.4 问题四:如何区分多种同时发生的事件

现象:一次中断产生,但可能由多个事件同时引发(例如,链路断开的同时,速度状态自然也无效了)。

解决方案: 你的ISR设计必须能处理复合事件。在读取并保存了MISR状态值后,用该值同时检查所有你使能了的事件位。

uint16_t misr_status = mdio_read(PHY_ADDR, 0x12); uint16_t triggered_events = misr_status & 0xFF00; // 高8位是状态位 if (triggered_events & (1 << 13)) { /* 处理链路变化 */ } if (triggered_events & (1 << 12)) { /* 处理速度变化 */ } if (triggered_events & (1 << 11)) { /* 处理双工变化 */ } // ... 按逻辑顺序或优先级处理

由于状态位是“读清零”的,一次读取就获取了所有瞬时状态,这是处理复合事件的正确方式。