1. 项目概述与核心价值如果你在2000年代初接触过功能电话Feature Phone或者模拟电话线的终端设备开发那你大概率绕不开摩托罗拉后来是飞思卡尔的DSP56800E系列处理器。那个时代智能手机还未兴起但人们对电话的功能需求已经超越了简单的通话。来电显示Caller ID、三方通话、免提对讲这些功能正在从高端商务电话向普通家庭设备普及。实现这些功能不仅需要硬件支持更需要一套稳定、高效的软件来处理复杂的电话信令和音频信号。摩托罗拉为此推出的Embedded SDK for DSP56858特别是其中的Feature Phone Application就是当时面向这类应用的一个“交钥匙”软件解决方案。这套SDK的价值远不止是一堆库文件的集合。它本质上是一个高度优化的、针对DSP56858硬件平台定制的实时信号处理框架。它将贝尔202调制解调器用于FSK来电显示数据解调、回声消除算法、全双工声学处理、以及各种电话状态机全部封装成可调用的API。对于开发者而言这意味着你不需要从零开始研究GR-30-CORE、SR-3004这些厚厚的电信规范也不用自己手写一个抗干扰的FSK解调器。SDK提供了经过验证的、符合电信标准的实现你只需要关心如何将这些模块“组装”起来并处理好用户交互比如通过AT命令即可。我当年参与过一个基于此平台的商务电话项目深刻体会到这套SDK带来的效率提升。它把最耗时、最容易出错的底层信号处理部分标准化了让团队能把精力集中在产品差异化功能和应用逻辑上。今天虽然模拟电话线已逐渐成为历史但其中涉及的实时音频处理、协议解析、系统集成等思想在如今的嵌入式音频、VoIP甚至一些IoT设备开发中依然有很强的借鉴意义。接下来我就结合手册内容和当年的实战经验为你深入拆解这个Feature Phone应用的方方面面。2. 核心组件与软件架构解析2.1 硬件平台与系统框图这个Feature Phone应用的核心硬件是DSP56858EVM评估板加上一块Telephony Daughter Card (TDC1)。DSP56858是摩托罗拉DSP56800E家族的一员主打高性价比和低功耗非常适合消费类通信产品。TDC1子卡则提供了与模拟电话线PSTN直接接口的Data Access Arrangement (DAA)电路、编解码器Codec以及必要的音频放大器。整个系统的信号流非常经典如图1-1所示电话线来的信号经过DAA保护与隔离后进入编解码器转换为数字PCM流通过SSI同步串行接口送入DSP56858。同样DSP处理后的音频数字流也通过SSI送回编解码器再经DAA送到电话线上。麦克风和扬声器则连接到TDC1的音频放大器接口构成完整的音频输入输出环。这里有个关键跳线设置必须移除EVM板上SSI0连接器JG6的所有跳线。这个操作是为了绕过板载的编解码器让音频数据流直接路由到TDC1子卡上的编解码器确保信号路径正确。注意硬件连接时麦克风务必接到TDC1的J3连接器的“Ch1 Line IN”和“Ch1 Line GND”切勿使用“Ch1 Mic IN”。因为“Line IN”设计为接收线路电平信号而“Mic IN”通常是为更低电平的麦克风准备的直接连接可能导致输入过载或信号失真。这个细节手册里提了一句但实际调试时如果接错会导致回声消除效果极差甚至无法工作需要特别注意。2.2 软件栈的层次化设计SDK的软件架构体现了清晰的分层思想如图1-2所示。最底层是板级支持包BSP和驱动程序包括SSI/Codec驱动、GPIO驱动、SCI串行通信接口驱动等它们负责直接操作DSP56858的硬件外设。中间层是电话功能库这是SDK的核心价值所在我们稍后详细展开。最上层则是Feature Phone Application本身它扮演着“胶水”和“主控”的角色。这个应用层的主要职责包括系统初始化配置DSP内核、外设时钟、中断向量表并初始化所有需要的库和驱动程序。主处理循环实现一个实时的、基于帧的音频处理流水线。通常以8kHz或16kHz的采样率运行在每个中断或任务周期内从驱动层获取最新的音频采样数据调用相应的库函数进行处理再将处理后的数据送回去。缓冲区管理为各个库分配和管理数据缓冲区Data Buffer。这些缓冲区是库函数输入输出的载体应用层需要确保数据在正确的时间被填充和消费避免溢出或欠载。事件调度与命令解析处理来自串口SCI的AT命令将其转换为对底层库的控制指令如摘挂机、音量调节同时接收底层库上报的事件如来电显示数据、特殊信号检测并将其格式化为字符串通过串口发送给主机Host。这种分层设计的好处是模块化。电话功能库不关心具体的硬件是哪个Codec应用层也不关心回声消除算法内部如何实现。只要接口一致你可以相对容易地更换底层驱动或升级某个算法库。2.3 五大核心电话功能库详解SDK包含了五个关键的软件库它们共同构成了功能电话的“大脑”。2.3.1 Type 1 and 2 Telephony Features Library类型1和2电话功能库这是整个系统的信号处理核心。它主要处理两件事带内信令的解调/生成和电话线状态监控。贝尔202 (Bell 202) FSK解调器用于解调来电显示数据。来电显示信息如号码、姓名在振铃间隙或通话中是通过一种类似1200bps调制解调器的FSK信号发送的。这个库里的解调器实现了符号定时恢复、载波频偏跟踪和自动增益控制(AGC)使其能在各种线路条件下稳定工作符合甚至超过Telcordia SR-3004标准。这是技术难点自己实现调试周期会非常长。CPE Alerting Signal (CAS) 检测与生成CAS是一个特定的单音信号通常是2130 Hz 2750 Hz的组合用于在通话中通知终端设备有来电等待信息。这个库能检测CAS并生成应答信号完成与交换机的握手。DTMF生成与检测支持生成标准的DTMF双音多频信号用于拨号也包含简单的铃流检测和铃音生成功能。完整的状态机它将上述所有功能封装在一个状态机里。开发者只需要定期喂给它8kHz的音频采样数据它就会自动在空闲、振铃检测、CAS检测、FSK数据接收等状态间迁移并在适当时机通过输出参数或回调函数上报事件和数据。2.3.2 Type 1 and 2 Telephony Parser Library类型1和2电话解析库功能库负责“听到”并“拆出”原始的二进制数据字节流而解析库则负责“读懂”这些字节流。它根据Telcordia规范将原始的FSK数据帧解析成有意义的字段消息格式判断区分是单数据消息格式(SDMF)还是多数据消息格式(MDMF)。字段提取解析出日期(TIME)、日期(DATE)、主叫号码(NMBR)、主叫姓名(NAME)等。辅助信息解析处理呼叫转移原因(XZRRD)、消息等待指示(XZVMI)、呼叫限定符(XZCLQ)等扩展信息。错误校验进行校验和(Checksum)计算和其他错误检测确保数据的完整性。这个库通常不需要实时运行可以在收到完整数据包后调用。2.3.3 Generic Echo Canceller Library通用回声消除库这是处理线路回声的利器。在电话系统中由于2-4线混合电路的不完美你说话的声音远端信号会有一部分泄漏回你自己的接收路径形成回声。这个库采用自适应滤波算法很可能是NLMS或其变种它需要两个输入参考信号即你要播放出去的远端语音和带回声的输入信号即本地麦克风采集到的包含近端语音和泄漏的远端回声。通过不断调整滤波器系数它能够估计并减去回声分量手册声称在白噪声输入下能达到超过40dB的回声衰减。库内部集成了语音活动检测(VAD)和状态机方便控制。一个关键参数是尾长(Tail Length)可在8ms到64ms之间以8ms为步进选择这决定了它能消除多长时间延迟的回声需要根据实际硬件混合电路的特性来设定。2.3.4 Full Duplex Speakerphone Library全双工免提电话库这是实现自然流畅免提通话的关键。它主要对抗声学回声——即扬声器播放的声音被麦克风再次拾取。这个库更为复杂因为它处理的是声学环境回声路径多变受房间大小、家具、人员移动影响。它同样包含一个自适应回声消除器AEC并且通常与一个非线性处理器(NLP)或抑制控制器结合以进一步消除残留回声。手册提到它支持与通用回声消除库的接口这意味着可以串联使用先用电学回声消除器消除线路回声再用声学回声消除器消除房间回声从而实现端到端的清晰通话。它同样允许配置回声尾长8ms-64ms并提供诊断软件用于“调优”。在实际项目中运行这个诊断程序来测量环境噪声和回声路径响应是优化免提性能不可或缺的一步。2.3.5 Feature Phone Application功能电话应用框架如前所述它是顶层框架负责调度和集成上述所有库。它还实现了一些本地功能如DTMF字符串拨号器、终端接口和一个AT命令集解析器。它的代码提供了如何初始化、调用和协调这些库的完整范例。2.4 资源评估与内存规划表1-1给出了在DSP56858上运行整个Feature Phone应用所需的资源估算这是一个非常宝贵的工程参考程序存储器(Program): 总计约9.8K字。其中电话功能库占2.8K解析库占1.4K回声消除库占0.7K免提库占2.2K应用框架本身占2.6K。数据存储器(Data): 总计约2.5K字。主要用于各种状态变量、滤波器系数、音频数据缓冲区。MIPS需求: 总计约36.4 MIPS。这意味着在DSP56858的运算能力下处理这些任务占用了相当一部分资源但也证明了其算法的高效性。手册特别强调了一个关键点除了解析库其他所有库都必须运行在内部存储器中。这是因为电话信号处理是硬实时任务对延迟极其敏感。外部存储器如SRAM的访问速度通常慢于内部RAM将其代码或关键数据放在外部会导致处理周期不稳定可能无法满足严格的实时性要求从而引起音频中断或信令丢失。在链接器配置linker.cmd时必须确保这些库的代码段和数据段被分配到内部RAM区域。3. 工程构建与目录结构实操3.1 SDK目录结构解析理解SDK的目录结构是开始开发的第一步。如图2-1和图2-3所示其组织方式非常清晰平台根目录 (如dsp56858evm): 对应具体的硬件平台。nos目录: 表示“无操作系统”支持这是大多数DSP嵌入式应用的典型环境。applications/: 存放示例应用我们的Feature Phone项目就在这里的c_sources/Feature Phone/目录下。这是你工程的起点。bsp/: 板级支持包包含针对DSP56858EVM的启动代码、外设驱动初始化等。config/: 默认的硬件和软件配置文件。include/: 所有库的API头文件。你需要包含这些头文件来调用库函数。sys/和tools/: 系统组件和工具。领域特定库目录 (如telephony/): 位于nos同级这里存放着电话功能库、回声消除库等.lib静态库文件及其头文件。这种结构将平台相关代码BSP、领域算法库Telephony和示例应用Applications分离便于维护和复用。当你新建自己的工程时最佳实践是复制applications/c_sources/Feature Phone目录作为模板然后在此基础上修改。3.2 使用CodeWarrior IDE构建项目手册提到使用Metrowerks CodeWarrior IDE这是当时摩托罗拉DSP的主流开发环境。构建过程如图4-1所示打开项目文件: 在CodeWarrior中打开FeaturePhone.mcp项目文件它位于/nos/applications目录下。检查库链接: 在项目设置中确保链接器包含了所有必需的库文件.lib。这些库的路径通常已经在项目模板中设置好指向telephony等目录。内存配置: 检查configintram目录下的linker.cmd文件。这个文件定义了代码和数据在内存中的布局。务必确认关键库的代码段如.text被分配到了内部程序RAM如PROM或IRAM关键数据段如.bss,.data被分配到了内部数据RAM如XRAM,YRAM。编译与链接: 执行编译。如果一切配置正确你会得到一个可下载到DSP56858EVM板上运行的.abs或.s19格式的可执行文件。实操心得在早期构建时最容易出错的地方就是库文件路径和内存分配。如果遇到“undefined symbol”链接错误首先检查库路径。如果程序运行异常如崩溃或结果不对很可能是内存分配问题某些需要高速访问的数据被意外放到了外部慢速内存中。使用CodeWarrior的Map文件生成功能仔细核对各个段Section的地址分配是否符合手册要求。3.3 关键的初始化与主循环流程虽然手册没有给出完整的main.c代码但根据其描述和常规DSP应用模式我们可以重构出核心流程// 伪代码展示应用框架核心逻辑 #include telephony_lib.h // 电话功能库头文件 #include echo_cancel_lib.h // 回声消除库头文件 #include speakerphone_lib.h // 免提库头文件 #include bsp_drivers.h // BSP驱动头文件 // 声明各库需要的数据结构状态机、缓冲区等 TelephonyState_t telState; EchoCancelState_t ecState; SpeakerphoneState_t spkState; AudioBuffer_t lineInBuf, lineOutBuf, micInBuf, spkOutBuf; int main(void) { // 1. 硬件初始化 BSP_Init(); // 初始化时钟、PLL、中断控制器 Codec_Init(8000); // 初始化Codec为8kHz采样率 SCI_Init(38400); // 初始化串口为38400bps用于AT命令 GPIO_Init(); // 初始化通用IO用于控制DAA等 // 2. 库初始化 TelephonyLib_Init(telState, telConfig); EchoCancelLib_Init(ecState, 32); // 假设选择32ms尾长 SpeakerphoneLib_Init(spkState, 24); // 假设选择24ms尾长 // 3. 缓冲区分配与关联 // ... 为各个缓冲区分配内存并将其指针传递给相应的库 // 4. 主循环通常由定时器中断或Codec DMA中断驱动 while (1) { // 等待音频帧中断例如每10ms产生一次对应80个采样点 if (audioFrameReady) { // A. 从Codec读取最新的线路输入和麦克风输入数据 Codec_ReadLineIn(lineInBuf); Codec_ReadMicIn(micInBuf); // B. 处理电话功能来电显示、DTMF检测等 TelephonyLib_Process(telState, lineInBuf, telEvents); if (telEvents.callerIdReady) { // 调用解析库解析数据并通过串口发送AT格式报告 ParserLib_Parse(telEvents.data, parsedInfo); SCI_SendString(NMBR7325551212\r\n); } // C. 处理回声消除和免提 // 先进行线路回声消除 EchoCancelLib_Process(ecState, lineOutBuf /*参考信号*/, lineInBuf /*带回声的输入*/, lineEchoFreeBuf); // 再进行声学回声消除和免提处理 SpeakerphoneLib_Process(spkState, lineEchoFreeBuf /*远端信号*/, micInBuf /*近端回声*/, spkOutBuf, lineOutBuf); // D. 将处理后的音频写回Codec Codec_WriteLineOut(lineOutBuf); Codec_WriteSpkOut(spkOutBuf); // E. 处理串口命令非阻塞方式 if (SCI_CommandReceived(atCmdBuffer)) { ATCommand_Parser(atCmdBuffer, controlSignals); // 根据解析结果控制库如设置音量、摘挂机状态 if (controlSignals.hookswitch OFF_HOOK) { TelephonyLib_SetHookState(OFF_HOOK); } } audioFrameReady 0; // 清除标志位 } // 可能还有低优先级后台任务如闪烁LED } }这个流程清晰地展示了如何将各个库串联起来形成一个实时的音频处理管道。中断服务程序(ISR)负责搬运音频数据主循环或更低优先级任务负责处理事件和命令。4. AT命令接口与主机通信详解主机通常是一台运行终端软件的PC通过串口与DSP应用通信使用一套基于Hayes AT命令集的扩展指令。这种设计非常巧妙它将复杂的底层操作抽象成简单的文本命令便于测试和集成。4.1 主机到DSP的命令 (HOST-to-DSP)通信格式为AT命令参数CR回车。串口配置为38400 bps, 8N1。4.1.1 免提控制 (ATVSP)这是控制通话模式的核心命令。ATVSP0:禁用免提。此时音频路径可能只经过手柄如果硬件支持。ATVSP1:启用全双工免提。这是默认模式同时启用声学回声消除允许双方同时讲话。ATVSP2:启用半双工免提。通常用于回声路径特别恶劣或算法处理能力有限的情况通过“谁讲话谁占用通道”的切换来避免回声但通话自然度会下降。4.1.2 摘挂机与静音控制 (ATVLS)这个命令用一个参数同时控制摘挂机状态和音频通道的静音。ATVLS0:挂机状态。电话线断开扬声器和麦克风均静音。此时DSP可能仍在监听振铃或来电显示FSK信号。ATVLS7:摘机状态正常通话。电话线接通扬声器和麦克风均解除静音。这是进行通话的状态。4.1.3 音量控制 (ATVGS)数字音量控制范围-24dB到24dB。ATVGS18: 将音量设置为18dB。ATVGS-20: 将音量设置为-20dB。ATVGS?: 查询当前音量手册未明确列出但很多AT命令集支持查询。和: 分别代表音量递增和递减1dB适合用于实体按键的调节。4.1.4 DTMF拨号 (ATVTS)发送DTMF音。可以一次发送一个数字也可以发送一串数字DSP会按顺序生成相应的DTMF信号并发送到电话线上。ATVTS5: 发送数字5的DTMF音。ATVTS123#: 发送字符串“123#”。这对于自动拨号或交互式语音应答IVR系统非常有用。4.1.5 数字开关配置 (ATVDS)用于配置全双工免提库内部的数字路由开关D1-D6。这是一个相对底层的命令用于选择不同的音频路径模式例如三端口路由电话线、扬声器、麦克风之间的连接方式。参数是一个6位的十六进制数每位对应一个开关0断开1闭合。例如ATVDS0x33二进制00110011表示闭合开关D1, D2, D5, D6。具体配置需要参考免提库的详细手册。4.1.6 呼叫等待豪华版功能 ($code)这是一组特殊的双字节命令用于响应呼叫等待事件。例如当有第二个来电时用户可以通过按键选择$3:会议。将两个来电方加入同一个通话。$5:丢弃第一个。挂断当前通话接听等待中的来电。$6:保持。保持当前通话接听等待中的来电。$F:应答/常规闪断。执行一个标准的闪断操作来接听等待的呼叫。4.2 DSP到主机的报告 (DSP-to-HOST)DSP通过串口主动上报事件格式为标签数据CR。4.2.1 来电显示数据报告这是最常见的上报信息当成功解调并解析出来电信息后DSP会发送一系列报告TIME1445CR DATE0315CR NMBR4085551234CR NAMEJANE DOECR如果号码保密或区域外NMBR字段会报告P或O。4.2.2 扩展参数报告这些标签提供附加信息XZRNAP: 主叫姓名不可用原因是主叫方要求隐私保护。XZCLQL: 呼叫限定符为“长途”。XZVMI1: 语音消息等待指示灯亮起有留言。4.2.3 特殊事件报告XZCAS1: 检测到CPE Alerting Signal (CAS)。这意味着有呼叫等待信息到来。XZUSE1: 检测到CAS但由于分机正在使用摘机后续不会有数据。这是一个防止冲突的机制。XZTMO1: FSK数据超时。在发送DTMF确认信号后500ms内未收到数据可能线路有故障或信息不完整。4.2.4 错误报告ERRMICLID_202: 来电显示贝尔202解调错误。ERRMZtext: 其他错误text为错误描述。注意事项在实现主机端软件时必须做好命令和报告的解析与同步。例如发送ATVLS7摘机后应等待DSP返回OKCR再执行下一步。处理上报事件时由于可能连续收到多行报告如TIME、DATE、NMBR需要有一个状态机来组装完整的来电显示记录而不是单独处理每一行。此外串口通信要考虑缓冲区大小避免数据丢失。5. 调试技巧与常见问题排查基于DSP56858和这套SDK开发功能电话应用调试工作往往集中在音频质量、信令可靠性和系统稳定性上。以下是一些实战中积累的经验和常见问题的排查思路。5.1 音频相关问题问题1通话中有明显的回声或啸叫。排查步骤确认硬件连接首先检查麦克风和扬声器的物理位置避免正对或过近。确认麦克风接入的是TDC1的“Line IN”而非“Mic IN”。运行诊断程序务必在运行主应用之前先运行全双工免提库自带的诊断应用。这个程序会测量环境的背景噪声和回声路径响应并生成一组优化的参数。忽略这一步直接调参事倍功半。调整回声尾长在初始化EchoCancelLib_Init和SpeakerphoneLib_Init时尝试调整尾长参数。如果实际回声延迟比如扬声器到麦克风的声学传播时间加上电路延迟大于设置的尾长回声消除器就无法完全覆盖会有残留回声。通常从32ms开始尝试。检查音量电平过高的扬声器音量或麦克风增益会导致系统环路增益大于1产生自激啸叫。尝试通过ATVGS命令逐步降低音量。检查数字开关路由确认ATVDS命令设置的内部音频路由是否正确。错误的路由可能导致信号直接短路绕过回声消除器。问题2声音小、失真或噪声大。排查步骤检查Codec配置确认采样率应为8kHz、数据格式通常为线性PCM、主从模式是否与DSP的SSI配置匹配。测量信号电平使用示波器或音频分析仪测量进入Codec的模拟信号电平是否在其输入动态范围内。信号过弱会导致信噪比差过强会导致削波失真。检查电源和地线模拟音频部分对电源噪声非常敏感。确保模拟地和数字地单点连接电源滤波电容焊接良好。5.2 来电显示与信令相关问题问题3无法收到来电显示信息。排查步骤确认线路和服务首先确保电话线路开通了来电显示业务并且测试时对方号码不是保密号码。检查挂机状态Type 1挂机来电显示是在第一次和第二次振铃之间发送的。确保DSP处于ATVLS0挂机状态并且振铃检测功能已使能。监听FSK信号如果有条件用示波器或音频录波软件抓取电话线上的信号。在振铃间隙应该能看到明显的1200Hz/2200Hz的FSK信号。如果根本没有信号问题在线路或交换机侧。检查CAS检测对于Type 2摘机来电显示呼叫等待先确认是否能正确检测到CAS信号XZCAS1上报。如果收不到CAS后续的FSK数据也不会被处理。调整解调器参数电话功能库可能提供一些微调参数如AGC目标电平、载波频偏容忍度。在信号较弱或噪声较大的线路上可能需要适当调整这些参数。问题4收到的来电显示信息乱码或错误。排查步骤检查波特率和解码贝尔202标准是1200bps。确认库的配置与此一致。乱码可能是位同步或帧同步错误。检查校验和解析库会上报校验和错误。如果频繁出错可能是线路噪声过大或者FSK信号质量太差。查看原始数据修改代码让DSP除了上报解析后的ASCII信息也把接收到的原始十六进制数据字节打印出来。与标准FSK数据帧格式对比可以定位是哪个字节出错。5.3 系统稳定性与资源问题问题5程序运行一段时间后死机或重启。排查步骤堆栈溢出这是嵌入式系统最常见的问题。检查链接器脚本中为堆栈Stack和堆Heap分配的空间是否充足。DSP56858内存有限在增加了多个库和缓冲区后初始分配可能不够。可以在代码中插入栈指针检查函数。中断冲突确保音频中断SSI、Timer、串口中断SCI的优先级设置正确且中断服务程序执行时间尽可能短。长时间关中断可能导致数据丢失。内存越界确保所有音频缓冲区的读写操作都没有超出分配的范围。使用调试器观察缓冲区指针。看门狗复位如果使能了看门狗确保在主循环或定时中断中定期喂狗。某个任务阻塞可能导致看门狗超时复位。问题6实时性不足音频出现卡顿或断裂。排查步骤MIPS核算重新计算所有库函数在最坏情况下的执行周期数确保总和不超出DSP在一个音频帧周期如10ms内能提供的MIPS。36.4 MIPS是典型值如果你的DSP主频较低或开启了其他功能可能不够。优化内存访问使用#pragma或关键字如section将最频繁访问的数据如音频采样缓冲区、滤波器系数放入最快的内部RAM中。避免在关键循环中使用外部慢速内存。简化主循环将非实时任务如复杂的AT命令解析、LCD刷新移到后台低优先级循环中确保音频中断服务例程(ISR)能及时执行。5.4 开发环境与工具使用心得利用CodeWarrior的调试器其实时仿真功能非常强大。可以设置硬件断点观察变量在中断上下文中的变化。特别是可以图形化地观察音频缓冲区中的数据直观判断信号处理效果。串口日志是关键除了标准的AT命令报告在调试阶段可以在代码中增加大量的SCI_Printf语句输出内部状态变量、函数返回值、错误码等。这比单纯用调试器单步跟踪效率更高尤其是对于时序相关的问题。版本管理SDK的库文件、头文件和你的应用代码要作为一个整体进行版本管理。记录下每个可稳定工作的版本组合避免因为升级了某个库而导致不兼容。开发这类高度集成的实时信号处理系统耐心和系统性的调试方法至关重要。从硬件连接检查到分模块测试例如先单独测试来电显示接收再集成回声消除最后进行全系统联调遵循这样的步骤能帮助你更快地定位和解决问题。摩托罗拉的这套SDK虽然年代较早但其设计思想和模块化架构对于今天从事嵌入式音频、语音处理开发的工程师来说仍然是一份很好的学习资料。