STM32基础知识:定时器的定时计数功能

描述

定时器定时计数功能

1 定时器概述

  • 定时器是对周期固定的脉冲信号进行计数,如MCU内部的外设时钟(APB)。
  • 计数器是对周期不确定的脉冲信号进行计数,如MCU的I/O引脚所引入的外部脉冲信号。
  • 定时器和计数器本质上都是计数器,定时器是计数器的一种特例。

2 STM32定时器分类

脉冲信号

对于STM32F103共存在6个外设定时器:高级定时器TIM1,通用定时器TIM2、TIM3、TIM4。

2.1 内核定时器

  • 系统节拍定时器:Systick定时器是属于内核中的一个外设,内嵌在NVIC中。

2.2 外设定时器

  • 常规定时器> STM32F103xx增强型产品中,内置了多达3个可同步运行的标准定时器(TIM2、TIM3和TIM4)。每个定时器都有一个16位的自动加载递加/递减计数器、一个16位的预分频器和4个独立的通道,每个通道都可用于输入捕获、输出比较、PWM和单脉冲模式输出.
    • 高级定时器:除具备通用定时器的功能外,还具备带死区控制的互补信号输出、紧急刹车关断输入等功能,可用于电机控制和数字电源设计。
    • 基本定时器:几乎没有任何输入/输出通道,常用作时基,实现基本的定时/计数功能。
    • 通用定时器:具备多路独立的捕获和比较通道,可以完成定时/计数、输入捕获、输出比较等功能。
  • 专用定时器
    • 看门狗定时器
    • 实时时钟
    • 低功耗定时器

3 定时器的时钟频率

脉冲信号

4 定时器的主要功能

4.1 定时计数

  • 计数内部时钟,即定时器模式
  • 计数外部脉冲,即计数器模式

4.2 输出比较

  • PWM输出
  • 电平翻转
  • 单脉冲输出
  • 强制输出

4.3 输入捕获

  • 捕获时保存定时器的当前计数值
  • 捕获时,可选择触发捕获中断
  • 触发捕获的信号边沿类型可选择(上升沿,下降沿,双边沿)

5 HAL库的定时器设计

5.1 定时器句柄结构的组成

脉冲信号

5.2 三种外设编程模型

  • 以后缀区分编程模型
  • 入口参数均为外设句柄的指针
方式函数
轮询方式HAL_TIM_Base_Start(TIM_HandleTypeDef *htim );HAL_TIM_Base_Stop TIM_HandleTypeDef *htim );
中断方式HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);HAL_TIM_Base_Stop_IT(TIM_HandleTypeDef *htim);
DMA方式HAL_TIM_Base_Start_DMA(TIM_HandleTypeDef *htim, uint32_t *pData, uint16_t Length);

6 定时器的定时/计数功能

6.1 时基单元

脉冲信号脉冲信号

①预分频模块

  • 预分频计数器:对预分频时钟CK_PSC进行分频
  • 预分频寄存器:TIMx_PSC:设置预分频系数PSC
  • 作用1:扩大定时器的定时范围
  • 作用2:获取精确的计数时钟

预分频模块工作原理: 定时器启动后,预分频计数器的初值为0,预分频时钟CK_PSC每来一个时钟,预分频计数器的值就加1。当计数值等于预分频寄存器所设定的预分频系数PSC时,预分频计数器的值将清零,开始下一轮计数。

预分频时序图:脉冲信号

假设预分频系数PSC=3,预分频计数器从0计数到PSC实际计数值为PSC+1,也就是进行了四分频。
脉冲信号

②计数模块

  • 核心计数器:对计数时钟CK_CNT进行二次计数
  • 计数器寄存器:TIMx_CNT:存放核心计数器运行时的当前计数值

③自动重载模块

  • 自动重载模块由自动重载寄存器TIMx_ARR组成
    • 递增计数模式:TIMx_ARR的值作为核心计数器的计数终值
    • 递减计数模式:TIMx_ARR的值作为核心计数器的计数初值

④计数模式

脉冲信号

计数模式计数器溢出值计数器重载值
递增计数CNT=ARRCNT=0
递减计数CNT=0CNT=ARR
中心对齐计数CNT=ARR-1CNT=1CNT=ARRCNT=0

6.2 定时器时序图

脉冲信号

①定时时间公式

②相关寄存器

  • 预分频寄存器TIMx_PSC: 设置预分频系数,将预分频时钟(CK_PSC)进行1~65536之间的任意值分频,得到计数时钟(CK_CNT)。
  • 计数器寄存器TIMx_CNT : 存放核心计数器运行时的当前计数值,便于用户实时掌握核心计数器的当前计数值。芯片复位后,默认值为0。
  • 自动重载寄存器TIMx_ARR: 为计数器设置计数边界或重载值。比如计数器递增计数时,记到多少发生溢出;递减计数时,从多少开始往下计数。

6.3 外部脉冲计数

脉冲信号

6.4 数据类型和接口函数

脉冲信号

成员变量:

成员变量ClockDivision的取值范围:

TIM_CLOCKDIVISION_DIV1对定时器时钟TIM_CLK进行1分频
TIM_CLOCKDIVISION_DIV2对定时器时钟TIM_CLK进行2分频
TIM_CLOCKDIVISION_DIV4对定时器时钟TIM_CLK进行4分频

成员变量CounterMode的取值范围

TIM_COUNTERMODE_UP递增计数模式
TIM_COUNTERMODE_DOWN递减计数模式
TIM_COUNTERMODE_CENTERALIGNED1中心对齐计数模式1
TIM_COUNTERMODE_CENTERALIGNED2中心对齐计数模式2
TIM_COUNTERMODE_CENTERALIGNED3中心对齐计数模式3

成员变量AutoReloadPreload的取值范围

TIM_AUTORELOAD_PRELOAD_DISABLE预装载功能关闭
TIM_AUTORELOAD_PRELOAD_ENABLE预装载功能开启
  1. 用于设置自动重载寄存器TIMx_ARR的预装载功能,即自动重装寄存器的内容是更新事件产生时写入有效,还是立即写入有效;
  2. 预装载功能在多个定时器同时输出信号时比较有用,可以确保多个定时器的输出信号在同一个时刻变化,实现同步输出;
  3. 单个定时器输出时,一般不开启预装载功能。

接口函数:

  1. 时基单元初始化函数:HAL_TIM_Base_Init

    函数原型HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim)
    功能描述按照定时器句柄中指定的参数初始化定时器时基单元
    入口参数htim:定时器句柄的地址
    返回值HAL状态值
    注意事项1. 该函数将调用MCU底层初始化函数HAL_TIM_Base_MspInit完成引脚、时钟和中断的设置2. 该函数由CubeMX自动生成
  2. 轮询模式启动函数:HAL_TIM_Base_Start

    函数原型HAL_StatusTypeDef HAL_TIM_Base_Start(TIM_HandleTypeDef *htim)
    功能描述在轮询方式下启动定时器运行
    入口参数htim:定时器句柄的地址
    返回值HAL状态值
    注意事项1. 该函数在定时器初始化完成之后调用2. 函数需要由用户调用,用于轮询方式下启动定时器运行
  3. 中断模式启动函数:HAL_TIM_Base_Start_IT

    函数原型HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)
    功能描述使能定时器的更新中断,并启动定时器运行
    入口参数htim:定时器句柄的地址
    返回值HAL状态值
    注意事项1. 该函数在定时器初始化完成之后调用2. 函数需要由用户调用,用于使能定时器的更新中断,并启动定时器运行3. 启动前需要调用宏函数 __HAL_TIM_CLEAR_IT 来清除更新中断标志
  4. 定时器中断通用处理函数HAL_TIM_IRQHandler

    函数原型void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim)
    功能描述作为所有定时器中断发生后的通用处理函数
    入口参数htim:定时器句柄的地址
    返回值
    注意事项1. 函数内部先判断中断类型,并清除对应的中断标志,最后调用回调函数完成中断处理2. 该函数由CubeMX自动生成
  5. 定时器更新中断回调函数HAL_TIM_PeriodElapsedCallback

    函数原型void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    功能描述回调函数,用于处理所有定时器的更新中断,用户在该函数内编写实际的任务处理程序
    入口参数htim:定时器句柄的地址
    返回值
    注意事项1.该函数由定时器中断通用处理函数HAL_TIM_IRQHandler调用,完成所有定时器的更新中断的任务处理2.函数内部需要根据定时器句柄的实例来判断是哪一个定时器产生的本次更新中断3.函数由用户根据具体的处理任务编写
  6. 计数值读取函数__HAL_TIM_GET_COUNTER

    #define __HAL_TIM_GET_COUNTER(__HANDLE__) ((__HANDLE__)- >Instance- >CNT)
    

    __HANDLE__:定时器句柄的地址

    该函数通过直接访问计数器寄存器TIMx_CNT来获取计数器的当前计数值。

  7. 定时器中断标志清除函数__HAL_TIM_CLEAR_IT

    #define __HAL_TIM_CLEAR_IT(__HANDLE__, __INTERRUPT__) ((__HANDLE__)- >Instance- >SR = ~(__INTERRUPT__))
    

    __HANDLE__:定时器句柄的地址

    __INTERRUPT__:定时器中断标志

    脉冲信号

任务实践1

基于STM32F103C8T6,开发板原理图

脉冲信号

利用开发板上的按键KEY2来触发外部脉冲,按键每按下一次,就利用PA1引脚发送一个周期2ms左右的脉冲,送到定时器2的外部触发引脚ETR(PA0)进行计数,并将计数结果通过串口发送到PC上显示。

注:本任务例程使用的开发板,KEY2与PA5相连接。KEY2原理图如下:

脉冲信号

使用按键时,需要设置PA5为输入上拉模式,这样在KEY2没有按下时,PA5可以读取到高电平,KEY2按下时PA5可以读取到低电平。

  1. 配置PA1为GPIO_Output(User Label:PULSE),PA5为GPIO_Input(User Label:KEY2),上拉模式。
    脉冲信号
    脉冲信号
    上述操作在stm32f1xx_hal_gpio.c中生成GPIO引脚初始化函数MX_GPIO_Init,并在main.c中调用

    void MX_GPIO_Init(void)
    {
    
      GPIO_InitTypeDef GPIO_InitStruct = {0};
    
      /* GPIO Ports Clock Enable */
      __HAL_RCC_GPIOA_CLK_ENABLE();
      __HAL_RCC_GPIOB_CLK_ENABLE();
    
      /*Configure GPIO pin Output Level */
      HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
    
      /*Configure GPIO pin : PA1 */
      GPIO_InitStruct.Pin = GPIO_PIN_1;
      GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
      GPIO_InitStruct.Pull = GPIO_NOPULL;
      GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
      HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
      /*Configure GPIO pin : PA5 */
      GPIO_InitStruct.Pin = GPIO_PIN_5;
      GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
      GPIO_InitStruct.Pull = GPIO_PULLUP;
      HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    }
    
  2. 配置定时器2时钟源为外部触发引脚ETR2,自动重载寄存器ARR配置为一个相对较大的计数,防止溢出。外部脉冲信号采用默认配置:不使用滤波,不进行脉冲信号反相,不进行脉冲信号分频。
    脉冲信号
    脉冲信号
    上述操作在tim.c生成引脚初始化函数:

    void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
    {
    
      GPIO_InitTypeDef GPIO_InitStruct = {0};
      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();
    
        __HAL_RCC_GPIOA_CLK_ENABLE();
        /**TIM2 GPIO Configuration
        PA0-WKUP     ------ > TIM2_ETR
        */
        GPIO_InitStruct.Pin = GPIO_PIN_0;
        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
      /* USER CODE BEGIN TIM2_MspInit 1 */
    
      /* USER CODE END TIM2_MspInit 1 */
      }
    }
    
    void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
    {
    
      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();
    
        /**TIM2 GPIO Configuration
        PA0-WKUP     ------ > TIM2_ETR
        */
        HAL_GPIO_DeInit(GPIOA, GPIO_PIN_0);
    
      /* USER CODE BEGIN TIM2_MspDeInit 1 */
    
      /* USER CODE END TIM2_MspDeInit 1 */
      }
    }
    

    上述操作在tim.c生成如下代码完成初始化定时器,并在main.c中调用

    /* TIM2 init function */
    void MX_TIM2_Init(void)
    {
    
      /* USER CODE BEGIN TIM2_Init 0 */
    
      /* USER CODE END TIM2_Init 0 */
    
      TIM_ClockConfigTypeDef sClockSourceConfig = {0};
      TIM_MasterConfigTypeDef sMasterConfig = {0};
    
      /* USER CODE BEGIN TIM2_Init 1 */
    
      /* USER CODE END TIM2_Init 1 */
      htim2.Instance = TIM2;
      htim2.Init.Prescaler = 0;
      htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
      htim2.Init.Period = 65535;
      htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
      htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
      if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
      {
        Error_Handler();
      }
      sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_ETRMODE2;
      sClockSourceConfig.ClockPolarity = TIM_CLOCKPOLARITY_NONINVERTED;
      sClockSourceConfig.ClockPrescaler = TIM_CLOCKPRESCALER_DIV1;
      sClockSourceConfig.ClockFilter = 0;
      if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
      {
        Error_Handler();
      }
      sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
      sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
      if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
      {
        Error_Handler();
      }
      /* USER CODE BEGIN TIM2_Init 2 */
    
      /* USER CODE END TIM2_Init 2 */
    
    }
    

    其中调用的HAL_TIM_Base_Init函数将调用HAL_TIM_Base_MspInit完成引脚初始化。

  3. 配置串口外设USART1,选择异步模式,无硬件流控。
    脉冲信号
    上述操作在usart.c中生成串口配置相关函数,并在main.c中调用。usart的封装逻辑与tim相同,不再赘述。

    void MX_USART1_UART_Init(void)
    {
    
      /* USER CODE BEGIN USART1_Init 0 */
    
      /* USER CODE END USART1_Init 0 */
    
      /* USER CODE BEGIN USART1_Init 1 */
    
      /* USER CODE END USART1_Init 1 */
      huart1.Instance = USART1;
      huart1.Init.BaudRate = 115200;
      huart1.Init.WordLength = UART_WORDLENGTH_8B;
      huart1.Init.StopBits = UART_STOPBITS_1;
      huart1.Init.Parity = UART_PARITY_NONE;
      huart1.Init.Mode = UART_MODE_TX_RX;
      huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
      huart1.Init.OverSampling = UART_OVERSAMPLING_16;
      if (HAL_UART_Init(&huart1) != HAL_OK)
      {
        Error_Handler();
      }
      /* USER CODE BEGIN USART1_Init 2 */
    
      /* USER CODE END USART1_Init 2 */
    
    }
    
    void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
    {
    
      GPIO_InitTypeDef GPIO_InitStruct = {0};
      if(uartHandle- >Instance==USART1)
      {
      /* USER CODE BEGIN USART1_MspInit 0 */
    
      /* USER CODE END USART1_MspInit 0 */
        /* USART1 clock enable */
        __HAL_RCC_USART1_CLK_ENABLE();
    
        __HAL_RCC_GPIOA_CLK_ENABLE();
        /**USART1 GPIO Configuration
        PA9     ------ > USART1_TX
        PA10     ------ > USART1_RX
        */
        GPIO_InitStruct.Pin = GPIO_PIN_9;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
        GPIO_InitStruct.Pin = GPIO_PIN_10;
        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
      /* USER CODE BEGIN USART1_MspInit 1 */
    
      /* USER CODE END USART1_MspInit 1 */
      }
    }
    
    void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
    {
    
      if(uartHandle- >Instance==USART1)
      {
      /* USER CODE BEGIN USART1_MspDeInit 0 */
    
      /* USER CODE END USART1_MspDeInit 0 */
        /* Peripheral clock disable */
        __HAL_RCC_USART1_CLK_DISABLE();
    
        /**USART1 GPIO Configuration
        PA9     ------ > USART1_TX
        PA10     ------ > USART1_RX
        */
        HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);
    
      /* USER CODE BEGIN USART1_MspDeInit 1 */
    
      /* USER CODE END USART1_MspDeInit 1 */
      }
    }
    
  4. 程序编写
    使用串口输出,需要在Keil的Options中勾选Use MicroLIB.
    脉冲信号
    在main.c中重定义printfscanf函数

    /* USER CODE BEGIN Includes */
    #include < stdio.h >
    /* USER CODE END Includes */
    
    /* USER CODE BEGIN 4 */
    int fputc (int ch, FILE *f)
    {
        HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
        return ch;
    }
    int fgetc(FILE *f)
    {
        uint8_t ch = 0;
        HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
        return ch;
    }
    /* USER CODE END 4 */
    

    用户变量定义代码

    /* USER CODE BEGIN PV */
    uint8_t Result = 0;
    /* USER CODE END PV */
    

    用户变量初始化代码

    /* USER CODE BEGIN 2 */
      HAL_TIM_Base_Start_IT(&htim2);
      printf("Timer count function test: n");
      /* USER CODE END 2 */
    

    用户应用代码

    /* USER CODE BEGIN 3 */
        if (HAL_GPIO_ReadPin(GPIOA, KEY2_Pin) == GPIO_PIN_RESET)
        {
          HAL_Delay(10);
          if (HAL_GPIO_ReadPin(GPIOA, KEY2_Pin) == GPIO_PIN_RESET)
          {
            HAL_GPIO_WritePin(GPIOA, PULSE_Pin, GPIO_PIN_SET);
            HAL_Delay(1);
            HAL_GPIO_WritePin(GPIOA, PULSE_Pin, GPIO_PIN_RESET);
            HAL_Delay(1);
            Result = __HAL_TIM_GET_COUNTER(&htim2);
            printf("Count = %d", Result);
          }
          while (HAL_GPIO_ReadPin(GPIOA, KEY2_Pin) == GPIO_PIN_RESET);
        }
      }
      /* USER CODE END 3 */
    

    实验现象

    脉冲信号

任务实践2

设计电子时钟,从00:00:00开始计时,并将计时信息通过串口UART1发送到PC进行显示。

  1. 配置定时器1产生1s的更新中断。计算PSC和ARR的值,将T=1s,TIM_CLK=8000000Hz带入公式
    得可取值PSC=1999,ARR=3999.
    在CubeMX中使能TIM1,采用内部时钟 (8MHz) ,设置PSC和ARR值,并在嵌套向量中断控制器NVIC设置中使能TIM1的更新中断。
    脉冲信号
    脉冲信号
    上述操作在tim.c中生成TIM1初始化函数,并在main.c中调用

  2. 配置USART1串口输出,生成代码后应在工程中重定向printfscanf函数,方法同任务实践1,这部分不再赘述。

  3. 编写代码
    main.c中进行用户数据类型定义

    /* USER CODE BEGIN PTD */
    typedef struct
    {
      uint8_t hour;
      uint8_t minutes;
      uint8_t second;
    }CLOCK_Typedef;
    /* USER CODE END PTD */
    

    用户变量定义

    /* USER CODE BEGIN PV */
    CLOCK_Typedef clock = {0};
    /* USER CODE END PV */
    

    用户初始化代码

    /* USER CODE BEGIN 2 */
     // 清除更新中断标志,避免定时器一启动就进入中断
      __HAL_TIM_CLEAR_IT(&htim1, TIM_IT_UPDATE);
      HAL_TIM_Base_Start_IT(&htim1);
      /* USER CODE END 2 */
    

    用户应用代码

    /* USER CODE BEGIN 3 */
        printf("Time:%02d:%02d:%02d.rn", clock.hour, clock.minutes, clock.second);
        HAL_Delay(1000);
      }
      /* USER CODE END 3 */
    

    编写定时器更新中断回调函数

    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    {
      if (htim- >Instance == TIM1)  // 判断时钟源
      {
        clock.second++;
        if (clock.second == 60)
        {
          clock.second = 0;
          clock.minutes++;
          if (clock.minutes == 60)
          {
            clock.minutes = 0;
            clock.hour++;
            if (clock.hour == 24)
            {
              clock.hour = 0;
            }
          }
        }
      }
    }
    
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分