STM32的定时器

定时器的介绍

介绍: STM32F103C8T6微控制器内部集成了多种类型的定时器,这些定时器在嵌入式系统中扮演着重要角色,用于计时、延时、事件触发以及PWM****波形生成、脉冲捕获等应用。

*几种定时器(STM32F103系列):

|--------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------|---------------------|
| 高级定时器(1) | 通用定时器(3) | 看门口(2) | 滴答定时器(1) |
| TIM1: * 1个16位带死区控制和紧急刹车,用于电机 控制的PWM高级控制定时器。 * 不仅具备基本的定时中断功能,还拥有内外时钟源选择、输入捕获、输出比较、编码器接口以及主从触发模式等多种功能。 | TIM2、TIM3和TIM4: * 3个16位定时器,每个定时器有多达4个用于输入捕获/输出比较/PWM或脉冲计数的通道和增量编码器输入。 * 这些是通用定时器,同样具有定时功能,但在功能上与高级定时器有所区别。 * 通用定时器通常用于实现一些基本的定时任务,如LED闪烁、脉冲宽度测量等。 | 2 个看门狗定时器 ( 独立的和窗口型的 ) | 系统时间定时器: 24 位自减型计数器 |
[7个定时器]

定时器的工作原理

定时器的核心:计数器
每个定时器都由一个16****位计数器预分频器自动重装寄存器时基单元 组成。预分频器可以对时钟进行分频,计数器则对预分频后的时钟进行计数。当计数器的值达到设定值时,会触发中断,从而执行相应的定时任务。

注意事项:设置预分频器系数(PSC)和自动重装值(ARR)

  • 代码PSC写 0,实际:分频系数是1(不分频);PSC写1,分频系数是2(2分频);
  • 代码ARR写65536-1,实际是2^16 = 65536。

三种定时器的介绍

|-------------------|-------------------------|----------|-------------------------------------------------------------------------------------------------------------------|
| 类型 | 编号 | 总线 | 功能 |
| 高级定时器(本章先不介绍) | TIM1、TIM8 | APB2 | 拥有通用定时器全部功能 ,并额外具有重复计数器、死区生 **成、互补输出、刹车输入(三项无刷电机的特供:foc)**等功能;-------三种计数模式。 |
| 通用定时器 | TIM2、TIM3、TIM4、TIM5 | APB1 | 拥有基本定时器全部功能,并额外具有 内外时钟源选择 输入捕获、输出比较 PWM 或 脉冲计数的通道编码器接口、主从触发模式等功能;------三种计数模式。 |
| 基本定时器(这个芯没有) | TIM6、TIM7 | APB1 | (时基)拥有定时中断、主模式触发 DAC(绕过CPU) 的功能。------只支持向上计数。 |

  • 定时器都是72MHz

定时器的框图

(参考手册中)

  • 基本定时器
  • 通用定时器
  • 简图:
  • 高级定时器:
  • 通用定时器时钟的来源

简图:

框图:

定时器的计数模式

|----------|-----------------------|-------------------|
| 计数模式 | 计数器溢出值 | 计数器重装值 |
| 向上计数 | CNT = ARR | CNT = 0 |
| 向下计数 | CNT = 0 | CNT = ARR |
| 中心对齐计数 | CNT = ARR - 1 CNT = 1 | CNT = ARR CNT = 0 |

定时器溢出时间

说明:

Tout: 定时器溢出时间;**Ft:**定时器的时钟源频率;

ARR **:**自动重装载寄存器的值;PSC **:**预分频器寄存器的值。

**举例:**要定时500ms,PSC、ARR、Ft的值是多少?

答:PSC = 7199,ARR = 4999,Ft = 72M。

(当频率大时,记一个数的时间短,ARR的值会变大;当频率小时,记一个数的时间短,ARR的值小)。

定时器寄存器


时基单元寄存器

  • 预分频寄存器( TIMx_PSC)-16位
  • 自动重装载寄存器( TIMx_ARR)-16位
  • DMA/中断使能寄存器 (TIMx_DIER)

第 0 位, 该位是更新中断允许位, 当定时器的更新中断,该位要设置为 1,来允许由于更新事件所产生的中断。

  • 状态寄存器( TIMx_SR)

该寄存器用来标记当前与定时器相关的各种事件/中断是否发生。


输入捕获/输出比较寄存器

  • 捕获/比较寄存器( TIMx_CCR1~4)

该寄存器用来存储捕获发生时, TIMx_CNT的值 ,我们从 TIMx_CCR1 就可以读出通道 1 捕获发生时刻的 TIMx_CNT 值,通过两次捕获(一次上升沿捕获,一次下降沿捕获)的差值,就可以计算出高电平脉冲的宽度

捕获/比较模式寄存器( TIMx_CCMRx)

  • TIMx_CCMR1(模式寄存器1):控制通道1和通道2

TIMx_CCMR1 是针对 2 个通道的配置,低八位7:0用于捕获 / 比较通道 1 的控制,而高八位15:8则用于捕获 / 比较通道 2的控制。

  • TIMx_CCMR2(模式寄存器2):控制通道3和通道4

四个通道的模式寄存器一样,重点介绍 TIMx_CMMR1 的7:0位:

输入捕获

输出比较

捕获/比较使能寄存器( TIMx_CCER)

  • TIMx_CCER(捕获/比较使能寄存器)

定时器中断实验配置步骤

定时器中断的函数

中断服务公共函数

更新中断的回调函数

其他中断回调函数

设置PSC值的函数:

设置/读取 ARR值的函数 :

小实验:定时器中断点灯

**实验目的:**使用定时器TIM2进行中断点灯,500msLED灯翻转一次。

**硬件清单:**上官二号、ST-Link。

  • timer.c文件代码
cs 复制代码
#include "timer.h"
#include "led.h"

TIM_HandleTypeDef time_hander = {0};     //定义一个全局结构体变量,结构体成员附一个默认值0。
                                         //时基单元初始化函数和中断公共回调函数中使用。
//定时器的时基单元初始化函数
void timer_base_init(uint16_t psc ,uint16_t arr){
    
    time_hander.Instance = TIM2;
    time_hander.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;  //设置的是影子寄存器,是否自动重装载。
    time_hander.Init.CounterMode = TIM_COUNTERMODE_UP;
    time_hander.Init.Prescaler = psc;
    time_hander.Init.Period = arr;
    HAL_TIM_Base_Init(&time_hander);
    
    HAL_TIM_Base_Start_IT(&time_hander);  //打开时钟和使能中断
    
}
//msp函数:初始化MCU相关的硬件,例如:GPIO,NVIC,CLOCK
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim){  //这个函数在HAL_TIM_Base_Init()中调用
    if(htim->Instance == TIM2){                      //先判断这个是不是TIM2定时器占用。
        __HAL_RCC_TIM2_CLK_ENABLE();
        HAL_NVIC_SetPriority(TIM2_IRQn,2,2);
        HAL_NVIC_EnableIRQ(TIM2_IRQn);
    } 
}
//中断服务函数
void TIM2_IRQHandler(void){
    //中断公共处理的函数
    HAL_TIM_IRQHandler(&time_hander);
}
//更新中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){  //据饼
    if(htim->Instance == TIM2){                     //判断这个回调函数是否被其他定时器占用。
       led1_toggle();
    } 
}
  • time.h文件代码:
cs 复制代码
#ifndef __TIMER_H__
#define __TIMER_H__

#include "stm32f1xx.h"

void timer_base_init(uint16_t psc ,uint16_t arr);

#endif
  • main.c文件代码
cs 复制代码
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "timer.h"

int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟, 72Mhz */
    led_init();                         /* LED初始化 */
    timer_base_init(7200-1,5000-1);     //500ms产生一次中断
 
    while(1)
    {    
    }
}

输出比较

简介

输出比较可以通过比较定时计数器的值 CNT 与设定的比较值 CCR,可以控制输出引脚的电平状态(置高或置低),从而实现生成一定频率和占空比的PWM****波形

简图:

框图(参考手册) :

PWM模式:

PWM介绍

定义: PWM波形(Pulse Width Modulation,脉冲宽度调制波形)是一种占空比可变的脉冲波形。 这种调制方式通过**改变脉冲的宽度来控制电路中的信号强度和频率。**具体来说,PWM波形中的高电平持续时间和低电平持续时间可以根据需要进行调整,从而实现对模拟信号电平的数字编码。

应用: PWM波形在各种领域都有广泛的应用,包括电源管理、电机控制、**LED 亮度调节等。此外,生成PWM波形的方法有多种,例如使用波形发生器、单片机或可编程逻辑器件**等。

相关参数:

**频率:**1/Ts(定时器计数溢出的时间);

**占空比:**Ton / Ts(高电平占整个周期的比例,单位%);

**分辨率:**占空比变化步距。例:占空比50%,51%;分辨率:1%。分辨率越低对硬件的要求越高。

PWM相关的函数

宏函数: 修改CRR寄存器的值

输出比较的引脚

  • TIM1_CHx(x:1、2、3、4)
  • TIM2_CHx(x:1、2、3、4)
  • TIM3_CHx(x:1、2、3、4)
  • TIM4_CHx(x:1、2、3、4)

定时器输出PWM波配置步骤

根据上面简图配置:

小实验:呼吸灯实验

实验目的: 使用 定时器4 通道3(看引脚定义表:PB8引脚) 生成 PWM 波控制 LED1,实现呼吸灯效果。

  • **频率:**2kHz。根据定时器溢出时间计算:PSC=71,ARR=499。

**硬件清单:**开发板、ST-Link。

  • pwm.c文件代码
cs 复制代码
#include "pwm.h"

TIM_HandleTypeDef pwm_handle = {0};
void pwm_init(uint16_t psc,uint16_t arr){
     
    pwm_handle.Instance = TIM4;
    pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
    pwm_handle.Init.Period = arr;
    pwm_handle.Init.Prescaler = psc;
    pwm_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    HAL_TIM_PWM_Init(&pwm_handle);
    
    //pwm模式和crr寄存器设置
    TIM_OC_InitTypeDef tim_oc_initstruct = {0};
    tim_oc_initstruct.OCMode = TIM_OCMODE_PWM1;          //pwm的模式
    tim_oc_initstruct.OCPolarity = TIM_OCPOLARITY_LOW;   //高电平有效还是低电平;led灯是低电平点亮,故低电平有效
    tim_oc_initstruct.Pulse = arr/2;                     //占空比CCR的值,这里随便填,后面crr修改函数
    HAL_TIM_PWM_ConfigChannel(&pwm_handle,&tim_oc_initstruct,TIM_CHANNEL_3);
    //使能输出,启动计时器
    HAL_TIM_PWM_Start(&pwm_handle,TIM_CHANNEL_3);
}
//初始化msp函数
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim){
    if(htim ->Instance == TIM4){                          //判断这个函数是否被定时器4占用
      
        __HAL_RCC_TIM4_CLK_ENABLE();                      //打开定时器4的时钟
      __HAL_RCC_GPIOB_CLK_ENABLE();                       //打开GPIO口的时钟
      
      GPIO_InitTypeDef gpio_initstruct;
        gpio_initstruct.Mode = GPIO_MODE_AF_PP;            //复用推挽输出,看GPIO口定义表
        gpio_initstruct.Pin = GPIO_PIN_8;
        gpio_initstruct.Pull = GPIO_PULLUP;
        gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;
      HAL_GPIO_Init(GPIOB,&gpio_initstruct);
    }
}
//修改crr的值的函数
void pwm_cmpare_set(uint16_t value){
    __HAL_TIM_SET_COMPARE(&pwm_handle,TIM_CHANNEL_3,value);
}
  • pwm.h文件代码
cs 复制代码
#ifndef __PWM_H__
#define __PWM_H__
#include "stm32f1xx.h"

void pwm_init(uint16_t psc,uint16_t arr);
void pwm_cmpare_set(uint16_t value);

#endif

main.c文件代码

cs 复制代码
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "pwm.h"

int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟, 72Mhz */
    led_init();                         /* LED初始化 */
    pwm_init(72-1,500-1);
    
    //在while函数中不停的修改CRR的值,来实现占空比不断改变
    while(1)
    { 
        for(uint16_t i = 0;i<300;i++){
            pwm_cmpare_set(i);
            delay_ms(5);
        }
        for(uint16_t i = 0;i<300;i++){
            pwm_cmpare_set(300-i);
            delay_ms(5);
        }
    }
}

写这个代码遇到的问题:

  • 调用函数时要注意:DeInit函数和init函数。
  • 根据引脚定义表,输出比较口对应哪一个GPIO口。
  • 关于外设GPIO口的配置:(参考手册110)

输入捕获

简介

输入捕获模式可以用来 测量脉冲宽度 或者 测量频率。STM32 的定时器,除了 TIM6 和 TIM7,其他定时器都有输入捕获功能。 STM32 的输入捕获,简单的说就是通过检测 TIMx_CHx 上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值(TIMx_CNT)存放到对应的通道的捕获/比较寄存器(TIMx_CCRx)里面,完成一次捕获。同时还可以配置捕获时是否触发中断/DMA 等。

框图:(参考手册253)

输入捕获引脚

  • 和上面输出比较的引脚相同,参考上面输出比较引脚

输入捕获相关函数

在输入捕获的时基单元里的定时器的使能更新中断的函数:

捕获回调函数:

读取输入捕获中CCR的值:

清除/设置 捕获的通道极性:

关闭定时器:

设置计数器的值 :

获取计数器的值:

定时器输入捕获实验步骤配置

小实验:测量按键按下的时长(测量脉冲宽度)

实验目的: 使用定时器 2****通道 2 (PA1) 来捕获 按键 2 (PA1)按下时间,并通过串口打印

  • 计一个数的时间:1us,PSC=71,ARR=65535
  • 下降沿捕获、输入通道 2****映射在 TI2 、不分频、不滤波。

**硬件清单:**开发板、ST-Link、USB转TTL

思路:

输入捕获回调函数中的流程图:

拓展:串口打印功能

  • 利用串口调试助手:
  • 引脚接线:

|-------------|--------------------|
| USB转TTL | 开发板(型号不同,引脚不同) |
| TXD | RX1(PA10) |
| RXD | TX1(PA9) |
| GND | GND |

  • 相关代码和配置:

注意事项:

  • 代码中的波特率要和串口助手中的波特率设置的相同,例如:9600 或 115200;
  • 上述代码中设置错了!!!

实验1:捕获一次下降沿

  • ic.c代码文件
cs 复制代码
#include "ic.h"
#include "stdio.h"

TIM_HandleTypeDef ic_handle = {0};
//初始化时基单元
void ic_init(uint16_t psc,uint16_t arr){
    ic_handle.Instance = TIM2;
    ic_handle.Init.Period = arr;
    ic_handle.Init.Prescaler = psc;
    ic_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
    HAL_TIM_IC_Init(&ic_handle);
     //输入通道的配置
    TIM_IC_InitTypeDef ic_initstruct = {0};
    ic_initstruct.ICFilter = 0;                        //滤波
    ic_initstruct.ICPolarity = TIM_ICPOLARITY_FALLING;   //输入极性的判断
    ic_initstruct.ICPrescaler = TIM_ICPSC_DIV1;          //分频系数:这里不分频
    ic_initstruct.ICSelection = TIM_ICSELECTION_DIRECTTI;  //输入通道选择,还有TRC输入口
    HAL_TIM_IC_ConfigChannel(&ic_handle,&ic_initstruct,TIM_CHANNEL_2);
    
    //打开计数器,是能更新中断,输入捕获中断
    __HAL_TIM_ENABLE_IT(&ic_handle,TIM_IT_UPDATE);
    HAL_TIM_IC_Start_IT(&ic_handle,TIM_CHANNEL_2);
}

//初始化msp函数
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim){
    if(htim->Instance == TIM2){
    //打开时钟
    __HAL_RCC_TIM2_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    //初始化GPIO口
    
    GPIO_InitTypeDef gpio_initstruct;
    gpio_initstruct.Mode = GPIO_MODE_INPUT;
    gpio_initstruct.Pin = GPIO_PIN_1;
    gpio_initstruct.Pull = GPIO_PULLUP;
    gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA,&gpio_initstruct);
    //初始化NVIC
    HAL_NVIC_SetPriority(TIM2_IRQn,2,2);
    HAL_NVIC_EnableIRQ(TIM2_IRQn);
    }
}

//中断服务函数
void TIM2_IRQHandler(void){
    HAL_TIM_IRQHandler(&ic_handle);
}
//捕获中断的回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){ 
    printf("下降沿触发\n");
}
  • ic.h文件代码
cs 复制代码
#ifndef __IC_H__
#define __IC_H__
#include "stm32f1xx.h"

void ic_init(uint16_t psc,uint16_t arr);

#endif
  • main.c文件代码
cs 复制代码
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "uart1.h"
#include "ic.h"


int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟, 72Mhz */
    led_init();                         /* LED初始化 */
    uart1_init(115200);                  //初始化串口。设置波特率115200
    printf("hello,world!!!\n");          //打印到串口助手
    ic_init(72-1,65536-1);                //设置中断溢出时长
    
    while(1)
    { 
        led1_on();
        led2_off();
        delay_ms(500);
        led1_off();
        led2_on();
        delay_ms(500);
    }
}

实验2:捕获一次完整的按键,并打印出按键按下的时长

  • ic.h文件代码
  • 根据上面流程图,写捕获中断的回调函数
cs 复制代码
#include "ic.h"
#include "stdio.h"
#include "string.h"

struct {
    uint8_t falling_flag;
    uint8_t success_flag;
    uint16_t timeout_cnt;
}capture_status = {0};

uint16_t time_cnt = 0;

TIM_HandleTypeDef ic_handle = {0};
//初始化时基单元
void ic_init(uint16_t psc,uint16_t arr){
    ic_handle.Instance = TIM2;
    ic_handle.Init.Period = arr;
    ic_handle.Init.Prescaler = psc;
    ic_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
    HAL_TIM_IC_Init(&ic_handle);
     //输入通道的配置
    TIM_IC_InitTypeDef ic_initstruct = {0};
    ic_initstruct.ICFilter = 0;                        //滤波
    ic_initstruct.ICPolarity = TIM_ICPOLARITY_FALLING;   //输入极性的判断
    ic_initstruct.ICPrescaler = TIM_ICPSC_DIV1;          //分频系数:这里不分频
    ic_initstruct.ICSelection = TIM_ICSELECTION_DIRECTTI;  //输入通道选择,还有TRC输入口
    HAL_TIM_IC_ConfigChannel(&ic_handle,&ic_initstruct,TIM_CHANNEL_2);
    
    //打开计数器,是能更新中断,输入捕获中断
    __HAL_TIM_ENABLE_IT(&ic_handle,TIM_IT_UPDATE);   //使能更新中断
    HAL_TIM_IC_Start_IT(&ic_handle,TIM_CHANNEL_2);   //打开计时器,并使能捕获中断
}

//初始化msp函数
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim){
    if(htim->Instance == TIM2){
    //打开时钟
    __HAL_RCC_TIM2_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    //初始化GPIO口
    
    GPIO_InitTypeDef gpio_initstruct;
    gpio_initstruct.Mode = GPIO_MODE_INPUT;
    gpio_initstruct.Pin = GPIO_PIN_1;
    gpio_initstruct.Pull = GPIO_PULLUP;
    gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA,&gpio_initstruct);
    //初始化NVIC
    HAL_NVIC_SetPriority(TIM2_IRQn,2,2);
    HAL_NVIC_EnableIRQ(TIM2_IRQn);
    }
}

//中断服务函数
void TIM2_IRQHandler(void){
    HAL_TIM_IRQHandler(&ic_handle);
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){
//    printf("下降沿触发\n");
    if(capture_status.success_flag == 0){
        if(capture_status.falling_flag == 1){
            printf("上升沿捕获\n");
            capture_status.success_flag =1;
            time_cnt = HAL_TIM_ReadCapturedValue(&ic_handle,TIM_CHANNEL_2);//获取计数器的值
            TIM_RESET_CAPTUREPOLARITY(&ic_handle,TIM_CHANNEL_2);        //清除捕获通道
            TIM_SET_CAPTUREPOLARITY(&ic_handle,TIM_CHANNEL_2,TIM_ICPOLARITY_FALLING);  
                                                                     //设置为下降沿捕获
             
        }else{
            printf("下降沿捕获\n");
            capture_status.falling_flag = 1;
            capture_status.timeout_cnt = 0;
            __HAL_TIM_DISABLE(&ic_handle);
            __HAL_TIM_SetCounter(&ic_handle,0);                        //计数器的值清零
            TIM_RESET_CAPTUREPOLARITY(&ic_handle,TIM_CHANNEL_2);       //清除通道设置
            TIM_SET_CAPTUREPOLARITY(&ic_handle,TIM_CHANNEL_2,TIM_ICPOLARITY_RISING);  
                                                                    //设置为上升沿捕获
             __HAL_TIM_ENABLE(&ic_handle);
        }
    }
}
//定时器定时中断的回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
    if(htim ->Instance == TIM2){
        //判断是否完成按键
        if(capture_status.falling_flag == 1)        //判断是否捕获到下降沿
            capture_status.timeout_cnt ++;   
    }
}

//定义一个函数,打印按键按下的时间
void pressed_time_get(void){
    if(capture_status.success_flag == 1){
        printf("按下时间:%lf s \n",((double)capture_status.timeout_cnt*65536+time_cnt)/1000000);
        memset(&capture_status,0,sizeof(capture_status));
    }
    
}
  • ic.h文件代码
cs 复制代码
#ifndef __IC_H__
#define __IC_H__
#include "stm32f1xx.h"

void ic_init(uint16_t psc,uint16_t arr);
void pressed_time_get(void);
#endif
  • main.c文件代码
cs 复制代码
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "uart1.h"
#include "ic.h"


int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟, 72Mhz */
    led_init();                         /* LED初始化 */
    uart1_init(115200);
    printf("hello,world!!!\n");
    ic_init(72-1,65536-1);
      
    while(1)
    { 
      pressed_time_get();
    }
}

要解决的问题:

  • 结构体中声明多个标志位。
  • 用到的函数。
  • 为什么要写到while函数中。

脉冲计数

简介

脉冲计数相关的函数

初始化函数

打开捕获定时器

定时器从模式的配置:通道、滤波、边沿捕获、映射

获取CNT的值

脉冲计数实验步骤配置

根据上面的框图:

小实验: 脉冲计数实验 (测量频率)

实验目的:定时器 2****通道 2 输入(PA1)低电平 脉冲(按下按键2 PA1)作为定时器 2 的时钟,并通过串口打印脉冲数。

  • PSC=1-1,ARR=65536-1
  • 外部时钟模式1、触发选择:下降沿触发、不分频、不滤波

**实验清单:**开发板、ST-Link、USB转TTL

实验思路:

  1. 初始化时基单元;
  2. 配置输入通道,从模式:时钟模式1;
  3. msp函数初始化GPIO口、CLOCK、NVIC;
  4. 打开计数器;
  5. 获取计数器的值并进行打印。
  • counter.c文件代码
cs 复制代码
#include "counter.h"
#include "stdio.h"

TIM_HandleTypeDef counter_handle = {0};

void counter_init(uint16_t psc,uint16_t arr){
    //初始化时基单元
    counter_handle.Instance = TIM2;
    counter_handle.Init.Prescaler = psc;
    counter_handle.Init.Period = arr;
    counter_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
    counter_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    
    HAL_TIM_IC_Init(&counter_handle);
    
    //配置输入通道(从模式)
    TIM_SlaveConfigTypeDef counter_initstruct;
    counter_initstruct.TriggerFilter = 0;                               //滤波器
    counter_initstruct.TriggerPolarity = TIM_TRIGGERPOLARITY_FALLING;   //极性的选择:上升沿或下降沿
    counter_initstruct.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;             //从模式的选择
    counter_initstruct.InputTrigger = TIM_TS_TI2FP2;                    //输入的通道。
    counter_initstruct.TriggerPrescaler =  TIM_TRIGGERPRESCALER_DIV1;   //分频:这里用不到
    HAL_TIM_SlaveConfigSynchro(&counter_handle,&counter_initstruct);
    
//使能中断和打开时钟
    HAL_TIM_IC_Start(&counter_handle,TIM_CHANNEL_2);
}

//msp配置mcu外设
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim){   //这是个公用的函数,用的时候要进行判断是否被占用
    if(htim->Instance == TIM2){
        //打开时钟
        __HAL_RCC_TIM2_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();
        //初始化GPIO口
        GPIO_InitTypeDef gpio_initstruct;
        gpio_initstruct.Pin = GPIO_PIN_1;
        gpio_initstruct.Mode = GPIO_MODE_INPUT;
        gpio_initstruct.Pull = GPIO_NOPULL;
        gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOA,&gpio_initstruct);
    }
}

//获取计数器的值
uint16_t Ncounter_num = 0;
uint16_t Ocounter_num = 0;
void counter_get(void){
    Ncounter_num = __HAL_TIM_GetCounter(&counter_handle);
//避免在while循环中一直打印计数器的值,设置打印的条件:计数器的值是否发生变化。
    if(Ncounter_num != Ocounter_num){
      printf("计数器的值:%d\n",Ncounter_num);
      Ocounter_num = Ncounter_num;
    }
}
  • counter.h文件代码
cs 复制代码
#ifndef __COUNTER_H__
#define __COUNTER_H__

#include "stm32f1xx.h"
void counter_init(uint16_t psc,uint16_t arr);
void counter_get(void);
#endif
  • main.c文件代码
cs 复制代码
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "uart1.h"
#include "counter.h"

int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟, 72Mhz */
    led_init();                         /* LED初始化 */
    uart1_init(115200);
    printf("hello,world!!!\n");
    counter_init(1-1,65536-1);
    
    while(1)
    { 
        counter_get();
    }
}
  • 结果:

遇到的问题和注意事项:

  • main.c文件中的波特率要和串口助手的波特率相同。
  • 在counter.c文件中声明函数获取计数器的值时,要进行条件编译,不然会在主函数while循环中,一直打印。 (给打印函数一个条件)
  • 要使能输入和打开计数器。这个函数没有捕获中断。
相关推荐
蓝斯4978 小时前
从.到实现一个Windows上的虚拟hid键盘设备
windows·单片机·计算机外设
熙芯XiChip9 小时前
硬实时与软实时RTOS核心区别
嵌入式硬件
伟大的前程9 小时前
unsigned int 定义的变量 在 电脑的 64 位系统中 和 32 位 cortex-m0 和 8 位的 51 单片机
单片机·嵌入式硬件
小渔村的拉线工9 小时前
1.HPM6E80 解析芯片整体工作原理和存储架构
单片机·嵌入式硬件·mcu·架构·hpm6e80
熙芯XiChip10 小时前
CAN与CANopen的本质区别
单片机
lilihewo10 小时前
Altium Designer 封装库的创建(分享pcb 3D封装库)
嵌入式硬件·51单片机
神罚天下ET11 小时前
典型arm位单片机启动流程(从上电到main.c)
c语言·arm开发·单片机
碧海银沙音频科技研究院12 小时前
蓝牙耳机使用WSDF5361锂保芯片进入船运模式实现方法
嵌入式硬件·算法
Zevalin爱灰灰13 小时前
模拟信号链电路——基于TL431的电压基准电路
单片机·嵌入式硬件
csdn杰哥13 小时前
瑞萨单片机开发教程[二十七] DTC 外部中断触发传输实验
单片机·嵌入式硬件·mongodb·瑞萨·ra6m5