目录
一、测量信号周期的基本原理
如何使用定时器的输入捕获模式测量一个信号的周期和频率呢?我们以PWM信号为例:
如果要测量一个PWM信号的周期和频率,可以测量它的周期,根据周期计算出频率即可。
测量周期需要测一个时间段:从第一个上升沿开始计时,到下一个上升沿停止计时,这时就得到了信号的周期。
使用通用定时器的输入捕获功能,可以捕获输入通道上信号的上升沿或下降沿,并产生相关的中断和事件。

二、定时器输入捕获功能

STM32系列中的通用定时器的通道如上图所示:每个通用定时器都有有四个输入输出通道(可能个别型号会有差异,以数据手册为准),每个定时器的输入输出通道的引脚其实是复用的。这就意味着,一个引脚在同一时刻只能工作在一种模式。
注意:
1、上图中可以看出:输入捕获的4个通道中,通道1可以接入通道1、2、3的信号进行比较。
2、通道1、2和3、4之间的信号可以在滤波之后进行两两交换,这些特性在使用中需要注意。
下图是芯片手册中通道1的功能框图

根据上图可以归纳出我们使用该通道时的操作:
1.使用通道1的输入比较模式的时候,通过CCMR1->CC1S[3:0]控制位来选择输出模式/输入模式(同时确认输入源)
00 - 输出模式
01:CC1通道被配置为输入,IC1映射在TI1上;
10:CC1通道被配置为输入,IC1映射在TI2上;
11:CC1通道被配置为输入,IC1映射在TRC上。此模式仅工作在内部触发器输入被选中时(由TIMx_SMCR寄存器的TS位选择)。
2.滤波功能选择,在输入信号质量不佳时,可以开启滤波功能。通过CCCMR1->IC1F[3:0]控制,这个控制位有4bit,用于设定滤波器的参数,全0代表不开启滤波功能。具体功能参考芯片手册
3.配置边沿检测器的检测方式,选择检测上升沿还是下降沿。通过CCER->CC1P控制位控制,0-代表检测上升沿,1-代表检测下降沿。
4.如果输入的信号频率过高,可以通过CCMR1->IC1PSC[1:0]控制位来设置分频系数对输入的信号进行分频操作。
5.使能开启捕获,CCER->CC1E。为1时使能捕获。
6.从预分频器出来的信号为IC1PS
A:会产生一个捕获事件,记录计数器的值
B:如果开启了中断,也会产生捕获比较中断
C:立即把计数器寄存器的值存入到捕获寄存器中,在下次捕获事件产生之前,捕获寄存器的值不会发生变化
三、使用定时器的输入捕获功能测量PWM的周期和频率
1.实验目标
使用STM32F103C8T6芯片测量一个频率在1MHz以内的PWM波形的频率和周期信息。
2.硬件原理图

如上图所示,PA1引脚为TIM2_CH2的通道引脚,PB6为TIM4_CH1的通道引脚。
在实验中,通过TIM2_CH2输出一个PWM波形,通过跳线连接到PB6引脚,经过TIM4_CH1通道传入芯片内部处理后经过串口输出到电脑端查看效果。
3.涉及到的寄存器
生成PWM波形的操作已在上节说明,本次不再赘述,下表是统计输入捕获PWM波形的操作涉及到的寄存器
|----|---------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|-------|
| 定时器基础配置相关 ||||
| 序号 | 寄存器/控制位 | 功能 | 值 |
| 1 | 自动重装载预装载允许位 CR1->ARPE | 控制写入ARR的数据是否直接写入影子寄存器 0:直接写入 1:不直接写入,先写入预装载寄存器 | 1 |
| 2 | 自动重装载寄存器 ARR | 控制定时器的计数溢出周期 本次测量输入信号,ARR尽量减少溢出,设置为最大值 | 65535 |
| 3 | 计数器 CNT | 时钟开始后,按照预分频后的时钟频率和设置好的计数方向进行计数 | 0 |
| 4 | 捕获/比较寄存器 CCR1 | 当发生捕获事件后CNT中计数值会被拷贝到CCR1中 | 0 |
| 5 | 计数器计数方向 CR1->DIR | 控制计数器的计数方向 0:向上计数 1:向下计数 | 0 |
| 6 | 计数器使能 CR1->CEN | 控制计数器的开始和停止 0:停止 1:开始 | 1 |
| 通道配置相关 ||||
| 1 | TI1选择 CR2->TI1S | 控制TI1在的选择 0:TIMx_CH1引脚连接到TI1输入 1:TIMx_CH1、TIMx_CH2、TIMx_CH3引脚经过异或后连接到TI1输入 | 0 |
| 2 | 输入/捕获1滤波器 CCMR1->IC1F[3:0] | 对输入的信号进行滤波 具体详见手册,本次不使用滤波功能 | 0000 |
| 3 | 边沿检测方式 CCER->CC1P | 设置边沿检测方式 0:上升沿有效 1:下降沿有效 | 0 |
| 4 | 捕获/比较1选择 CCMR1->CC1S[1:0] | 选择通道的输出模式或输入模式及引脚 00:CC1通道被配置为输出; 01:CC1通道被配置为输入,IC1映射在TI1上; 10:CC1通道被配置为输入,IC1映射在TI2上; 11:CC1通道被配置为输入,IC1映射在TRC上。此模式仅工作在内部触发器输入被选中时(由 TIMx_SMCR寄存器的TS位选择)。 | 01 |
| 5 | 输入/捕获1预分频 CCMR1->IC1PSC[1:0] | 对触发信号进行预分频 00:无预分频器,捕获输入口上检测到的每一个边沿都触发一次捕获; 01:每2个事件触发一次捕获; 10:每4个事件触发一次捕获; 11:每8个事件触发一次捕获。 | 00 |
| 6 | 捕获使能 CCER->CC1E | 控制通道的使能 CC1通道配置为输出: 0: 关闭- OC1禁止输出。 1: 开启- OC1信号输出到对应的输出引脚。 CC1通道配置为输入: 该位决定了计数器的值是否能捕获入TIMx_CCR1寄存器。 0:捕获禁止; 1:捕获使能。 | 1 |
| 7 | 允许捕获/比较1中断 DIER->CC1IE | 控制通道1的中断是否开启 0:不开启中断 1:开启中断 | 1 |
| 8 | 捕获/比较1中断标志 SR->CC1IF | 判断有无发生捕获事件 0:未发生 1:发生捕获事件,计数器的值已被拷贝至CCR1 | |
4.定时器参数设定
为了计算方便,我们设定时钟的基础频率为1MHz,即预分频PSC为72。
为了可以测量较低频率的信号,将自动重装载寄存器的值设置位最大:65535。这样在不对输入的信号进行分频操作的情况下,我们可以测量的信号的频率范围大致为:16Hz~1MHz。
5.代码编写
文件名:tim4.c // 用于检测PWM信号的周期和频率信息
cpp
#include "tim4.h"
// 初始化
void TIM4_Init(void)
{
// 1.开启时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
// 2.设置GPIOB6引脚的工作模式,检测PWM波形,引脚需要工作在浮空输入模式:CNF-01;MODE-00;
GPIOB->CRL |= GPIO_CRL_CNF6_0;
GPIOB->CRL &= ~GPIO_CRL_CNF6_1;
GPIOB->CRL &= ~GPIO_CRL_MODE1;
// 3.设置TIM4定时器的基本参数
TIM4->PSC = 71;
// 3.2.设置预装载寄存器的值。
TIM4->ARR = 65535;
// 3.3.设置定时器的计数方向,0代表向上计数
TIM4->CR1 &= ~TIM_CR1_DIR;
// 4.通道设置
// 4.1.配置定时4器通道1的输入信号源,直接接收TI1S
TIM4->CR2 &= ~TIM_CR2_TI1S;
// 4.2.不使用滤波器
TIM4->CCMR1 &= ~TIM_CCMR1_IC1F;
// 4.3.上升沿检测
TIM4->CCER &= ~TIM_CCER_CC1P;
// 4.4.设置定时器4通道1的通道方向,输入,CCxS-01
TIM4->CCMR1 &= ~TIM_CCMR1_CC1S_1;
TIM4->CCMR1 |= TIM_CCMR1_CC1S_0;
// 4.5.设置输入信号的预分频系数,不分频
TIM4->CCMR1 &= ~TIM_CCMR1_IC1PSC;
// 4.6.使能捕获
TIM4->CCER |= TIM_CCER_CC1E;
// 4.7.开启中断触发
TIM4->DIER |= TIM_DIER_CC1IE;
// 5.中断配置
NVIC_SetPriorityGrouping(3);
NVIC_SetPriority(TIM4_IRQn, 2);
NVIC_EnableIRQ(TIM4_IRQn);
}
// 开启定时器,开始检测
void TIM4_Start(void)
{
TIM4->CR1 |= TIM_CR1_CEN;
}
// 停止定时器,停止检测
void TIM4_Stop(void)
{
TIM4->CR1 &= ~TIM_CR1_CEN;
}
// 返回测得的计数值,单位为us
uint16_t TIM4_PWM_Period(void)
{
return TIM4->CCR1;
}
// 中断服务函数
void TIM4_IRQHandler(void)
{
// 判断中断源
if(TIM4->SR & TIM_SR_CC1IF)
{
// 清除中断标志位
TIM4->SR &= ~TIM_SR_CC1IF;
// 将计数器清零,重新开始计数,等待下一个中断事件发生时,底层硬件电路会将计数器内的值先放入CCR1中
TIM4->CNT = 0;
}
}
文件名:tim4.h // 用于检测PWM信号的周期和频率信息
cpp
#ifndef __TIM4_H
#define __TIM4_H
#include "stm32f10x.h"
// 函数声明
// 初始化
void TIM4_Init(void);
// 开启定时器,开始检测PWM方波
void TIM4_Start(void);
// 停止定时器,停止检测PWM方波
void TIM4_Stop(void);
// 返回测得的计数值,单位为us
uint16_t TIM4_PWM_Period(void);
#endif
文件名:tim2.c // 用于生成PWM波形
cpp
#include "tim2.h"
// 初始化
void TIM2_Init(void)
{
// 1.开启时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
// 2.设置GPIOA1引脚的工作模式,PWM方波输出,引脚需要工作在复用功能推挽输出模式:CNF-10;MODE-11;
GPIOA->CRL &= ~GPIO_CRL_CNF1_0;
GPIOA->CRL |= GPIO_CRL_CNF1_1;
GPIOA->CRL |= GPIO_CRL_MODE1;
// 3.设置TIM2定时器,为了使得LED刷新频率可以达到合适的效果,需要考虑人眼的余晖效应,这里将频率定为100Hz
// 3.1.设置定时器的输入频率,假设ARR的值为99(100次计数),输出100Hz的频率,在输入的时钟频率为72MHz的前提下,PSC的值的计算过程为:
// 输出频率 = 1 / [(1 / (定时器输入时钟频率 / (PSC+1))) * (ARR + 1)]
// 由频率计算公式推到出周期计算公式:
// 输出时钟周期 = (1 / (定时器输入时钟频率 / (PSC+1))) * (ARR + 1)
// 已知:输如频率 = 72MHz ARR+1 = 100; 计算PSC
// 1/100Hz = (1 / (72MHz / (PSC+1))) * 100
// 72MHz / (PSC+1) = 100Hz * 100
// 72MHz / (100Hz * 100) = (PSC+1)
// PSC+1 = 7200
// PSC = 7199
// 不开启预装载功能(默认)
TIM2->CR1 &= ~TIM_CR1_ARPE;
TIM2->PSC = 7199;
// 3.2.设置预装载寄存器的值,为了方便调整PWM的占空比,这个值设定为99,也就是计数100次后溢出。
TIM2->ARR = 99;
// 3.3.设置定时器的计数方向,0代表向上计数
TIM2->CR1 &= ~TIM_CR1_DIR;
// 3.4.设置定时器的通道方向,输出,CCxS-00
TIM2->CCMR1 &= ~TIM_CCMR1_CC2S;
// 3.5.配置定时2器通道2的输出模式:PWM模式1
TIM2->CCMR1 &= ~TIM_CCMR1_OC2M_0;
TIM2->CCMR1 |= TIM_CCMR1_OC2M_1;
TIM2->CCMR1 |= TIM_CCMR1_OC2M_2;
// 3.6.配置一个默认的占空比:99%,因为LED是共阳,因此占空比高代表LED暗。
TIM2->CCR2 = 99;
// 3.7.开启定时器通道输出使能
TIM2->CCER |= TIM_CCER_CC2E;
}
// 开启定时器,开始输出PWM方波
void TIM2_Start(void)
{
TIM2->CR1 |= TIM_CR1_CEN;
}
// 停止定时器,停止输出PWM方波
void TIM2_Stop(void)
{
TIM2->CR1 &= ~TIM_CR1_CEN;
}
// 设置PWM占空比
void SET_DutyCycle(uint8_t dutycycle)
{
TIM2->CCR2 = dutycycle;
}
文件名:tim2.h // 用于生成PWM波形
cpp
#ifndef __TIME2_H
#define __TIME2_H
#include "stm32f10x.h"
// 函数声明
// 初始化
void TIM2_Init(void);
// 开启定时器,开始输出PWM方波
void TIM2_Start(void);
// 停止定时器,停止输出PWM方波
void TIM2_Stop(void);
// 设置PWM占空比
void SET_DutyCycle(uint8_t dutycycle);
#endif
文件名:main.c
cpp
#include "tim2.h"
#include "tim4.h"
#include "usart.h"
#include "delay.h"
int main(void)
{
// 初始化串口
Usart_Init();
// 初始化定时器2
TIM2_Init();
// 初始化定时器4
TIM4_Init();
// 开启定时器2、4
TIM2_Start();
TIM4_Start();
printf("system Start\n");
// 设置PWM的占空比
SET_DutyCycle(50);
//定义变量,存放频率和周期的值
float cycle = 0.0;
float frequency = 0.0;
while(1)
{
// 计算周期,单位是ms
cycle = TIM4_PWM_Period() / 1000.0F;
// 计算频率,单位是Hz
frequency = 1000.0F / cycle;
printf("周期 = %f ms ", cycle);
printf("频率 = %f Hz\n", frequency);
// 延时1秒钟
Delay_nms(1000);
}
}