I2C总线毛刺抑制与FIFO配置:提升嵌入式通信稳定性的关键技术

发布时间:2026/7/24 18:49:16
I2C总线毛刺抑制与FIFO配置:提升嵌入式通信稳定性的关键技术 1. I2C总线通信的稳定基石毛刺抑制与FIFO的价值在嵌入式系统里I2C总线就像一条连接各个芯片的“数据高速公路”。主控芯片Controller通过SCL时钟线发号施令SDA数据线则负责搬运数据一主多从的架构让系统设计变得简洁。但这条“公路”并非总是平坦尤其是在电机驱动、开关电源旁或者长距离走线的场景下电磁干扰EMI就像路面上突然出现的碎石和坑洼会在SCL和SDA线上产生短暂的尖峰脉冲也就是我们常说的“毛刺”。这些毛刺如果被误判为有效的起始条件或数据位轻则导致数据错乱重则让总线挂死整个通信瘫痪。因此毛刺抑制能力是衡量一个I2C模块是否足够“强壮”、能否胜任工业环境的关键指标。与此同时数据流的顺畅管理同样至关重要。想象一下主控芯片正在处理一个紧急中断而传感器数据却源源不断地从I2C总线涌来如果没有一个缓冲区数据就会丢失。FIFOFirst In, First Out先进先出队列就是这个关键的缓冲区。它允许主控芯片批量处理数据而不是每收到一个字节就被打断一次极大地减轻了CPU的中断负载提升了系统整体效率。对于需要高速、连续读取大量数据的应用比如图像传感器或高精度ADCFIFO的深度和触发策略直接决定了系统的性能上限。本文将深入TI MSPM0系列微控制器中UNICOMM-I2C模块的“内脏”不仅解读手册中关于模拟/数字毛刺滤波器和FIFO操作的寄存器描述更结合我多年在电机控制和智能传感项目中的踩坑经验分享如何配置这些功能来应对真实世界的挑战。你会看到从原理到寄存器操作再到代码中的细节处理每一步都关乎通信的最终可靠性。2. 时钟同步与总线仲裁I2C稳定通信的前置课在深入毛刺和FIFO之前必须理解I2C总线两个基础但至关重要的机制时钟同步和总线仲裁。它们是总线能够支持多主机、并容忍不同速度设备共存的前提也是后续分析毛刺影响和FIFO超时的基础。2.1 时钟同步机制解析I2C总线的SCL线采用“线与”逻辑。这意味着任何设备都可以将SCL线拉低而只有当所有设备都释放SCL线输出高电平时SCL线才会被上拉电阻拉到高电平。时钟同步正是基于这一特性。其工作过程如下假设总线上有两个主机Device 0和Device 1各自产生内部时钟。当Device 0启动传输并拉低SCL时Device 1会检测到SCL线被拉低即使自己的内部时钟仍处于高电平周期也必须立即进入“等待状态”并开始对SCL低电平时间进行计数。只有当Device 0释放SCL且SCL线被上拉至高电平后Device 1的“高电平周期”计数器才开始计时。最终总线上有效的SCL时钟周期由低电平时间最长的设备决定而高电平时间则由最先结束高电平周期的设备决定。这个过程确保了所有设备都在同一个实际的SCL时钟沿上进行数据采样实现了时钟同步。注意时钟同步机制使得低速设备可以“拖慢”高速设备这是I2C总线兼容性强的优点但也意味着在计算通信超时时必须考虑最慢设备的影响。在配置后文提到的SMBus超时检测时这一点尤为关键。2.2 总线仲裁与毛刺的潜在风险当多个主机同时尝试启动传输时总线仲裁机制会决定谁获得控制权。仲裁发生在SDA线上每个主机在发送地址和数据位的同时会监听SDA线的实际电平。如果某个主机发送了‘1’释放SDA但检测到SDA线为‘0’被其他主机拉低则该主机立即失去仲裁切换为从机模式并监听总线。这里隐藏着一个风险如果在仲裁关键期如前几个地址位出现毛刺一个本应输出‘1’的主机可能因毛刺干扰误检测到SDA线为‘0’从而错误地认为自己仲裁失败退出竞争。这会导致本应获得总线权的主机异常退出通信失败。因此有效的毛刺抑制对于多主机系统的稳定仲裁至关重要。3. 毛刺抑制技术深度剖析模拟与数字滤波的工程抉择UNICOMM-I2C模块提供了两种毛刺抑制手段模拟毛刺滤波器和数字毛刺滤波器。它们的工作原理、适用场景和配置方法截然不同选择哪一种取决于你的具体应用场景和芯片型号。3.1 模拟毛刺滤波器简单粗暴的硬件卫士模拟毛刺滤波器是一种纯硬件电路通常由RC低通滤波网络构成直接作用于I2C模块的输入引脚。在UNICOMM-I2C的高级实例Advanced UNICOMM-I2CC/I2CT中此功能默认启用。核心特性与工作原理固定阈值其滤波宽度是固定的例如典型值为50ns。这意味着任何宽度小于50ns的脉冲毛刺会被物理过滤掉无法传递到内部的数字逻辑。无需时钟这是它最大的优点。只要芯片上电滤波器就在工作甚至在低功耗模式下它也能用于唤醒I2C模块当检测到有效的起始条件时。局限性滤波器的实际时间常数会随着芯片的工艺角、工作电压和环境温度PVT发生漂移。手册中标注的50ns是一个典型值在最坏情况下可能会有较大偏差。因此在对时序要求极其苛刻的应用中需要留有一定余量。配置方法 模拟滤波器的启用和禁用通过GFCTL寄存器的AGFEN位控制。默认情况下该位为1启用。除非你有非常确切的理由例如为了测量极精确的时序或者该引脚复用为其他对边沿要求极高的功能否则不建议禁用它。// 假设 I2C0 是 Advanced 实例 // 禁用模拟毛刺滤波器通常不需要这么做 I2C0-GFCTL ~(1 0); // 清除 AGFEN 位 // 重新启用模拟毛刺滤波器 I2C0-GFCTL | (1 0); // 设置 AGFEN 位3.2 数字毛刺滤波器精准可编程的软件哨兵数字毛刺滤波器则是一种基于I2C功能时钟I2Cclk的软件可配置滤波器存在于基础实例Basic UNICOMM-I2CC/I2CT中高级实例通常也兼容。核心特性与工作原理可编程宽度通过配置GFCTL寄存器中的DGFSEL字段你可以选择毛刺抑制的时钟周期数选项通常是1、2、3、4、8、16、31个I2Cclk周期。抑制宽度 DGFSEL值 * I2Cclk周期。工作原理它对输入信号进行采样只有当信号电平在连续多个采样周期内保持稳定才会被确认为有效电平并传递下去。例如设置DGFSEL3则一个毛刺必须持续至少3个I2Cclk周期才会被识别为有效跳变。时钟依赖这是它的命门。数字滤波器需要I2Cclk来工作。在低功耗模式下如果I2Cclk停止数字滤波器将失效无法用于总线唤醒。时序影响启用数字滤波器会引入固定的信号延迟。手册中明确警告如果DGFSEL设置为0x731个周期在计算整个事务的预期时间时必须额外增加31个时钟周期。这在计算SMBus超时时必须纳入考量。配置方法与计算示例 假设你的I2Cclk频率为20MHz周期50ns你希望滤除宽度小于100ns的毛刺。所需时钟周期数 毛刺宽度 / I2Cclk周期 100ns / 50ns 2个周期。查看DGFSEL可选值大于等于2的最小值是2对应DGFSEL值可能为0x1具体需查数据手册映射。因此应配置DGFSEL 0x1。// 配置数字毛刺滤波器为2个时钟周期抑制假设DGFSEL0x1对应2周期 // 首先确保I2C模块已禁用CR.ENABLE 0 I2C0-CR ~(1 0); // 禁用I2C // 配置GFCTL寄存器先清除DGFSEL位域再设置新值 // 假设DGFSEL位于寄存器[2:0]位且0x1代表2周期 I2C0-GFCTL ~(0x7 0); // 清除[2:0]位 I2C0-GFCTL | (0x1 0); // 设置DGFSEL为0x1 // 重新使能I2C I2C0-CR | (1 0);重要警告对GFCTL寄存器的任何修改都必须在CR.ENABLE位为0模块禁用时进行。在通信过程中动态修改滤波参数是未定义行为极可能导致总线错误。3.3 工程实践中的选型与避坑指南特性模拟毛刺滤波器数字毛刺滤波器可用性仅限Advanced I2C实例Basic/Advanced实例均可用默认状态默认启用50ns默认旁路DGFSEL0抑制原理硬件RC滤波固定脉宽时钟采样可编程周期数抑制宽度固定如50ns可编程N * I2Cclk周期优点无需时钟低功耗下可唤醒稳定宽度精确可调不受PVT影响缺点受PVT影响宽度有漂移依赖时钟低功耗下失效引入延迟适用场景对低功耗唤醒有要求干扰脉宽固定且已知需要精确控制滤波宽度I2Cclk稳定避坑心得上电初始化的第一件事在初始化I2C模块的时钟、引脚等基本配置后立即根据你的硬件环境布线长度、噪声源和芯片型号规划并配置毛刺滤波器。不要使用默认配置就指望万事大吉。长线传输的挑战当I2C总线通过排线或PCB长走线连接时信号边沿会变缓更容易受到干扰。此时过强的毛刺抑制如数字滤波设置过大可能会把原本有效的、但边沿稍缓的信号也滤掉导致通信失败。建议用示波器观察SCL/SDA波形根据实际噪声情况微调DGFSEL。多主机系统的考量在多主机系统中由于时钟同步和仲裁的存在过长的数字滤波延迟可能会影响仲裁的实时性。建议在满足噪声抑制的前提下尽可能选择较小的DGFSEL值。测量与验证最可靠的方法是用示波器的“毛刺捕获”功能实际测量你电路板上的噪声脉冲宽度然后以此为依据设置滤波器。没有仪器的话可以在代码中尝试不同配置进行长时间的压力测试如连续通信数小时观察误码率。4. FIFO操作机制与DMA协同实战FIFO是提升I2C通信效率尤其是大数据量传输时CPU效率的利器。UNICOMM-I2C模块为发送TX和接收RX各配备了一个独立的FIFO。4.1 FIFO基础状态与清零操作模块通过状态寄存器SR提供了一系列标志位来反映FIFO的实时状态状态位含义典型应用场景RXFERX FIFO空判断是否所有数据都已读取完毕RXFFRX FIFO满避免数据溢出OVERRUN错误TXFETX FIFO空判断是否所有数据都已发送完毕TXFFTX FIFO满避免写入数据丢失写入会失败RXCLRRX FIFO清除完成状态配合清除操作使用TXCLRTX FIFO清除完成状态配合清除操作使用手动清除FIFO是一个需要谨慎操作的过程通常在通信异常恢复或重新初始化时使用。必须遵循严格的序列// 清除RX FIFO的完整序列 I2C0-IFLS | (1 RXCLR_BIT_POS); // 步骤1置位RXCLR启动清除 while(!(I2C0-SR (1 RXCLR_STAT_BIT_POS))); // 步骤2等待状态位变为1 I2C0-IFLS ~(1 RXCLR_BIT_POS); // 步骤3清除RXCLR位 // TX FIFO清除过程类似使用TXCLR位警告切勿在步骤2完成前重复执行步骤1。也建议在修改FIFO触发水位RXIFLSEL/TXIFLSEL前先执行完整的FIFO清除序列确保FIFO处于确定的空状态。4.2 FIFO触发水位与中断/DMA策略这是FIFO高效运用的核心。通过IFLS寄存器可以配置FIFO在何种“水量”时触发中断或DMA请求。接收FIFORX触发逻辑 当RX FIFO中的数据量达到或超过设定的触发水位时产生RXTRG中断或DMA请求。水位选项通常是空、1/4满、1/2满、3/4满、几乎满N-1、非空1。策略选择低水位如1/4满适合实时性要求高的场景数据一来就尽快处理减少单次中断处理的数据量但中断频率高。高水位如3/4满或几乎满适合批量处理减少中断次数提高CPU效率但要求FIFO深度足够且处理程序必须在FIFO满之前清空它否则会溢出。发送FIFOTX触发逻辑 当TX FIFO中的空余位置数量即还能写入多少数据达到或超过设定的触发水位时产生TXTRG中断或DMA请求。水位选项通常是空、3/4空、1/2空、1/4空、几乎空1、非满N-1。策略选择高水位如1/4空即FIFO快满了意味着空位不多需要尽快补充数据。这可以确保发送流不中断但要求数据源能及时响应。低水位如空只在FIFO完全发空时才请求新数据适合低速或非连续发送。4.3 与DMA的无缝协作DMA是解放CPU的终极武器。UNICOMM-I2C为TX和RX提供了独立的DMA触发事件。配置流程与要点初始化DMA通道配置源/目标地址、传输数据宽度通常是字节、突发大小、总传输长度等。对于I2C接收DMA的源地址是I2C-RXDATA寄存器对于发送目标地址是I2C-TXDATA寄存器。配置I2C FIFO触发水位根据你的数据传输块大小和实时性要求设置IFLS.RXIFLSEL和IFLS.TXIFLSEL。例如如果每次希望DMA搬运16字节数据且RX FIFO深度为8那么可以设置为“1/2满”4字节时触发这样DMA一次请求会搬运4字节多次后完成16字节。使能DMA事件在I2C的IMASK寄存器中使能DMA_TRIG_RX和/或DMA_TRIG_TX事件。关键点每个DMA通道在同一时间只能有一个事件源被使能。不要同时使能CPU_INT和DMA_TRIG对应同一FIFO的事件。启动传输对于控制器发送先通过CPU或DMA向TX FIFO写入第一批数据然后设置CTR.FRM_START启动传输。后续数据将由DMA根据TX FIFO水位自动填充。对于接收设置好DMA目标缓冲区启动接收后数据会自动由DMA搬离RX FIFO。一个DMA接收的实战代码片段概念性// 1. 假设已配置好DMA通道0从 I2C0-RXDATA 搬运到数组 rxBuffer每次搬运1字节总长256字节。 // 2. 配置I2C FIFO RX触发水位为半满假设深度8则4字节触发 I2C0-IFLS (I2C0-IFLS ~(0x7 RXIFLSEL_SHIFT)) | (0x2 RXIFLSEL_SHIFT); // 0x2代表1/2满 // 3. 使能RX的DMA触发事件禁用RX的CPU中断 I2C0-IMASK.DMA_TRIG_RX 1; I2C0-IMASK.CPU_INT.RXTRG 0; // 4. 配置I2C为目标地址、接收模式、传输字节数若支持BLEN I2C0-TA (TARGET_ADDR 1) | 0x01; // 地址读方向 I2C0-CTR.BLEN 256; // 告诉I2C模块期望接收256字节 I2C0-CTR.START 1; I2C0-CTR.FRM_START 1; // 启动传输 // 此后I2C模块每接收到4字节数据就会发DMA请求DMA自动将数据从RXDATA搬到rxBuffer。 // CPU可以处理其他任务仅在DMA传输完成中断中处理整个数据包。致命陷阱DMA传输描述符源/目标地址、传输长度必须与I2C的控制器/目标模式以及RX/TX配置正确匹配。绝对禁止在I2C传输正在进行且前一个DMA传输未完成时动态切换DMA触发源或修改DMA配置。如果需要更改必须先禁用I2C模块和DMA通道修改后再重新使能。5. 高级功能与异常处理SMBus超时与挂起通信对于工业级或可靠性要求高的应用SMBus协议扩展和异常恢复机制是必须掌握的。5.1 SMBus超时检测机制SMBus在标准I2C基础上增加了超时限制防止总线因设备故障而永久挂死。UNICOMM-I2C硬件支持两种关键超时检测时钟低超时Clock Low Timeout对应SMBus的tTIMEOUT最大35ms。SCL线被持续拉低超过此时间即视为错误。配置使能TIMEOUT_CTL.TCNTAEN并在TIMEOUT_CNT.TCNTLA中设置超时周期值。计数器每个滴答等于520个I2Cclk周期。计算示例若I2Cclk40MHz周期25ns。则1个计数520 * 25ns 13µs。要设置35ms超时所需计数值 35ms / 13µs ≈ 2692。转换为12位寄存器值0xFF0是4080设置TCNTLA 2692 / 16 ≈ 168 (0xA8)。时钟高/总线空闲超时Clock High Timeout对应tHIGH:MAX典型50µs。SCL线持续高电平超过此时间控制器可以认为总线空闲并发起新的起始条件。配置使能TIMEOUT_CTL.TCNTBEN配置TIMEOUT_CNT.TCNTLB。此计数器每个滴答等于1个I2Cclk周期精度更高。计算示例若I2Cclk10MHz周期100ns。要设置50µs超时所需时钟周期数 50µs / 100ns 500。TCNTLB是8位值乘以16所以TCNTLB 500 / 16 31.25 ≈ 31 (0x1F)。5.2 累积时钟扩展超时与软件应对SMBus还定义了tLOW:SEXT目标设备累积扩展时间和tLOW:MEXT控制器累积扩展时间。这些超时是针对单个设备在一条消息中总共拉低SCL的时间。硬件单次超时检测可能不足以覆盖需要软件辅助。软件策略对于控制器I2CC由于控制器只在字节边界拉伸时钟因此使用上述的“时钟低超时”Counter A机制通常已足够。对于目标设备I2CT目标可能在字节内的任意时刻如应答位后拉伸时钟风险更大。最佳实践是结合DMA和FIFO将RX FIFO触发水位设置为较高如3/4满利用DMA批量搬运数据极大减少软件中断处理频率。在目标地址匹配或第一个命令字节到达的中断服务程序ISR中尽快完成关键解析如判断操作码然后依靠DMA处理后续数据字节避免在字节传输过程中进行耗时操作。使能时钟低超时作为最后防线。5.3 通信挂起与安全恢复流程当系统需要进入低功耗模式或总线出现严重错误需要复位I2C模块时不能直接禁用模块否则可能导致总线状态混乱。CTR.SUSPEND位提供了安全的挂起机制。安全挂起与禁用流程确保总线可进入空闲对于控制器确保已发送或接收了NACK/STOP对于目标确保已收到NACK。查询SR.BUSY位直到为0。设置挂起位CTR.SUSPEND 1。硬件会完成当前传输排空TX FIFO然后进入挂起状态停止响应外部总线。轮询确认空闲等待SR.BUSY 0确认。清空RX FIFO读取所有RXDATA确保FIFO为空。禁用模块CR.ENABLE 0。此时可以安全地修改配置或进入低功耗。恢复通信流程清除挂起位CTR.SUSPEND 0。重新使能模块CR.ENABLE 1。经验之谈在挂起前务必确认TX FIFO已空SR.TXFE 1且所有数据已发出。否则残留的数据会在恢复后意外发送造成协议错误。一个健壮的做法是在设置SUSPEND后短暂延时再检查BUSY和TXFE。6. 工程配置全流程与调试技巧将上述所有知识点串联起来形成一个完整的I2C控制器初始化与通信流程。6.1 I2C控制器初始化步骤详解以下以MSPM0的Advanced UNICOMM-I2CC为例配置为100kHz标准模式使用中断和FIFO// 步骤1确保模块禁用 I2C0-CR ~(1 0); // 清除 ENABLE 位 // 步骤2配置时钟TPR寄存器 // 假设系统时钟 SYSCLK 20MHz, 目标 SCL 100kHz。 // TPR 计算公式简化TPR (SYSCLK / (2 * SCL)) - 1 // TPR (20,000,000 / (2 * 100,000)) - 1 100 - 1 99 // 注意实际计算需考虑高低电平比例此处为示例。需查阅数据手册精确公式。 I2C0-TPR 99; // 步骤3配置毛刺滤波器根据实际情况选择 // 启用模拟滤波器Advanced实例默认启用此处显式设置 I2C0-GFCTL | (1 0); // AGFEN 1 // 或配置数字滤波器若需更精确控制 // I2C0-GFCTL (I2C0-GFCTL ~(0x7 1)) | (0x2 1); // 例如 DGFSEL2 // 步骤4配置FIFO触发水位 // 设置RX FIFO半满触发TX FIFO半空触发假设深度8 I2C0-IFLS (0x2 RXIFLSEL_SHIFT) | (0x2 TXIFLSEL_SHIFT); // 0x2常代表1/2 // 步骤5配置目标地址和模式7位地址示例地址0x50 I2C0-TA (0x50 1); // 地址左移1位最低位为R/W方向初始为0写 // 步骤6使能所需中断 I2C0-IMASK.CPU_INT.RXTRG 1; // 使能RX FIFO触发中断 I2C0-IMASK.CPU_INT.TXTRG 1; // 使能TX FIFO触发中断 I2C0-IMASK.CPU_INT.RXDONE 1; // 使能接收完成中断 I2C0-IMASK.CPU_INT.ERROR 1; // 使能错误中断 // 步骤7可选配置DMA触发需先初始化DMA // I2C0-IMASK.DMA_TRIG_RX 1; // I2C0-IMASK.DMA_TRIG_TX 1; // 步骤8使能I2C控制器 I2C0-CR | (1 0); // 设置 ENABLE 位 // 步骤9启动传输以发送为例 // 先向TX FIFO写入数据可写多个直到TXFF置位 I2C0-TXDATA dataByte0; I2C0-TXDATA dataByte1; // 配置控制寄存器设置传输字节数、ACK、START/STOP I2C0-CTR.BLEN 2; // 发送2字节 I2C0-CTR.ACK 1; // 最后字节后发送ACK如果是读操作则控制是否发送NACK I2C0-CTR.STOP 1; // 传输结束后发送STOP条件 I2C0-CTR.START 1; // 生成START条件 I2C0-CTR.FRM_START 1; // 启动帧传输6.2 常见问题排查与调试技巧实录即使配置无误在实际硬件调试中仍会遇到各种问题。下面是我在实验室里用逻辑分析仪和示波器“蹲”出来的经验。问题1通信完全无响应SCL/SDA线一直为高。排查电源与上拉首先测量I2C设备供电是否正常。SCL和SDA线的上拉电阻是否焊接阻值是否合适通常4.7kΩ~10kΩ在高速模式下可能需要更小的阻值。引脚配置确认MCU的I2C引脚是否已正确配置为复用功能并且开漏输出模式已使能。很多MCU的GPIO默认是推挽输出会与总线的“线与”逻辑冲突。地址问题确认设备地址是否正确7位还是8位格式。许多传感器地址的最低有效位LSB是固定的或由引脚决定。用逻辑分析仪抓取起始信号后的第一个字节看地址是否匹配。问题2能收到应答ACK但后续数据错误或丢失。排查时序问题用示波器测量SCL和SDA的时序。检查SCL频率是否与配置相符通过测量周期。检查建立时间Setup Time和保持时间Hold Time是否满足从设备的数据手册要求。毛刺抑制设置过大会压缩有效数据窗口导致建立/保持时间不足。FIFO溢出/下溢在中断服务程序中检查SR.RXFF接收溢出或SR.TXFF发送溢出错误标志。这通常是因为CPU或DMA处理速度跟不上数据流。解决方法优化中断服务程序减少处理时间或调整FIFO触发水位使用DMA或降低通信速率。时钟拉伸冲突如果从设备支持时钟拉伸而主设备未使能该功能CR.CLKHOLD位可能导致主设备在从设备未准备好时继续发送数据。确保主从设备的时钟拉伸配置一致。问题3通信随机失败尤其在环境干扰大时。排查毛刺抑制不足这是最常见原因。用示波器放大看SCL和SDA线上的毛刺。根据毛刺宽度调整数字滤波器DGFSEL值或确保模拟滤波器已启用。电源噪声在MCU和I2C设备的电源引脚就近放置去耦电容如100nF。长距离通信时考虑在总线两端增加RC滤波或ESD保护器件。总线电容过大总线上挂载设备过多或走线过长会导致上升沿变缓可能无法达到逻辑高电平阈值。解决方法减小上拉电阻值如从10kΩ改为2.2kΩ但会增加功耗或降低通信速率或使用I2C缓冲器芯片。问题4使用DMA时数据搬运不完整或错位。排查DMA传输长度与I2C BLEN不匹配确保DMA配置的传输总字节数与I2C控制器CTR.BLEN设置的期望字节数一致。DMA与CPU访问冲突确保在DMA传输期间CPU没有同时去读取RXDATA或写入TXDATA。这需要通过正确的软件流程设计来避免。DMA触发条件与FIFO水位检查IFLS寄存器配置的触发水位。如果水位设置得太接近FIFO深度可能在DMA响应请求前发生溢出。适当降低触发水位如从3/4满改为1/2满给DMA响应留出时间余量。调试时逻辑分析仪是你的最佳伙伴。设置好I2C协议解码可以直观地看到起始、地址、数据、应答、停止等每一个环节快速定位问题发生在协议层的哪一步。结合示波器观察模拟波形能发现时序和噪声层面的根本原因。记住嵌入式调试三分靠代码七分靠仪器。