尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

STM32F103定时器深度解析:从时钟树到PWM与输入捕获实战

STM32F103定时器深度解析:从时钟树到PWM与输入捕获实战 1. 项目缘起为什么从定时器开始如果你刚开始接触STM32或者从51单片机转过来第一个让你感到既熟悉又陌生的外设大概率就是定时器。在51上定时器配置就那么几个寄存器中断服务函数里写个TH0、TL0就完事了。但到了STM32尤其是STM32F103这种资源丰富的Cortex-M3内核芯片打开参考手册光是通用定时器TIMx的章节就上百页更别提高级定时器了。很多人一看就懵直接去网上找段PWM或者定时中断的代码复制粘贴能跑通就万事大吉。但问题往往就出在这里。我见过不少项目初期功能都正常到了后期需要精确计时、多路PWM同步或者复杂捕获时各种诡异的问题就冒出来了定时不准、PWM输出有毛刺、中断响应不及时。回头一查根子都在最初对定时器的理解不够透彻配置时留下了隐患。定时器是STM32的“心脏”之一它不仅仅是计个时、产生个中断那么简单更是实现电机控制、通信同步、信号采集等核心功能的基础。所以我觉得有必要把STM32F103的定时器掰开了、揉碎了从最基础的定时功能开始结合常见的PWM输出、输入捕获等场景把它的工作机制、配置要点和那些容易踩的坑系统地梳理一遍。这不是一个简单的“点灯”实验而是一次对定时器外设的深度探索目标是让你以后用到定时器时心里有底手上有谱。2. STM32F103定时器家族概览与选型逻辑在动手写代码之前我们必须先搞清楚STM32F103这颗芯片给我们提供了哪些定时器资源以及它们各自适合干什么。盲目选型后面配置起来会事倍功半。STM32F103的定时器是一个大家族主要分为以下几类高级控制定时器TIM1, TIM8这是功能最强大的定时器具备通用定时器的所有功能并且额外支持互补输出、死区插入、紧急刹车等专为电机控制和电源应用设计。如果你要做三相电机FOC控制、开关电源它是不二之选。通用定时器TIM2, TIM3, TIM4, TIM5我们最常打交道的“多面手”。它们支持基本的定时、输入捕获、输出比较、PWM生成、单脉冲输出等。TIM2和TIM5是32位的其他是16位的。32位定时器在需要很长定时周期时非常有用。基本定时器TIM6, TIM7功能最简单只能用于最基本的定时产生中断或DMA请求。通常用于给DAC提供触发信号或者作为其他功能的时基。独立看门狗IWDG和窗口看门狗WWDG用于系统故障恢复属于特殊定时器。系统滴答定时器SysTick集成在Cortex-M3内核中为操作系统提供时钟节拍也可以拿来用作简单的延时。对于大多数应用如周期性任务调度、生成PWM波、测量脉冲宽度或频率通用定时器TIM2-TIM5就完全够用了。本次实验我们以TIM3为例进行讲解因为它是一个典型的16位通用定时器资源适中原理具有普遍性。这里有一个关键的选型逻辑先看功能再看位数和通道数。比如你需要4路完全同步的PWM吗那就需要选择支持“主/从模式”的定时器进行同步。你需要测量一个高达1MHz的信号频率吗那就要考虑定时器的计数时钟频率和捕获单元的性能。对于初学者从TIM3开始把通用定时器的套路摸清再去看高级定时器会发现很多概念是相通的只是寄存器更多、功能更复杂而已。3. 定时器的核心时钟树与计数模式剖析定时器之所以能“定时”核心在于它有一个计数器在时钟驱动下不断累加或递减。因此理解定时器的时钟来源和计数模式是第一步也是最容易出错的一步。3.1 时钟从哪里来——时钟树分析STM32的时钟树有点复杂但针对定时器我们只需要关注这几条路径内部时钟CK_INT通常来自APB总线时钟。这里有个关键点当APB预分频器系数不为1时比如APB1时钟是36MHz而AHB是72MHzAPB1预分频系数就是2定时器的时钟会被倍频x2。例如在标准72MHz系统时钟下APB1总线时钟是36MHz但连接到APB1上的定时器TIM2-4的时钟输入实际上是72MHz。这个细节在计算定时周期时至关重要很多人直接拿APB1时钟去算结果发现定时快了或慢了一倍问题就出在这里。外部时钟模式1定时器通道引脚TIx上的外部信号。外部时钟模式2外部触发输入ETR引脚上的信号。内部触发输入ITRx一个定时器可以作为另一个定时器的预分频器实现定时器级联。对于最基本的定时功能我们使用内部时钟CK_INT。在标准库或HAL库中初始化定时器时库函数通常会帮我们处理好这个时钟源选择。但你必须心里清楚你配置的“定时器时钟TIMxCLK”到底是多少MHz。3.2 计数器如何工作——四种计数模式详解通用定时器的核心是一个16位TIM2/5是32位的计数器CNT它可以向上、向下或向上/向下中央对齐计数。计数模式由寄存器TIMx_CR1的DIR和CMS位控制。向上计数模式计数器从0开始累加到自动重装载值TIMx_ARR然后产生一个更新事件溢出计数器清零重新开始。这是最常用的模式。定时时间 (ARR 1) * (PSC 1) / TIMxCLK。这里1是因为预分频器和重装载值都是从0开始计数的。向下计数模式计数器从自动重装载值ARR开始递减到0产生更新事件然后重新从ARR开始递减。中央对齐模式向上/向下计数计数器从0开始向上计数到ARR产生一个更新事件然后向下计数到0再产生一个更新事件。这样一个周期内会产生两次更新事件。这种模式常用于生成对称的PWM波形或者需要更频繁更新事件的场合。编码器模式此模式专用于连接正交编码器通过两个输入通道的信号自动判断方向并计数常用于电机位置反馈。注意ARR和PSC预分频器都是16位寄存器最大值65535。如果需要更长的定时就需要加大PSC分频系数但这会降低定时精度。对于32位定时器TIM2/5ARR是32位的可以轻松实现很长的定时。3.3 预分频器与自动重装载寄存器的协同这是定时器定时的核心公式定时时间 (自动重装载值ARR 1) * (预分频器PSC 1) / 定时器输入时钟频率(TIMxCLK)预分频器PSC负责对输入时钟TIMxCLK进行分频得到计数器实际使用的计数时钟CK_CNT。CK_CNT TIMxCLK / (PSC 1)。PSC是一个缓冲寄存器写入的新值只在下次更新事件时才生效这可以保证定时周期的平滑切换不会产生毛刺。自动重装载寄存器ARR决定了计数器的周期。在向上计数模式下计数器从0数到ARR就溢出。ARR同样有缓冲机制通过ARPE位控制可以设置预装载使新值在更新事件后才生效。实操心得在需要动态改变定时周期比如改变PWM频率时通常会同时改变ARR和PSC。一个技巧是先改变PSC再改变ARR或者通过设置RCR重复计数寄存器来精细控制更新事件的产生频率避免在PWM周期中间产生不完整的波形。4. 从零开始配置一个1ms定时中断理论说得再多不如动手做一遍。我们使用标准外设库虽然现在HAL库更流行但标准库对理解寄存器更有帮助来配置TIM3实现一个1ms的定时中断并在中断服务函数里翻转一个LED灯。步骤1时钟使能定时器是挂载在APB1总线上的TIM2-4, TIM6-7所以首先要使能APB1总线上的TIM3时钟以及对应的GPIO时钟用于LED。RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 假设LED在PA8 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);步骤2定时器基本参数配置这是核心配置。假设系统时钟为72MHzAPB1预分频为2所以TIM3的时钟TIM3CLK 72MHz。 我们要实现1ms中断即定时周期T 0.001s。 代入公式T (ARR1)*(PSC1) / TIM3CLK我们可以先设定PSC 7199这样CK_CNT 72MHz / (71991) 10kHz。 然后计算ARRARR T * CK_CNT - 1 0.001 * 10000 - 1 9。 所以ARR 9PSC 7199。TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; TIM_TimeBaseInitStruct.TIM_Period 9; // ARR TIM_TimeBaseInitStruct.TIM_Prescaler 7199; // PSC TIM_TimeBaseInitStruct.TIM_ClockDivision TIM_CKD_DIV1; // 时钟分频与数字滤波器相关一般用不到 TIM_TimeBaseInitStruct.TIM_CounterMode TIM_CounterMode_Up; // 向上计数 TIM_TimeBaseInitStruct.TIM_RepetitionCounter 0; // 高级定时器才有通用定时器写0 TIM_TimeBaseInit(TIM3, TIM_TimeBaseInitStruct);为什么TIM_ClockDivision是TIM_CKD_DIV1这个参数不是用于定时器时钟分频的而是用于配置死区时间和数字滤波器的采样时钟在通用定时器做简单定时时通常设为DIV1即可。步骤3使能定时器更新中断我们需要定时器在计数溢出更新事件时产生中断。TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);步骤4配置NVIC嵌套向量中断控制器使能了定时器中断还得告诉NVIC你要响应这个中断并设置优先级。NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel TIM3_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority 1; // 抢占优先级 NVIC_InitStruct.NVIC_IRQChannelSubPriority 0; // 子优先级 NVIC_InitStruct.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStruct);步骤5编写中断服务函数在stm32f10x_it.c文件中找到TIM3_IRQHandler函数并编写如下内容void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_Update) ! RESET) { // 清除中断标志位这一步非常重要否则会连续进入中断 TIM_ClearITPendingBit(TIM3, TIM_IT_Update); // 用户代码翻转LED GPIO_WriteBit(GPIOA, GPIO_Pin_8, (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_8))); } }关键点一定要先判断中断源然后立刻清除对应的中断标志位。这是中断服务函数的铁律。忘记清除标志位会导致程序不断进入中断卡死在里面。步骤6启动定时器最后别忘了让定时器开始跑起来。TIM_Cmd(TIM3, ENABLE);至此一个1ms的定时中断就配置完成了。下载程序你应该能看到LED以1ms的间隔被翻转但由于人眼视觉暂留你会看到LED常亮或微闪。可以把ARR改大比如9000实现1秒的定时10kHz * 1000 1s就能看到清晰的闪烁了。5. 进阶应用一生成精确可控的PWM信号PWM脉冲宽度调制是定时器最经典的应用之一用于控制LED亮度、电机速度、舵机角度等。STM32的通用定时器每个通道都可以独立配置成PWM输出模式。5.1 PWM生成原理与关键寄存器PWM的本质是输出一个周期固定、占空比可调的方波。在STM32定时器中这是通过输出比较单元实现的。周期由自动重装载寄存器ARR决定。PWM频率 CK_CNT / (ARR 1)。占空比由捕获/比较寄存器CCRxx1,2,3,4决定。在PWM模式下CCRx的值会与计数器CNT不断比较。当CNT CCRx时输出一种电平有效电平。当CNT CCRx时输出另一种电平无效电平。极性可以设置有效电平是高电平还是低电平。5.2 配置TIM3通道1PA6输出1kHz占空比50%的PWM步骤1GPIO初始化将PA6配置为复用推挽输出因为定时器的PWM功能需要用到引脚的第二功能AFIO。GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin GPIO_Pin_6; GPIO_InitStruct.GPIO_Mode GPIO_Mode_AF_PP; // 复用推挽输出 GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStruct);步骤2定时器时基单元初始化设定PWM频率。假设TIM3CLK72MHz要产生1kHz PWM。PWM频率 72MHz / ((PSC1)*(ARR1)) 1000Hz我们可以令PSC 71则CK_CNT 72MHz / 72 1MHz。 那么ARR 1MHz / 1000Hz - 1 999。TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; TIM_TimeBaseInitStruct.TIM_Period 999; // ARR TIM_TimeBaseInitStruct.TIM_Prescaler 71; // PSC TIM_TimeBaseInitStruct.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInitStruct.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, TIM_TimeBaseInitStruct);步骤3PWM输出通道配置这是配置的核心设置通道为PWM模式并定义极性。TIM_OCInitTypeDef TIM_OCInitStruct; TIM_OCInitStruct.TIM_OCMode TIM_OCMode_PWM1; // 选择PWM模式1 TIM_OCInitStruct.TIM_OutputState TIM_OutputState_Enable; // 使能输出 TIM_OCInitStruct.TIM_OCPolarity TIM_OCPolarity_High; // 输出极性高电平为有效电平 TIM_OCInitStruct.TIM_Pulse 500; // 设置捕获比较寄存器CCR1的值决定占空比 // 占空比 CCR1 / (ARR1) 500 / 1000 50% TIM_OC1Init(TIM3, TIM_OCInitStruct); // 初始化通道1 TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); // 使能CCR1预装载模式1 vs 模式2PWM1模式下当CNT CCRx时输出有效电平我们设为了高电平PWM2模式则相反。根据你的硬件电路比如是共阳极还是共阴极LED来选择合适的模式和极性。步骤4使能自动重装载预装载和启动定时器TIM_ARRPreloadConfig(TIM3, ENABLE); // 使能ARR预装载保证波形稳定 TIM_Cmd(TIM3, ENABLE); // 启动定时器为什么需要预装载无论是ARR还是CCRx直接修改它们的值可能会在当前的PWM周期中间生效导致产生一个“破碎”的脉冲。使能预装载后新值会被写入一个缓冲寄存器只在下一个更新事件计数器溢出时才真正生效从而保证每个PWM周期的完整性。现在用示波器或者逻辑分析仪测量PA6引脚应该能看到一个1kHz、占空比50%的方波。你可以通过修改TIM3-CCR1的值来动态改变占空比例如TIM_SetCompare1(TIM3, 200); // 将占空比改为20%6. 进阶应用二输入捕获测量脉冲宽度与频率输入捕获是定时器的另一个强大功能用于测量外部信号的脉冲宽度高电平/低电平时间或频率。其原理是当捕获引脚上出现指定的边沿上升沿或下降沿时定时器当前的计数值CNT会被瞬间锁存到对应的捕获/比较寄存器CCRx中并可以产生中断。通过记录两次捕获事件时的CCRx值就能计算出时间差。6.1 测量高电平脉宽的标准方法以测量PA0TIM2通道1上的高电平脉宽为例。步骤1GPIO与定时器时基初始化将PA0配置为浮空输入或上拉输入并初始化TIM2的时基单元。这里时基的配置很关键它决定了测量的精度和范围。假设TIM2CLK72MHz我们设置PSC71则CK_CNT1MHz即计数器每1us加1。ARR设为最大值65535那么最大能测量的连续时间为65.535ms。// GPIO初始化 GPIO_InitStruct.GPIO_Pin GPIO_Pin_0; GPIO_InitStruct.GPIO_Mode GPIO_Mode_IPU; // 上拉输入 GPIO_Init(GPIOA, GPIO_InitStruct); // TIM2时基初始化 TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; TIM_TimeBaseInitStruct.TIM_Period 65535; TIM_TimeBaseInitStruct.TIM_Prescaler 71; TIM_TimeBaseInitStruct.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInitStruct.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_TimeBaseInitStruct);步骤2输入捕获通道配置我们需要配置通道1首先在上升沿捕获记录下高电平开始的时刻。TIM_ICInitTypeDef TIM_ICInitStruct; TIM_ICInitStruct.TIM_Channel TIM_Channel_1; TIM_ICInitStruct.TIM_ICPolarity TIM_ICPolarity_Rising; // 上升沿捕获 TIM_ICInitStruct.TIM_ICSelection TIM_ICSelection_DirectTI; // 映射到TI1上 TIM_ICInitStruct.TIM_ICPrescaler TIM_ICPSC_DIV1; // 不分频每个边沿都捕获 TIM_ICInitStruct.TIM_ICFilter 0x0; // 不滤波 TIM_ICInit(TIM2, TIM_ICInitStruct);步骤3编写中断服务函数逻辑这是测量的核心。我们需要在中断中改变捕获边沿并计算时间差。// 全局变量 __IO uint32_t Capture_Val1 0, Capture_Val2 0; __IO uint32_t Period_Val 0; __IO uint8_t Capture_Flag 0; // 0:等待上升沿1:已捕获上升沿等待下降沿 void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_CC1) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); if (Capture_Flag 0) { // 第一次捕获是上升沿 Capture_Val1 TIM_GetCapture1(TIM2); // 获取上升沿时刻的CNT值 Capture_Flag 1; // 改变捕获极性为下降沿 TIM_OC1PolarityConfig(TIM2, TIM_ICPolarity_Falling); } else if (Capture_Flag 1) { // 第二次捕获是下降沿 Capture_Val2 TIM_GetCapture1(TIM2); // 获取下降沿时刻的CNT值 // 计算高电平脉宽 if (Capture_Val2 Capture_Val1) { Period_Val Capture_Val2 - Capture_Val1; // 直接相减 } else { // 处理计数器溢出情况 Period_Val (65535 - Capture_Val1) Capture_Val2 1; } // 脉宽微秒 Period_Val * (1 / 1MHz) Period_Val (us) // 可以在这里将Period_Val转换为实际时间或者通过串口打印 Capture_Flag 0; // 重置标志准备下一次测量 // 改变捕获极性回上升沿 TIM_OC1PolarityConfig(TIM2, TIM_ICPolarity_Rising); } } // 别忘了处理更新中断防止计数器溢出导致计算错误 if (TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 可以在这里处理溢出计数用于测量更长的脉宽 } }关键点与避坑指南计数器溢出处理上述代码中if (Capture_Val2 Capture_Val1)的判断就是处理计数器没有溢出的情况。如果从上升沿到下降沿之间计数器发生了溢出即CNT从65535回到了0那么Capture_Val2会小于Capture_Val1。此时脉宽 (ARR最大值 - Capture_Val1 1) Capture_Val2。对于更长的脉宽可能发生多次溢出就需要在更新中断里维护一个溢出计数器。输入滤波TIM_ICInitStruct.TIM_ICFilter可以设置数字滤波器用于滤除输入信号上的毛刺。值越大滤波时间越长抗干扰能力越强但会损失对快速边沿的响应。需要根据实际信号质量调整。捕获分频TIM_ICInitStruct.TIM_ICPrescaler可以设置每N个边沿捕获一次用于测量频率很高的信号避免中断过于频繁。6.2 使用PWM输入模式测量频率和占空比上述方法需要软件干预改变极性且测量占空比和频率需要分别捕获上升沿和下降沿计算稍复杂。STM32定时器提供了一个更强大的“PWM输入模式”可以自动地用一个通道测量频率另一个通道测量占空比。原理将输入信号同时映射到两个捕获通道例如TI1映射到IC1和IC2。设置IC1为上升沿捕获IC2为下降沿捕获并且将IC2的捕获事件作为复位触发源TIM_SlaveMode_Reset。这样每次上升沿IC1会捕获到一个值周期而下降沿IC2会捕获到另一个值高电平时间并且计数器会在上升沿被自动复位。硬件自动完成了周期测量软件只需要读取两个CCR寄存器的值即可。配置相对复杂但一旦配好软件逻辑会简单很多特别适合测量固定PWM信号。具体配置步骤涉及从模式、触发源选择等这里不展开但它是输入捕获应用中的一个高效技巧。7. 高级话题与常见问题排查7.1 定时器同步主从模式在需要多个定时器精确同步或者用一个定时器控制另一个定时器的场景下就需要用到主从模式。例如你想让TIM2和TIM3同时启动或者用TIM2的更新事件作为TIM3的触发源时钟。主定时器输出一个触发信号TRGO可以是更新事件、捕获比较事件等。从定时器接收触发信号TRGI并据此执行复位、启动、停止、计数等操作。 配置方法是通过TIMx-SMCR寄存器设置从模式如复位模式、门控模式、触发模式和触发源ITR0, ITR1...或TI1F_ED等。7.2 互补输出与死区插入针对高级定时器TIM1/TIM8在驱动H桥电路控制电机时同一桥臂的上、下两个开关管不能同时导通否则会短路。高级定时器的互补输出功能可以生成一对互补的PWM信号CHx和CHxN并且可以在它们之间插入一段“死区时间”确保在一个管子完全关闭后另一个管子才开启。死区时间由TIMx-BDTR寄存器的DTG位设置计算方式有公式可循需要根据你使用的开关管MOSFET/IGBT的开启/关断时间来谨慎设定。7.3 常见问题与排查思路定时不准快了一倍或慢了一倍首要怀疑对象时钟源。确认你计算时使用的TIMxCLK是否正确。牢记APB预分频系数不为1时定时器时钟会x2。使用RCC_GetClocksFreq函数可以获取系统各时钟频率进行验证。检查PSC和ARR的计算公式定时时间 (ARR1)*(PSC1)/TIMxCLK。很多人会忘记1。中断响应延迟如果定时器中断服务函数里执行了太耗时的操作会影响定时精度。对于精确定时中断服务函数应尽可能短或者使用DMA。PWM输出没有波形GPIO模式错误必须配置为复用推挽输出GPIO_Mode_AF_PP而不是普通的推挽输出。定时器未启动检查TIM_Cmd是否调用。输出未使能检查TIM_OCInitStruct.TIM_OutputState是否设置为Enable。引脚复用功能未重映射对于有些定时器通道默认引脚可能不是你要用的那个需要开启AFIO时钟并调用GPIO_PinRemapConfig进行重映射例如TIM3_CH2默认是PA7如果你想用PB5就需要重映射。输入捕获值跳动大不准信号质量问题使用示波器观察输入信号看是否有毛刺或振铃。可以适当增加输入捕获滤波器的值TIM_ICInitStruct.TIM_ICFilter。中断处理不及时如果信号频率很高中断处理函数太长可能导致丢失捕获事件。可以考虑使用DMA将捕获值直接传输到内存或者提高中断优先级。未处理计数器溢出对于长脉冲测量必须在更新中断里处理溢出计数否则在溢出边界计算会出错。使用HAL库配置的注意事项HAL库封装程度高但有时会隐藏细节。例如在CubeMX中配置定时器它会自动计算PSC和ARR但你要清楚它背后的计算逻辑。HAL库的中断处理逻辑在回调函数中如HAL_TIM_PeriodElapsedCallback记得在stm32f1xx_it.c中调用HAL_TIM_IRQHandler。动态修改PWM占空比使用__HAL_TIM_SET_COMPARE(htimx, TIM_CHANNEL_x, value)宏它直接操作寄存器速度快。而HAL_TIM_PWM_Start和HAL_TIM_PWM_Stop函数调用则涉及状态检查速度较慢不适合在高速循环中调用。定时器是STM32学习的重中之重它像一把瑞士军刀功能多且强大。我建议的学习路径是从基本的定时中断开始理解时钟和计数原理然后动手做PWM输出理解比较单元再尝试输入捕获掌握边沿检测和计算最后再去啃高级定时器和主从模式这些硬骨头。每学一个功能最好都用示波器或者逻辑分析仪看看实际波形这比看一千遍代码都管用。遇到问题多查参考手册的定时器章节虽然枯燥但里面的时序图和寄存器描述是最权威的答案。
返回列表