MPC5634 eTPU模块实战指南:从架构解析到电机控制应用
2026/8/25 17:12:49 网站建设 项目流程

1. 项目概述与核心价值

最近在整理一个老项目的技术文档,翻到了当年基于MPC5634做电机控制时,对eTPU模块的一堆调试笔记。MPC5634这颗芯片,在汽车电子和工业控制领域算是一代经典了,尤其是它内置的增强型时间处理单元,也就是eTPU,功能非常强大。但说实话,它的学习曲线也比较陡峭,官方手册虽然详尽,但更像一本字典,新手直接上手容易找不到北。我这系列笔记,就是想从一个实际使用者的角度,把eTPU从入门到能干活的核心脉络给捋清楚,重点不是复述手册,而是分享那些手册里不会写、但实际调试中又绕不开的“坑”和技巧。

简单来说,eTPU是MPC5634内部一个独立于主CPU的协处理器,专门用来处理高精度、高复杂度的定时和脉冲信号。比如,你要做无刷电机的FOC控制,需要产生6路带死区时间的互补PWM,或者要精确捕获发动机的曲轴和凸轮轴信号,这些任务如果全扔给主CPU,中断响应和计算负担会非常重。而eTPU可以自己搞定这些时序逻辑,主CPU只需要配置好参数、下发指令,然后定期查询状态即可,大大解放了CPU资源。这个系列的第一篇,我们先不急着写代码,而是把eTPU的“世界观”建立起来,理解它的架构、工作模式和开发流程,这是后续一切实操的基础。如果你正在接触MPC5xxx系列芯片,或者任何带有eTPU/TPU模块的飞思卡尔/恩智浦处理器,这篇内容应该能帮你少走不少弯路。

2. eTPU模块整体架构与设计思路拆解

2.1 为什么需要eTPU:从CPU的瓶颈说起

在深入eTPU细节之前,我们必须先理解它存在的意义。传统上,处理复杂的定时任务(比如多路PWM生成、输入捕获、脉冲计数等)依赖于主CPU的定时器模块和中断服务程序。这种方法在任务简单、频率不高时可行,但一旦遇到汽车引擎控制、变频驱动这类场景,问题就暴露了:首先是中断风暴,高频率的定时器中断会疯狂抢占CPU,导致主循环其他任务无法及时执行;其次是精度问题,从中断触发到ISR开始执行,存在不可预知的中断延迟,在纳秒级精度的时序要求面前,这点延迟可能就是灾难;最后是灵活性不足,复杂的时序逻辑(如带故障保护的互补PWM、解码正交编码器等)用纯软件状态机实现,代码复杂且难以维护。

eTPU的设计哲学就是硬件化、并行化、任务化。它把自己打造成一个拥有独立指令集和内存的“微型计算机”,专门负责时间相关的任务。你可以把它想象成一个实时的、可编程的逻辑阵列(PLC)。主CPU(主核)是总经理,负责制定战略(配置任务参数);eTPU则是一个拥有专业技能的项目执行团队,接到任务后自主、精准地执行,过程中只会在关键节点(任务完成、出错)向总经理汇报。这种架构彻底将主核从繁琐的、高实时的时序处理中解放出来。

2.2 eTPU核心架构全景图

MPC5634的eTPU模块是一个相当复杂的子系统,我们可以从以下几个层面来理解它的架构:

1. 双核与通道结构:MPC5634的eTPU是“增强型”的,一个重要特征就是双eTPU引擎(eTPU_A和eTPU_B)。这两个引擎在架构上完全一致,可以独立工作,也可以协同处理更复杂的任务。每个引擎拥有32个物理通道(Channel),这意味着单个eTPU引擎最多可以同时处理32个独立的定时/信号任务。通道是eTPU执行任务的基本单位,每个通道都可以被配置为输入(如捕获)、输出(如PWM)或双向模式。

2. 独立的微引擎与内存:这是eTPU的“大脑”。每个eTPU引擎内部包含:

  • 微控制器(Microengine):运行eTPU专用的指令集,负责执行你编写的eTPU函数(微码)。
  • 程序存储器(Program Memory):存放eTPU的微码。这部分代码是预先编译好、通过主核加载进去的,上电后通常不会改变。官方会提供标准函数库(如ETPU_Base,包含PWM、输入捕获等通用函数),你也可以用高级语言(如C)编写自定义函数,再通过专用工具链编译成微码。
  • 数据存储器(Data Memory):分为全局数据RAM和每个通道独有的局部数据RAM。用于存放主核下发的配置参数、eTPU函数运行时的变量以及通道状态信息。主核通过访问映射到系统内存的eTPU寄存器来与这部分数据交互。

3. 时间基准与调度器:eTPU拥有自己独立的、高精度的时间基准(Time Base),通常由系统时钟分频而来。其核心是一个24位的自由运行计数器(TCR),这是所有定时操作的参考源。eTPU的调度器不是基于优先级的,而是基于时间事件的。当一个通道的匹配事件(比如定时器计数值达到设定值)发生时,该通道对应的任务才会被调度执行。这种“事件驱动”的调度方式,保证了定时操作的绝对精确性。

4. 与主核的接口:主核(PowerPC e200z0)通过增强型直接内存访问(eDMA)内存映射寄存器与eTPU通信。配置参数、启动命令的写入,以及状态标志、捕获数据的读取,都通过读写特定的寄存器来完成。中断信号则用于通知主核特定事件(如捕获完成、错误发生)。

理解这个架构,就明白了eTPU开发的典型流程:主核初始化硬件、加载微码函数库、配置通道参数、启动通道;之后eTPU便自主运行,主核仅在需要时查询或更新参数。这种“配置即执行”的模式,是发挥eTPU效能的关键。

3. 开发环境搭建与核心工具链解析

3.1 工具链选型:官方的与第三方的

玩转eTPU,一半的功夫在开发环境上。不同于普通的单片机编程,eTPU开发涉及主核应用程序eTPU微码函数两部分,需要两套工具链(或一个集成的环境)协同工作。

1. 官方“黄金组合”:CodeWarrior + eTPU Compiler对于MPC5634这类经典芯片,飞思卡尔(现恩智浦)时代的CodeWarrior for Power Architecture是许多老工程师的首选。它的优势在于高度集成和官方支持。你需要两个关键组件:

  • 主核编译器:CodeWarrior内置的GCC或Diab编译器,用于编译运行在PowerPC主核上的C/C++应用程序。
  • eTPU C Compiler:这是一个独立的命令行工具,用于将用eTPU C语言(一种ANSI C的子集,增加了硬件相关扩展)编写的函数,编译成eTPU微引擎可执行的微码(.bin或.s19格式)。CodeWarrior项目可以配置构建后步骤(Post-build step),自动调用此编译器处理eTPU C文件。

这套组合的缺点是CodeWarrior版本较老,界面和用户体验与现代IDE(如Eclipse)有差距,且对新操作系统的兼容性可能存在问题。

2. 现代替代方案:S32 Design Studio + eTPU Standalone Tools恩智浦后来推出了基于Eclipse的S32 Design Studio,它支持更新的编译器(如GNU ARM for Power Architecture)。对于eTPU开发,恩智浦提供了独立的eTPU Compiler and Debugger工具包。你可以在S32DS中开发主核程序,然后使用独立的图形界面或命令行工具来编译和调试eTPU代码。这种方式更灵活,也更容易集成到自动化构建系统中。

3. 第三方高级语言工具:ETPU Graphical Configurator有些第三方公司(如Aurix)提供了图形化配置工具,可以通过拖拽和配置参数的方式生成eTPU初始化代码和微码,极大降低了入门门槛。但对于MPC5634,这类高级工具可能不易获得或需要付费。

实操心得:对于新手,我建议先从官方示例代码入手。恩智浦官网通常会提供MPC5634的示例包,里面包含了已经编译好的标准eTPU函数库(.h和.lib文件)以及主核的演示工程。你初期可以不必自己编译eTPU微码,直接使用这些库文件,专注于学习如何在主核上配置和调用eTPU函数。这是快速建立信心和理解交互流程的关键一步。

3.2 工程结构与文件组织

一个清晰的eTPU项目目录结构能避免很多混乱。一个典型的项目可能如下所示:

My_ETPU_Project/ ├── App_Core/ # 主核应用程序 │ ├── src/ │ │ ├── main.c # 主函数,系统初始化 │ │ ├── eTPU_app.c # eTPU通道配置、控制逻辑 │ │ └── ... │ ├── inc/ │ │ ├── eTPU_app.h │ │ └── ... │ └── linker_script.ld ├── ETPU_Functions/ # eTPU微码部分 │ ├── src/ │ │ ├── my_etpu_func.c # 自定义的eTPU C函数(如果需要) │ │ └── ... │ ├── inc/ │ │ └── etpu_*.h # eTPU标准库头文件 │ ├── lib/ │ │ └── etpu_*.lib # 已编译的eTPU标准库文件 │ └── build_script.bat # 编译eTPU微码的脚本 └── Docs/ # 数据手册、应用笔记 ├── MPC5634M.pdf └── ETPU_Reference_Manual.pdf

关键点在于将主核代码和eTPU代码物理分离。etpu_*.lib和对应的头文件是桥梁,主核程序通过头文件中的函数声明和数据结构来调用库中的功能,链接器会将微码库链接到最终的可执行文件中,并在初始化时将其加载到eTPU的程序存储器。

3.3 第一个“Hello World”:点亮LED的PWM

理论学习再多,不如动手一试。我们用一个最简单的任务来串联整个流程:使用eTPU产生一路固定占空比的PWM波,控制一个LED闪烁。

步骤拆解:

  1. 硬件连接:将MPC5634的某个eTPU通道引脚(例如,eTPU_A通道0,对应芯片的某个具体GPIO)连接到一个LED(串联限流电阻)。

  2. 主核程序初始化(main.c中):

    • 初始化系统时钟、引脚复用(将对应引脚配置为eTPU功能,而非普通GPIO)。
    • 初始化eTPU模块:使能eTPU时钟、配置eTPU引擎的工作模式(比如时钟分频,决定时间基准TCR的计数频率)。
    • 加载eTPU微码:这是关键一步。你需要将编译好的eTPU函数库(包含PWM生成函数)的二进制映像,通过主核写入到eTPU的程序存储器地址空间。通常,库文件会提供一个初始化函数(如etpu_init())来完成此事。
    // 伪代码示例 #include "etpu_pwm.h" // PWM函数头文件 #include "etpu_init.h" // eTPU初始化头文件 int main(void) { // 1. 系统基础初始化 sys_init(); // 2. 初始化eTPU_A引擎,加载微码 etpu_init(FS_ETPU_ENGINE_A, ETPU_PWM_FUNCTION_BASE_ADDR); // 3. 配置PWM通道 uint8_t channel = 0; // 使用eTPU_A通道0 uint32_t pwm_freq_hz = 2; // 2Hz,LED慢闪 uint32_t pwm_duty_percent = 30; // 30%占空比 etpu_pwm_init(channel, pwm_freq_hz, pwm_duty_percent, ...其他参数); // 4. 启动PWM输出 etpu_pwm_start(channel); while(1) { // 主循环,eTPU在后台独立工作 } }
  3. 理解etpu_pwm_init背后:这个函数并不是直接操作硬件寄存器,而是按照eTPU PWM函数约定的参数数据结构,向该通道对应的数据RAM区域写入配置参数(频率、占空比、极性等),然后向通道的命令寄存器写入一个“初始化”命令。eTPU微引擎检测到这个命令,就会开始执行PWM生成的微码。

  4. 编译与下载:确保你的工程正确包含了eTPU库文件(.lib)。编译整个项目,生成的可执行文件既包含主核代码,也包含了eTPU微码。通过调试器下载到芯片的Flash中。

  5. 上电观察:复位芯片后,你应该能看到LED以2Hz的频率闪烁,占空比约为30%。此时,主核的while循环几乎是空的,但PWM波形依然精准输出,这就是eTPU独立工作的威力。

注意事项:在调试时,务必使用示波器或逻辑分析仪测量实际引脚输出的波形,验证频率和占空比是否与设定值一致。这是检验eTPU是否正常工作的唯一可靠方法。软件点灯成功,不代表eTPU的时序是正确的。

4. eTPU通道配置与参数详解

4.1 通道模式:输入、输出与双向

eTPU的每个通道都可以被动态配置为不同的功能模式,这是通过给通道分配不同的“函数号(Function Number)”来实现的。函数号对应eTPU程序存储器中某个特定微码函数的入口地址。例如,函数号0x01可能代表“PWM生成”,0x02代表“输入捕获”。

  • 输出模式(如PWM):通道引脚由eTPU内部驱动。配置时,需要设置引脚极性(高电平有效还是低电平有效)、输出使能等。在PWM例子中,我们配置的就是输出模式。
  • 输入模式(如输入捕获):通道引脚作为输入,eTPU监测引脚上的边沿(上升沿、下降沿或双边沿)。当检测到边沿时,eTPU会记录下当前TCR时间戳,并可以产生中断通知主核。这在测量脉冲宽度或频率时至关重要。
  • 双向模式:某些复杂函数(如串行通信)可能需要通道在输入和输出之间切换。

配置通道的核心是填充一个称为通道参数结构体的数据块,并将其写入通道的数据RAM。这个结构体的定义完全取决于你分配给该通道的eTPU函数。例如,PWM函数的结构体可能包含period_ticks(周期计数值)、duty_ticks(高电平计数值)、polarity等字段。

4.2 时间基准与分频:精度的源泉

eTPU的所有定时操作都基于其24位时间基准计数器TCR。TCR的计数频率(TCR_CLK)由系统时钟(SYS_CLK)分频而来,公式通常为:TCR_CLK = SYS_CLK / (ETPU_DIV + 1)ETPU_DIV是一个可配置的分频系数寄存器。

为什么分频如此重要?

  1. 精度与范围权衡:TCR_CLK越高,定时分辨率越高(每个TCR tick代表的时间越短),但24位计数器溢出的时间也越短。例如,系统时钟80MHz,若分频系数为0(TCR_CLK=80MHz),则每个tick为12.5ns,精度极高。但24位最大计数值为16,777,215,这意味着TCR大约每0.21秒(16.78M / 80MHz)就会溢出一次。对于周期超过0.21秒的PWM,你就需要处理计数器溢出逻辑。若将分频设为39(TCR_CLK=2MHz),则每个tick为500ns,溢出时间延长到约8.39秒,适合更长的周期,但精度降低。
  2. 功耗考虑:更高的TCR_CLK意味着eTPU微引擎和逻辑电路运行在更高频率,功耗会相应增加。

参数计算示例:假设我们需要生成一个频率为10kHz(周期100us)的PWM,系统时钟为80MHz,我们选择TCR_CLK为2MHz(分频系数39,每个tick=500ns)。

  • 周期对应的TCR ticks数 = 周期时间 / tick时间 = 100us / 500ns = 200 ticks。
  • 若要占空比为30%,则高电平时间 ticks数 = 200 ticks * 30% = 60 ticks。 在调用etpu_pwm_init时,我们就需要传入period_ticks=200duty_ticks=60这两个参数。eTPU函数内部会根据这些值来设置匹配寄存器。

4.3 中断与DMA:与主核的高效协作

eTPU虽然独立,但仍需与主核通信。两种主要方式是中断和eDMA。

  • 中断:eTPU每个通道都可以在特定事件(如捕获完成、周期结束、错误发生)时向主核发出中断。主核需要配置中断控制器,并编写对应的中断服务程序来读取数据或处理事件。中断响应及时,但频繁中断仍会消耗主核资源。
  • eDMA:这是更高效的数据搬运方式。你可以配置eDMA,当eTPU通道的数据RAM中特定位置更新(例如,新的捕获时间戳写入)时,自动将数据搬运到主核内存的指定缓冲区中,搬运完成后才通知主核。这实现了“零CPU开销”的数据采集,特别适合高速、连续的输入捕获应用。

在实际项目中,通常采用混合策略:对于关键、需要立即处理的事件(如过流故障保护)使用中断;对于高速数据流(如编码器位置采样)使用eDMA搬运到缓冲区,主核定期处理缓冲区数据。

5. 从标准函数到自定义函数开发

5.1 使用标准函数库:站在巨人的肩膀上

恩智浦为eTPU提供了丰富的标准函数库,覆盖了绝大多数常见应用:

  • PWM (Pulse Width Modulation): 标准PWM、中心对齐PWM、带死区的互补PWM。
  • IC (Input Capture): 输入捕获,测量脉冲宽度、频率。
  • OC (Output Compare): 输出比较,在指定时间产生单个或周期性事件。
  • QOM (Quadrature Decoder): 正交编码器解码,用于电机位置和速度反馈。
  • SPI, UART: 串行通信接口。
  • GCT (Gated Clock Timer): 门控时钟计时。

使用这些库函数是开发eTPU应用最快、最稳定的方式。你需要做的是:

  1. 将对应的库文件(.lib)和头文件(.h)添加到你的工程。
  2. 仔细阅读该函数的《应用笔记》或头文件注释,了解其参数结构体每个成员的含义、取值范围和单位(是秒、微秒还是TCR ticks)。
  3. 在主核代码中,包含头文件,声明一个该函数的参数结构体变量,填充它,然后调用初始化函数。

5.2 何时需要自定义函数?

当标准库无法满足你的特殊时序需求时,就需要开发自定义eTPU函数。例如:

  • 需要实现一种非标准的通信协议。
  • 需要极其复杂的多通道联动逻辑。
  • 需要对标准函数的行为进行微小但关键的修改。

开发自定义函数意味着你要使用eTPU C语言编写微码,并用eTPU编译器进行编译。eTPU C是标准C的一个受限子集,它不支持浮点数、递归、函数指针等复杂特性,但增加了对硬件通道、TCR、匹配/捕获寄存器的直接访问关键字(如__channel,__tcr)。

自定义函数开发流程简述:

  1. 编写eTPU C源码:创建一个.c文件,按照eTPU C语法编写函数。函数通常包含一个初始化入口(init)和一个主处理入口(main)。init由主核命令触发,用于设置初始状态;main由通道事件(如匹配发生)触发,是核心逻辑。
  2. 定义主机接口(Host Interface):在源码中,使用#pragma指令或特定语法定义参数结构体(PARAM)和共享变量(SHARED),这些是主核可以访问的数据区域。
  3. 编译生成微码:使用eTPU编译器编译.c文件,生成.s19.bin格式的微码文件,以及一个.h头文件(包含参数结构体定义和函数号)。
  4. 集成到主工程:将生成的微码文件加载到eTPU程序存储器(可以链接到主核程序镜像中,由启动代码复制;也可以在运行时通过主核动态加载)。在主核程序中,包含生成的头文件,像使用标准函数一样调用你的自定义函数。

实操心得:自定义eTPU函数开发门槛较高,需要对eTPU架构和指令集有深入理解,且调试困难(通常需要eTPU仿真器或高级调试工具)。强烈建议在万不得已时才走这条路,并尽量复用标准库函数的框架进行修改。可以先在标准函数的基础上,通过配置不同的参数和组合多个标准函数来尝试实现需求。

6. 调试技巧与常见问题排查实录

6.1 调试工具箱:从printf到专业工具

eTPU的调试比普通软件更复杂,因为它涉及两个处理单元。

  1. 主核侧调试

    • printf/串口输出:最基础但有效。在初始化eTPU和配置通道的关键步骤后,通过串口打印状态信息(如返回值),确认主核程序执行流程是否正确。
    • 调试器(JTAG/SWD):可以单步执行主核代码,查看和修改内存、寄存器。重点观察:
      • eTPU模块的控制寄存器(MCR)是否使能。
      • 通道的模式寄存器(CMDR)是否配置正确。
      • 通道的参数RAM区域是否写入了预期的数值。
      • eTPU全局状态寄存器(GSR)是否有错误标志。
  2. eTPU侧调试

    • 逻辑分析仪/示波器这是调试eTPU输出的终极武器。直接测量eTPU通道引脚的实际波形,与预期对比。可以清晰看到PWM频率、占空比、死区时间、捕获触发电平等是否准确。
    • eTPU Trace/仿真器:一些高端调试工具(如Lauterbach Trace32配合专用适配器)支持eTPU指令追踪,可以像调试主核代码一样单步执行eTPU微码,查看eTPU的寄存器、变量和程序流。这是最强大的手段,但硬件成本高昂。
    • “软件示波器”法:对于输入捕获,可以让eTPU在捕获事件时,除了记录时间戳,还将一个空闲的GPIO引脚拉高/拉低。用示波器同时观察信号引脚和这个GPIO,可以直观看到eTPU是否在预期的边沿做出了反应,以及反应延迟。

6.2 常见问题速查与解决方案

下面是一个基于我个人和同行经验总结的常见问题排查表:

现象可能原因排查步骤与解决方案
引脚无输出1. 引脚复用未配置为eTPU功能。
2. eTPU模块时钟未使能。
3. 通道未使能或处于禁用状态。
4. 输出极性配置错误(比如配置为低有效,但用万用表测电压)。
1. 检查SIU(系统集成单元)的引脚控制寄存器,确保PCR的PA位设置为eTPU。
2. 检查ME(模式控制)模块,确保eTPU的时钟门控已打开。
3. 检查通道状态寄存器,确认通道已激活(ACTIVE位)。
4. 用示波器观察,或者尝试反转输出极性配置。
PWM频率/占空比不对1. TCR时钟分频计算错误。
2. 传入的period_ticksduty_ticks参数计算错误或单位弄混。
3. 参数未成功写入通道RAM。
1. 核对系统时钟频率和ETPU分频寄存器值,计算实际的TCR频率。
2. 在初始化函数后,通过调试器读取通道参数RAM区域,确认写入的值是否与计算值一致。
3. 使用示波器测量实际周期和脉宽,反推回TCR ticks,与设定值对比。
输入捕获值不准或跳变1. 输入滤波未配置或配置不当,噪声引起误触发。
2. TCR溢出未处理。
3. 捕获中断服务程序读取数据太慢,导致数据被覆盖。
1. 启用eTPU通道的数字输入滤波器,并合理设置滤波时钟和采样数。
2. 在捕获函数中,确保使用处理了溢出补偿的时间差计算函数(如库函数提供的etpu_ic_get_time()),而不是直接相减两个原始时间戳。
3. 优化ISR,或改用DMA方式搬运捕获数据。
eTPU函数调用失败(返回错误码)1. 请求的通道号非法或已被占用。
2. eTPU微码未成功加载或加载地址错误。
3. 参数结构体地址未对齐(某些eTPU函数要求参数地址4字节或8字节对齐)。
1. 检查芯片数据手册,确认使用的通道物理存在且未被其他功能占用。
2. 确认etpu_init()函数被正确调用,且传入的函数基地址与链接脚本中eTPU微码的加载地址一致。
3. 使用编译器指令(如__attribute__((aligned(8))))确保参数结构体对齐。
多通道协同工作异常1. 通道间优先级冲突(虽然eTPU是时间调度,但资源如链接通道可能冲突)。
2. 共享全局变量(在eTPU C中)访问冲突。
3. 主核配置不同通道的顺序产生竞态条件。
1. 仔细阅读所用eTPU函数的文档,了解其对链接通道等资源的需求,避免分配冲突。
2. 避免在无同步机制下,让多个eTPU函数实例访问同一全局变量。如需共享,使用原子操作或通过主核中转。
3. 确保一个通道完全初始化并启动后,再初始化可能依赖它的另一个通道。

6.3 性能优化与资源管理心得

  • TCR分频是艺术:不要一味追求最高精度。根据你项目中所有eTPU任务中最高的时间分辨率需求,以及最长的定时周期需求,来折中选择一个合适的TCR_CLK。一个项目通常统一使用一个分频设置。
  • 通道规划要提前:在硬件设计阶段,就要根据信号类型和功能关联性,规划好eTPU通道的分配。例如,做三相电机驱动,6路PWM最好分配到同一个eTPU引擎的相邻通道,甚至使用硬件上支持“链接”的通道组,以确保死区时间等的高度同步。
  • 善用DMA减轻CPU负担:对于高速数据采集(如多个编码器),一定要设计基于eDMA的乒乓缓冲区或环形缓冲区,让数据自动搬运,主核定期批量处理。
  • 微码函数复用:同一个eTPU函数(如PWM)可以被加载到程序存储器一次,然后分配给多个通道实例使用。每个通道有自己的参数RAM。不要为每个通道加载一份单独的微码副本。

学习eTPU的过程,是一个从“敬畏”到“掌控”的过程。初期会觉得它复杂难懂,但一旦理解了其硬件任务化的思想,并成功调试通第一个功能,你就会发现它带来的设计简洁性和性能提升是巨大的。这篇笔记涵盖了入门所需的基础概念和实战要点,下一篇我们将深入一个具体案例,比如如何用eTPU实现一个带故障保护的三相互补PWM,并探讨更高级的同步与触发机制。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询