[RISC-V MCU 应用开发] 九、CH32V103应用教程——输入捕获

[复制链接]
 楼主| RISCVLAR 发表于 2020-11-7 16:06 | 显示全部楼层 |阅读模式
TI, ic, ni, IO, ST
本帖最后由 RISCVLAR 于 2020-12-29 16:16 编辑

CH32V103应用教程——输入捕获

本章教程通过CH32V103开发板PA8引脚检测PWM脉宽和周期,并通过串口调试助手打印显示。

1、输入捕获简介及相关函数介绍
输入捕获模式是定时器的基本功能之一,其通常用于测量频率或者测量脉宽。

输入捕获可对输入信号的上升沿、下降沿或者双边沿进行捕获,其捕获原理为:当发生并捕获信号跳变沿之后,计数器(CNT)值将被锁存到捕获比较寄存器(CCR)中,将前后两次捕获到的CCR寄存器中的值相减,即可计算出频率或者脉宽。如果捕获脉宽时长超过捕获定时器的周期,会发生溢出,此时需要进行额外处理。

关于定时器输入捕获具体介绍,可参考CH32V103应用手册。输入捕获程序所需函数所在库函数在定时器中断教程中已介绍,本章不做过多介绍。


2、硬件设计
本章教程使用输入捕获中PWM输入模式,需要用到两个开发板,一个用于产生PWM输出,一个用于输入捕获。本章教程使用两个CH32V103开发板,一个直接下载CH32V103 EVT中PWM输出例程,一个直接下载本章教程中例程,然后将两个开发板PA8引脚连接起来。

3、软件设计
本章教程使用PWM输入模式进行输入捕获,其使用两个通道和两个捕获寄存器,具体实现方式为:当使用PWM输入模式时,PWM信号由输入通道TI1进入,配置滤波后的定时器输入1(TI1FP1)为触发信号并设置上升沿捕获。当上升沿的时候捕获比较通道IC1和IC2同时捕获,计数器CNT清零,到了下降沿的时候,IC2捕获,此时计数器CNT的值被锁存到捕获比较寄存器CCR2中,到了下一个上升沿的时候,IC1捕获,计数器CNT 的值被锁存到捕获比较寄存器CCR1中。其中CCR2+1测量的是脉宽, CCR1+1测量的是周期。这里要注意的是 CCR2 和 CCR1 的值在计算占空比和频率的时候都必须加1,因为计数器是从0开始计数的。
使用PWM输入模式实现输入捕获具体程序如下:
capture.h文件
  1. #ifndef __CAPTURE_H
  2. #define __CAPTURE_H

  3. #include "ch32v10x_conf.h"

  4. void Input_Capture_Init( u16 arr, u16 psc );
  5. void TIM1_CC_IRQHandler(void);

  6. #endif
capture.h文件主要是函数的声明。
capture.c文件
  1. #include "capture.h"

  2. void TIM1_CC_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));

  3. /* ----------------   PWM信号 周期和占空比的计算--------------- */
  4. // arr :自动重装载寄存器的值
  5. // clk_cnt:计数器的时钟,等于 Fck_int / (psc+1) = 72M/(psc+1)
  6. // PWM信号的周期 T = (arr +1)* (1/clk_cnt) = arr*(psc+1) / 72M
  7. // 占空比P=CCR/(arr+1)
  8. /*******************************************************************************
  9. * Function Name  : Input_Capture_Init
  10. * Description    : Initializes TIM1 input capture.
  11. * Input          : arr: the period value.
  12. *                  psc: the prescaler value.
  13. *                  ccp: the pulse value.
  14. * Return         : None
  15. *******************************************************************************/
  16. void Input_Capture_Init( u16 arr, u16 psc )
  17. {
  18.     GPIO_InitTypeDef GPIO_InitStructure;
  19.     TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
  20.     TIM_ICInitTypeDef TIM_ICInitStructure;
  21.     NVIC_InitTypeDef NVIC_InitStructure;

  22.     RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1, ENABLE );//使能GPIOA时钟和TIM1时钟

  23.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
  24.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  25.     GPIO_Init( GPIOA, &GPIO_InitStructure);
  26.     GPIO_ResetBits( GPIOA, GPIO_Pin_8 );

  27.     //定时器周期,实际就是设定自动重载寄存器 ARR 的值, ARR 为要装载到实际自动重载寄存器(即影子寄存器) 的值, 可设置范围为 0 至 65535。
  28.     TIM_TimeBaseInitStructure.TIM_Period = arr;
  29.     //定时器预分频器设置,时钟源经该预分频器才是定时器计数时钟CK_CNT,它设定 PSC 寄存器的值。
  30.     //计算公式为: 计数器时钟频率 (fCK_CNT) 等于fCK_PSC / (PSC[15:0] + 1),可实现 1 至 65536 分频。
  31.     TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
  32.     //时钟分频,设置定时器时钟 CK_INT 频率与死区发生器以及数字滤波器采样时钟频率分频比。可以选择 1、 2、 4 分频。
  33.     TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
  34.     TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //设置计数模式,向上计数模式
  35.     TIM_TimeBaseInitStructure.TIM_RepetitionCounter =  0x00;        //设置重复计数器的值,0。重复计数器,只有 8 位,只存在于高级定时器。
  36.     TIM_TimeBaseInit( TIM1, &TIM_TimeBaseInitStructure); //初始化

  37.     // 当用作PWM输入捕获模式时,只需要设置触发信号的那一路即可(用于测量周期)
  38.     // 另外一路(用于测量占空比)会由硬件自带设置,不需要再配置

  39.     // TIM_Channel:捕获通道 ICx 选择,可选 TIM_Channel_1、 TIM_Channel_2、TIM_Channel_3 或 TIM_Channel_4 四个通道。
  40.     TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
  41.     //配置输入捕获预分频器值,如果需要捕获输入信号的每个有效边沿,则设置 1 分频 即可
  42.     TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
  43.     //输入捕获滤波器设置,可选设置 0x0 至 0x0F。它设定 CHCTLRx 寄存器ICxF[3:0]位的值。一般我们不使用滤波器,即设置为 0
  44.     TIM_ICInitStructure.TIM_ICFilter = 0x0;
  45.     //输入捕获边沿触发选择,可选上升沿触发、下降沿触发或边沿跳变触发。
  46.     TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
  47.     //输入通道选择,捕获通道 ICx 的信号可来自三个输入通道,分别为TIM_ICSelection_DirectTI、 TIM_ICSelection_IndirectTI 或 TIM_ICSelection_TRC
  48.     TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;

  49.     TIM_PWMIConfig( TIM1, &TIM_ICInitStructure );

  50.     NVIC_InitStructure.NVIC_IRQChannel = TIM1_CC_IRQn;       //TIM1捕获比较中断
  51.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//设置抢占优先级
  52.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;       //设置响应优先级
  53.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;          //使能通道
  54.     NVIC_Init(&NVIC_InitStructure);

  55.     TIM_ITConfig( TIM1, TIM_IT_CC1 | TIM_IT_CC2, ENABLE ); //使能TIM1捕获中断

  56.     // 选择输入捕获的触发信号
  57.     TIM_SelectInputTrigger(TIM1, TIM_TS_TI1FP1);

  58.     // 选择从模式: 复位模式
  59.     // PWM输入模式时,从模式必须工作在复位模式,当捕获开始时,计数器CNT会被复位
  60.     TIM_SelectSlaveMode(TIM1, TIM_SlaveMode_Reset);
  61.     TIM_SelectMasterSlaveMode(TIM1,TIM_MasterSlaveMode_Enable);
  62.     TIM_Cmd( TIM1, ENABLE );  //定时器使能
  63. }

  64. /*******************************************************************************
  65. * Function Name  : TIM1_CC_IRQHandler
  66. * Description    : This function handles TIM1  Capture Compare Interrupt exception.
  67. * Input          : None
  68. * Output         : None
  69. * Return         : None
  70. *******************************************************************************/
  71. void TIM1_CC_IRQHandler(void)
  72. {

  73.     if( TIM_GetITStatus( TIM1, TIM_IT_CC1 ) != RESET )   //若捕获比较1发生中断
  74.     {
  75.         printf( "cycle:%d\r\n", (TIM_GetCapture1(TIM1)+1) );      //打印得到的捕获比较1寄存器值,其值加1表示周期
  76.         TIM_SetCounter( TIM1, 0 );
  77.     }

  78.     if( TIM_GetITStatus( TIM1, TIM_IT_CC2 ) != RESET )   //若捕获比较2发生中断
  79.     {
  80.         printf( "Pulsewidth:%d\r\n", (TIM_GetCapture2(TIM1)+1) ); //打印得到的捕获比较2寄存器值,其值加1表示脉宽
  81.     }

  82.     TIM_ClearITPendingBit( TIM1, TIM_IT_CC1 | TIM_IT_CC2 ); //清除TIM1捕获比较1和捕获比较2中断挂起位
  83. }

capture.c文件主要是输入捕获相关函数配置,包括输入捕获相关函数初始化配置和定时器中断服务函数,其具体配置流程如下:
1、使能GPIOA时钟和TIM1时钟

  1. RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1, ENABLE );//使能GPIOA时钟和TIM1时钟
2、GPIO初始化配置
  1. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
  2. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  3. GPIO_Init( GPIOA, &GPIO_InitStructure);
  4. GPIO_ResetBits( GPIOA, GPIO_Pin_8 );
3、根据TIM_TimeBaseInitStruct中指定的参数初始化TIM1时基单元外围设备。
  1. //定时器周期,实际就是设定自动重载寄存器 ARR 的值, ARR 为要装载到实际自动重载寄存器(即影子寄存器) 的值, 可设置范围为 0 至 65535。
  2.     TIM_TimeBaseInitStructure.TIM_Period = arr;
  3.     //定时器预分频器设置,时钟源经该预分频器才是定时器计数时钟CK_CNT,它设定 PSC 寄存器的值。
  4.     //计算公式为: 计数器时钟频率 (fCK_CNT) 等于fCK_PSC / (PSC[15:0] + 1),可实现 1 至 65536 分频。
  5.     TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
  6.     //时钟分频,设置定时器时钟 CK_INT 频率与死区发生器以及数字滤波器采样时钟频率分频比。可以选择 1、 2、 4 分频。
  7.     TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
  8.     TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //设置计数模式,向上计数模式
  9.     TIM_TimeBaseInitStructure.TIM_RepetitionCounter =  0x00;        //设置重复计数器的值,0。重复计数器,只有 8 位,只存在于高级定时器。
  10.     TIM_TimeBaseInit( TIM1, &TIM_TimeBaseInitStructure); //初始化
4、输入捕获结构体初始化,配置IC1捕获通道
  1. // TIM_Channel:捕获通道 ICx 选择,可选 TIM_Channel_1、 TIM_Channel_2、TIM_Channel_3 或 TIM_Channel_4 四个通道。
  2.     TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
  3.     //配置输入捕获预分频器值,如果需要捕获输入信号的每个有效边沿,则设置 1 分频 即可
  4.     TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
  5.     //输入捕获滤波器设置,可选设置 0x0 至 0x0F。它设定 CHCTLRx 寄存器ICxF[3:0]位的值。一般我们不使用滤波器,即设置为 0
  6.     TIM_ICInitStructure.TIM_ICFilter = 0x0;
  7.     //输入捕获边沿触发选择,可选上升沿触发、下降沿触发或边沿跳变触发。
  8.     TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
  9.     //输入通道选择,捕获通道 ICx 的信号可来自三个输入通道,分别为TIM_ICSelection_DirectTI、 TIM_ICSelection_IndirectTI 或 TIM_ICSelection_TRC
  10.     TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
5、初始化PWM输入模式
  1. TIM_PWMIConfig( TIM1, &TIM_ICInitStructure );
6、配置中断优先级
  1. NVIC_InitStructure.NVIC_IRQChannel = TIM1_CC_IRQn;       //TIM1捕获比较中断
  2. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //设置抢占优先级
  3. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;            //设置响应优先级
  4. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;            //使能通道
  5. NVIC_Init(&NVIC_InitStructure);  //NVIC初始化
7、使能捕获中断
  1. TIM_ITConfig( TIM1, TIM_IT_CC1 | TIM_IT_CC2, ENABLE ); //使能TIM1捕获中断
8、选择输入捕获触发信号以及主从模式
  1. TIM_SelectInputTrigger( TIM1, TIM_TS_TI1FP1 );                            //选择过滤定时器输入1作为触发源
  2. TIM_SelectSlaveMode( TIM1, TIM_SlaveMode_Reset );                     //TIM1从机模式,所选触发信号(TRGI)的上升沿重新初始化。
  3. TIM_SelectMasterSlaveMode( TIM1, TIM_MasterSlaveMode_Enable );//设置或重置TIM1主从模式,使能定时器与从机之间同步
9、使能定时器
  1. TIM_Cmd( TIM1, ENABLE );
10、编写中断服务函数
  1. void TIM1_CC_IRQHandler(void)
  2. {
  3.     if( TIM_GetITStatus( TIM1, TIM_IT_CC1 ) != RESET )   //若捕获比较1发生中断
  4.     {
  5.         printf( "cycle:%d\r\n", (TIM_GetCapture1(TIM1)+1) ); //打印得到的捕获比较1寄存器值,其值加1表示周期
  6.     }
  7.     if( TIM_GetITStatus( TIM1, TIM_IT_CC2 ) != RESET )   //若捕获比较2发生中断
  8.     {
  9.         printf( "Pulsewidth:%d\r\n", (TIM_GetCapture2(TIM1)+1) ); //打印得到的捕获比较2寄存器值,其值加1表示脉宽
  10.     }
  11.     TIM_ClearITPendingBit( TIM1, TIM_IT_CC1 | TIM_IT_CC2 ); //清除TIM1捕获比较1和捕获比较2中断挂起位
  12. }
capture.c文件主要是进行输入捕获的相关配置以及中断服务函数功能的设置,通过以上配置,即可进行输入捕获并通过中断服务函数打印输出脉宽和周期。
main.c文件
  1. int main(void)
  2. {
  3.     USART_Printf_Init(115200);
  4.     printf("SystemClk:%d\r\n",SystemCoreClock);

  5.     Input_Capture_Init( 1000-1, 72-1 );

  6.     while(1);
  7. }
main.c文件主要是相关函数的初始化。

4、下载验证
将编译好的程序下载到开发板并复位,将两个开发板PA8引脚连接起来,打开串口调试助手,可看见在不停打印PWM脉宽和周期,具体如下:
图片1.png



8、输入捕获.rar

488.51 KB, 下载次数: 108

评论

[url=home.php?mod=space&uid=3248304]@lly1234567[/url] :都可以,同时需要注意对应GPIO引脚的初始化配置  发表于 2021-6-29 09:45
请问要是捕获四路pwm该怎么写捕获通道ICx选择呢? 是分别写还是像这样TIM_Channel_1|TIM_Channel_2|TIM_Channel_3 | TIM_Channel_4一起写入呢  发表于 2021-6-26 12:30
xdqfc 发表于 2020-12-29 09:55 | 显示全部楼层
我没有看到通道2配置为下降沿捕获的代码,我之前用GD32的时候,必须配置通道2捕获方式是下降沿捕获,且捕获的信号选择为TI1的滤波后的信号,楼主的程序真的能够捕获到信号的下降沿的数据吗??

评论

[url=home.php?mod=space&uid=3141872]@RISCVLAR[/url] :感谢回复,谢谢。  发表于 2020-12-29 16:11
你好,感谢你的意见,首先声明一下,因为本次例程为PWM输入捕获,只需要设置触发那一路即可,另外一路会由硬件自带设置,不需要进行配置;但当我们用作普通输入捕获时,是需要对捕获通道2进行配置的,或许我在文中或程序中讲解或注释不清,给你带来这种疑问,我会注意修改的。  发表于 2020-12-29 13:02
xdqfc 发表于 2020-12-29 10:11 | 显示全部楼层
还有下面这句,解释为时钟分频因子,跟PSC是不是有混淆之嫌TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分频因子

假如估计没有错的话,这个参数应该的采样外部信号抗干扰用的,并不是计数器的输入信号,跟PSC不是一回事,譬如捕获外部信号,肯定要设定一下抗干扰,还有,这个参数还是设定PWM死区时间时候用的。

评论

感谢指出,后面我会严谨对待注释的,正如你所说,TIM_ClockDivision用于定时器时钟源是“外部触发输入”时的情况,它是“外部触发输入时钟”的分频系数。在定时器时钟源内部时钟时可认为是没有意义的,再次感谢,以后会注意这些的。  发表于 2020-12-29 10:38
您需要登录后才可以回帖 登录 | 注册

本版积分规则

133

主题

296

帖子

45

粉丝
快速回复 在线客服 返回列表 返回顶部