zoukankan      html  css  js  c++  java
  • 基于STM32 CubeMX 配置 PWM输出和输入捕获

    PWM输出和输入捕获

    1、试验目标

      1.输出2路PWM脉冲信号

      2.捕获1路PWM脉冲信号

      本次试验会使用到2个定时器,一个高级定时器用于脉冲捕获,一个普通定时器用于PWM脉冲输出。

    2、准备材料

      1. STM32F103C8

      2. STM32CubeMX

    2、STM32CubeMX配置

      2.1时钟树

        系统时钟为72M,APB1 和APB2 的定时器时钟都为72MHZ。

      2.2 PWM输出配置

      PWM的输出配置比较简单,这里我们使用到了TIM2普通定时器控制输出,具体参数如下图。

     

       在 Parameter Settings 页配置预分频系数为 72-1,计数周期(自动加载值)为 10000-1,定时器溢出频率,即PWM的周期,就是 72MHz/(71+1)/(9999+1) = 100Hz

      2.3 PWM输入捕获配置

      PWM捕获,本次试验使用到了STM32F103C8的高级定时器TIM1。配置如下图。

     中断配置勾线这里,因为我们需要使用中断回调函数来计算频率占空比。

      2.4 配置中断分组和中断使能

    2.5串口输出

    2.6生成工程

      这里选择分离C.h文件,IDE 根据自己的环境选择,这里我使用的GUN编译方式的IDE所以选择了SW4SEM32。

     以上CubeMX的PWM配置就完成了。

    配置完毕后,生成工程打开。下面我们来分析代码和如何使用。

    3、代码实现

      3.1 tim.c

        该代码主要配置了Tim1 和Tim2 的相关配置,为什么要这么配置,在接下来的第4大点会详细说明。这里主要了解 HAL_TIM_IC_CaptureCallback  捕获中断回调函数

    这里函数主要处理计算占空比和频率。

    /**
      ******************************************************************************
      * @file    tim.c
      * @brief   This file provides code for the configuration
      *          of the TIM instances.
      ******************************************************************************
      * @attention
      *
      * <h2><center>© Copyright (c) 2021 STMicroelectronics.
      * All rights reserved.</center></h2>
      *
      * This software component is licensed by ST under BSD 3-Clause license,
      * the "License"; You may not use this file except in compliance with the
      * License. You may obtain a copy of the License at:
      *                        opensource.org/licenses/BSD-3-Clause
      *
      ******************************************************************************
      */
    
    /* Includes ------------------------------------------------------------------*/
    #include "tim.h"
    
    /* USER CODE BEGIN 0 */
    /// 计算占空比时使用
    __IO uint16_t IC2Value = 0;
    __IO uint16_t IC1Value = 0;
    __IO float DutyCycle = 0;
    __IO float Frequency = 0;
    /* USER CODE END 0 */
    
    TIM_HandleTypeDef htim1; // 高级定时器捕获PWM
    TIM_HandleTypeDef htim2; // 普通定时器输出PWM
    
    /* TIM1 init function */
    void MX_TIM1_Init(void)
    {
      TIM_ClockConfigTypeDef sClockSourceConfig = {0};
      TIM_SlaveConfigTypeDef sSlaveConfig = {0};
      TIM_MasterConfigTypeDef sMasterConfig = {0};
      TIM_IC_InitTypeDef sConfigIC = {0};
    
      htim1.Instance = TIM1;
    
      htim1.Init.Prescaler = 72-1;
      htim1.Init.CounterMode = TIM_COUNTERMODE_UP;                  /* 计数方式 上计数 */
      htim1.Init.Period = 65535;                                    /* 计数器更新上限值         */
      htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;            /* 采样时钟分频 */
      htim1.Init.RepetitionCounter = 0;                             /* 重装值=0 */
      htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; /* 自动装载值软件使能 */
      if (HAL_TIM_Base_Init(&htim1) != HAL_OK)                      /* 初始定时器 */
      {
        Error_Handler();
      }
      sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;     /* 内部时钟源 */
      if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
      {
        Error_Handler();
      }
      if (HAL_TIM_IC_Init(&htim1) != HAL_OK)
      {
        Error_Handler();
      }
      ///选择从模式: 复位模式
      sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET;
      sSlaveConfig.InputTrigger = TIM_TS_TI1FP1;                           /* 选择定时器输入触发: TI1FP1 */
      sSlaveConfig.TriggerPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
      sSlaveConfig.TriggerFilter = 0;
      if (HAL_TIM_SlaveConfigSynchro(&htim1, &sSlaveConfig) != HAL_OK)
      {
        Error_Handler();
      }
      sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
      sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
      if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
      {
        Error_Handler();
      }
        ///IC1捕获 上升沿触发 TI1FP1
      sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
      sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
      sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
      sConfigIC.ICFilter = 0;
      if (HAL_TIM_IC_ConfigChannel(&htim1, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
      {
        Error_Handler();
      }
      ///IC2捕获 下降沿捕获 TI1FP2
      sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;
      sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI;
      if (HAL_TIM_IC_ConfigChannel(&htim1, &sConfigIC, TIM_CHANNEL_2) != HAL_OK)
      {
        Error_Handler();
      }
    
    }
    /* TIM2 init function */
    void MX_TIM2_Init(void)
    {
      TIM_ClockConfigTypeDef sClockSourceConfig = {0};
      TIM_MasterConfigTypeDef sMasterConfig = {0};
      TIM_OC_InitTypeDef sConfigOC = {0};
    
      htim2.Instance = TIM2;
        /**   htim2.Init.Prescaler 分频计算
     * 定时器时钟源TIMxCLK = 2 * PCLK1
     * 				PCLK1 = HCLK / 2
     * 				=> TIMxCLK = HCLK/2 = SystemCoreClock / 2 *2=72MHz     (APB1)
     * 设定定时器频率为=TIMxCLK/(TIM_Prescaler+1)=10KHz
     * */
      htim2.Init.Prescaler = 72-1;
      htim2.Init.CounterMode = TIM_COUNTERMODE_UP;                  /* 计数方式上升沿有效       */
      htim2.Init.Period = 10000-1;                                  /* 累计 TIM_Period个后产生一个更新或者中断 当定时器从0计数到10000,即为10000次,为一个定时周期*/
      htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
      htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
      if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
      {
        Error_Handler();
      }
      sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;    /* 内部时钟源 */
      if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
      {
        Error_Handler();
      }
      if (HAL_TIM_PWM_Init(&htim2) != HAL_OK)
      {
        Error_Handler();
      }
      sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
      sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
      if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
      {
        Error_Handler();
      }
      ///PWM模式配置
      sConfigOC.OCMode = TIM_OCMODE_PWM1;                       /* 配置为PWM模式1*/
      sConfigOC.Pulse = 5000;                                   /* 默认占空比为50%*/
      sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;               /* 当定时器计数值小于CCR1_Val时为高电平*/
      sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
      if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)   /* 配置PWM通道*/
      {
        Error_Handler();
      }
      if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
      {
        Error_Handler();
      }
      HAL_TIM_MspPostInit(&htim2);                             /* 外置GPIO初始化 */
    
    }
    
    void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
    {
    
      GPIO_InitTypeDef GPIO_InitStruct = {0};
      if(tim_baseHandle->Instance==TIM1)
      {
      /* USER CODE BEGIN TIM1_MspInit 0 */
    
      /* USER CODE END TIM1_MspInit 0 */
        /* TIM1 clock enable */
        __HAL_RCC_TIM1_CLK_ENABLE();         /*定时器时钟使能*/
    
        __HAL_RCC_GPIOA_CLK_ENABLE();       /*GPIO时钟使能*/
        /**TIM1 GPIO Configuration
        PA8     ------> TIM1_CH1
        */
        GPIO_InitStruct.Pin = GPIO_PIN_8;               /* 36脚的F103 不能改变引脚编号*/
        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;         /* 输入模式*/
        GPIO_InitStruct.Pull = GPIO_NOPULL;             /* 无上下拉*/
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
        /* TIM1 interrupt Init */
        HAL_NVIC_SetPriority(TIM1_CC_IRQn, 0, 0);   /* 配置中断分组*/
        HAL_NVIC_EnableIRQ(TIM1_CC_IRQn);                                     /* 使能中断*/
      /* USER CODE BEGIN TIM1_MspInit 1 */
    
      /* USER CODE END TIM1_MspInit 1 */
      }
      else if(tim_baseHandle->Instance==TIM2)
      {
      /* USER CODE BEGIN TIM2_MspInit 0 */
    
      /* USER CODE END TIM2_MspInit 0 */
        /* TIM2 clock enable */
        __HAL_RCC_TIM2_CLK_ENABLE();
      /* USER CODE BEGIN TIM2_MspInit 1 */
    
      /* USER CODE END TIM2_MspInit 1 */
      }
    }
    void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle)
    {
    
      GPIO_InitTypeDef GPIO_InitStruct = {0};
      if(timHandle->Instance==TIM2)
      {
      /* USER CODE BEGIN TIM2_MspPostInit 0 */
    
      /* USER CODE END TIM2_MspPostInit 0 */
    
        __HAL_RCC_GPIOA_CLK_ENABLE();
        /**TIM2 GPIO Configuration
        PA0-WKUP     ------> TIM2_CH1
        PA1     ------> TIM2_CH2
        */
        GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1;        /* 这里定义了2路PMW输出 用PA0 和PA1*/
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
      /* USER CODE BEGIN TIM2_MspPostInit 1 */
    
      /* USER CODE END TIM2_MspPostInit 1 */
      }
    
    }
    
    void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
    {
    
      if(tim_baseHandle->Instance==TIM1)
      {
      /* USER CODE BEGIN TIM1_MspDeInit 0 */
    
      /* USER CODE END TIM1_MspDeInit 0 */
        /* Peripheral clock disable */
        __HAL_RCC_TIM1_CLK_DISABLE();
    
        /**TIM1 GPIO Configuration
        PA8     ------> TIM1_CH1
        */
        HAL_GPIO_DeInit(GPIOA, GPIO_PIN_8);
    
        /* TIM1 interrupt Deinit */
        HAL_NVIC_DisableIRQ(TIM1_CC_IRQn);
      /* USER CODE BEGIN TIM1_MspDeInit 1 */
    
      /* USER CODE END TIM1_MspDeInit 1 */
      }
      else if(tim_baseHandle->Instance==TIM2)
      {
      /* USER CODE BEGIN TIM2_MspDeInit 0 */
    
      /* USER CODE END TIM2_MspDeInit 0 */
        /* Peripheral clock disable */
        __HAL_RCC_TIM2_CLK_DISABLE();
      /* USER CODE BEGIN TIM2_MspDeInit 1 */
    
      /* USER CODE END TIM2_MspDeInit 1 */
      }
    }
    
    /* USER CODE BEGIN 1 */
    /**
      * @brief  Conversion complete callback in non blocking mode  捕获回调函数
      * @param  htim : hadc handle
      * @retval None
      */
    void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
    {
        if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
        {
            /* 获取输入捕获值 */
            IC1Value = HAL_TIM_ReadCapturedValue(&htim1,TIM_CHANNEL_1);
            IC2Value = HAL_TIM_ReadCapturedValue(&htim1,TIM_CHANNEL_2);
            if (IC1Value != 0)
            {
                /* 占空比计算 */
                DutyCycle = (float)((IC2Value+1) * 100) / (IC1Value+1);
                /* 频率计算 */
                Frequency = 72000000/72/(float)(IC1Value+1);
            }
            else
            {
                DutyCycle = 0;
                Frequency = 0;
            }
    
        }
    }
    /* USER CODE END 1 */
    
    /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
    

    3.2 main.c

     用于计算的变量

    //计算占空比时的全局表变量
    extern __IO uint16_t IC2Value;
    extern __IO uint16_t IC1Value;
    extern __IO float DutyCycle;
    extern __IO float Frequency;
    

     使能和输出PWM

        /// 使能捕获/比较2中断请求
        HAL_TIM_IC_Start_IT(&htim1,TIM_CHANNEL_1);
        HAL_TIM_IC_Start_IT(&htim1,TIM_CHANNEL_2);
    
        /// 开始输出PWM
        HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);
        HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);
    

      打印调试输出

      while (1)
      {
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
        printSwo("IC1Value =",IC1Value,LINE_FEED_EN);
        printSwo("IC2Value =",IC2Value,LINE_FEED_EN);
        printSwo("占空比:",DutyCycle,LINE_FEED_EN);
        printSwo("频率:",Frequency,LINE_FEED_EN);
        HAL_Delay(500);
      }
    

      

    3.3 输出结果

    短接任意一路输出  PA0与PA8  PWM输出与捕获,或 PA1与PA8  PWM输出与捕获。

    4、分析总结

    4.1输入捕获相关硬件信号知识

      脉冲信号从外部进来需要经过6个步骤。功能图如下图所示。

        第1;确认通道;

          脉冲进来时需要确定是从哪个定时器的通道进来的,通常这个通道的叫法为TI1/2/3/4。这里我选择了高级定时器1的TI1 和TI2,

          对应的引脚是默认引脚是PA8 输入捕获,F103C8支持重映像,即不能使用其它引脚代替。

          

        第2;滤波器与边沿检测器;

           信号进来后为了避免干扰,就需要进行滤波。当前一个高电平信号过来,假设采样速度为1M的速度进行计算,如果滤波系数设置为4,

           那么高电平信号需要维持连续维持4us的高电平,才计数为一个高电平。边沿检测器用来设置信号在捕获的时候是什么边沿有效,可以

           是上升沿,下降沿,或者是双边沿。

        第3;捕获通道;

            是指图中的IC1/2/3/4,每个捕获通道都有相对应的捕获寄存器CCR1/2/3/4,当发生捕获的时候,计数器CNT 的值就会被锁存到捕

            获寄存器中。

        第4;预分频;

            捕获的信号会经过一个预分频器,比如2次上升沿算为一次计数。本次试验设定为0,即不分频。

        第5;捕获/比较寄存器;

            经过预分频器的信号ICxPS 是最终被捕获的信号,当发生捕获时(第一次),计数器CNT 的值会被锁存到捕获寄存器CCR 中,还会产生CCxI 中断,

          相应的中断位CCxIF(在SR 寄存器中)会被置位,通过软件或者读取CCR 中的值可以将CCxIF 清0。如果发生第二次捕获(即重复捕获:CCR 寄存

          器中已捕获到计数器值且 CCxIF 标志已置 1),则捕获溢出标志位CCxOF(在SR 寄存器中)会被置位,CCxOF 只能通过软件清零。

            输出比较就是通过定时器的外部引脚对外输出控制信号,有冻结、将通道X(x=1,2,3,4)设置为匹配时输出有效电平、将通道X 设置为匹配时输出无

          效电平、翻转、强制变为无效电平、强制变为有效电平、PWM1 和PWM2 这八种模式。电机控制很常用,这里不展开讨论。

      4.2 PWM输入模式

        大概了解上述小点后,这里说明我们本次测试使用到的PWM输入模式,它是最便捷的测量脉宽和频率的方法。当使用PWM 输入模式的时候,因为一个输入通道

        (TIx)会占用两个捕获通道(ICx),所以一个定时器在使用PWM输入的时候最多只能使用两个输入通道(TIx)。本次试验就是使用TIM1的CH1 和CH2。

        工作原理是这样的:

          PWM 信号由输入通道TI1 进入,因为是PWM 输入模式的缘故,信号会被分为两路,一路是TI1FP1,另外一路是TI2FP2。其中一路是周期,另一路是占空比,

        具体哪一路信号对应周期还是占空比,得从程序上设置哪一路信号作为触发输入,作为触发输入的哪一路信号对应的就是周期,另一路就是对应占空比。作为触发

        输入的那一路信号还需要设置极性,是上升沿还是下降沿捕获,一旦设置好触发输入的极性,另外一路硬件就会自动配置为相反的极性捕获,无需软件配置。一句

        话概括就是:选定输入通道,确定触发信号,然后设置触发信号的极性即可,因为是PWM 输入的缘故,另一路信号则由硬件配置,无需软件配置。

          当使用PWM 输入模式的时候必须将从模式控制器配置为复位模式(配置寄存器SMCR 的位SMS[2:0]来实现),即当我们启动触发信号开始进行捕获的时候,

        同时把计数器CNT 复位清零。所以我们在STM32CubeMX中要勾选为复位模式。下图参考手册的时序图

                            

      4.3 PWM输出模式

        PWM 输出就是对外输出脉宽(即占空比)可调的方波信号,信号频率由自动重装寄存器ARR 的值决定,占空比由比较寄存器CCR 的值决定。

      PWM 模式分为两种,PWM1 和PWM2。下图的表格展示区别。

         

        本次试验使用的 PWM1 模式递增计数模式,计数器从0 计数到自动重载值(TIMx_ARR 寄存器的内容),然后重新从0 开始计数并生成计数器上溢事件。

      回到2.2小点的PWM配置图

         配置预分频系数为 72-1,计数周期(自动加载值)为 10000-1,定时器溢出频率,即PWM的周期,就是

                        72MHz/(71+1)/(9999+1) = 100Hz

        输出频率:

                    arr = 计数器值    psc = 预分频值

                    Fpwm =Tclk / ((arr+1)*(psc+1))(单位:Hz)
        输出占空比:
                        duty circle = TIMx->CCRx / arr(单位:%)
                        TIMx->CCRx 用户设定值
        比如: 定时器频率Tclk = 72Mhz arr=10000   psc=71    那么PWM频率就是720000/10000/72= 100Hz
                      arr=10000,    TIMx->CCRx=5000     则pwm的占空比为50%
                          CCRx的值影响占空比,arr的值影响频率

    参考资料:STM32中文参考手册V10

         【野火】《STM32 HAL 库开发实战指南—基于F103指南者》

     

  • 相关阅读:
    一步一步理解XMLDOM(一)
    按轨迹周期运动
    Python中’__main__’模块的作用
    多进程IPC与Python支持
    Eclipse启动多个Android模拟器
    解决Android平台移植ffmpeg的一揽子问题
    开源项目 GitHub地址
    使用viewpager嵌套实现上下左右滑动切换图片(IOS双向滚动翻页效果相同)
    Android中ScrollView消除阴影的办法
    如果项目为android library怎么运行
  • 原文地址:https://www.cnblogs.com/siyun/p/14810277.html
Copyright © 2011-2022 走看看