从零开始学嵌入式之STM32——29.通用定时器-输入捕获模式测量信号的周期和频率

目录

一、测量信号周期的基本原理

二、定时器输入捕获功能

三、使用定时器的输入捕获功能测量PWM的周期和频率

1.实验目标

2.硬件原理图

3.涉及到的寄存器

4.定时器参数设定

5.代码编写

6.运行效果


一、测量信号周期的基本原理

如何使用定时器的输入捕获模式测量一个信号的周期和频率呢?我们以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);
    }
}

6.运行效果

https://mp.csdn.net/mp_others/manage/video

相关推荐
421!2 小时前
ESP32学习笔记之GPIO
开发语言·笔记·单片机·嵌入式硬件·学习·算法·fpga开发
惶了个恐2 小时前
嵌入式硬件第二弹——51单片机(2)
单片机·嵌入式硬件·51单片机
易水寒陈2 小时前
使用vscode开发stm32
ide·vscode·stm32
BUG_yechiyu2 小时前
RT-Thread Nano版本使用RTC,同时解决复位日期丢失,时间并正确问题
stm32
LCG元2 小时前
STM32实战:基于STM32CubeMX的串口通信与DMA传输优化
stm32·单片机·嵌入式硬件
heanyu2 小时前
STM32学习 1 ----串口通讯--阻塞式收发+支持printf
stm32·嵌入式硬件·学习
莎士比亚的文学花园2 小时前
硬件入门——51单片机
单片机·嵌入式硬件·51单片机
2501_918126913 小时前
学习所有6502写游戏动作的语句
汇编·嵌入式硬件·学习·游戏·个人开发
Suifqwu3 小时前
stm32进阶-FLASH存储优化
stm32·单片机·嵌入式硬件