系列文章目录
STM32单片机系列专栏
C语言术语和结构总结专栏
文章目录
1. IC输入捕获
2. 频率测量
3. 主模式、从模式、触发源选择
4. 输入捕获基本结构
5. PWMI模式
6. 代码示例
6.1 PWM.c
6.2 PWM.h
6.3 IC.c
6.4 IC.h
6.5 完整工程文件
输出比较可以看下面这篇文章:
STM32定时器的OC比较和PWM
1. IC输入捕获
输入捕获(Input Capture,简称IC)是微控制器中定时器功能之一,主要用于精确地捕获外部事件(如信号的边沿)发生的时刻。在STM32中,IC功能通过定时器来实现,可以对外部输入信号的上升沿或下降沿进行时间的捕获,通过读取捕获时定时器的计数器(CNT)的值,从而知道外部事件发生的确切时刻。并且当前CNT的值将被锁存到CCR中。
输入捕获功能有以下功能:
- 测量两个事件之间的时间间隔。
- 测量PWM波形的频率、占空比、脉冲间隔,电平持续时间等。
- 测量外部信号的脉宽。
每个高级定时器和通用定时器都有4个输入捕获通道。可配置为PWMI模式(PWM输入模式),同时测量频率和占空比,也可以配合主从触发模式,实现硬件全自动测量。这两个模式的组合,可以实现全自动检测,不需要中断也不影响主程序,从而减轻CPU的负担。
注意因为输入捕获和输出比较使用的为同一个寄存器,所以输出捕获和输出比较只能使用一个,不能同时使用。
2. 频率测量
测频法(适合高频信号):在闸门时间T内(通常为1s),对上升沿计次,得到N,则频率
fx = N / T 在单位时间内出现的脉冲数量越多,脉冲的频率就越高。
测周法(适合低频信号):两个上升沿内持续的时间(因为周期的倒数就是频率),使用一个已知的标准频率fc的计次时钟来驱动计数器,从第一个上升沿到下一个上升沿,得到N ,则频率
fx = = fc / N 在给定的周期内,脉冲数量保持不变时,周期越长,每个脉冲的宽度就越宽。
中界频率:测频法与测周法误差相等的频率点
fm = √(fc / T) 这个指标是用来描述在脉冲调制中,信号的中心频率位置。
3. 主模式、从模式、触发源选择
主模式可以将定时器内部的信号映射到TRGO引脚,用于触发其他的外设。
从模式可以接收其他外设或自身外设的信号,用于控制自身定时器的运行,也就是被其他的信号控制。
触发源选择是选择从模式的触发信号源,选择一个指定的信号,得到TRGI,通过TRGI去触发从模式。从模式可以在列表中选择一项操作来自动执行。例如:如果想让TI1FP1信号自动触发CNT清零,触发源选择TI1FP1,从模式选择Reset,实现硬件全自动。
主模式:
- 复位 - 主输出TRGO不激活。
- 使能 - 定时器计数器使能时TRGO被激活。
- 更新 - 每当定时器更新事件发生时,比如计数器溢出时,TRGO被激活。
- 比较匹配 - 比如当定时器的比较匹配事件发生时,如CCR1匹配时,TRGO被激活。
- 比较 - OC1REF的比较匹配事件激活TRGO。
- 比较 - OC4REF的比较匹配事件激活TRGO。
从模式:
- 关闭从模式,定时器不受外部信号控制。
- 编码器模式1,使用内部TRGI(定时器内部触发输入)作为时钟源,通常与编码器的输出相连,用于计数编码器的脉冲。
- 编码器模式2,同上,但使用两个内部触发输入,允许同时检测方向和速度。
- 编码器模式3,同上,但使用两个内部触发输入,允许同时检测方向和速度,并且可以检测到位置信息。
- 复位模式,当TRGI发生时,定时器计数器会被复位。
- 门控模式,TRGI信号作为启动和停止计数的使能信号。
- 触发模式,定时器仅在TRGI信号发生时更新其寄存器,并开始计数。
- 外部时钟模式1,使用TRGI作为定时器的时钟源。
4. 输入捕获基本结构
当配置好时基单元后,启动定时器,此时CNT就会在预分频器之后的时钟驱动下,不断自增。预分频器之后的时钟频率。就是驱动CNT的标准频率fc。
下面的GPIO输入方波信号,经过滤波器和边沿检测,选择TI1FP1为上升沿触发,之后输出选择直连的通道,分频器选择不分频。当TI1FP1出现上升沿之后,CNT(最大65535)的当前计数值会进入CCR1中。同时触发源选择。选择TI1FP1为触发信号,从模式选择复位模式,从而清零CNT。
5. PWMI模式
这部分前面和输入捕获一样,但后面使用了两个通道,可以同时测量周期和占空比,TI1FP2为下降沿触发。这样CCR1就是一整个周期的计数值,CCR2是高电平之间的计数值。此时用CCR2除以CCR1 就是占空比。
6. 代码示例
第一步:开始RCC时钟,打开将要使用的TIM外设时钟和GPIO外设时钟。
第二步:GPIO初始化,把GPIO配置为输入模式,一般为上拉输入或浮空输入模式。
第三步:配置时基单元,让CNT计数器在内部时钟的驱动下自增运行,PSC预分频器、CNT计数器和ARR自动重装载,也包括包括时钟源选择。
第四步:配置输入捕获单元,包括滤波器、极性、直连通道/交叉通道、分频器。
第五步:选择从模式的触发源(TI1FP1)
第六步:选择触发后执行的操作,这里执行Reset操作。
第七步:调用TIM_Cmd函数,开启定时器。
下面的代码实现功能为: 通过PWMI得到时钟频率和占空比。
6.1 PWM.c
要使用的库函数文件依然为:stm32f10x_tim.h,拖到最下面,在这里可以找到定时器TIM需要使用到的函数。
#include "stm32f10x.h" //PWM初始化
void PWM_Init(void)
{//开启时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟//GPIO重映射
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,重映射必须先开启AFIO的时钟
// GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE); //将TIM2的引脚部分重映射,具体的映射方案需查看参考手册
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //将JTAG引脚失能,作为普通GPIO引脚使用//GPIO初始化GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //GPIO_Pin_15;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为复用推挽输出 //受外设控制的引脚,均需要配置为复用模式 //配置时钟源TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟//时基单元初始化TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; //预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元//输出比较初始化TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值//则最好执行此函数,给结构体所有成员都赋一个默认值//避免结构体初值不确定的问题TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值TIM_OC1Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1//TIM使能TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}//PWM设置CCR
void PWM_SetCompare1(uint16_t Compare)
{TIM_SetCompare1(TIM2, Compare); //设置CCR1的值
}//PWM设置PSC
void PWM_SetPrescaler(uint16_t Prescaler)
{TIM_PrescalerConfig(TIM2, Prescaler, TIM_PSCReloadMode_Immediate); //设置PSC的值
}
RCC_APB1PeriphClockCmd
- TIM2 代表定时器2,它是STM32的一个基础硬件定时器。在STM32的某些系列中,TIM2连接到的是APB1总线。
- ENABLE 是一个宏定义,用来开启某项功能,这里用来开启TIM2的时钟。如果传递 DISABLE 则会关闭外设的时钟。
- 简单来说,RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); 这行代码的作用是开启连接到APB1总线的定时器2(TIM2)的时钟。只有开启了时钟,程序中关于TIM2的其他功能(如计时、计数、PWM发生等)才能正常工作。
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
- GPIO的初始化中,选择AF_PP复用推挽输出,因为对于普通的开漏推挽输出,引脚的控制权是来自于输出数据寄存器的,如果想用定时器来控制引脚,就需要使用复用开漏/推挽输出模式。
6.2 PWM.h
接着是PWM.h文件,这部分引用声明一下即可
#ifndef __PWM_H
#define __PWM_Hvoid PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);
void PWM_SetPrescaler(uint16_t Prescaler);#endif
6.3 IC.c
在这段代码中,我们设置了一个STM32的定时器TIM3以输入捕获(IC)模式运行。代码的主要目的是初始化TIM3以捕获来自外部信号(从PWM波)的频率和占空比。
#include "stm32f10x.h"//输入捕获初始化
void IC_Init(void)
{//开启时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //开启TIM3的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟//GPIO初始化GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA6引脚初始化为上拉输入//配置时钟源TIM_InternalClockConfig(TIM3); //选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟//时基单元初始化TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; //计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元//PWMI模式初始化TIM_ICInitTypeDef TIM_ICInitStructure; //定义结构体变量TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择配置定时器通道1TIM_ICInitStructure.TIM_ICFilter = 0xF; //输入滤波器参数,可以过滤信号抖动TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //极性,选择为上升沿触发捕获TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //捕获预分频,选择不分频,每次信号都触发捕获TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //输入信号交叉,选择直通,不交叉TIM_PWMIConfig(TIM3, &TIM_ICInitStructure); //将结构体变量交给TIM_PWMIConfig,配置TIM3的输入捕获通道//此函数同时会把另一个通道配置为相反的配置,实现PWMI模式//选择触发源及从模式TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1); //触发源选择TI1FP1TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset); //从模式选择复位//即TI1产生上升沿时,会触发CNT归零//TIM使能TIM_Cmd(TIM3, ENABLE); //使能TIM3,定时器开始运行
}//获取输入捕获的频率
uint32_t IC_GetFreq(void)
{return 1000000 / (TIM_GetCapture1(TIM3) + 1); //测周法得到频率fx = fc / N,这里不执行+1的操作也可以
}//获取输入捕获的占空比
uint32_t IC_GetDuty(void)
{return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1); //占空比Duty = CCR2 / CCR1 * 100,这里不执行+1的操作也可以
}
6.4 IC.h
同样这部分引用声明一下即可
#ifndef __IC_H
#define __IC_Hvoid IC_Init(void);
uint32_t IC_GetFreq(void);
uint32_t IC_GetDuty(void);#endif