1. 项目概述与核心价值

如果你正在基于TI的TMS320VC5410A这颗经典的定点DSP进行嵌入式系统开发,尤其是在通信、音频处理或实时控制领域,那么你一定会和它的三大核心外设打交道:硬件定时器、时钟生成器和DMA控制器。这些模块是决定系统性能、实时性和功耗的关键。很多人拿到芯片手册,看到一堆寄存器位图和时序图就头疼,配置起来也常常是“知其然不知其所以然”,调不通的时候只能对着代码和示波器干瞪眼。

我在这颗芯片上“摸爬滚打”了十多年,从最初的语音编解码器到后来的多通道数据采集系统,几乎每一个项目都离不开对这三个模块的深度调优。今天,我就抛开那些生硬的官方手册语言,以一个一线工程师的视角,带你彻底吃透VC5410A的定时器、时钟和DMA。我们不止看它们“是什么”,更要深挖“为什么这么设计”以及“实际配置时有哪些坑”。你会发现,理解了它们之间的协同工作逻辑,你的DSP系统设计水平会有一个质的飞跃。

简单来说,硬件定时器是你的“系统心跳”,它用最少的CPU干预为你提供精准的时基;时钟生成器(特别是PLL)是你的“频率合成器”,让你能灵活地用一个低频晶振跑出高频的CPU核心时钟;而六通道DMA则是你的“数据搬运工”,它能悄无声息地在后台完成大数据量的搬运,把CPU彻底解放出来去做真正的信号处理算法。这三者配合好了,系统才能既“跑得快”又“反应准”。

2. 硬件定时器:系统的精准心跳

硬件定时器是嵌入式系统的基石。在VC5410A中,它不仅仅是一个简单的倒计时器,更是实现任务调度、外设同步、脉冲宽度调制(PWM)和采样率生成的瑞士军刀。

2.1 定时器架构与工作原理

VC5410A的定时器本质上是一个16位递减计数器,搭配一个4位预分频器(Prescaler)。它的工作流程非常直观:

  1. 时钟源 :定时器的驱动时钟直接来自CPU的时钟输出(CLKOUT)。这意味着定时器的计时精度与系统主频直接相关。
  2. 预分频 :4位预分频器(PSC)可以对输入时钟进行分频,分频系数为1到16。这相当于给你的16位计数器前面加了一个“减速齿轮”,让你能够实现更长的定时周期。计算公式是: 定时器输入时钟 = CLKOUT / (TDDR + 1) ,其中TDDR是预分频器寄存器中的值(0-15)。
  3. 递减计数 :16位的主计数器(TIM)在每个经过预分频后的时钟周期减1。
  4. 周期匹配与中断 :当TIM的值递减到0时,会产生两个关键动作:第一,触发一个定时器中断(TINT),通知CPU一个定时周期已完成;第二,自动从周期寄存器(PRD)中重新加载值到TIM寄存器,开始下一个周期的计数。

这个过程完全由硬件完成,不需要CPU的持续干预。你只需要初始化好PRD、TDDR等寄存器并启动定时器,它就会像心脏一样持续、稳定地跳动。

2.2 关键寄存器详解与配置实战

控制定时器主要通过三个内存映射寄存器(MMR),地址位于数据空间的0x0024到0x0026。

TIM(定时器计数器寄存器,地址0x0024) 这是一个16位递减计数器。你可以在任何时候读取它来获取当前的计数值,用于实现超时检查或精确延时。 注意 :直接写入TIM寄存器会立即重置当前计数周期。

PRD(定时器周期寄存器,地址0x0025) 这是定时器的“重装值”。当TIM减到0后,PRD的值会自动加载到TIM中。因此,定时周期由PRD和预分频器共同决定。计算公式为: 定时周期 = (CLKOUT周期) * (TDDR + 1) * (PRD + 1) 例如,如果CLKOUT频率为100MHz(周期10ns),TDDR=3(分频比4),PRD=24999,那么定时周期 = 10ns * 4 * 25000 = 1,000,000 ns = 1ms。这样你就得到了一个1ms的定时中断。

TCR(定时器控制寄存器,地址0x0026) 这是定时器的大脑,每一位都至关重要。

名称 功能描述 配置心得
15-12 PSC 预分频计数器。只读,显示当前预分频计数器的值。 调试时可以通过读取它来观察定时器是否在运行。
11-10 TRB 定时器重载控制。写1到TRB位,会立即将PRD和TDDR的值分别加载到TIM和PSC。 这是启动定时器的关键一步 !光设置好PRD和TCR其他位,不执行TRB=1,定时器不会开始新周期。
9 TSS 定时器停止状态。0=运行,1=停止。 上电默认是1(停止)。需要先配置好PRD和TDDR,再将TSS清零来启动定时器。
8-6 保留 必须写0。
5 SOFT 与FREE位配合,用于仿真器调试时的定时器行为。 通常设为0。
4 FREE 同上。通常设为1,使得在仿真器断点时定时器继续运行,不影响实时性。 对于有严格时序要求的系统,建议设为1。
3 PWID 脉冲宽度标识。只读,在定时器输出到TOUT引脚时有用。 内部定时器模式一般不用关心。
2 ARB 自动重载控制。必须设为1,才能使能TIM减到0后从PRD自动重载。 务必设为1 ,否则定时器只工作一次就停止了。
1-0 TDDR 定时器分频比。设置预分频器的分频值(0-15)。 用于扩展定时范围。公式中的分频系数是 TDDR+1

一个典型的定时器初始化C语言代码片段如下(假设使用C编译器):

void Timer_Init(unsigned int period, unsigned char div) {
    // 1. 停止定时器
    *((volatile unsigned int*)0x0026) |= 0x0200; // 设置TSS=1,停止

    // 2. 设置周期和分频
    *((volatile unsigned int*)0x0025) = period; // 写入PRD
    *((volatile unsigned int*)0x0026) = (*(volatile unsigned int*)0x0026) & 0xFFF0; // 清空低4位
    *((volatile unsigned int*)0x0026) |= (div & 0x0F); // 设置TDDR

    // 3. 配置控制位(FREE=1, ARB=1, 清TSS)
    *((volatile unsigned int*)0x0026) &= 0x0F09; // 保留必要位,清TSS等
    *((volatile unsigned int*)0x0026) |= 0x0034; // 设置FREE=1, ARB=1, TSS保持0(运行)

    // 4. 重载定时器,启动计数
    *((volatile unsigned int*)0x0026) |= 0x0400; // 设置TRB=1,触发重载
}

注意 :对寄存器的操作必须是原子操作。在实际系统中,这段代码可能需要在禁止中断的环境下执行,或者使用位域操作来确保不会误改其他位。

2.3 定时器中断服务程序(ISR)编写要点

定时器中断是其实时性的体现。在中断服务程序中,有几条黄金法则:

  1. 快进快出 :ISR中只做最必要、最轻量的工作,比如设置一个标志位、递增一个计数器。繁重的处理任务应该放到主循环中根据标志位来执行。
  2. 清除中断标志 :VC5410A的中断系统需要手动清除中断标志。对于定时器中断,你需要操作中断标志寄存器(IFR)。通常会在ISR开始时或结束时执行 asm(“ bitm IFR, #0x0008”); (假设TINT是中断向量表中的第4个)来清除定时器中断标志。 忘记清除会导致中断持续触发,系统卡死
  3. 避免阻塞操作 :绝对不要在ISR中调用可能阻塞的函数(如某些复杂的算法、软件延时循环)。
  4. 注意重入问题 :如果你的ISR和主循环会访问共享的全局变量(如那个标志位),务必使用临界区保护(如关中断)或确保读/写操作是原子的。

3. 时钟生成器:系统节奏的指挥家

VC5410A的时钟生成器核心是一套灵活的锁相环(PLL)电路。它的价值在于,允许你使用一个相对低频、稳定且便宜的外部晶体或时钟源,在芯片内部合成出高频的CPU工作时钟,同时还能生成供外设使用的时钟。

3.1 PLL与DIV模式深度解析

芯片支持两种基本时钟模式:

  • PLL模式 :输入时钟(CLKIN)通过PLL电路进行倍频。倍频系数由软件可编程,提供高达31种不同的倍频比(如x1, x2, x3, ..., x31)。这是最常用的模式,能让CPU运行在远高于外部晶振的频率上。
  • DIV模式 :输入时钟直接进行2分频或4分频。此时PLL电路可以被完全关闭,有助于降低系统功耗。

硬件配置与软件覆盖 :这是最容易出错的地方。芯片上电或复位时,时钟模式由三个硬件引脚CLKMD1/2/3的电平决定(见输入资料中的Table 3-10)。例如,CLKMD[3:1]=001对应PLL x10模式。 但是 ,复位完成后,你可以通过软件改写时钟模式寄存器(CLKMD)来动态切换模式。 一个关键限制是 :硬件引脚同时决定了内部振荡器的使能/禁止。如果硬件配置为禁止振荡器(CLKMD[3:1]=000),那么后续软件也无法将其开启。因此,硬件设计阶段就必须根据你的时钟方案正确设置这三个引脚的上拉/下拉电阻。

3.2 时钟模式寄存器(CLKMD)配置实战

CLKMD寄存器位于地址0x0058。其位定义是配置时钟的核心。

位域 名称 描述 配置要点
15-12 PLLMUL PLL倍频系数。 与PLLDIV、PLLNDIV共同决定最终的倍频比。具体映射关系需查表(数据手册的PLL倍频选择表)。例如,要设置PLL x15模式,需要写入特定的组合值。
11 PLLDIV PLL分频选择。
10-3 PLLCOUNT PLL锁定时间计数器。 极其重要 !PLL从启动到频率稳定(锁定)需要时间。这个计数器根据输入时钟频率设置一个延迟,确保在PLL锁定之前,CPU不会切换到不稳定的时钟上。计算方式通常为: PLLCOUNT = (锁定时间 * 输入频率) / 16 。如果设得太小,系统可能在PLL未锁定时就跑起来了,导致极不稳定的崩溃。
2 PLLON/OFF PLL使能。1=开启PLL,0=关闭PLL(进入DIV模式)。 在切换时钟模式时,需要遵循严格的序列,先配置好PLLMUL/PLLDIV和PLLCOUNT,最后再打开PLLON。
1 PLLNDIV PLL分频选择。
0 PLLSTATUS PLL状态(只读)。1=锁定,0=未锁定。 软件可以轮询此位,确保PLL锁定后再进行后续关键操作,但这并非强制,因为PLLCOUNT机制通常已足够。

一个从DIV模式切换到PLL x20模式的软件流程示例:

void PLL_Init_To_x20(void) {
    volatile unsigned int *clkmd = (volatile unsigned int *)0x0058;
    unsigned int current_mode = *clkmd;

    // 1. 检查当前是否已在PLL模式且倍频正确,避免不必要的切换
    if ((current_mode & 0x1800) != 目标PLL配置值) { // 简化判断,实际需比对PLLMUL等位
        // 2. 切换到DIV模式(关闭PLL),确保稳定过渡
        *clkmd = current_mode & 0xFFFD; // 清除PLLON位(假设位2是PLLON)

        // 3. 插入足够的延时,等待PLL完全关闭(通常需要几十个输入时钟周期)
        software_delay(100); // 简单的软件循环延时

        // 4. 配置新的PLL参数和锁定时间
        // 假设PLL x20对应的配置字是0x900F(需查表确认),其中包含了PLLCOUNT
        *clkmd = 0x900F; // 写入新配置,但PLLON位为0

        // 5. 再次延时,让配置稳定
        software_delay(10);

        // 6. 使能PLL
        *clkmd |= 0x0004; // 设置PLLON位

        // 7. 等待PLL锁定。可以轮询PLLSTATUS,或依赖PLLCOUNT的延时。
        // 简单做法是等待PLLCOUNT规定的时钟周期数。
        // 更稳健的做法是轮询:
        // while (!(*clkmd & 0x0001)); // 等待PLLSTATUS=1
    }
}

重要提示 :在PLL切换期间,CPU时钟会发生变化。 绝对不能在PLL未锁定的情况下执行对时序敏感的操作,如访问高速外部存储器 。通常,时钟初始化代码是在上电后、主程序运行前,在RAM中执行的(因为Flash访问可能受时钟影响)。

3.3 时钟与功耗管理

VC5410A的时钟生成器与低功耗模式紧密相关。通过CLKMD寄存器关闭PLL(进入DIV模式)本身就是一种省电手段。此外,芯片还有IDLE1/2/3等低功耗指令,可以停止部分或全部内部时钟。 需要注意的是 ,当时钟被分频或停止时,所有依赖此时钟的外设(包括定时器和串口)也会同步变慢或停止。在设计低功耗唤醒流程时,必须考虑外设的重新初始化。

4. 直接内存访问(DMA)控制器:数据搬运的引擎

DMA是提升DSP系统性能的“神器”。它允许数据在内存与内存之间、内存与外设之间自动传输,完全不需要CPU参与。VC5410A的DMA控制器尤其强大,拥有6个独立通道。

4.1 DMA核心概念与工作流程

你可以把DMA通道想象成一个智能的“快递员”。你只需要告诉它:

  1. 货源地址 (Source Address):数据从哪里来。
  2. 目的地地址 (Destination Address):数据到哪里去。
  3. 快递单大小 (Transfer Count):要搬运多少“件”数据(元素),以及这些“件”如何打包成“箱”(帧)。
  4. 搬运规则 (Address Modification):每送完一件,下一个货/目的地址怎么变(加1、减1、加一个偏移量)。
  5. 触发信号 (Synchronization Event):什么时候开始送(比如,收到一帧数据后)。 设置好后,启动这个“快递员”,它就会在后台默默工作。CPU可以同时去处理其他任务,只有当一整“箱”(帧)或所有“货”(块)送完时,DMA才会通过中断“打电话”通知CPU。

4.2 DMA通道配置详解

每个DMA通道都由一组寄存器控制。由于寄存器众多,它们采用“子库寻址”方式访问:通过DMSA(子库地址寄存器)选择一个寄存器,再通过DMSDI/DMSDN(子库数据寄存器)进行读写。

关键寄存器配置步骤:

  1. 禁止通道 :在配置任何通道前,先通过DMPREC寄存器禁用该通道(DE位清0)。
  2. 配置源/目的地址 (DMSRCn, DMDSTn):设置数据的起点和终点。 注意地址空间 :通过DMMCRn寄存器中的SLAXS和DLAXS位,可以指定源和目的是内部还是外部存储器。这是VC5410A的一个增强特性。
  3. 配置传输计数 (DMCTRn, DMFRCn):
    • DMCTRn :定义每个帧(Frame)中有多少个元素(Element)。元素是传输的基本单位,通常是16位字。
    • DMFRCn :定义一个块(Block)中有多少帧。通过帧和元素的组合,可以轻松实现二维数据传输(例如,传输一个矩阵)。
    • 总传输量 = (DMFRCn + 1) * (DMCTRn + 1) 。因为寄存器存储的是计数值减1。
  4. 配置地址修改模式 (DMMCRn中的SIND/DIND):决定每传输一个元素后,源和目的地址如何变化。选项包括:
    • 00 :不变(用于访问固定地址的外设寄存器,如McBSP的数据寄存器)。
    • 01 :后递增(+1)。最常用,用于顺序搬运数组。
    • 10 :后递减(-1)。
    • 11 :按索引寄存器(DMIDX0/1)的值增减。用于实现复杂步长的访问,如抽取数据。
  5. 配置同步事件 (DMSEFCn中的DSYN):决定什么事件触发一次DMA传输。这是DMA与外部世界交互的关键。
    • 0000 :无同步,配置好后立即开始连续传输。
    • 0001 - 1100 :与McBSP的接收/发送事件同步。这是最典型的应用,实现串口数据的自动搬运。
    • 1101 :定时器中断事件同步。
    • 1110 :外部中断3同步。
  6. 配置中断与自动初始化 (DMMCRn中的IMOD, DINM, AUTOINIT):
    • DINM :中断使能。
    • IMOD :中断模式。0=仅在块传输完成时中断;1=在每帧传输完成和块传输完成时都中断。
    • AUTOINIT :自动初始化。置1后,当一次块传输完成,DMA会自动从重载寄存器(DMGSA, DMGDA, DMGCR, DMGFR)中加载参数,并重新开始传输,实现循环缓冲(Ping-Pong Buffer),这是实现连续无丢失数据流的关键。
  7. 启用通道 :最后,通过DMPREC寄存器使能通道(DE位置1)。 注意DMPREC的硬件问题 :如资料3.8.3节所述,直接写DMPREC在特定时序下可能导致问题。 强烈建议使用DMCECTL寄存器(地址0x003E)来安全地启用/禁用通道 。例如,启用通道0和通道1: *(volatile unsigned int*)0x003E = 0x8003; (Set/Reset位=1, CH0和CH1=1)。

4.3 DMA与McBSP协同工作实例:音频数据采集

假设我们用McBSP0接收来自ADC的16位单声道音频数据,采样率48kHz,希望用DMA将数据实时搬运到内部DARAM的一个1024点的环形缓冲区中。

步骤分析:

  1. DMA通道选择 :选择一个通道,例如通道0。
  2. 源地址 :McBSP0的数据接收寄存器 DRR10 (地址0x0021)。SLAXS=0(内部外设)。
  3. 目的地址 :内部DARAM中的一个数组 audio_buffer[1024] 的起始地址。DLAXS=0(内部内存)。
  4. 传输计数 :我们希望每收到一个样本就搬运一次。所以设置 DMCTR0 = 0 (每帧1个元素), DMFRC0 = 1023 (一个块有1024帧)。这样,DMA会在搬完1024个样本后产生中断。
  5. 地址修改 :源地址 SIND 设为 00 (不变,因为总是从 DRR10 读)。目的地址 DIND 设为 01 (后递增),这样数据就能顺序存入数组。
  6. 同步事件 DSYN = 0001b (McBSP0接收事件)。这样,每次McBSP0收到一个完整的字,就会触发DMA进行一次搬运。
  7. 自动初始化与中断 :设置 AUTOINIT=1 ,并配置好重载寄存器,让目的地址在传输完成后自动回到 audio_buffer 起始处,实现环形缓冲。设置 DINM=1 , IMOD=0 ,仅在每次填满整个1024点的缓冲区(块传输完成)时产生中断,通知CPU处理这批数据。
  8. 启动 :先配置McBSP0使其开始接收数据,然后通过DMCECTL安全启用DMA通道0。

通过这样的配置,CPU完全不用操心数据的搬入,只需要在DMA中断服务程序中处理已经准备好的1024个音频样本即可,效率极高。

4.4 DMA使用中的陷阱与最佳实践

  1. 内存冲突 :DMA的优先级高于CPU。如果DMA和CPU同时访问同一块内存(尤其是同一内存块),会导致访问冲突,可能引发不可预知的数据错误。 解决方案 :精心规划内存布局,将DMA源/目的缓冲区与CPU频繁访问的数据/代码区域分开;或者使用双缓冲(Ping-Pong Buffer),让DMA和CPU分别操作不同的缓冲区。
  2. 外部访问限制 :VC5410A的DMA虽然支持外部存储器访问,但限制颇多(如资料3.8.2节所述):仅支持单字传输、不支持外设与外部存储器互传、不支持外部到外部传输、最多只有两个通道可用于外部访问。 设计系统时,尽量让DMA在内部存储器之间工作 ,将需要CPU处理的数据预先通过DMA从外部搬入内部。
  3. 缓冲区对齐 :虽然手册未强制要求,但出于性能考虑,建议将DMA缓冲区地址对齐到偶数或更优的边界(如4字节边界),这可以避免因为非对齐访问带来的额外时钟周期消耗。
  4. 调试困难 :DMA在后台运行,出错时现象往往诡异(数据错乱、程序跑飞)。 调试时,可以先将同步事件设为“无”(DSYN=0000),并设置一个很小的传输计数,手动启动DMA,检查单次传输是否正确。然后再逐步加入同步、增大数据量、启用自动初始化

5. 三大模块的协同设计与系统优化

单独理解每个模块只是第一步,让定时器、时钟和DMA协同工作,才能发挥VC5410A的最大潜力。

5.1 时钟为基,定时同步,DMA搬运

一个典型的数据流处理系统可以这样构建:

  1. 时钟生成器 提供稳定的高速CPU时钟(如100MHz via PLL x10 from 10MHz晶振)和供McBSP使用的低速串行时钟(可由CPU时钟分频得到)。
  2. 硬件定时器 产生一个精确的周期性中断(例如1ms),作为系统的“时间片”。在这个中断里,你可以检查DMA传输的状态标志、执行系统状态监测或低优先级的后台任务。
  3. McBSP 在外部时钟或内部时钟驱动下,以固定速率(如48kHz)收发数据。
  4. DMA 被配置为与McBSP接收事件同步。每当McBSP收到一个数据,DMA就自动将其搬运到内部缓冲区。当缓冲区半满或全满时,DMA触发中断。
  5. CPU 在DMA中断(高优先级)中,处理已经准备好的半块或整块数据,进行滤波、变换等算法处理。在定时器中断(低优先级)或主循环中,执行系统控制和人机交互等任务。

这种架构下,CPU的负载被大大减轻,绝大部分时间都在执行核心算法,实时性得到保障。

5.2 性能优化技巧

  1. 利用DMA双字模式 :对于需要搬运32位数据(如两个16位样本组成一个32位字)的场景,启用DMMCRn中的 CTMOD 位(双字模式)。这样一次DMA操作能搬运32位,理论上带宽翻倍。
  2. 灵活使用地址索引 :DMA的 DMIDX DMFRI 寄存器非常强大。例如,在处理图像数据时,你可以设置源地址按一行像素的宽度(如640)进行索引跳跃,实现从二维帧缓冲区中抽取一行数据,而无需CPU介入。
  3. 动态调整时钟与功耗 :在系统空闲或处理低负载任务时,可以通过软件将PLL切换到低倍频模式甚至DIV模式,以降低功耗。同时,让CPU进入IDLE状态,由定时器或外部事件唤醒。这种动态电源管理对于电池供电设备至关重要。
  4. 规避DMA与CPU的总线竞争 :如果发现系统在启用DMA后整体速度变慢,可能是总线竞争导致。可以尝试调整DMA通道的优先级(DMPREC中的DPRC字段),或者优化CPU和DMA对内存的访问模式,尽量减少交叉访问。

5.3 常见问题排查清单

现象 可能原因 排查步骤
定时器不产生中断 1. 定时器未启动(TSS=1)。
2. 中断未使能(IMR寄存器相应位)。
3. 中断标志未清除(IFR寄存器)。
4. PRD值设为0。
1. 检查TCR寄存器,确认TSS=0, TRB已触发。
2. 检查ST1寄存器中的INTM位是否为0(全局中断开启),并检查IMR。
3. 在ISR中或主循环中检查并清除IFR。
4. 确认PRD值大于0。
PLL配置后系统不稳定或死机 1. PLL锁定时间(PLLCOUNT)不足。
2. 在PLL未锁定时访问了高速存储器。
3. 硬件CLKMD引脚配置与软件目标模式冲突。
1. 增大PLLCOUNT值,确保足够锁定时间。
2. 将时钟初始化代码放在RAM中运行,并在PLL锁定前避免复杂操作。
3. 测量硬件引脚电平,确认与软件配置匹配。
DMA传输数据错位或丢失 1. 源/目的地址修改模式(SIND/DIND)设置错误。
2. 缓冲区指针在自动初始化后未正确复位。
3. 同步事件选择错误,或源外设(如McBSP)未正确配置。
4. DMA与CPU访问同一内存区造成数据破坏。
1. 仔细核对SIND/DIND配置,特别是用于外设寄存器时应用“不变”模式。
2. 检查DMA重载寄存器(DMGSA/DMGDA)的值是否正确。
3. 用示波器或逻辑分析仪检查同步事件信号(如McBSP的接收时钟和帧同步)是否正常。
4. 检查内存映射,确保缓冲区独立。
启用DMA后,McBSP数据收发异常 1. McBSP的接收/发送中断与DMA通道中断冲突(INTOSEL配置)。
2. DMA搬运速度跟不上McBSP数据速率,导致溢出。
1. 检查DMPREC寄存器的INTOSEL字段,正确分配中断源。通常让DMA使用独立中断,McBSP用自己的中断。
2. 计算DMA理论带宽(考虑总线周期、等待状态),确保其大于McBSP数据率。优化DMA传输为双字模式或使用更高优先级。

掌握TMS320VC5410A的硬件定时器、时钟生成器和DMA控制器,是从“能用”到“用好”这颗DSP的关键跨越。这些模块的灵活性和强大功能,为构建高效、实时的数字信号处理系统提供了坚实的基础。实际开发中,最耗费时间的往往不是编写算法,而是将这些底层外设精准地配置和协同起来。希望这篇结合了手册原理与实战经验的详解,能帮你少走弯路,更自信地驾驭这颗经典的处理器。记住,多读手册、善用仿真器、敢于用示波器验证时序,是嵌入式开发的不二法门。

更多推荐