使用HAL库在STM32上实现定时器功能

目录

一、定时器介绍

1、概念:

2、定时器的分类:

 3、定时器模式:

4.定时时钟计算方法

5.工作过程

二、PWM介绍

1.定义

2.基本原理

3.优点及应用范围

4.主要参数

5.PWM的产生

6.PWM工作原理

7.PWM输出的模式区别

8、PWM相关配置寄存器 

 三、定时器捕获介绍

四、解决的问题

五、定时器串口定时点亮LED灯

1、搭建STM32HAL库环境:

 2、使用HAL库建立工程:

3、完善keil5工程: 

4、电路连接:

 5、烧录运行结果展示:

六、PWM完成呼吸灯

1、使用HAL库建立工程:

 2、完善keil5工程:

 3、电路连接:

4、烧录运行结果展示:

5、Keil虚拟示波器,观察 pwm输出波形:

(1、进入keil5仿真:

( 2、开始仿真:

七、定时器pwm完成呼吸灯和对于pwm输出信号的采集与捕获

 1、使用HAL库建立工程

2、完善keil工程:

 3、电路连接:

4、烧录运行结果展示:

八、总结 

九、参考资料


一、定时器介绍

1、概念:

        能够对内部时钟信号或外部输入信号进行计数,数值达到设定要求时,向CPU发起中断请求,完成外部程序的运行。
  本质就是进行计数,选择内部时钟脉冲,作为计数器时,技术信号的来源选择非周期脉冲信号。
      STM32中定时器可分为高级定时器、通用定时器、基本定时器三类,他们都是由一个可编程的16位预分频器(TIMX_PSC)驱动的16位。

2、定时器的分类:

 定时器一般来说可分为3类:

1、基本定时器:功能最少,只能充当基本的时基,甚至都没有外部引脚
2、通用定时器:拥有基本定时器的全部功能,同时有输入捕获模式,用以接收外部的PWM,脉冲之类的信息
3、高级定时器:又有通用定时器的全部功能,又有互补输出模式,功能最为强大

通常我们使用的都是通用定时器,通用定时器特点:

1.位于ABP1低速总线上
2.16位向下,向上/向下(中心对齐模式)计数模式,自动重装载计数器(TIMx_CNT)
3.16位可编程(可以实现修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为1~65535任意数值
4.四个独立通道(TIMx_CH1~4),通道用来支持:
①输入捕获
②输出比较
③PWM生成
④单脉冲模式输出
5.可使用外部信号(TIM_ETR)控制定时器和定时器互连的同步电路

 3、定时器模式:

  • 向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件。
  • 向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件。
  • 中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。
  • 4.定时时钟计算方法

    Tout = ((arr+1)(psc+1))/Tclk
    其中:
    Tclk:定时器的输入时钟频率(单位MHZ)
    Tout:定时器溢出时间(单位为us)
    arr: 计数装载值
    psc: 时钟分频系数

    5.工作过程

      在选定的时钟源(可以是内部的也可以是外部的)和预分频器TIMX_PSC的驱动下,根据设置的计数模式(向上、向下、中央对齐)自动。

      装载计数器TIMX_CNT开始计数;如果使能了相应的事件(更新事件、触发事件、输入捕获、输出比较)则会产生相应的中断。

  • 如果没有开启输入和输出,只使能了计数器计数溢出后自动装载,可以做为一个简单定时器使用,计数器自己开始周期计数
  • 如果开启了通道输入捕获,当检测到ICx信号上相应的边沿后,计数器(CNT)的当前值被锁存到捕获/比较寄存器(TIMx_CCRx)中,通过中断的方式可以读取出来假设为
    n1,然后更改输入捕获的信号级性(上升沿或下降沿),当再次检测到ICx信号上相应的边沿后,计数器(CNT)的当前值再次被锁存到捕获/比较寄存器(TIMx_CCRx)中假设为
    n2;n2 -n1节可算出电平的持续时间
  • 如果开启了输出控制,可以产生一个由TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空比的PWM信号。
  • 如果选择外部的同步时钟信号(TI1F_ED、TI1FP1、TI2FP2)作为计数器的时钟源,可以用来统计脉冲,实现脉冲频率采集功能
  • 二、PWM介绍

    1.定义

      PWM(Pulse Width Modulation)即脉冲宽度调制,简称脉宽调制。它是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术;它是一种模拟控制方式,根据相应载荷的变化来调制晶体管基极或MOS管栅极的偏置,来实现晶体管或MOS管导通时间的改变,从而实现开关稳压电源输出的改变。
    基本原理

    2.基本原理

      PWM就是对逆变电路开关器件的通断进行控制,使输出端得到一系列幅值相等的脉冲,用这些脉冲来代替正弦波或所需要的波形。也可以这样理解,PWM是一种对模拟信号电平进行数字编码的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个具体模拟信号的电平进行编码。PWM信号仍然是数字的,因为在给定的任何时刻,满幅值的直流供电要么完全有(ON),要么完全无(OFF)。电压或电流源是以一种通(ON)或断(OFF)的重复脉冲序列被加到模拟负载上去的。只要带宽足够,任何模拟值都可以使用 PWM 进行编码。
      该信号在预定义的时间和速度中设置为高(5v或3.3v)和低(0v)。通常,我们将PWM的高电平称为1,低电平为0。

    3.优点及应用范围

      由于其控制简单、灵活和动态响应好等优点而成为电力电子技术应用最广泛的控制方式,其应用领域包括测量,通信, 功率控制与变换,电动机控制、伺服控制、调光、开关电源,甚至某些音频放大器,因此学习PWM具有十分重要的现实意义。

    4.主要参数

  • PWM占空比:
    PWM信号保持高电平的时间百分比称为占空比。如果信号始终为高电平,则它处于100%占空比,如果它始终处于低电平,则占空比为0%。如图1所示,T1为占空比,T为一个PWM周期。

  • PWM的频率:
    PWM信号的频率决定PWM完成一个周期的速度。STM32的MDK编译器可以选择5MHZ,10MHZ,20MHZ和50MHZ。

  • 5.PWM的产生

      STM32的定时器除了TIM6和7,其他的定时器都可以用来产生PWM输出。其中高级定时器TIM1和TIM8可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4路的 PWM 输出,这样,STM32 最多可以同时产生 30 路 PWM 输出。
      通过STM32控制板,有两种方式能产生PWM,第一是利用普通IO口输出PWM,第二种是利用定时器的PWM的IO口或复用IO口。一般能够输出PWM的端口都会在主要功能那一栏出现CHx的标志,而普通定时器没有出现这种标志。如图所示,上面的红框就是普通的定时器,不是专用的PWM端口

    注意:一般而言,尽量选用PWM口进行PWM输出,因为普通IO口模拟PWM的输出频率越高,进入定时器中断的次数就越快,中断间隔的时间越短,如果再有其他类型的中断也要处理时,会因为中断的优先级嵌套等待响应,影响控制精度,PWM输出误差增大,也会影响其他如ADC等中断处理,甚至会较出现单片机逻辑出错,死机或者跑飞的情况。

    6.PWM工作原理

      在下图的通用定时器框图中,主要涉及到最顶上的一部分(计数时钟的选择)、中间部分(时基单元)、右下部分(PWM输出)这三个部分。 

         在PWM输出模式下,除了CNT(计数器当前值)、ARR(自动重装载值)之外,还多了一个值CCRx(捕获/比较寄存器值);
         当CNT小于CCRx时,TIMx_CHx通道输出低电平;
         当CNT等于或大于CCRx时,TIMx_CHx通道输出高电平;
          这个时候就可以对其下一个准确的定义了:所谓脉冲宽度调制模式(PWM模式),就是可以产生一个由TIMx_ARR寄存器确定频率,由TIMx_CCRx寄存器确定占空比的信号。它是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术

    7.PWM输出的模式区别

    PWM模式1:在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为有效电平,否则为无效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为无效电平(OC1REF=0),否则为有效电平(OC1REF=1)

    PWM模式2:在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为无效电平,否则为有效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为有效电平,否则为无效电平
    注意:PWM的模式只是区别什么时候是有效电平,但并没有确定是高电平有效还是低电平有效。这需要结合CCER寄存器的CCxP位的值来确定。

    例如:若PWM模式1,且CCER寄存器的CCxP位为0,则当TIMx_CNT<TIMx_CCR1时,输出高电平;同样的,若PWM模式1,且CCER寄存器的CCxP位为2,则当TIMx_CNT<TIMx_CCR1时,输出低电平。

    8、PWM相关配置寄存器 

             包含三个寄存器:捕获/比较模式寄存器(TIMx_CCMR1/2)、捕获/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~4)。设置TIMx_CCMRx寄存器OCxPE位以使能相应的预装载寄存器,最后还要设置TIMx_CR1寄存器的ARPE位,(在向上计数或中心对称模式中)使能自动重装载的预装载寄存器。在TIMx_CCMRx寄存器中的OCxM位写入110(PWM模式1)或111(PWM模式2),能够独立地设置每个OCx输出通道产生一路PWM。

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

    作用:在PWM输出模式下,确定PWM的模式、使能相应的预装载寄存器等操作

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

    作用:在PWM输出模式下,确定PWM的输出极性和输出使能

    (3)捕获/比较寄存器(TIMx_CCR1) 

  • 作用:在PWM输出模式下,确定比较的值
  •  三、定时器捕获介绍

    输入捕获简介

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

  • 测量频率

  • 当捕获通道 TIx(如TIM2_CH1) 上出现上升沿时,发生第一次捕获,计数器 CNT 的值会被锁存到捕获寄存器 CCR 中,而且还会进入捕获中断,在中断服务程序中记录一次捕获(可以用一个标志变量来记录),并把捕获寄存器中的值读取到 value1中。当出现第二次上升沿时,发生第二次捕获,计数器 CNT 的值会再次被锁存到捕获寄存器 CCR 中,并再次进入捕获中断,在捕获中断中,把捕获寄存器的值读取到 value3 中,并清除捕获记录标志。利用 value3和value1 的差值我们就可以算出信号的周期(频率)。

  • 测量脉宽

  • 当捕获通道 TIx(如TIM2_CH1) 上出现上升沿时,发生第一次捕获,计数器 CNT 的值会被锁存到捕获寄存器 CCR 中,而且还会进入捕获中断,在中断服务程序中记录一次捕获(可以用一个标志变量来记录),并把捕获寄存器中的值读取到 value1中。然后把捕获边沿改变为下降沿捕获,目的是捕获后面的下降沿。当下降沿到来的时候,发生第二次捕获,计数器 CNT的值会再次被锁存到捕获寄存器 CCR中,并再次进入捕获中断,在捕获中断中,把捕获寄存器的值读取到 value3中,并清除捕获记录标志。然后把捕获边沿设置为上升沿捕获。 在测量脉宽过程中需要来回的切换捕获边沿的极性,如果测量的脉宽时间比较长,定时器就会发生溢出,溢出的时候会产生更新中断,我们可以在中断里面对溢出进行记录处理。

    四、解决的问题

    深入了解STM32定时器原理,掌握脉宽调制pwm生成方法。

    1. 使用STM32F103的 Tim2~Tim5其一定时器的某一个通道pin(与GPIOx管脚复用,见下图),连接一个LED,用定时器计数方式,控制LED以2s的频率周期性地亮-灭。

    2. 接上,采用定时器pwm模式,让 LED 以呼吸灯方式渐亮渐灭,周期为1~2秒,自己调整到一个满意效果。使用Keil虚拟示波器,观察 pwm输出波形。

    3. 再接上,采用定时器的另外一个通道,编程采集上面的pwm输出信号,获得其周期和脉宽,并重定向输出到串口显示。

    五、定时器串口定时点亮LED灯

    1、搭建STM32HAL库环境:

    请参考我的这篇博客:STM32使用HAL库点亮流水灯-CSDN博客

     2、使用HAL库建立工程:

    (1)打开STM32CubeMX,在主界面点击:ACCESS TO MCU SELECTOR:

      (2)选择的单片机型号以及点击开始工程项目: 

    (3)配置RCC

    System Cor,选择RCC,在右侧弹出的菜单栏中选Crystal/Ceramic Resonator

    (4)配置SYS

    选择调试接口,点System Cor,选择SYS。,在右侧弹出的菜单栏中选Serial Wire

    (5)配置IO口输出

    这里选择PA0作为LED灯的输出,将其选为GPIO-OUT,这里我们只使用一个灯,做演示用。

    (6)配置定时器2和定时器3

    这里我们使用定时器2和定时器3来实现定时的功能。如图所示,定时器2配置:依次点击位置2,选中定时器2;位置3,配置定时器2的时钟源为内部时钟;位置4,分频系数为71;位置5,向上计数模式,计数周期为5000,使能自动重载模式。

     定时器3配置:依次按照下图所示进行配置即可:

     注:分频系数那里虽然写的是71,但系统处理的时候会自动加上1,所以实际进行的是72分频。由于时钟我们一般会配置为72MHZ,所以72分频后得到1MHZ的时钟。1MHZ的时钟,计数5000次,得到时间5000/1000000=0.005秒。也就是每隔0.005秒定时器2会产生一次定时中断。

    (7)配置中断

    如下图所示,开启定时器2和定时器3的中断。

     接着如下图所示,生成定时器2和定时器3中断优先级配置代码:

    (8)配置USART

    选择Connectivity,点开USART1,Mode选择异步通信Asynchronous

    波特率为115200,1位停止位,无校验位(这里不需要改,默认就是这样)

     (9)进入CLK Configuration (时钟配置)中,进行时钟配置:

     (10)进入Project Manager(工程管理),进行工程设置点击生成工程与代码:

    注意:路径不能包含中文和空格,不然生成的工程文件无法在Keil中打开;

    3、完善keil5工程: 

    (1) 在main.c中添加启动定时器代码:

    	HAL_TIM_Base_Start_IT(&htim2);
    	HAL_TIM_Base_Start_IT(&htim3);
    

    该函数表示启动相应的定时器,“h”表示HAL库,“tim2”表示定时器2。所以这行代码的意思就是启动定时器2和定时器3。 

    (2)串口通信(注:若不需要串口通信,此步可以忽略!)

    在main.c中定义STM32需要给上位机发送的消息

    	uint8_t hello[20]="hello windows!\r\n";
    

    将下列代码放入后续的定时器中断回调函数 :

      HAL_UART_Transmit(&huart1,hello,20,100000);
    

     (3) 在main.c中添加定时器中断回调函数:
     

    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    {
    	static uint32_t time_cnt =0;
    	static uint32_t time_cnt3 =0;
    	if(htim->Instance == TIM2)
    	{
    		if(++time_cnt >= 400)
    		{
    			time_cnt =0;
    			HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_1);
    		}
    	}
    	if(htim->Instance == TIM3)
    	{
    		if(++time_cnt3 >= 1000)
    		{
    			time_cnt3 =0;
        HAL_UART_Transmit(&huart1,hello,20,100000);
    		}
    			
    	}
    }
    

         该函数为定时器的中断回调函数,当产生定时中断的时候,会自动调用这个函数。在函数内部定义了定时器的一个静态变量:time_cnt与定时器3 的time_cnt3。
          例如time_cnt,当它大于等于100的时候,才会执行if里面的代码。也就是说需要发生400次中断,才会让LED的状态翻转。前面已经算过了,一次定时中断的时间是0.005秒,所以400次中断的时间是0.005400=2秒。也就是说每隔2秒,LED的状态翻转一次。
         例如time_cnt3,当它大于等于1000的时候,才会执行if里面的代码。也就是说需要发生1000次中断,才会让串口发一次消息。0.005
    1000=5秒,符合题目要求。

    (4)完整main.c代码如下:
     

    /* USER CODE BEGIN Header */
    /**
      ******************************************************************************
      * @file           : main.c
      * @brief          : Main program body
      ******************************************************************************
      * @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 "main.h"
    #include "tim.h"
    #include "usart.h"
    #include "gpio.h"
    
    /* Private includes ----------------------------------------------------------*/
    /* USER CODE BEGIN Includes */
    
    /* USER CODE END Includes */
    
    /* Private typedef -----------------------------------------------------------*/
    /* USER CODE BEGIN PTD */
    
    /* USER CODE END PTD */
    
    /* Private define ------------------------------------------------------------*/
    /* USER CODE BEGIN PD */
    /* USER CODE END PD */
    
    /* Private macro -------------------------------------------------------------*/
    /* USER CODE BEGIN PM */
    
    /* USER CODE END PM */
    
    /* Private variables ---------------------------------------------------------*/
    
    /* USER CODE BEGIN PV */
    
    /* USER CODE END PV */
    
    /* Private function prototypes -----------------------------------------------*/
    void SystemClock_Config(void);
    static void MX_NVIC_Init(void);
    /* USER CODE BEGIN PFP */
    
    /* USER CODE END PFP */
    
    /* Private user code ---------------------------------------------------------*/
    /* USER CODE BEGIN 0 */
    
    /* USER CODE END 0 */
    
    /**
      * @brief  The application entry point.
      * @retval int
      */
      uint8_t hello[20]="hello windows!\r\n";
    int main(void)
    {
      /* USER CODE BEGIN 1 */
    
      /* USER CODE END 1 */
    
      /* MCU Configuration--------------------------------------------------------*/
    
      /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
      HAL_Init();
    
      /* USER CODE BEGIN Init */
    
      /* USER CODE END Init */
    
      /* Configure the system clock */
      SystemClock_Config();
    
      /* USER CODE BEGIN SysInit */
    
      /* USER CODE END SysInit */
    
      /* Initialize all configured peripherals */
      MX_GPIO_Init();
      MX_TIM2_Init();
      MX_TIM3_Init();
      MX_USART1_UART_Init();
    
      /* Initialize interrupts */
      MX_NVIC_Init();
    	HAL_TIM_Base_Start_IT(&htim2);
    	HAL_TIM_Base_Start_IT(&htim3);
    
      /* USER CODE BEGIN 2 */
    
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    }
    
    /**
      * @brief System Clock Configuration
      * @retval None
      */
    void SystemClock_Config(void)
    {
      RCC_OscInitTypeDef RCC_OscInitStruct = {0};
      RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
      /** Initializes the RCC Oscillators according to the specified parameters
      * in the RCC_OscInitTypeDef structure.
      */
      RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
      RCC_OscInitStruct.HSEState = RCC_HSE_ON;
      RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
      RCC_OscInitStruct.HSIState = RCC_HSI_ON;
      RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
      RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
      RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
      if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
      {
        Error_Handler();
      }
    
      /** Initializes the CPU, AHB and APB buses clocks
      */
      RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                  |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
      RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
      RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
      RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
      RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    
      if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
      {
        Error_Handler();
      }
    }
    
    /**
      * @brief NVIC Configuration.
      * @retval None
      */
    static void MX_NVIC_Init(void)
    {
      /* TIM2_IRQn interrupt configuration */
      HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
      HAL_NVIC_EnableIRQ(TIM2_IRQn);
      /* TIM3_IRQn interrupt configuration */
      HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
      HAL_NVIC_EnableIRQ(TIM3_IRQn);
    }
    
    /* USER CODE BEGIN 4 */
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    {
    	static uint32_t time_cnt =0;
    	static uint32_t time_cnt3 =0;
    	if(htim->Instance == TIM2)
    	{
    		if(++time_cnt >= 400)
    		{
    			time_cnt =0;
    			HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_0);
    		}
    	}
    	if(htim->Instance == TIM3)
    	{
    		if(++time_cnt3 >= 1000)
    		{
    			time_cnt3 =0;
        HAL_UART_Transmit(&huart1,hello,20,100000);
    		}
    			
    	}
    }
    
    /* USER CODE END 4 */
    /**
      * @brief  This function is executed in case of error occurrence.
      * @retval None
      */
    void Error_Handler(void)
    {
      /* USER CODE BEGIN Error_Handler_Debug */
      /* User can add his own implementation to report the HAL error return state */
      __disable_irq();
      while (1)
      {
      }
      /* USER CODE END Error_Handler_Debug */
    }
    
    #ifdef  USE_FULL_ASSERT
    /**
      * @brief  Reports the name of the source file and the source line number
      *         where the assert_param error has occurred.
      * @param  file: pointer to the source file name
      * @param  line: assert_param error line source number
      * @retval None
      */
    void assert_failed(uint8_t *file, uint32_t line)
    {
      /* USER CODE BEGIN 6 */
      /* User can add his own implementation to report the file name and line number,
         ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
      /* USER CODE END 6 */
    }
    #endif /* USE_FULL_ASSERT */
    
    

    4、电路连接:

    USB转TTL与STM32的连接,参考下图: 

     PA0——LED黄灯

     5、烧录运行结果展示:

    六、PWM完成呼吸灯

    1、使用HAL库建立工程:

     (1)打开STM32CubeMX,在主界面点击:ACCESS TO MCU SELECTOR:

     (2)选择的单片机型号以及点击开始工程项目: 

    (3)配置RCC

    System Cor,选择RCC,在右侧弹出的菜单栏中选Crystal/Ceramic Resonator

    (4)配置SYS

    选择调试接口,点System Cor,选择SYS。,在右侧弹出的菜单栏中选Serial Wire

    (5)配置定时器3和定时器4

    这里我们选择定时器3和定时器4来实现定时的功位置3,分频系数为71,向上计数模式,计数周期为500,使能自动重载模式。通道1选择:PWM Generation CH1(PWM输出通道1)
    设置分频系数为71,计数周期为500,其它默认。
    设置占空比初始值为10,其实这里不写也没影响。(因为后续我们可以根据代码对其进行修改)

     定时器四我们也选择PWM Generation CH1(PWM输出通道1),计数周期根据自己需要进行设置

     (5)进入CLK Configuration (时钟配置)中,进行时钟配置:

     (6)进入Project Manager(工程管理),进行工程设置点击生成工程与代码:

    注意:路径不能包含中文和空格,不然生成的工程文件无法在Keil中打开;

     2、完善keil5工程:

    (1)设置占空比

    打开工程main.c文件。首先定义一个变量,用来存储占空比:初值设为10:

    uint16_t duty_num3 = 10;
    uint16_t duty_num4 = 10;
    

    (2)开启PWM信道

    开始TIM3的通道3,输出PWM。
    开始TIM4的通道4,输出PWM。

    	HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
    	HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_1);
    

    (3)调用代码

    这里我们设置为每隔50毫秒,占空比加10,如果超过500(也就是PWM周期),自动变成0。(即灯会从亮倒暗,逐渐变化)

      while (1)
      {
        /* USER CODE END WHILE */
     HAL_Delay(50);
    		duty_num3 = duty_num3 + 10;
    			duty_num4 = duty_num4 + 10;
    		if(duty_num3 > 500)
    		{
    			duty_num3 = 0;
    		}
    		__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_1,duty_num3);
    			if(duty_num4 > 500)
    		{
    			duty_num4 = 0;
    		}
    		__HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_1,duty_num4);
        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    }
    

    (4)完整main.c代码如下所示:

    /* USER CODE BEGIN Header */
    /**
      ******************************************************************************
      * @file           : main.c
      * @brief          : Main program body
      ******************************************************************************
      * @attention
      *
      * Copyright (c) 2023 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 "main.h"
    #include "tim.h"
    #include "gpio.h"
    
    /* Private includes ----------------------------------------------------------*/
    /* USER CODE BEGIN Includes */
    
    /* USER CODE END Includes */
    
    /* Private typedef -----------------------------------------------------------*/
    /* USER CODE BEGIN PTD */
    
    /* USER CODE END PTD */
    
    /* Private define ------------------------------------------------------------*/
    /* USER CODE BEGIN PD */
    
    /* USER CODE END PD */
    
    /* Private macro -------------------------------------------------------------*/
    /* USER CODE BEGIN PM */
    
    /* USER CODE END PM */
    
    /* Private variables ---------------------------------------------------------*/
    
    /* USER CODE BEGIN PV */
    
    /* USER CODE END PV */
    
    /* Private function prototypes -----------------------------------------------*/
    void SystemClock_Config(void);
    /* USER CODE BEGIN PFP */
    
    /* USER CODE END PFP */
    
    /* Private user code ---------------------------------------------------------*/
    /* USER CODE BEGIN 0 */
    
    /* USER CODE END 0 */
    
    /**
      * @brief  The application entry point.
      * @retval int
      */
      uint16_t duty_num3 = 10;
    uint16_t duty_num4 = 10;
    
    
    int main(void)
    {
      /* USER CODE BEGIN 1 */
    
      /* USER CODE END 1 */
    
      /* MCU Configuration--------------------------------------------------------*/
    
      /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
      HAL_Init();
    
      /* USER CODE BEGIN Init */
    
      /* USER CODE END Init */
    
      /* Configure the system clock */
      SystemClock_Config();
    
      /* USER CODE BEGIN SysInit */
    
      /* USER CODE END SysInit */
    
      /* Initialize all configured peripherals */
      MX_GPIO_Init();
      MX_TIM3_Init();
      MX_TIM4_Init();
      	HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
    	HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_1);
    
      /* USER CODE BEGIN 2 */
    
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        /* USER CODE END WHILE */
            HAL_Delay(50);
    		duty_num3 = duty_num3 + 10;
    			duty_num4 = duty_num4 + 10;
    		if(duty_num3 > 500)
    		{
    			duty_num3 = 0;
    		}
    		__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_1,duty_num3);
    			if(duty_num4 > 500)
    		{
    			duty_num4 = 0;
    		}
    		__HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_1,duty_num4);
        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    }
    
    /**
      * @brief System Clock Configuration
      * @retval None
      */
    void SystemClock_Config(void)
    {
      RCC_OscInitTypeDef RCC_OscInitStruct = {0};
      RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
      /** Initializes the RCC Oscillators according to the specified parameters
      * in the RCC_OscInitTypeDef structure.
      */
      RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
      RCC_OscInitStruct.HSEState = RCC_HSE_ON;
      RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
      RCC_OscInitStruct.HSIState = RCC_HSI_ON;
      RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
      RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
      RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
      if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
      {
        Error_Handler();
      }
    
      /** Initializes the CPU, AHB and APB buses clocks
      */
      RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                  |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
      RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
      RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
      RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
      RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    
      if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
      {
        Error_Handler();
      }
    }
    
    /* USER CODE BEGIN 4 */
    
    /* USER CODE END 4 */
    
    /**
      * @brief  This function is executed in case of error occurrence.
      * @retval None
      */
    void Error_Handler(void)
    {
      /* USER CODE BEGIN Error_Handler_Debug */
      /* User can add his own implementation to report the HAL error return state */
      __disable_irq();
      while (1)
      {
      }
      /* USER CODE END Error_Handler_Debug */
    }
    
    #ifdef  USE_FULL_ASSERT
    /**
      * @brief  Reports the name of the source file and the source line number
      *         where the assert_param error has occurred.
      * @param  file: pointer to the source file name
      * @param  line: assert_param error line source number
      * @retval None
      */
    void assert_failed(uint8_t *file, uint32_t line)
    {
      /* USER CODE BEGIN 6 */
      /* User can add his own implementation to report the file name and line number,
         ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
      /* USER CODE END 6 */
    }
    #endif /* USE_FULL_ASSERT */
    

     3、电路连接:

    USB转TTL与STM32的连接,参考下图:

    PA6——黄灯;

    连接好的实物图如下所示:

    4、烧录运行结果展示:

    5、Keil虚拟示波器,观察 pwm输出波形:

    (1、进入keil5仿真:

    (1)点击第一步,Target界面中,选择跟正确的晶振大小,使用8MHz的外部晶振:

    (2)接着进行Debug页的设置:  

     (3)点击图示圈住的地方进入仿真调试界面: 

      (4)选择逻辑分析仪: 

     (5)点击Setup设置添加要进行观察的引脚:

     添加引脚信息:添加引脚信息时候,PA脚输入:PORTA,PB脚输入PORTB,PC脚输入PORTC;接着输入".",接着在“.”后面输入对应引脚号,最后回车即可;例如:假如我这里要输入PA0这个引脚的信息:添加一个引脚,添加一个引脚信息:PORTA.0,最后回车完成添加!!!

     接着对例子引脚的配置信息进行修改,如下图所示: 

    知道了怎么进行添加引脚之后,我们选择添加PA6,并且选择PA6的波形颜色为红色,如下图所示:

    ( 2、开始仿真:

    (1)点击图示圈住的部分,进行仿真  

      (2)仿真结果: 

    七、定时器pwm完成呼吸灯和对于pwm输出信号的采集与捕获

     1、使用HAL库建立工程

      (1)打开STM32CubeMX,在主界面点击:ACCESS TO MCU SELECTOR:

     (2)选择的单片机型号以及点击开始工程项目: 

    (3)配置RCC

    System Cor,选择RCC,在右侧弹出的菜单栏中选Crystal/Ceramic Resonator

    (4)配置SYS

    选择调试接口,点System Cor,选择SYS。,在右侧弹出的菜单栏中选Serial Wire

     (5)按照下图所示,选择对应的单片机端口配置使用:

     (6)串口通信配置,如下图所示:

    (7)定时器定时配置(TIM2),按照图中步骤进行配置即可:

     

    (8)PWM波形捕获(TIM1),按照图中步骤进行配置即可:

    (9)PWM生成配置(TIM3),按照图中步骤进行配置即可: 

    2、完善keil工程:

     完善之后的完整main.c代码如下:

    #include "main.h"
    #include "tim.h"
    #include "usart.h"
    #include "gpio.h"
    
    uint8_t i = 0;
    
    float Duty = 0;
    float Frequency = 0;
    uint16_t Cap_val1 = 0;
    uint16_t Cap_val2 = 0;
    
    int main(void)
    {
        HAL_Init();
        SystemClock_Config();
        MX_GPIO_Init();
        MX_TIM1_Init();
        MX_TIM3_Init();
        MX_USART1_UART_Init();
        MX_TIM2_Init();
        /* USER CODE BEGIN WHILE */
    
        printf("串口通信测试\r\n");
        HAL_TIM_Base_Start_IT(&htim2); // 使能定时器及其更新中断
        HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); // 使能定时器及其PWM输出
        HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_1);       // 使能定时器及其输入捕获
        HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_2);       // 使能定时器及其输入捕获
        __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 10); // 设置一个PWM波形进行测量
    
        while (1)
        {
            // 串口发送 频率 占空比
            printf("Cap_val1 is :%d ,  Cap_val2 is : %d \r\n", Cap_val1, Cap_val2);
            printf("Duty is :%0.2f%% Frequency is : %0.2f ms\r\n", Duty, Frequency);
            HAL_Delay(1000);
        }
    }
    
    
    // 定时TIM2 定时亮灯的中断函数
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *tim)
    {
        if (tim == &htim2)
        {
            HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
        }
    }
    
    // 定时输入捕获回调函数 计算占空比和频率
    void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
    {
        if (htim->Instance == TIM1)
        {
            if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
            {
                Cap_val1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
            }
            if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
            {
                Cap_val2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2);
                Duty = 100 - (float)Cap_val2 / (float)Cap_val1 * 100;
                Frequency = 0.001 * Cap_val1;
            }
        }
    }
    
    

     3、电路连接:

     USB转TTL与STM32的连接,参考下图:

    PA6——黄灯;

    连接好的实物图如下所示:

    4、烧录运行结果展示:

    (1)板子上LED灯呼吸灯:

    (2)板子采集上面的pwm输出信号,获得其周期和脉宽,并重定向输出到串口显示:

    八、总结 

            之前本人是通过中断或者粗略计时来实现LED灯亮与灭,实则既没那么精确又浪费了资源。而定时器可以很好地满足我们的需求,通过本次实验,我学会了利用定时器来控制LED的亮与灭的精确定时。

            本人通过这次训练,新学习到了PWM的相关理论知识,并学会了对其的基本应用。特别是采用定时器pwm模式将LED灯的呼吸效果做出来的时候,彷佛感觉这灯真的在呼吸,就是差点呼吸的声音,很逼真的。总的来说,自己在每做一次STM32的实践或者实验,都在边学边做边进步的路上。希望阅读本文的你也也也是一样。完!!!

    九、参考资料

    1、STM32速成笔记—定时器 – 知乎 (zhihu.com)

     2、STM32第八章-TIM输入捕获 – 知乎 (zhihu.com)

    3、【嵌入式07】定时器&amp;PWM练习_冉冉—ry的博客-CSDN博客 

    4、定时器&PWM_pwm定时器_独有凝雨姿的博客-CSDN博客 

    作者:-HSheng

    物联沃分享整理
    物联沃-IOTWORD物联网 » 使用HAL库在STM32上实现定时器功能

    发表评论