• STM32-NUCLEO-F411RE—捕获PWM信号测出占空比和频率


    使用STM32CubeMX生产初始化代码,底板硬件使用NUCLEO-F411RE开发板

    在这里插入图片描述

    一、新建工程

    在这里插入图片描述

    二、选择芯片型号

    我使用的是STM的NUCLEO开发板 ,在筛选其中填入411
    在这里插入图片描述

    三、配置时钟

    首先选择外部晶振:
    在这里插入图片描述
    开发板焊接了外部晶振,所以我 RCC(Reset and Cock Control) 配置选择了 Crystal/Ceramic Resonator(石英/陶瓷谐振器),配置完成后,右边的 Pinout view 里相关引脚就会被标绿。

    配置时钟频率:
    在这里插入图片描述
    外部高速时钟配置完成后,进入 Clock Configuration 选项,根据实际情况,将系统时钟配置为 96MHz,最后按下回车,软件会自动调整分频和倍频参数。

    四、配置调试模式

    在这里插入图片描述

    ST-Link 就是 Serial Wire 调试模式,一定要设置!!!
    以前使用 M0 的芯片,不配置这个模式没出现问题,但现在这个型号,如果不配置 Serial Wire 模式,程序一旦通过 ST-Link 烧录到芯片中,芯片就再也不能被ST-Link 识别了。(后来我是通过 STMISP 工具烧录程序/擦除后才恢复正常的)

    五、根据内部原理分析

    在这里插入图片描述
    这里以TIM_CH1为例,当从CH1输入一个PWM波,通过输入滤波后将会产生两路信号:tim_ti1fp1 & tim_ti1fp2,分别送至tim_ic1 & tim_ic2,也就是说一个TI信号将会被映射成两路的IC信号,所以可以通过进行边沿检测来测量PWM的频率以及占空比。

    六、配置定时器模式参数

    具体步骤如下:

    1、设置定时器Slave Mode为Reset Mode,也就是当检测到上升沿时,定时器复位;

    2、PWM由CH1进入,触发源设置为TI1FP1,并设置IC1为上升沿捕获;

    3、当第一次捕获到上升沿时,定时器复位,计数寄存器CNT清零;

    4、当IC2捕获到下降沿时,计数器CNT的值将会被存到捕获寄存器CCR2中;

    5、当IC1再次捕获到上升沿时,计数器CNT的值将会被存到捕获寄存器CCR1中,同时将定时器复位;

    因此,CCR1的值就是周期,CCR2的值就是占空比

    配置如下:

    我们将PA8的TIM1-CH1设置为捕获输入管脚,通道1设置为直接模式,通道2设置为间接模式
    在这里插入图片描述

    预分频系数设为APB总线频率设置为96-1,因为内部是加1操作所以要在这里减1,同样预装载值也要减1操作,为了后面方便计算我这里设置成1000-1

    输入捕获能捕获到的最小的频率为 96M / { (ARR+1)x(PSC+1) } 注意,这里的计数周期不能设置的太小,如果我们设置的计数周期 < PWM周期那么就无法捕获PWM脉冲,一般驱动电机的PWM是10k ~ 25kHz,我们设置的周期为1ms,对应频率为1kHz,那么就可以捕获1kHz以上的PWM 信号

    而最终计算得到的频率值 = 分频后得到的时钟频率 / 上升沿个数 = ( 定时器时钟频率 / 预分频系数 )/ 上升沿个数 ;
    最终计算的到的占空比 = 下降沿个数 / 上升沿个数;
    
    • 1
    • 2

    输入捕获需要开启定时器的中断,无论是计时溢出还是输入捕获都需要使用到中断。
    在这里插入图片描述
    配置NVIC(Nested Vector Interrupt Controller):
    在这里插入图片描述

    七、生成 Keil 工程

    设置 IDE 和 工程目录及名称:
    在这里插入图片描述
    将每种外设的代码存放到不同的 .c /.h 文件中,便于管理(不然都会被放到 main.c 中)。
    在这里插入图片描述

    八、中断函数写在哪

    在使用标准库时,我们是将中断处理写在最底层的中断处理函数中,如 EXTI0_IRQHandler(),但 Hal 库增加了回调函数,将中断底层一些必要的操作 “隐藏” 了起来(如清除中断)。

    中断的调用顺序是(以 EXTI0 为例):EXTI0_IRQHandler() —> HAL_GPIO_EXTI_IRQHandler() —> HAL_GPIO_EXTI_Callback()。

    TIM2 的中断服务函数已经在 stm32f1xx_it.c 中定义(STM32CubeMX 自动生成的)

    /**
      * @brief This function handles TIM1 global interrupt.
      */
    void TIM1_IRQHandler(void)
    {
      /* USER CODE BEGIN TIM1_IRQn 0 */
    
      /* USER CODE END TIM1_IRQn 0 */
      HAL_TIM_IRQHandler(&htim1);
      /* USER CODE BEGIN TIM1_IRQn 1 */
    
      /* USER CODE END TIM1_IRQn 1 */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    HAL_TIM_IRQHandler() 是 HAL 库的定时器总中断,里面代码很多,这里不展示,我们只需要知道一点——当 TIM2 计数值溢出或发生其他事件(如捕获到上升/下降沿信号)时,系统会执行一系列的中断回调函数,其中包括我们将要用到的 计数溢出回调函数HAL_TIM_PeriodElapsedCallback() 和 输入捕获回调函数HAL_TIM_IC_CaptureCallback()

    九、代码部分

    下面是生成 Keil 工程中关于 TIM1(输入PWM捕获)初始化的代码:

    /* USER CODE BEGIN Header */
    /**
      ******************************************************************************
      * @file    tim.c
      * @brief   This file provides code for the configuration
      *          of the TIM instances.
      ******************************************************************************
      * @attention
      *
      * Copyright (c) 2022 STMicroelectronics.
      * All rights reserved.
      *
      * This software is licensed under terms that can be found in the LICENSE file
      * in the root directory of this software component.
      * If no LICENSE file comes with this software, it is provided AS-IS.
      *
      ******************************************************************************
      */
    /* USER CODE END Header */
    /* Includes ------------------------------------------------------------------*/
    #include "tim.h"
    
    /* USER CODE BEGIN 0 */
    
    /* USER CODE END 0 */
    
    TIM_HandleTypeDef htim1;
    
    /* TIM1 init function */
    void MX_TIM1_Init(void)
    {
    
      /* USER CODE BEGIN TIM1_Init 0 */
    
      /* USER CODE END TIM1_Init 0 */
    
      TIM_ClockConfigTypeDef sClockSourceConfig = {0};
      TIM_SlaveConfigTypeDef sSlaveConfig = {0};
      TIM_MasterConfigTypeDef sMasterConfig = {0};
      TIM_IC_InitTypeDef sConfigIC = {0};
    
      /* USER CODE BEGIN TIM1_Init 1 */
    
      /* USER CODE END TIM1_Init 1 */
      htim1.Instance = TIM1;
      htim1.Init.Prescaler = 96-1;
      htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
      htim1.Init.Period = 1000-1;
      htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
      htim1.Init.RepetitionCounter = 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;
      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();
      }
      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();
      }
      sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;
      sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI;
      if (HAL_TIM_IC_ConfigChannel(&htim1, &sConfigIC, TIM_CHANNEL_2) != HAL_OK)
      {
        Error_Handler();
      }
      /* USER CODE BEGIN TIM1_Init 2 */
    
      /* USER CODE END TIM1_Init 2 */
    
    }
    
    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();
        /**TIM1 GPIO Configuration
        PA8     ------> TIM1_CH1
        */
        GPIO_InitStruct.Pin = GPIO_PIN_8;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
        GPIO_InitStruct.Alternate = GPIO_AF1_TIM1;
        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 */
      }
    }
    
    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 */
      }
    }
    
    /* USER CODE BEGIN 1 */
    
    /* USER CODE END 1 */
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158

    主函数 工作

    __IO uint16_t TIM1_IC2Value = 0;
    __IO uint16_t TIM1_IC1Value = 0;
    __IO float TIM1_DutyCycle = 0;
    __IO float TIM1_Frequency = 0;
    
      MX_TIM1_Init();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    开启捕获通道

    HAL_TIM_IC_Start_IT (&htim1,TIM_CHANNEL_1);
    HAL_TIM_IC_Start_IT (&htim1,TIM_CHANNEL_2);	
    
    • 1
    • 2

    我们的实验代码的核心部分为中断回调函数:

    //定时器输入捕获中断处理回调函数
    void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)// 捕获中断发生时执行
    {
    		if(htim ->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
    		{
    			/* 获取输入捕获值 */
    			TIM1_IC1Value = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
    			TIM1_IC2Value = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2);
    			
    			// 注意:捕获寄存器CCR1和CCR2的值在计算占空比和频率的时候必须加1
    			if (TIM1_IC1Value != 0)
    			{
    				/* 占空比计算 */
    				TIM1_DutyCycle = (float)((TIM1_IC2Value+1) * 100) / (TIM1_IC1Value + 1);
    
    				/* 频率计算 */
    				TIM1_Frequency = (96000000/(96))/(float)(TIM1_IC1Value + 1);
    				
    				printf("TIM1_占空比:%0.2f%%   TIM1_频率:%0.2fHz\n",TIM1_DutyCycle,TIM1_Frequency);
    			}
    			else
    			{
    				TIM1_DutyCycle = 0;
    				TIM1_Frequency = 0;
    			}
    		
    		}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    十、测试部分

    在这里插入图片描述

  • 相关阅读:
    我们离成为C++、C#、MySQL之父有多远?
    maven教程
    《Principles of Model Checking》Chapter 4 Regular Properties
    echart扩展插件词云echarts-wordcloud
    【单目3D目标检测】项目实战-道路车辆/行人3D目标检测
    服务器和电脑的区别
    论文超详细精读|万字:2s-AGCN
    A Review of Generalized Zero-Shot Learning Methods
    python用最小二乘法实现平面拟合
    Python高级篇(08):生成器
  • 原文地址:https://blog.csdn.net/weixin_41226265/article/details/126776927