炸鸡派-定时器基础例程

定时器简介

基本定时器,计数中断、产生DMA请求。

通用定时器,PWM输出、输入捕获、脉冲计数。

高级定时器,输出比较、互补输出带死区控制、PWM输入。

中心对齐的计数模式可以生成对称的PWM波形信号。计数可以先增后减。

这种模式下,PWM 的高电平和低电平时间相等,从而使得 PWM 波形在中心线上对称。

main.c

cpp 复制代码
int main(void)
{
  // 初始化FLASH、SYSTIC中断,使能SYSCFG时钟和PWR时钟
  HAL_Init();

  // 配置系统时钟,包括PWR等级,各AHB、APB1/2时钟参数
  SystemClock_Config();

  // 初始化GPIO
  MX_GPIO_Init();
  // 初始化DMA
  MX_DMA_Init();
  // 初始化USART1 UART
  MX_USART1_UART_Init();
  // 初始化TIM11定时器
  MX_TIM11_Init();
  // 使用DMA接收UART数据,接收50字节
  HAL_UART_Receive_DMA(&huart1, RXbuf, 50);
  // 使能UART空闲中断
  __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
  // 启动TIM11定时器中断
  HAL_TIM_Base_Start_IT(&htim11);

  // 主循环
  while (1)
  {
    // 判断时间计数是否达到500ms
    if (timecount >= 500)
    {
      // 重置时间计数
      timecount = 0;
      // 打印提示信息
      printf("500ms time up\r\n");
      // 切换GPIOC_PIN_13的状态(闪烁LED)
      HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
    }
  }
}

DMA.c

MX_DMA_Init

可以看到 DMA的初始化,其实就是

使能DMA时钟、

DMA中断优先级、

使能DMA中断。

cpp 复制代码
void MX_DMA_Init(void)
{
  // 使能DMA2控制器时钟
  __HAL_RCC_DMA2_CLK_ENABLE();

  // 配置DMA中断
  // 设置DMA2_Stream2中断优先级为最高(0)
  HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 0, 0);
  // 使能DMA2_Stream2中断
  HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);
}

在串口的MSP_Init中,DMA配置为普通模式。

根据 main函数中的

50字节接收完后,DMA自动停止。

USART

cpp 复制代码
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 */

}

在上面的代码中,调用了下面的UART相关的硬件初始化。 MCU Specific Package Initialization,MCU特定包初始化。

串口硬件(及DMA)配置

cpp 复制代码
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_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* USART1 DMA Init */
    /* USART1_RX Init */
    hdma_usart1_rx.Instance = DMA2_Stream2;
    hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4;
    hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart1_rx.Init.Mode = DMA_NORMAL;
    hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW;
    hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);

    /* USART1 interrupt Init */
    HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
  /* USER CODE BEGIN USART1_MspInit 1 */

  /* USER CODE END USART1_MspInit 1 */
  }
}
cpp 复制代码
hdma_usart1_rx.Instance = DMA2_Stream2;          // 指定使用的 DMA 控制器和流(DMA2 的 Stream2)
hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4;     // 指定 DMA 通道(通道 4,通常对应 USART1 的接收)
hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; // 数据传输方向:外设到内存
hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;    // 外设地址不递增(USART 接收寄存器固定)
hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;       // 内存地址递增(数据存储到连续内存)
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; // 外设数据对齐方式:字节对齐
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;    // 内存数据对齐方式:字节对齐
hdma_usart1_rx.Init.Mode = DMA_NORMAL;              // DMA 模式:普通模式(非循环)
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW;    // DMA 优先级:低
hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;// 禁用 FIFO 模式
cpp 复制代码
#define DMA_NORMAL                    0x00000000U                  /*!< Normal mode: 普通模式,传输完成后停止 */
#define DMA_CIRCULAR                  ((uint32_t)DMA_SxCR_CIRC)    /*!< Circular mode: 循环模式,传输完成后自动重新开始 */
#define DMA_PFCTRL                    ((uint32_t)DMA_SxCR_PFCTRL)  /*!< Peripheral flow control mode: 外设流控模式,外设控制传输 */

串口中断

中断间的调用关系

USART1_IRQHandler

HAL_UART_IRQHandler

UART_Receive_IT

DMA2_Stream2_IRQHandler

HAL_DMA_IRQHandler

HAL_UART_RxCpltCallback

HAL_UART_RxHalfCpltCallback

HAL_UART_ErrorCallback

USART1_IRQHandler

通用的处理函数中判断了接收中断的使能和接收状态的挂起。

USART1_IRQHandler 的调用时机

USART1_IRQHandler 是 USART1 的中断处理函数,用于处理与 USART1 相关的中断事件。它的调用时机取决于 USART1 的中断标志位是否被置位。以下是具体的触发条件:

触发条件
  • 接收中断(RXNE) :当 USART1 的接收缓冲寄存器中有新数据时,接收中断标志位(UART_FLAG_RXNE)会被置位。

  • 发送中断(TC) :当 USART1 的发送缓冲寄存器中的数据被发送完成时,发送完成中断标志位(UART_FLAG_TC)会被置位。

  • 空闲中断(IDLE) :当 USART1 检测到一个空闲信号(即接收到一个持续的低电平)时,空闲中断标志位(UART_FLAG_IDLE)会被置位。

  • 错误中断(如帧错误、奇偶校验错误等):当 USART1 检测到通信错误时,相应的错误标志位会被置位。

DMA2_Stream2_IRQHandler 的调用时机

DMA2_Stream2_IRQHandler 是 DMA2 Stream2 的中断处理函数,用于处理与 DMA2 Stream2 相关的中断事件。它的调用时机取决于 DMA2 Stream2 的中断标志位是否被置位。以下是具体的触发条件:

触发条件
  • 传输完成中断(TC) :当 DMA2 Stream2 完成一次数据传输时,传输完成中断标志位(DMA_FLAG_TCIF2)会被置位。

  • 半传输中断(HT) :当 DMA2 Stream2 完成一半数据传输时,半传输中断标志位(DMA_FLAG_HTIF2)会被置位。

  • 传输错误中断(TE) :当 DMA2 Stream2 发生传输错误时,传输错误中断标志位(DMA_FLAG_TEIF2)会被置位。

  1. 处理中断事件

    • 如果是传输完成中断(DMA_FLAG_TCIF2),HAL 库会调用 HAL_UART_RxCpltCallback 回调函数。

    • 如果是半传输中断(DMA_FLAG_HTIF2),HAL 库会调用 HAL_UART_RxHalfCpltCallback 回调函数。

    • 如果是传输错误中断(DMA_FLAG_TEIF2),HAL 库会调用 HAL_UART_ErrorCallback 回调函数。

MX_TIM11_Init

TIM1, TIM8, TIM9, TIM10, TIM11 连接到 APB2 总线。

系统时钟频率的计算

根据系统时钟配置,系统时钟来源于高速内部晶振

STM32F4的HSI频率为16Mhz。

因此 PLL输出频率等于 800Mhz,也即系统时钟频率。

根据配置,AHB 总线时钟(HCLK)等于 SYSCLK。APB2的时钟等于HCLK的时钟。

即APB2的时钟频率也为 800Mhz。

定时器周期计算

cpp 复制代码
void MX_TIM11_Init(void)
{
  // 设置定时器实例为TIM11
  htim11.Instance = TIM11;

  // 配置定时器参数
  htim11.Init.Prescaler = 99;  // 预分频器值为99
  htim11.Init.CounterMode = TIM_COUNTERMODE_UP;  // 向上计数模式
  htim11.Init.Period = 999;  // 自动重装载值为999
  htim11.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;  // 时钟分频因子为1
  htim11.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;  // 禁用自动重装载寄存器的预装载

  // 初始化定时器
  if (HAL_TIM_Base_Init(&htim11) != HAL_OK)
  {
    Error_Handler();  // 初始化失败则调用错误处理函数
  }
}

最后得 0.5s。

HAL_UART_Receive_DMA

初始化串口的接收DMA。

主要是初始化 接收数据缓冲区、接收数据大小和各种回调(如传输完成、半完成、结束和终止)。

cpp 复制代码
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, 
                                                  uint8_t *pData, 
                                                    uint16_t Size)
{
  /* 检查是否已有接收过程在进行 */
  if (huart->RxState == HAL_UART_STATE_READY)
  {
    /* 检查接收数据指针和接收大小是否有效 */
    if ((pData == NULL) || (Size == 0U))
    {
      return HAL_ERROR;
    }

    /* 锁定当前处理过程 */
    __HAL_LOCK(huart);

    /* 设置接收类型为标准接收 */
    huart->ReceptionType = HAL_UART_RECEPTION_STANDARD;

    /* 启动DMA接收 */
    return (UART_Start_Receive_DMA(huart, pData, Size));
  }
  else
  {
    /* 如果已有接收过程在进行,则返回忙状态 */
    return HAL_BUSY;
  }
}
cpp 复制代码
HAL_StatusTypeDef UART_Start_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  uint32_t *tmp;

  // 初始化接收缓冲区指针和接收数据大小
  huart->pRxBuffPtr = pData;
  huart->RxXferSize = Size;

  // 重置错误码和设置接收状态
  huart->ErrorCode = HAL_UART_ERROR_NONE;
  huart->RxState = HAL_UART_STATE_BUSY_RX;

  // 设置DMA传输完成、半完成、错误和中止的回调函数
  huart->hdmarx->XferCpltCallback = UART_DMAReceiveCplt;
  huart->hdmarx->XferHalfCpltCallback = UART_DMARxHalfCplt;
  huart->hdmarx->XferErrorCallback = UART_DMAError;
  huart->hdmarx->XferAbortCallback = NULL;

  // 启动DMA接收
  tmp = (uint32_t *)&pData;
  HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->DR, *(uint32_t *)tmp, Size);

  // 清除溢出错误标志
  __HAL_UART_CLEAR_OREFLAG(huart);

  // 解锁处理
  __HAL_UNLOCK(huart);

  // 如果需要,启用奇偶校验错误中断
  if (huart->Init.Parity != UART_PARITY_NONE)
  {
    ATOMIC_SET_BIT(huart->Instance->CR1, USART_CR1_PEIE);
  }

  // 启用UART错误中断(帧错误、噪声错误、溢出错误)
  ATOMIC_SET_BIT(huart->Instance->CR3, USART_CR3_EIE);

  // 通过设置UART CR3寄存器中的DMAR位来启用接收DMA传输
  ATOMIC_SET_BIT(huart->Instance->CR3, USART_CR3_DMAR);

  return HAL_OK;
}

__HAL_UART_ENABLE_IT

在UART通信中,空闲中断通常在接收到数据之后,数据线上出现一段空闲时间时触发。这个空闲时间可以由用户配置,用于检测数据包的结束或链接的空闲状态。

cpp 复制代码
/*使能中断*/
__HAL_UART_ENABLE_IT(&huart1,  //串口1
                UART_IT_IDLE); //空闲中断

HAL_TIM_Base_Start_IT

启动定时器的基本计数功能,并使能中断。

cpp 复制代码
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)
{
  uint32_t tmpsmcr;

  // 参数检查:确保提供的定时器实例有效
  assert_param(IS_TIM_INSTANCE(htim->Instance));

  // 状态检查:确保定时器处于就绪状态
  if (htim->State != HAL_TIM_STATE_READY)
  {
    return HAL_ERROR;
  }

  // 设置定时器状态为忙碌
  htim->State = HAL_TIM_STATE_BUSY;

  // 使能定时器更新中断(溢出中断)
  __HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);

  // 使能定时器外设,除非处于触发模式(在触发模式下,使能会在触发时自动完成)
  if (IS_TIM_SLAVE_INSTANCE(htim->Instance))
  {
    // 如果定时器是从模式,检查是否需要手动使能
    tmpsmcr = htim->Instance->SMCR & TIM_SMCR_SMS;
    if (!IS_TIM_SLAVEMODE_TRIGGER_ENABLED(tmpsmcr))
    {
      __HAL_TIM_ENABLE(htim);
    }
  }
  else
  {
    // 如果定时器不是从模式,直接使能
    __HAL_TIM_ENABLE(htim);
  }

  // 返回函数执行状态
  return HAL_OK;
}

在从模式下,定时器的计数行为(如计数频率、计数方向和计数周期)可以由外部信号或另一个定时器的输出控制。

触发从模式:外部触发信号可以启动、停止或重新初始化定时器的计数。

外部输入从模式:定时器的计数行为(如计数方向、计数值)可以由外部输入信号控制。

内部触发从模式:定时器可以由内部事件(如另一个定时器的更新或捕获事件)触发。

TIM1_TRG_COM_TIM11_IRQHandler

中断在xxx_it.c

当中断源是 TIM1/TIM11 的触发/通信事件时,就会执行这个 ISR。

1. 触发事件(TRG)

  • 功能:处理定时器的触发输入事件。

  • 示例

    • 定时器的触发输入信号(如外部信号)导致的中断。

    • 例如,当外部信号通过 TRG 输入引脚触发定时器时,会调用此中断服务例程。

2. 通信事件(COM)

  • 功能:处理定时器的通信事件。

  • 示例

    • 输出比较事件(OC):定时器的输出比较事件,例如定时器的通道输出匹配。

    • 捕获事件(IC):定时器的输入捕获事件,例如定时器的通道输入捕获。

    • 通信事件(COM):例如,定时器的主从模式通信事件。

3. 不包括的事件

  • 更新事件(UIF):定时器的更新事件(如定时器溢出事件)通常由其他中断服务例程处理,例如:

    • TIM1 的更新事件由 TIM1_UP_IRQHandler 处理。

    • TIM11 的更新事件由 TIM11_IRQHandler 处理。

cpp 复制代码
void TIM1_TRG_COM_TIM11_IRQHandler(void)
{
  /* USER CODE BEGIN TIM1_TRG_COM_TIM11_IRQn 0 */
	timecount += 1;
  /* USER CODE END TIM1_TRG_COM_TIM11_IRQn 0 */
  HAL_TIM_IRQHandler(&htim11);
  /* USER CODE BEGIN TIM1_TRG_COM_TIM11_IRQn 1 */

  /* USER CODE END TIM1_TRG_COM_TIM11_IRQn 1 */
}

注意,基础的溢出事件执行的是 TIMx_UP_IRQHandler 或 TIMx_IRQHandler。

TIM1的触发(Trigger)

触发功能允许一个定时器(或外部信号)触发另一个定时器的特定事件。在 TIM1 中,触发事件可以是启动、停止或重新初始化定时器的计数。触发可以由以下来源产生:

  • 内部触发:来自同一微控制器内其他定时器的事件。

  • 外部触发:来自微控制器外部的信号,如 GPIO 引脚上的信号。

例如,假设我们有两个定时器 TIM1 和 TIM2,我们希望在 TIM2 每次更新(计数溢出)时,启动 TIM1 的计数。这可以通过配置 TIM1 的触发输入来实现,使其响应 TIM2 的更新事件。

TIM1的换向(Commutation)

换向功能通常用于无刷直流电机(BLDC)控制,它涉及到在正确的时刻改变电机相位的电流方向。在 TIM1 中,换向可以通过配置定时器的互补输出来实现,这些输出可以连接到电机驱动器的开关器件。

例如,对于一个三相电机,我们需要在特定的时刻改变三相绕组的电流方向。TIM1 可以配置为在检测到特定的转子位置信号时,通过其互补输出改变电流方向,从而实现换向。

举例

假设我们正在控制一个无刷直流电机,我们使用 TIM1 来生成 PWM 信号,控制电机的三相绕组。以下是如何使用 TIM1 的触发和换向功能的示例:

  1. 配置 TIM1 为 PWM 模式:设置 TIM1 的通道为 PWM 模式,以生成控制电机绕组的信号。

  2. 配置触发输入:如果我们需要 TIM1 的 PWM 输出与另一个定时器(如 TIM2)同步,我们可以配置 TIM1 的触发输入来响应 TIM2 的更新事件。

  3. 配置换向 :使用 TIM1 的互补输出和输入捕获功能来检测转子的位置。根据转子位置信号,通过软件或硬件逻辑来控制 TIM1 的输出,以在正确的时刻改变电流方向。

  4. 启动 TIM1:使能 TIM1 的更新中断,并启动定时器。在中断服务例程中,根据转子位置更新 TIM1 的捕获比较寄存器,以改变 PWM 输出的占空比,从而控制电机的速度和方向。

定时器知识点

2 个基本定时器(TIM6 和 TIM7)、

10 个通用定时 器(TIM2~TIM5,TIM9~TIM14)、

2 个高级控制定时器(TIM1 和 TIM8)。
定时器的中断服务函数声明在.s文件的中断向量表中

用的时候可以自己在it.c或者自己的驱动中去实现声明和定义。

计数中断

初始化,配置重载值、计数值,到点了执行中断。

不同定时器的中断函数不同,不同模式的中断函数也不同。

直接查手册去做。

通用定时器PWM输出

引脚的复用功能一般在GPIO配置中设置,如

cpp 复制代码
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == GTIM_TIMX_PWM)
    {
        GPIO_InitTypeDef gpio_init_struct;
        GTIM_TIMX_PWM_CHY_GPIO_CLK_ENABLE();                            /* 使能通道y的GPIO时钟 */
        GTIM_TIMX_PWM_CHY_CLK_ENABLE();                                 /* 使能定时器时钟 */

        gpio_init_struct.Pin = GTIM_TIMX_PWM_CHY_GPIO_PIN;              /* 使能通道y的GPIO口 */
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;                        /* 复用推挽输出 */
        gpio_init_struct.Pull = GPIO_PULLUP;                            /* 上拉 */
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;                  /* 高速 */
        gpio_init_struct.Alternate = GTIM_TIMX_PWM_CHY_GPIO_AF;         /* 定时器x通道y的GPIO复用 */
        HAL_GPIO_Init(GTIM_TIMX_PWM_CHY_GPIO_PORT, &gpio_init_struct);
    }
}

这里PWM引脚被设置为服用推挽,并设置了复用参数。

而定时器的PWM初始化设置中,

cpp 复制代码
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc)
{
    TIM_OC_InitTypeDef timx_oc_pwm_chy = {0};                                               /* 定时器输出句柄 */
    
    g_timx_pwm_chy_handle.Instance = GTIM_TIMX_PWM;                                         /* 定时器x */
    g_timx_pwm_chy_handle.Init.Prescaler = psc;                                             /* 预分频系数 */
    g_timx_pwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;                            /* 递增计数模式 */
    g_timx_pwm_chy_handle.Init.Period = arr;                                                /* 自动重装载值 */
    HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle);                                               /* 初始化PWM */

    timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1;                                               /* PWM1模式 */
    timx_oc_pwm_chy.Pulse = arr / 2;                                                        /* 设置比较值,此值用来确定占空比 */

    timx_oc_pwm_chy.OCPolarity = TIM_OCPOLARITY_LOW;                                        /* 输出比较极性为低 */

    HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle,  //定时器配置句柄
                                   &timx_oc_pwm_chy,   //
                                GTIM_TIMX_PWM_CHY); /* 配置TIMx通道y */

    HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle, GTIM_TIMX_PWM_CHY);                           /* 开启对应PWM通道 */
}

PWM模式1和PWM模式2的区别在于,

PWM 模式 1:简单直接,适用于大多数基本 PWM 应用,其中输出引脚在计数器匹配到 CC 寄存器值时切换状态。

PWM 模式 2:提供更复杂的控制,允许更灵活的 PWM 波形生成,适用于需要特殊 PWM 波形的应用,如中心对齐模式。
输出比较极性决定了当定时器的计数器匹配到捕获/比较(CC)寄存器的值时,输出引脚是置高还是置低。

再就是定时器通道和PWM引脚的关系,定时器一般4个PWM通道,每个通道可以配置为不同的功能。定时器通道可以配置一个具备该功能的复用引脚,来进行输出。

  1. 多任务处理

多个通道允许定时器同时执行多个任务。例如,在一个通道上生成一个PWM信号来控制电机速度,同时在另一个通道上进行输入捕获来测量旋转编码器的脉冲。

  1. 同步操作

在需要同步多个事件的应用中,多个通道可以协调工作。例如,在电机控制中,可能需要同时控制多个相位的电流,每个通道控制一相。

  1. 复杂的PWM控制

对于需要复杂PWM控制的应用,如无刷直流电机(BLDC)控制,多个通道可以用于生成相位差的PWM信号,以实现电机的换向。

通用定时器输入捕获

输入捕获模式可以用来测量脉冲宽度或者测量频率。

先配置通道为上升沿触发中断, 触发后开始计时,同时配置下降沿触发中断,触发后计时结束。时间差就是我们测量的脉冲宽度。

cpp 复制代码
void gtim_timx_cap_chy_init(uint16_t arr, uint16_t psc)
{
    TIM_IC_InitTypeDef timx_ic_cap_chy = {0};
    
    g_timx_cap_chy_handle.Instance = GTIM_TIMX_CAP;                                        /* 定时器5 */
    g_timx_cap_chy_handle.Init.Prescaler = psc;                                            /* 预分频系数 */
    g_timx_cap_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;                           /* 递增计数模式 */
    g_timx_cap_chy_handle.Init.Period = arr;                                               /* 自动重装载值 */
    HAL_TIM_IC_Init(&g_timx_cap_chy_handle);                                               /* 初始化定时器 */
    
    timx_ic_cap_chy.ICPolarity = TIM_ICPOLARITY_RISING;                                    /* 上升沿捕获 */
    timx_ic_cap_chy.ICSelection = TIM_ICSELECTION_DIRECTTI;                                /* 映射到TI1上 */
    timx_ic_cap_chy.ICPrescaler = TIM_ICPSC_DIV1;                                          /* 配置输入分频,不分频 */
    timx_ic_cap_chy.ICFilter = 0;                                                          /* 配置输入滤波器,不滤波 */
    HAL_TIM_IC_ConfigChannel(&g_timx_cap_chy_handle, &timx_ic_cap_chy, GTIM_TIMX_CAP_CHY); /* 配置TIM5通道1 */

    __HAL_TIM_ENABLE_IT(&g_timx_cap_chy_handle, TIM_IT_UPDATE);                            /* 使能更新中断 */
    HAL_TIM_IC_Start_IT(&g_timx_cap_chy_handle, GTIM_TIMX_CAP_CHY);                        /* 开始捕获TIM5的通道1 */
}

备注的映射到TI1上,这里的TI1是Timer Input 1的缩写,

也就是输入捕获通道1,是一个特定引脚。(在GPIO中配置复用)
定时器中断服务函数

HAL_tim.c中,定时器共用处理函数,根据配置情况执行了不同配置下的回调。
捕获比较中断函数

通用定时器脉冲计数

脉冲计数,需要将定时器配置为从模式,外部触发模式1。

cpp 复制代码
/* 从模式:外部触发模式 1 */
 tim_slave_config.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;/* 外部触发模式 1 */
 tim_slave_config.InputTrigger = TIM_TS_TI1FP1;/* TI1FP1 作为触发输入源 */
 tim_slave_config.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING;/* 上升沿 */
 tim_slave_config.TriggerPrescaler = TIM_TRIGGERPRESCALER_DIV1;/* 不分频 */
 tim_slave_config.TriggerFilter = 0x0; /* 滤波:本例中不需要任何滤波 */
HAL_TIM_SlaveConfigSynchronization(&g_timx_cnt_chy_hand

TIM_SLAVEMODE_EXTERNAL1 是定时器的最基本的从模式,允许定时器通过外部输入(如 TI1FP1)来触发更新事件。

TIM_TS_TI1FP1 表示定时器的触发输入源是来自定时器的输入捕获通道 1(TI1)的滤波输入(FP1)。这里的 FP 指的是滤波器(Filter),它用于减少噪声或干扰对触发信号的影响。数字 1 表示这是第一个滤波输入。

GPIO也要配置为输入捕获通道。

cpp 复制代码
    gpio_init_struct.Alternate = GTIM_TIMX_CNT_CHY_GPIO_AF;                /* 复用为捕获TIMx的通道 */
    HAL_GPIO_Init(GTIM_TIMX_CNT_CHY_GPIO_PORT, &gpio_init_struct);

TIM的复用引脚在数据手册上能查到,N是代表互补输出的

在实际应用中,TIM1_CH1N 与 TIM1_CH1 是一对互补通道。

如果死区时间(Deadtime)为0,则 TIM1_CH1N 是 TIM1_CH1 的反相信号;如果死区时间不为0,则会在 TIM1_CH1N 上插入死区时间,以防止上下功率管同时导通 。

死区时间内,两个互补信号不同时导通。

中断逻辑程序的逻辑代码是 放在更新中断回调函数里面的,这是 HAL 库回调机制标准的做法。

因为我们在通用定时器输 入捕获实验中使用过 HAL_TIM_PeriodElapsedCallback 更新中断回调函数,所以本实验我们不 使用 HAL 库这套回调机制,而是直接将中断处理写在定时器中断服务函数中。(标准做法是在定时器中断服务函数中调用更新中断回调函数,这里把那个函数删了)

高级定时器输出比较模式

高级定时器输出比较模式下翻转功能,通过定时器 4 个通道分别输 出 4 个 50%占空比、不同相位的 PWM。

输出比较模式下翻转功能作用是:当计数器的值等于捕获/比较寄存器影子寄存器的值时,OC1REF 发生翻转,进而控制通道输出(OCx)翻转。

通过翻转功能实现输出 PWM 的具体原理如下:

PWM 频率由自动重载寄存器(TIMx_ARR)的值决定,在这个过程中,只要自动重 载寄存器的值不变,那么 PWM 占空比就固定为 50%。

我们可以通过捕获/比较寄存器 (TIMx_CCRx)的值改变 PWM 的相位。

输出比较模式的初始化函数

高级定时器互补输出带死区控制

main函数

例程功能

1,利用 TIM1_CH1(PE9)输出 70%占空比的 PWM,它的互补输出通道(PE8)则是输出 30% 占空比的 PWM。

2,刹车功能,当给刹车输入引脚(PE15)输入低电平时,进行刹车,即 PE9 和 PE8 停止输 出 PWM。

使能时钟

时钟使能,后面仨代码其实重复了,但为了便于理解使用了DEFINE,其实就是开了E口的使能

复用和初始化GPIO

GPIO端口复用和初始化
复用了一个刹车,两个TIM通道

定时器的 PWM初始化,

这里 arr = 1000-1,psc = 180-1。

初始化定时器模式和周期

cpp 复制代码
g_timx_cplm_pwm_handle.Instance = ATIM_TIMX_CPLM;                               /* 定时器x */
g_timx_cplm_pwm_handle.Init.Prescaler = psc;                                    /* 预分频系数 */
g_timx_cplm_pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP;                   /* 递增计数模式 */
g_timx_cplm_pwm_handle.Init.Period = arr;                                       /* 自动重装载值 */
g_timx_cplm_pwm_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV4;             /* CKD[1:0] = 10, tDTS = 4 * tCK_INT = Ft / 4 = 45Mhz */
g_timx_cplm_pwm_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;  /* 使能影子寄存器TIMx_ARR */
HAL_TIM_PWM_Init(&g_timx_cplm_pwm_handle) ;

这里用的定时器1,挂载在APB2总线。

虽然主通道和互补通道的极性都设置为低电平有效,但它们的输出信号是互补的。

  • 当主通道(OCy)为低电平时,互补通道(OCyN)为高电平。

  • 当主通道(OCy)为高电平时,互补通道(OCyN)为低电平。

这种互补关系是通过定时器的硬件逻辑实现的,而不是通过软件直接设置的。即使它们的极性设置相同,硬件会自动确保它们的输出信号是互补的。

PWM通道配置

输出通道(OCx)和互补输出通道(OCxN)在主输出使能(MOE,Master Output Enable)为0时的空闲状态(Idle State),

TIM_OCIDLESTATE_SET:

当MOE=0时,主输出通道(OCx)的输出电平被设置为高电平。

主输出使能(Master Output Enable,MOE)通常位于定时器的基本死区和锁存器寄存器(TIMx_BDTR)中。通过设置或清除MOE位,可以启用或禁用定时器的所有输出信号,包括主输出通道(OCx)和互补输出通道(OCxN)。

设置定时器死区参数

设置定时器的死区时间

死区参数解释

TIM_OSSR_DISABLE (禁用运行模式下的关闭输出状态)

运行模式下的关闭输出状态(Off-State in Run Mode)。

那么刹车信号被触发时,定时器的输出不会被强制关闭。相反,输出信号的状态将由其他配置决定。
TIM_OSSI_DISABLE(禁用空闲模式下的关闭输出状态)

空闲模式下的关闭输出状态(Off-State in Idle Mode)

在定时器处于空闲模式(如定时器停止计数)时,输出不会被强制关闭。输出信号的状态由其他配置决定
寄存器锁就是锁住寄存器,禁止写入。
TIM_BREAK_ENABLE(使能刹车功能)
刹车输入信号的有效极性
自动输出功能。刹车事件结束后,定时器的输出将自动恢复到正常状态。

开启PWM和PWMN信号输出

开始输出PWM信号
开始输出PWM互补信号

设置定时器死区时间

带死区互补输出情况

死区时间内,两互补输出不同时导通。

由于开关器件的开关速度和寄生电容的影响,Q1的漏极和源极之间的电压不会立即下降到0,可能需要一定时间才能完全关闭。如果Q2在Q1完全关闭之前就导通,可能会导致Q1和Q2同时导通,从而产生直通电流,导致断路。

因此设置死区,确保一方完全关闭后,另一方才导通。

由上到下分别是 PE9 输出 70%占空比的 PWM 波和 PE8 互补输出 30%占空 比的 PWM 波。

互补输出的 PWM 波的正脉宽减去正常的 PWM 的负脉宽的值除以 2 就是死区 时间,也可以是正常的 PWM 的正脉宽减去互补输出的 PWM 波的负脉宽的值除以 2。

我们使用第一种方法得到:死区时间 =(702--698)/2 us= 2us。与我们理论计算得到的值 2.22us 差不多,这样的误差是正常的。

高级定时器PWM输入

例程:

首先通过 TIM3_CH4(PB1)输出 PWM 波。然后把 PB1 输出的 PWM 波用杜邦线接入 PC6 (定时器 8 通道 1),最后通过串口打印 PWM 波的脉宽和频率等信息。

cpp 复制代码
/*定时器初始化和通道配置*/
gtim_timx_pwm_chy_init(10 - 1, 90 - 1); /* 1Mhz的计数频率, 100Khz的PWM */
atim_timx_pwmin_chy_init();             /* 初始化PWM输入捕获 */
cpp 复制代码
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc)
{
    TIM_OC_InitTypeDef timx_oc_pwm_chy = {0};                       /* 定时器输出句柄 */
    
    g_timx_pwm_chy_handle.Instance = GTIM_TIMX_PWM;                 /* 定时器x */
    g_timx_pwm_chy_handle.Init.Prescaler = psc;                     /* 预分频系数 */
    g_timx_pwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;    /* 递增计数模式 */
    g_timx_pwm_chy_handle.Init.Period = arr;                        /* 自动重装载值 */
    HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle);                       /* 初始化PWM */

    timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1;                       /* 选择PWM1模式 */
    timx_oc_pwm_chy.Pulse = arr / 2;                                /* 设置比较值,此值用来确定占空比 */

    timx_oc_pwm_chy.OCPolarity = TIM_OCPOLARITY_LOW;                                        /* 输出比较极性为低 */
    HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle, &timx_oc_pwm_chy, GTIM_TIMX_PWM_CHY); /* 配置TIMx通道y */
    HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle, GTIM_TIMX_PWM_CHY);                           /* 开启对应PWM通道 */
}
cpp 复制代码
void atim_timx_pwmin_chy_init(void)
{
    GPIO_InitTypeDef gpio_init_struct = {0};
    TIM_SlaveConfigTypeDef slave_config = {0};
    TIM_IC_InitTypeDef tim_ic_pwmin_chy = {0};

    ATIM_TIMX_PWMIN_CHY_CLK_ENABLE();
    ATIM_TIMX_PWMIN_CHY_GPIO_CLK_ENABLE();

    gpio_init_struct.Pin = ATIM_TIMX_PWMIN_CHY_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_AF_PP; 
    gpio_init_struct.Pull = GPIO_PULLDOWN;
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
    gpio_init_struct.Alternate = ATIM_TIMX_PWMIN_CHY_GPIO_AF;
    HAL_GPIO_Init(ATIM_TIMX_PWMIN_CHY_GPIO_PORT, &gpio_init_struct);

    g_timx_pwmin_chy_handle.Instance = ATIM_TIMX_PWMIN;             /* 定时器8 */
    g_timx_pwmin_chy_handle.Init.Prescaler = 0;                     /* 定时器预分频系数 */
    g_timx_pwmin_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;  /* 递增计数模式 */
    g_timx_pwmin_chy_handle.Init.Period = 65535;                    /* 自动重装载值 */
    HAL_TIM_IC_Init(&g_timx_pwmin_chy_handle);
    
    /* 从模式配置,IT1触发更新 */
    slave_config.SlaveMode = TIM_SLAVEMODE_RESET;                   /* 从模式:复位模式 */
    slave_config.InputTrigger = TIM_TS_TI1FP1;                      /* 定时器输入触发源:TI1FP1 */
    slave_config.TriggerPolarity = TIM_INPUTCHANNELPOLARITY_RISING; /* 上升沿检测 */
    slave_config.TriggerFilter = 0;                                 /* 不滤波 */
    HAL_TIM_SlaveConfigSynchro(&g_timx_pwmin_chy_handle, &slave_config);

    /* IC1捕获:上升沿触发TI1FP1 */
    tim_ic_pwmin_chy.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;  /* 上升沿检测 */
    //表示输入捕获的信号来源为一个直接信号
    tim_ic_pwmin_chy.ICSelection = TIM_ICSELECTION_DIRECTTI;        /* 选择输入端IC1映射到TI1 */
    tim_ic_pwmin_chy.ICPrescaler = TIM_ICPSC_DIV1;                  /* 不分频 */
    tim_ic_pwmin_chy.ICFilter = 0;                                  /* 不滤波 */
    HAL_TIM_IC_ConfigChannel(&g_timx_pwmin_chy_handle, &tim_ic_pwmin_chy, TIM_CHANNEL_1);
    
    /* IC2捕获:上升沿触发TI1FP2 */
    tim_ic_pwmin_chy.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING; /* 下降沿检测 */
    tim_ic_pwmin_chy.ICSelection = TIM_ICSELECTION_INDIRECTTI;      /* 选择输入端IC2映射到TI1 */
    HAL_TIM_IC_ConfigChannel(&g_timx_pwmin_chy_handle, &tim_ic_pwmin_chy, TIM_CHANNEL_2);
    
    HAL_NVIC_SetPriority(ATIM_TIMX_PWMIN_IRQn, 1, 3);               /* 设置中断优先级,抢占优先级1,子优先级3 */
    HAL_NVIC_EnableIRQ( ATIM_TIMX_PWMIN_IRQn );                     /* 开启TIMx中断 */
    
    /* TIM1/TIM8有独立的输入捕获中断服务函数 */
    if ( ATIM_TIMX_PWMIN == TIM1 || ATIM_TIMX_PWMIN == TIM8)
    {
        HAL_NVIC_SetPriority(ATIM_TIMX_PWMIN_CC_IRQn, 1, 3);        /* 设置中断优先级,抢占优先级1,子优先级3 */
        HAL_NVIC_EnableIRQ(ATIM_TIMX_PWMIN_CC_IRQn);                /* 开启TIMx中断 */
    }

    __HAL_TIM_ENABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_UPDATE);
    HAL_TIM_IC_Start_IT(&g_timx_pwmin_chy_handle, TIM_CHANNEL_1);
    HAL_TIM_IC_Start_IT(&g_timx_pwmin_chy_handle, TIM_CHANNEL_2);
}

输入捕获通道映射

直接映射 TIM_ICSELECTION_DIRECTTI

  • 含义:表示输入捕获通道直接连接到对应的输入引脚。

  • 具体映射

    • 对于通道1(IC1),信号来源是通道1的输入引脚(TI1)。

    • 对于通道2(IC2),信号来源是通道2的输入引脚(TI2)。

    • 对于通道3(IC3),信号来源是通道3的输入引脚(TI3)。

    • 对于通道4(IC4),信号来源是通道4的输入引脚(TI4)。

间接映射 TIM_ICSELECTION_INDIRECTTI

  • 含义:表示输入捕获通道的信号来源于另一个通道的间接输入信号。

  • 具体映射

    • 对于通道2(IC2),信号来源是通道1的滤波输入信号(TI1FP1)。

    • 对于通道3(IC3),信号来源是通道2的滤波输入信号(TI2FP2)。

    • 对于通道4(IC4),信号来源是通道3的滤波输入信号(TI3FP3)。

这里通道配置的时候,把定时器8配置成了 从模式:复位模式,

并给出了输入触发源TI1FP1,意思就是通道1(TI1)的输入信号经过内部过滤(FP1)后的版本。

进行定时器的输入捕获配置的时候,给通道1配置了直接映射。

表示输入捕获通道直接连接到对应的输入引脚,因此通道1的信号来源就是通道1,即TI1。

通道2配置了间接映射,因此通道2的信号来源是TI1FP1。

TIM1/TIM8独立的捕获中断服务函数

cpp 复制代码
 /* TIM1/TIM8 有独立的捕获中断服务函数,需要单独定义,对于TIM2~5等,则不需要以下定义 */
#define ATIM_TIMX_PWMIN_CC_IRQn                 TIM8_CC_IRQn
#define ATIM_TIMX_PWMIN_CC_IRQHandler           TIM8_CC_IRQHandler

设置优先级,开启TIM中断,

cpp 复制代码
/*启用定时器的更新中断*/
__HAL_TIM_ENABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_UPDATE);

/*开启通道1的输入捕获模式*/
HAL_TIM_IC_Start_IT(&g_timx_pwmin_chy_handle, TIM_CHANNEL_1);

/*开启通道2的输入捕获模式*/
HAL_TIM_IC_Start_IT(&g_timx_pwmin_chy_handle, TIM_CHANNEL_2);
相关推荐
Miuney_MAX2 小时前
【单片机】之HC32F460中断向量选择
单片机·嵌入式硬件
XINVRY-FPGA5 小时前
XC3S1000-4FGG320I Xilinx AMD Spartan-3 SRAM-based FPGA
嵌入式硬件·机器学习·计算机视觉·fpga开发·硬件工程·dsp开发·fpga
猫猫的小茶馆8 小时前
【ARM】ARM的介绍
c语言·开发语言·arm开发·stm32·单片机·嵌入式硬件·物联网
猫猫的小茶馆8 小时前
【PCB工艺】数模电及射频电路基础
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·pcb工艺
点灯小铭8 小时前
基于单片机的智能药物盒设计与实现
数据库·单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
梓德原8 小时前
【基础】详细分析带隙型稳压电路的工作原理
单片机·嵌入式硬件·物联网
国科安芯9 小时前
航天医疗领域AS32S601芯片的性能分析与适配性探讨
大数据·网络·人工智能·单片机·嵌入式硬件·fpga开发·性能优化
小李做物联网10 小时前
【物联网毕业设计】60.1基于单片机物联网嵌入式项目程序开发之图像厨房监测系统
stm32·单片机·嵌入式硬件·物联网
贝塔实验室11 小时前
新手如何使用Altium Designer创建第一张原理图(三)
arm开发·单片机·嵌入式硬件·fpga开发·射频工程·基带工程·嵌入式实时数据库
@good_good_study11 小时前
STM32 ADC多通道采样实验
stm32·单片机·嵌入式硬件