STM32G474之TIM1捕获1模式

2024-08-30 17:36
文章标签 模式 捕获 tim1 stm32g474

本文主要是介绍STM32G474之TIM1捕获1模式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

STM32G474采用TIM8产生方波信号,使用TIM1工作于捕获1模式,并计算方波频率。捕获方波周期,在有些开发中,还是能用到。建议开发时使用HAL库自带的库函数。使用寄存器方法也可以实现,但是后期修改不太方便。

测试时,将PA8引脚复用为TIM1_CH1,LED灯引脚为PC13

TIM1_CH1重映射,见下面的表格:

 1、测试程序

#include "Timer1.h"
#include "LED.h"
#include "stm32g4xx_hal.h"
#include "stdio.h"  //getchar(),putchar(),scanf(),printf(),puts(),gets(),sprintf()

//STM32G474采用TIM8产生方波信号,使用TIM1工作于捕获1模式,并计算方波频率。
//捕获方波周期,在有些开发中,还是能用到。建议开发时使用HAL库自带的库函数。
//使用寄存器方法也可以实现,但是后期修改不太方便.

//将PA8引脚复用为TIM1_CH1
//LED灯引脚为PC13

TIM_HandleTypeDef htim1;  //TIM1句柄
double uwTimclock1;
uint32_t uwPrescalerValue1;

uint32_t uwIC2Value1 = 0; //用来保存第1次捕获到的值
uint32_t uwIC2Value2 = 0; //用来保存第2次捕获到的值
uint32_t uwDiffCapture = 0; //两次捕获到的差值
uint16_t uhCaptureIndex = 0; //用来表示捕获下标

float uwFrequency = 0;//用来保存计算到的频率

uint16_t LED_cnt;

void Timer1_Init(void);
void TIM8_Init(void);
void Print_Period(void);

//使用TIM1捕获
void Timer1_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_IC_InitTypeDef sConfigIC = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

  __HAL_RCC_TIM1_CLK_ENABLE();  //使能“定时器1”的时钟,Enable TIM1 clock
  __HAL_RCC_GPIOA_CLK_ENABLE(); //GPIOA时钟使能

    GPIO_InitStruct.Pin = GPIO_PIN_8;                  //选择引脚编号8
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;            //复用功能推挽模式
    GPIO_InitStruct.Pull = GPIO_NOPULL;                //引脚上拉和下拉都没有被激活
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; //引脚的输出速度为120MHz
    GPIO_InitStruct.Alternate = GPIO_AF6_TIM1;         //将PA8引脚复用为TIM1_CH1
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    //根据GPIO_InitStruct结构变量指定的参数初始化GPIOA的外设寄存器

  uwTimclock1 = HAL_RCC_GetPCLK2Freq();
    //读取PCLK2的时钟频率,Return the PCLK2 frequency
    //若PCLK2的分频器值为1,则和SystemCoreClock的值相等

    //Frequency computation: for this example TIMx (TIM1) is clocked by APB2Clk
    uwPrescalerValue1 = (uint32_t) ((uwTimclock1 / 1000000U) - 1U);
    //uwPrescalerValue1=170

  htim1.Instance = TIM1;
    htim1.Init.Period = 0xFFFF;//在配置为捕获时,定时器周期必须设置为0xFFFF
  htim1.Init.Prescaler = uwPrescalerValue1;
    //设置TIM1预分频器为uwPrescalerValue1
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    //设置时钟分频系数,TIM1_CR1中的CKD[9:8]=00b,tDTS=ttim_ker_ck;
    //溢出时间为(FFFF+1)*1*170/170000000/1=65.535ms
    //最大误差为170/170000000=1us,则允许采集的最大周期为65.535毫妙,最小周期为1微妙

    htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.RepetitionCounter = 0;//重复计数(1-0)次,产生1次中断,比较重要
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    //因为该定时器为捕获,因此不使能“自动重装载”
    HAL_TIM_IC_Init(&htim1);//TIM1输入捕获初始化

  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;//TIMx_EGR.UG位用作产生TIM1_TRGO触发信号
  sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;//TIMx_EGR.UG位用作产生TIM1_TRGO2触发信号
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;//Master/slave mode is selected
  HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig);
    //Configures the TIM in master mode.

  sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;//上升沿捕获
  sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
    //将“TIM输入1,TIM输入2,TIM输入3和TIM输入4”,分别和“IC1、IC2、IC3和IC4”一一对应连接起来 
  sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
  sConfigIC.ICFilter = 0;
  HAL_TIM_IC_ConfigChannel(&htim1, &sConfigIC, TIM_CHANNEL_1);
    //因为捕获引脚为TIM1_CH1,所以要配置TIM1捕获1通道
    //该函数调用后,会记录htim->Channel=HAL_TIM_ACTIVE_CHANNEL_1
    //所以htim要被申请为全局变量;
    //TIM_CHANNEL_1表示通道1

  sBreakDeadTimeConfig.BreakAFMode = TIM_BREAK_AFMODE_INPUT;//刹车输入引脚BRK处于输入模式
  sBreakDeadTimeConfig.Break2AFMode = TIM_BREAK_AFMODE_INPUT;
  HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig);

    HAL_NVIC_SetPriority(TIM1_CC_IRQn, 0, 0);
    //设置NVIC中断分组4:4位抢占优先级,0位响应优先级
    //选择中断优先级组4,即抢占优先级为4位,取值为0~15,响应优先级组为0位,取值为0
    //因为捕获脉宽时间,不能受到其它中断影响产生误差,所以这里设置TIM1中断优先级为0

    HAL_NVIC_EnableIRQ(TIM1_CC_IRQn);//使能TIM1产生捕获中断

  HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_1);
    //Start the Input Capture in interrupt mode
    //TIM_CHANNEL_1表示通道1

    TIM8_Init();
}

//HAL_TIM_IRQHandler()会调用这个函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
  if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
  {
    if(uhCaptureIndex == 0)
    {
      uwIC2Value1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
            //保存读到的第1个捕获数据
            //Get the 1st Input Capture value
            //TIM_CHANNEL_1表示通道1

      uhCaptureIndex = 1;
    }
    else if(uhCaptureIndex == 1)
    {
      uwIC2Value2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
      //保存读到的第2个捕获数据
            //Get the 2nd Input Capture value
            //TIM_CHANNEL_1表示通道1

      if (uwIC2Value2 >= uwIC2Value1)
      {
        uwDiffCapture = (uwIC2Value2 - uwIC2Value1);//计算差值 
      }
      else if (uwIC2Value2 < uwIC2Value1)
      {
        uwDiffCapture = ( (0xFFFF - uwIC2Value1) + uwIC2Value2 ) + 1;
                //计算差值
                //0xFFFF is max TIM1_CCRx value

      }
     
      uwFrequency = uwTimclock1 /uwPrescalerValue1 / uwDiffCapture;
      uhCaptureIndex = 0;//允许再次捕获
    }
  }
}

//定时器1捕获中断
void TIM1_CC_IRQHandler(void)
{
  HAL_TIM_IRQHandler(&htim1);
}

//用来产生方波信号///
void TIM8_Init(void)
{
    TIM_HandleTypeDef htim8;  //TIM8句柄
    RCC_ClkInitTypeDef    clkconfig;
    uint32_t              uwTimclock8 = 0;
    uint32_t              pFLatency;
    uint32_t              uwPrescalerValue8 = 0;
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

  __HAL_RCC_TIM8_CLK_ENABLE();//使能“定时器8”的时钟,Enable TIM8 clock
  HAL_RCC_GetClockConfig(&clkconfig, &pFLatency);//Get clock configuration
  uwTimclock8 = HAL_RCC_GetPCLK2Freq();
    //读取PCLK2的时钟频率,Return the PCLK2 frequency
    //若PCLK2的分频器值为1,则和SystemCoreClock的值相等

    uwPrescalerValue8 = (uint32_t) ((uwTimclock8 / 1000000U) - 1U);
    //uwPrescalerValue8=170

  htim8.Instance = TIM8;
  htim8.Init.Period = (1000000U / 1000U) - 1U;
    //定时器周期999
  htim8.Init.Prescaler = uwPrescalerValue8;
    //设置TIM8预分频器为uwPrescalerValue8
  htim8.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    //设置时钟分频系数,TIM8_CR1中的CKD[9:8]=00b,tDTS=ttim_ker_ck;
    //溢出时间为(999+1)*1*170/170000000/1=1毫秒

  htim8.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim8.Init.RepetitionCounter = 0;//重复计数(1-0),产生一次中断
  htim8.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;//TIM_AUTORELOAD_PRELOAD_DISABLE;
  HAL_TIM_Base_Init(&htim8);

  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
    //TIM8_TRGO是adc_ext_trg9,用来触发ADC1/2/3/4/5
//  sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
    //TIM8_TRGO2是adc_ext_trg10,用来触发ADC1/2/3/4/5

  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  HAL_TIMEx_MasterConfigSynchronization(&htim8, &sMasterConfig);
    //Configures the TIM in master mode.

   HAL_TIM_Base_Start_IT(&htim8);
   HAL_NVIC_EnableIRQ(TIM8_UP_IRQn);//使能TIM8产生中断
   HAL_NVIC_SetPriority(TIM8_UP_IRQn, 5, 0U);
     //设置NVIC中断分组4:4位抢占优先级,0位响应优先级
     //选择中断优先级组4,即抢占优先级为4位,取值为0~15,响应优先级组为0位,取值为0
    //这里设置TIM8中断优先级为5

    LED_cnt=0;
}

//TIM8更新中断,每1ms中断一次
//为了节省内存,该中断处理程序使用“寄存器”处理,该死的HAL库,就是这么屌;

void TIM8_UP_IRQHandler(void)
{
    if( (TIM8->SR & TIM_FLAG_UPDATE) == TIM_FLAG_UPDATE)
  {//读取TIM8状态寄存器TIMx_SR的bit0(UIF),UIF=1表示产生了“TIM8更新事件”
        if( (TIM8->DIER & TIM_IT_UPDATE)  == TIM_IT_UPDATE )
    {//读取TIM8中断使能寄存器TIMx_DIER的bot0(UIE),查看UIE=1?
      TIM8->SR = ~(TIM_IT_UPDATE);
        LED_cnt++;
        if(LED_cnt>25)//每25毫秒,LED灯闪烁一次
        {
            LED_cnt=0;
              LED1_Toggle(); //LED1引脚输出电平翻转
        }
    }
  }
}

void Print_Period(void)
{
    float f;

    printf("uwDiffCapture=%u\r\n",uwDiffCapture);
    f=uwFrequency;
  printf("uwFrequency=%0.2fHz\r\n",f);
    f=1000/f;
    printf("Period=%0.2fms\r\n",f);
}

2、测试结果

3、误差分析: 

测试有点误差,LED闪烁为25*2=50ms,实际测试时间偏大,是由于TIM8的中断优先级不是最高级导致的,但测试原理是正确的 。

这篇关于STM32G474之TIM1捕获1模式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1121487

相关文章

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

模版方法模式template method

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/template-method 超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。 上层接口有默认实现的方法和子类需要自己实现的方法

【iOS】MVC模式

MVC模式 MVC模式MVC模式demo MVC模式 MVC模式全称为model(模型)view(视图)controller(控制器),他分为三个不同的层分别负责不同的职责。 View:该层用于存放视图,该层中我们可以对页面及控件进行布局。Model:模型一般都拥有很好的可复用性,在该层中,我们可以统一管理一些数据。Controlller:该层充当一个CPU的功能,即该应用程序

迭代器模式iterator

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/iterator 不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素

《x86汇编语言:从实模式到保护模式》视频来了

《x86汇编语言:从实模式到保护模式》视频来了 很多朋友留言,说我的专栏《x86汇编语言:从实模式到保护模式》写得很详细,还有的朋友希望我能写得更细,最好是覆盖全书的所有章节。 毕竟我不是作者,只有作者的解读才是最权威的。 当初我学习这本书的时候,只能靠自己摸索,网上搜不到什么好资源。 如果你正在学这本书或者汇编语言,那你有福气了。 本书作者李忠老师,以此书为蓝本,录制了全套视频。 试

利用命令模式构建高效的手游后端架构

在现代手游开发中,后端架构的设计对于支持高并发、快速迭代和复杂游戏逻辑至关重要。命令模式作为一种行为设计模式,可以有效地解耦请求的发起者与接收者,提升系统的可维护性和扩展性。本文将深入探讨如何利用命令模式构建一个强大且灵活的手游后端架构。 1. 命令模式的概念与优势 命令模式通过将请求封装为对象,使得请求的发起者和接收者之间的耦合度降低。这种模式的主要优势包括: 解耦请求发起者与处理者

springboot实战学习(1)(开发模式与环境)

目录 一、实战学习的引言 (1)前后端的大致学习模块 (2)后端 (3)前端 二、开发模式 一、实战学习的引言 (1)前后端的大致学习模块 (2)后端 Validation:做参数校验Mybatis:做数据库的操作Redis:做缓存Junit:单元测试项目部署:springboot项目部署相关的知识 (3)前端 Vite:Vue项目的脚手架Router:路由Pina:状态管理Eleme

状态模式state

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/state 在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。 在状态模式中,player.getState()获取的是player的当前状态,通常是一个实现了状态接口的对象。 onPlay()是状态模式中定义的一个方法,不同状态下(例如“正在播放”、“暂停

软件架构模式:5 分钟阅读

原文: https://orkhanscience.medium.com/software-architecture-patterns-5-mins-read-e9e3c8eb47d2 软件架构模式:5 分钟阅读 当有人潜入软件工程世界时,有一天他需要学习软件架构模式的基础知识。当我刚接触编码时,我不知道从哪里获得简要介绍现有架构模式的资源,这样它就不会太详细和混乱,而是非常抽象和易

使用Spring Boot集成Spring Data JPA和单例模式构建库存管理系统

引言 在企业级应用开发中,数据库操作是非常重要的一环。Spring Data JPA提供了一种简化的方式来进行数据库交互,它使得开发者无需编写复杂的JPA代码就可以完成常见的CRUD操作。此外,设计模式如单例模式可以帮助我们更好地管理和控制对象的创建过程,从而提高系统的性能和可维护性。本文将展示如何结合Spring Boot、Spring Data JPA以及单例模式来构建一个基本的库存管理系统