打印
[RISC-V MCU 应用开发]

九、CH32V103应用教程——输入捕获

[复制链接]
4567|7
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
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文件
#ifndef __CAPTURE_H
#define __CAPTURE_H

#include "ch32v10x_conf.h"

void Input_Capture_Init( u16 arr, u16 psc );
void TIM1_CC_IRQHandler(void);

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

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

/* ----------------   PWM信号 周期和占空比的计算--------------- */
// arr :自动重装载寄存器的值
// clk_cnt:计数器的时钟,等于 Fck_int / (psc+1) = 72M/(psc+1)
// PWM信号的周期 T = (arr +1)* (1/clk_cnt) = arr*(psc+1) / 72M
// 占空比P=CCR/(arr+1)
/*******************************************************************************
* Function Name  : Input_Capture_Init
* Description    : Initializes TIM1 input capture.
* Input          : arr: the period value.
*                  psc: the prescaler value.
*                  ccp: the pulse value.
* Return         : None
*******************************************************************************/
void Input_Capture_Init( u16 arr, u16 psc )
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_ICInitTypeDef TIM_ICInitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

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

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init( GPIOA, &GPIO_InitStructure);
    GPIO_ResetBits( GPIOA, GPIO_Pin_8 );

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

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

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

    TIM_PWMIConfig( TIM1, &TIM_ICInitStructure );

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

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

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

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

/*******************************************************************************
* Function Name  : TIM1_CC_IRQHandler
* Description    : This function handles TIM1  Capture Compare Interrupt exception.
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void TIM1_CC_IRQHandler(void)
{

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

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

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

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

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

    Input_Capture_Init( 1000-1, 72-1 );

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

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




8、输入捕获.rar

488.51 KB

使用特权

评论回复
评论
RISCVLAR 2021-6-29 09:45 回复TA
@lly1234567 :都可以,同时需要注意对应GPIO引脚的初始化配置 
lly1234567 2021-6-26 12:30 回复TA
请问要是捕获四路pwm该怎么写捕获通道ICx选择呢? 是分别写还是像这样TIM_Channel_1|TIM_Channel_2|TIM_Channel_3 | TIM_Channel_4一起写入呢 

相关帖子

沙发
xdqfc| | 2020-12-29 09:55 | 只看该作者
我没有看到通道2配置为下降沿捕获的代码,我之前用GD32的时候,必须配置通道2捕获方式是下降沿捕获,且捕获的信号选择为TI1的滤波后的信号,楼主的程序真的能够捕获到信号的下降沿的数据吗??

使用特权

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

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

使用特权

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

本版积分规则

132

主题

293

帖子

41

粉丝