嵌入式UART通信进阶:中断与流控机制在JN516x上的实战解析
1. 项目概述与核心价值
在嵌入式开发领域,UART(通用异步收发传输器)串口通信几乎是每个工程师的“必修课”。它简单、可靠,是连接微控制器与传感器、调试终端、无线模块乃至另一颗MCU的“血管”。然而,很多开发者对UART的认知往往停留在“配置波特率、发送接收字节”的层面,一旦项目复杂度上升,面临大数据量传输、低功耗要求或实时性挑战时,通信不稳定、数据丢失、CPU占用率飙升等问题便接踵而至。
究其根源,在于未能深入理解并有效运用UART的两个核心高级特性:中断驱动与流控机制。前者决定了你的系统是“忙等待”的阻塞式,还是“事件触发”的高效异步式;后者则决定了在双方处理速度不匹配时,数据链路是“优雅协调”还是“野蛮冲撞”。本文将以恩智浦(NXP)JN516x系列无线微控制器的UART外设及其集成外设API为例,深入剖析中断与流控的实战应用。JN516x作为一款在Zigbee、Thread等低功耗物联网领域广泛应用的芯片,其UART设计颇具代表性,理解其机制对掌握其他平台(如STM32、ESP32等)的串口编程也大有裨益。
我将带你绕过数据手册中枯燥的寄存器描述,直接切入工程实践。你会看到如何将“发送FIFO空”、“接收数据达到阈值”、“超时”、“硬件错误”这些状态,转化为清晰的中断事件;如何根据项目需求,在简单的2线模式、带硬件流控的4线模式以及自动流控模式间做出选择;并最终构建一个稳定、高效、资源占用低的串口通信子系统。无论你是正在调试一个物联网传感器节点,还是为一个工业控制器设计通信协议,这里的内容都将提供直接的参考和避坑指南。
2. UART中断机制深度解析与实战配置
中断是嵌入式系统实现异步、事件驱动编程的基石。对于UART而言,轮询(Polling)FIFO状态寄存器的方式会无情地吞噬CPU周期,尤其在低功耗应用中,这简直是“电量杀手”。而中断机制允许CPU在数据未就绪时进入休眠或处理其他任务,仅在特定事件发生时被唤醒,极大地提升了系统整体效率。
2.1 JN516x UART中断源详解
根据提供的材料,JN516x的UART可以产生多种中断条件,通过vAHI_UartSetInterrupt()函数进行启用和配置。理解每个中断的触发条件和应用场景是正确使用它们的前提。
发送FIFO空中断 (Transmit FIFO empty):
- 触发条件:发送FIFO变为完全空。这意味着最后一个字节已经从FIFO转移到移位寄存器并开始发送。
- 应用场景:这是实现“零拷贝”或高效流式发送的关键。当你想发送一大块数据时,可以先将一部分数据写入FIFO,然后等待此中断。中断触发时,说明FIFO有空间了,你可以立即填充下一批数据,从而保持发送链路持续饱和,避免出现发送间隙。对于需要连续发送的场合(如音频流、持续传感器数据上报),此中断必不可少。
- 注意事项:在中断服务程序(ISR)或回调函数中填充新数据时,务必先检查FIFO的剩余空间(通过
u16AHI_UartReadTxFifoLevel()),避免写入超过容量的数据。JN516x的发送FIFO深度通常为16字节,但具体需查阅芯片数据手册。
接收数据可用中断 (Receive data available):
- 触发条件:接收FIFO中的数据量达到预设的阈值。阈值可配置为1、4、8或14字节。
- 应用场景:这是最常用的接收中断。设置阈值等于1,可以实现字节到达的即时响应,适用于对实时性要求极高的指令解析。设置较高的阈值(如8或14),则可以批量读取数据,减少中断触发频率,降低CPU中断负载,特别适合处理以数据包为单位的通信协议(如Modbus、自定义帧)。
- 清除机制:该中断并非在触发后立即清除,而是当FIFO中的数据被读取,使得数据量低于预设阈值时,中断标志才会清除。这是一个关键细节,意味着如果你的ISR只读取了部分数据,未使存量低于阈值,中断会持续有效,可能导致重复进入ISR。因此,在ISR中通常需要循环读取,直到FIFO为空或低于阈值。
超时中断 (Timeout):
- 触发条件:这是一个辅助性中断,仅在“接收数据可用中断”启用时自动启用。当满足以下所有条件时触发: a. 接收FIFO中至少有一个字符。 b. 在“足以接收至少4个字符的时间间隔”内,没有新字符进入FIFO。 c. 在“足以读取至少4个字符的时间间隔”内,没有从FIFO中读取字符。
- 应用场景:处理“非定长数据帧”的利器。例如,你接收一串以特定结束符(如
\n)结尾的变长字符串。当字符流暂停(条件b),且你尚未读取(条件c),超时中断触发,提示你“当前FIFO中的数据可能已经构成一个完整的消息单元”,应该去读取并处理了。这避免了为等待一个可能永远不会到来的结束符而长时间阻塞。 - 清除机制:从接收FIFO中读取一个字符即可清除此中断并重置超时计时器。
接收线路状态中断 (Receive line status):
- 触发条件:RxD线路上发生错误,包括:线路中断(Break)、帧错误(Framing error,如停止位丢失)、奇偶校验错误(Parity error)、溢出错误(Over-run,即FIFO已满但新数据到来)。
- 应用场景:通信质量监控和故障诊断。一旦发生此类中断,必须立即读取线路状态寄存器(通过
u8AHI_UartReadLineStatus())来确认具体错误类型,并进行相应处理(如丢弃错误数据、重置接收状态、向上层报告错误)。忽略这些错误可能导致后续数据全部错乱。
调制解调器状态中断 (Modem status):
- 触发条件:仅适用于UART0工作在4线模式时,检测到CTS(Clear To Send)线路状态发生变化。
- 应用场景:在手动流控模式下,用于感知对方设备是否准备好接收数据。当CTS线被对方置为有效(通常为低电平)时,产生中断,通知本方可以开始发送数据。
2.2 中断回调函数注册与处理要点
JN516x的API采用了回调函数(Callback)机制来处理UART中断,这比传统的向量中断服务程序(ISR)更易于管理和集成到应用框架中。
- 注册:必须为UART0或UART1分别调用
vAHI_Uart0RegisterCallback()或vAHI_Uart1RegisterCallback()来注册你的中断处理函数。这个注册操作通常在UART初始化阶段完成。 - 回调函数原型:回调函数会接收一个参数,通常是一个枚举值,用于指示具体是哪种中断条件触发了本次调用(对应上述5种中断源)。你需要在这个函数内部通过判断该枚举值来执行不同的处理逻辑。
- 关键差异与坑点:
- 中断清除:这是JN516x UART中断处理中最需要警惕的一点。文档明确指出,对于“接收数据可用”和“超时”中断,硬件不会在调用回调函数前自动清除中断标志。中断标志的清除依赖于“从接收FIFO中读取数据”这个动作。这意味着,你的回调函数必须在返回前,至少从接收FIFO中读取一个字节,否则该中断将持续触发,导致系统不断重入回调函数,最终可能引发栈溢出或系统死锁。这是一个非常经典的陷阱。
- 睡眠模式:如果系统需要进入深度睡眠(RAM掉电),那么注册的回调函数��针会丢失。唤醒后,如果仍需使用UART中断,必须重新注册回调函数。通常这个操作放在唤醒后的初始化流程中。
实操心得:在编写UART中断回调函数时,我习惯采用以下结构来确保安全性和效率:
void myUart0Callback(E_AHI_UART_INTERRUPT_T eInterrupt) { switch(eInterrupt) { case E_AHI_UART_INT_RX_DATA: // 接收数据中断 while(u16AHI_UartReadRxFifoLevel(UART0) > 0) { // 循环读取直到FIFO空 uint8_t data = u8AHI_UartReadData(UART0); // 处理数据,例如放入环形缓冲区 ring_buffer_put(&rx_buf, data); } // 读取操作自动清除了中断标志 break; case E_AHI_UART_INT_TX_FIFO_EMPTY: // 发送FIFO空中断 if (tx_data_remaining > 0) { uint16_t space = 16 - u16AHI_UartReadTxFifoLevel(UART0); // 假设FIFO深度16 uint16_t to_send = (tx_data_remaining < space) ? tx_data_remaining : space; u16AHI_UartBlockWriteData(UART0, tx_buffer_ptr, to_send); tx_buffer_ptr += to_send; tx_data_remaining -= to_send; } else { // 所有数据发送完毕,可以禁用发送空中断以节省中断资源 vAHI_UartSetInterrupt(UART0, E_AHI_UART_INT_TX_FIFO_EMPTY, FALSE); } break; case E_AHI_UART_INT_RX_TIMEOUT: // 超时中断,通常意味着一个“数据包”接收完成 // 即使FIFO未空,也触发处理逻辑 process_received_packet(); // 同样,需要通过读取操作来清除中断标志 (void)u8AHI_UartReadData(UART0); // 读取一个字节即可清除 break; case E_AHI_UART_INT_RX_LINE_STATUS: // 线路错误,必须处理 uint8_t status = u8AHI_UartReadLineStatus(UART0); // 根据status位判断具体错误并处理,如重置接收状态 handle_line_error(status); break; case E_AHI_UART_INT_MODEM_STATUS: // CTS信号变化,用于流控 uint8_t modem_status = u8AHI_UartReadModemStatus(UART0); if (modem_status & CTS_STATUS_BIT) { // CTS有效 start_transmission_if_needed(); } break; default: break; } }这个结构确保了每种中断都得到妥善处理,特别是接收类中断的标志清除。
3. 数据传输模式:2线、4线与自动流控实战指南
选择正确的数据传输模式是保证通信可靠性的另一支柱。JN516x的UART支持2线、4线(仅UART0)和1线仅发送(仅UART1)模式。我们将重点讨论前两者。
3.1 2线模式:简单直接,风险自担
2线模式仅使用TxD(发送)和RxD(接收)两条线,没有硬件流控信号。这意味着发送方可以随时发送,接收方也必须随时准备接收。
发送流程:
- 应用调用
vAHI_UartWriteData()(单字节)或u16AHI_UartBlockWriteData()(数据块)将数据写入发送FIFO。 - DMA引擎会自动将FIFO中的数据搬移到移位寄存器,并按位串行发出。
- 如何知道何时能写入下一批数据?有三种策略:
- 轮询:循环调用
u16AHI_UartReadTxFifoLevel()检查FIFO剩余空间,或调用u8AHI_UartReadLineStatus()检查FIFO是否空。简单但低效。 - 中断驱动:启用“发送FIFO空中断”。当FIFO空时触发中断,在中断服务程序中填充新数据。这是高效的方式。
- 定时器调度:如果数据产生是周期性的(如每秒发送一次传感器读数),可以配置一个定时器,定期检查并发送数据。
- 轮询:循环调用
- 应用调用
接收流程:
- 数据从RxD线进入,由DMA引擎自动存入接收FIFO。
- 应用需要及时读取,防止FIFO溢出。读取方式同样有轮询和中断两种:
- 轮询:定期调用
u16AHI_UartReadRxFifoLevel()或u8AHI_UartReadLineStatus()。 - 中断驱动:启用“接收数据可用中断”,并设置一个合适的触发阈值(如8字节)。当数据达到阈值时触发中断,在中断中批量读取。
- 超时中断辅助:结合“接收数据可用中断”使用,用于处理数据流中断的情况,判定帧结束。
- 轮询:定期调用
注意事项:在2线模式下,通信双方必须严格遵守相同的波特率、数据位、停止位和校验位格式。任何不匹配都会导致帧错误或乱码。此外,由于没有流控,如果接收方处理速度慢于发送方,就会发生数据溢出(Overrun),这是2线模式最大的风险。因此,2线模式更适合于低速率、间歇性、或接收方处理能力绝对有保障的场景。
3.2 4线模式与手动流控:精细化的握手艺术
4线模式在TxD和RxD的基础上,增加了RTS(Request To Send,请求发送)和CTS(Clear To Send,清除发送)两条硬件流控线。这是一种“握手”协议,用于防止接收方缓冲区溢出。
信号逻辑(以低电平有效为例):
- 接收方:当它的接收FIFO有足够空间时,会置低RTS(RTS有效),意思是“我准备好了,你可以发”。
- 发送方:检测到对方的RTS有效(即本方CTS被置低)时,才允许开始发送数据。如果CTS为高,则应暂停发送。
发送方(Source Device)手动流控实现:
- 配置UART0为4线模式(默认即是)。
- 发送前,必须持续监测CTS线状态。有两种方式:
- 轮询:调用
u8AHI_UartReadModemStatus()检查CTS位。 - 中断:启用“调制解调器状态中断”,当CTS状态变化时收到通知。
- 轮询:调用
- 一旦检测到CTS有效(低电平),即可调用写入函数向发送FIFO填充数据。
- 数据填充后,硬件会自动发送。
- 如果CTS在发送过程中变为无效(高电平),说明对方要求暂停,发送方应停止向FIFO写入新数据,但已进入FIFO的数据会继续发完。
接收方(Destination Device)手动流控实现:
- 应用需要根据自身接收FIFO的可用空间,主动控制RTS线。
- 当FIFO空间充足(例如空闲大于8字节)时,调用
vAHI_UartSetRTS()置低RTS,向对方发出“可以发送”信号。 - 数据开始涌入,FIFO填充。
- 当FIFO快满(例如剩余空间小于4字节),或应用来不及处理时,调用
vAHI_UartSetRTS()置高RTS,向对方发出“暂停发送”信号。 - 应用从FIFO中读取数据,腾出空间后,再次置低RTS,请求更多数据。
关键API与流程对应表:
| 角色 | 动作 | 相关API函数 | 说明 |
|---|---|---|---|
| 发送方 | 检查是否可发 | u8AHI_UartReadModemStatus() | 读取Modem状态寄存器,查看CTS位 |
| 发送方 | 感知CTS变化 | vAHI_UartSetInterrupt(..., E_AHI_UART_INT_MODEM_STATUS, TRUE) | 启用调制解调器状态中断 |
| 发送方 | 写入数据 | vAHI_UartWriteData()/u16AHI_UartBlockWriteData() | 向发送FIFO写入数据 |
| 接收方 | 控制RTS | vAHI_UartSetRTS() | 置位/清除RTS引脚电平 |
| 接收�� | 检查FIFO | u16AHI_UartReadRxFifoLevel() | 获取接收FIFO当前数据量 |
| 双方 | 读取数据 | u8AHI_UartReadData()/u16AHI_UartBlockReadData() | 从接收FIFO读取数据 |
3.3 自动流控:硬件代劳,解放CPU
手动流控虽然可靠,但需要应用程序频繁地��查FIFO状态和控制RTS引脚,增加了软件复杂度。JN516x的UART0提供了自动流控功能,可以将这部分工作交给硬件自动完成。
工作原理:
- 通过
vAHI_UartSetAutoFlowCtrl()函数启用并配置自动流控。 - 在接收方:你设置一个接收FIFO的触发水位(例如11字节)。硬件会持续监控FIFO填充量。
- 当FIFO数据量低于触发水位时,硬件自动置低RTS(允许发送)。
- 当FIFO数据量达到或超过触发水位时,硬件自动置高RTS(暂停发送)。
- 在发送方:你启用自动监测CTS功能。硬件会持续监测CTS线。
- 当CTS有效(低电平)时,自动将发送FIFO中的数据发出。
- 当CTS无效(高电平)时,自动暂停发送,即使FIFO中有数据。
- 通过
配置函数详解:
vAHI_UartSetAutoFlowCtrl()允许你配置:- 接收FIFO触发水位:可选8, 11, 13, 15字节。这个值需要根据你的应用数据包大小和处理速度谨慎选择。设置过低(如8),可能导致RTS频繁切换;设置过高(如15,接近16字节的FIFO深度),则流控反应迟钝,仍有溢出风险。通常设置为FIFO深度的1/2到3/4之间较为合理。
- CTS自动监测:在发送方启用此功能。
- 信号有效极性:可以配置RTS/CTS为高电平有效或低电平有效,以匹配对接设备。
自动流控下的应用层操作:
- 发送方:应用层的工作简化了,只需要确保有数据要发送时,将其写入发送FIFO即可(
vAHI_UartWriteData)。硬件会在对方准备好(CTS有效)时自动发送。你仍然可以查询发送FIFO空间(u16AHI_UartReadTxFifoLevel)来决定一次写入多少数据,但不再需要关心CTS状态。 - 接收方:应用层的工作也简化了,只需要定时或通过中断从接收FIFO中读取数据即可。硬件会自动通过RTS线控制数据流的启停,防止FIFO溢出。
- 发送方:应用层的工作简化了,只需要确保有数据要发送时,将其写入发送FIFO即可(
实操心得:自动流控极大地简化了编程模型,特别适合稳定的点对点高速数据传输。但在以下场景需谨慎:
- 多主设备或复杂拓扑:自动流控依赖于标准的RTS/CTS硬件连线。在非标准或软件模拟流控的场景不适用。
- 需要精确控制数据流的场景:例如,你需要每接收N个字节就执行一个特定操作,自动流控的“黑盒”特性可能让你难以在精确的字节边界进行干预。此时手动流控更可控。
- 调试阶段:在通信不稳定时,手动流控可以让你在代码中插入更多状态检查和日志,便于定位问题是硬件连线、配置错误还是软件逻辑问题。
4. 模式选择、配置流程与常见问题排查
4.1 如何选择正确的模式?
面对2线、4线手动、4线自动三种模式,选择依据如下:
| 模式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 2线模式 | 接线简单,仅需2根线;软件实现最简单。 | 无流控,存在数据溢出风险;依赖双方严格同步的软件处理速度。 | 波特率较低(如9600bps);数据量小且间歇性发送;接收方处理能力远超发送方;与不支持流控的老设备通信。 |
| 4线手动流控 | 可靠性高,能有效防止数据丢失;控制权完全在软件,灵活性强。 | 需要4根线;软件实现复杂,需主动管理RTS/CTS状态。 | 高速率、大数据量连续传输;双方处理速度不确定或可能阻塞;需要精细控制数据流节奏的协议。 |
| 4线自动流控 | 可靠性高;极大简化了软件逻辑,CPU占用低。 | 需要4根线;配置相对固定,灵活性不如手动模式。 | 稳定的高速点对点通信;希望降低软件复杂度和CPU干预的应用;作为默认的可靠通信方案。 |
一个简单的决策流程:首先问,通信双方处理速度能否匹配且稳定?如果否,则必须使用带流控的4线模式。然后问,你的应用是否需要非常精细地控制每一个数据块的发送时机?如果是,选手动流控;如果只要求可靠传输不关心细节,选自动流控。
4.2 完整的配置流程示例(以UART0, 4线自动流控,中断驱动为例)
// 假设使用UART0, 波特率115200, 8数据位, 1停止位, 无校验 void uart0_init_with_autoflow_and_interrupt(void) { // 1. 配置UART基本参数(波特率等) vAHI_UartSetControl(UART0, // 选择UART0 FALSE, // 禁用奇偶校验 FALSE, // 奇偶校验类型(无关) FALSE, // 停止位长度(1位) FALSE, // 数据位长度(8位) 7, // 波特率分频器高位(具体值根据时钟计算,此处为示例) 131); // 波特率分频器低位(计算得出115200) // 2. 启用UART(默认即为4线模式,控制RTS/CTS引脚) vAHI_UartEnable(UART0, E_AHI_UART_0, // 启用UART0 TRUE); // 启用FIFO // 3. 配置自动流控 // 参数:UART0, 使能自动流控, RTS/CTS低电平有效, 接收FIFO触发水位为11字节 vAHI_UartSetAutoFlowCtrl(UART0, TRUE, FALSE, E_AHI_UART_AUTO_11_BYTE); // 4. 配置中断 // 启用接收数据可用中断(阈值设为8字节)和接收线路状态中断 vAHI_UartSetInterrupt(UART0, E_AHI_UART_INT_RX_DATA, TRUE); vAHI_UartSetInterrupt(UART0, E_AHI_UART_INT_RX_LINE_STATUS, TRUE); // 注意:启用RX_DATA中断会自动连带启用TIMEOUT中断 // 5. 注册中断回调函数 vAHI_Uart0RegisterCallback(myUart0Callback); // 6. (可选)如果需要发送FIFO空中断,也在此启用 // vAHI_UartSetInterrupt(UART0, E_AHI_UART_INT_TX_FIFO_EMPTY, TRUE); } // 发送一段数据(非阻塞,利用FIFO和自动流控) void uart0_send_data(const uint8_t *data, uint16_t length) { // 在自动流控下,我们只需将数据写入FIFO,硬件会在CTS有效时自动发送 // 但一次写入不能超过FIFO剩余空间 uint16_t free_space = 16 - u16AHI_UartReadTxFifoLevel(UART0); // 假设FIFO深度16 uint16_t to_write = (length < free_space) ? length : free_space; u16AHI_UartBlockWriteData(UART0, data, to_write); // 如果数据没写完,可以记录剩余数据和指针,等待发送FIFO空中断再继续写入 if (to_write < length) { // ... 保存剩余数据,并启用发送FIFO空中断 vAHI_UartSetInterrupt(UART0, E_AHI_UART_INT_TX_FIFO_EMPTY, TRUE); } }4.3 常见问题与排查技巧实录
在实际项目中,UART通信问题层出不穷。下面是我总结的一些常见问题及其排查思路:
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 完全收不到数据 | 1. 物理连接错误(Rx/Tx接反、线缆断开)。 2. 波特率、数据位、停止位、校验位配置不匹配。 3. UART外设未使能。 4. 引脚复用冲突(DIO被配置为GPIO或其他功能)。 | 1. 用万用表或示波器检查线路连通性,确认Rx/Tx交叉连接。 2. 双方代码仔细核对串口参数,确保完全一致。可用示波器测量一个字节的波形,计算实际波特率。 3. 确认调用了 vAHI_UartEnable()。4. 检查芯片数据手册,确认使用的UART引脚没有被其他功能占用。 |
| 数据错乱、乱码 | 1. 波特率偏差过大(时钟源不准)。 2. 电磁干扰严重。 3. 地线连接不良,共模干扰。 4. 软件读取FIFO速度太慢,导致溢出。 | 1. 确保双方使用相同且稳定的时钟源(如外部晶振)。 2. 增加线路滤波电容,使用屏蔽线,降低通信速率。 3. 确保通信双方有良好的共地。 4. 提高接收中断优先级,或在中断中采用更���效的数据搬移方法(如DMA到缓冲区)。启用流控。 |
| 通信一段时间后死机或重启 | 1. 中断服务程序(回调函数)处理时间过长,导致其他中断丢失或栈溢出。 2. 中断标志未正确清除,导致中断重入。 3. (JN516x特定)接收数据/超时中断未在回调函数中读取FIFO,导致中断无法清除,无限触发。 | 1. 遵循ISR设计原则:快进快出。只做最必要的操作(如读取数据到缓冲区),复杂处理放到主循环。 2. 仔细检查每种中断的清除机制。对于JN516x的UART接收中断,务必在回调函数中读取数据。 |
| 流控模式下,发送方长时间等待 | 1. 接收方RTS信号一直为“禁止发送”状态(如未正确初始化、软件bug未置低RTS)。 2. 硬件连线错误(RTS/CTS未交叉连接)。 3. 自动流控触发水位设置过高,接收方FIFO一直处于“满”的状态。 | 1. 用逻辑分析仪或示波器观察RTS/CTS信号线。确认接收方在准备好时确实发出了有效信号。 2. 确认流控线是交叉连接:A的RTS接B的CTS,A的CTS接B的RTS。 3. 适当调低自动流控的接收FIFO触发水位。 |
| 发送大数据块时丢失后半部分 | 1. 采用轮询发送,且未检查FIFO空间,导致写入速度超过发送速度,FIFO溢出后被丢弃。 2. 使用中断发送,但中断优先级过低,被其他中断长时间阻塞,无法及时填充FIFO。 | 1. 改用中断驱动发送,或轮询时严格检查u16AHI_UartReadTxFifoLevel()。2. 提高UART发送中断的优先级,确保能及时响应。 |
| 低功耗模式下串口不工作 | 1. 进入深度睡眠后,UART模块或相关时钟被关闭。 2. 唤醒后未重新初始化UART或未重新注册中断回调函数。 | 1. 查阅芯片低功耗模式文档,确认所需UART在睡眠模式下是否保持活动,以及时钟是否保持。 2. 在系统唤醒后的初始化流程中,重新配置UART并调用 vAHI_UartXRegisterCallback()重新注册中断。 |
一个高级调试技巧:当你怀疑是流控或中断逻辑问题时,可以尝试编写一个“回声”测试程序。设备收到任何字节后,立即原样发回。用PC串口工具发送一长串数据,观察回显是否完整、有无延迟。这能快速隔离是发送问题、接收问题还是流控握手问题。
