定时器简介
基本定时器,计数中断、产生DMA请求。
通用定时器,PWM输出、输入捕获、脉冲计数。
高级定时器,输出比较、互补输出带死区控制、PWM输入。
中心对齐的计数模式可以生成对称的PWM波形信号。计数可以先增后减。
这种模式下,PWM 的高电平和低电平时间相等,从而使得 PWM 波形在中心线上对称。
main.c
int main(void)
{// 初始化FLASH、SYSTIC中断,使能SYSCFG时钟和PWR时钟HAL_Init();// 配置系统时钟,包括PWR等级,各AHB、APB1/2时钟参数SystemClock_Config();// 初始化GPIOMX_GPIO_Init();// 初始化DMAMX_DMA_Init();// 初始化USART1 UARTMX_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){// 判断时间计数是否达到500msif (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中断。
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
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)配置
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 ConfigurationPA9 ------> USART1_TXPA10 ------> 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 */}
}
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 模式
#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
)会被置位。
-
处理中断事件:
-
如果是传输完成中断(
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。
定时器周期计算
void MX_TIM11_Init(void)
{// 设置定时器实例为TIM11htim11.Instance = TIM11;// 配置定时器参数htim11.Init.Prescaler = 99; // 预分频器值为99htim11.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数模式htim11.Init.Period = 999; // 自动重装载值为999htim11.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // 时钟分频因子为1htim11.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; // 禁用自动重装载寄存器的预装载// 初始化定时器if (HAL_TIM_Base_Init(&htim11) != HAL_OK){Error_Handler(); // 初始化失败则调用错误处理函数}
}
最后得 0.5s。
HAL_UART_Receive_DMA
初始化串口的接收DMA。
主要是初始化 接收数据缓冲区、接收数据大小和各种回调(如传输完成、半完成、结束和终止)。
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;}
}
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通信中,空闲中断通常在接收到数据之后,数据线上出现一段空闲时间时触发。这个空闲时间可以由用户配置,用于检测数据包的结束或链接的空闲状态。
/*使能中断*/
__HAL_UART_ENABLE_IT(&huart1, //串口1UART_IT_IDLE); //空闲中断
HAL_TIM_Base_Start_IT
启动定时器的基本计数功能,并使能中断。
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。
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 */
}
TIM1的触发(Trigger)
触发功能允许一个定时器(或外部信号)触发另一个定时器的特定事件。在 TIM1 中,触发事件可以是启动、停止或重新初始化定时器的计数。触发可以由以下来源产生:
-
内部触发:来自同一微控制器内其他定时器的事件。
-
外部触发:来自微控制器外部的信号,如 GPIO 引脚上的信号。
例如,假设我们有两个定时器 TIM1 和 TIM2,我们希望在 TIM2 每次更新(计数溢出)时,启动 TIM1 的计数。这可以通过配置 TIM1 的触发输入来实现,使其响应 TIM2 的更新事件。
TIM1的换向(Commutation)
换向功能通常用于无刷直流电机(BLDC)控制,它涉及到在正确的时刻改变电机相位的电流方向。在 TIM1 中,换向可以通过配置定时器的互补输出来实现,这些输出可以连接到电机驱动器的开关器件。
例如,对于一个三相电机,我们需要在特定的时刻改变三相绕组的电流方向。TIM1 可以配置为在检测到特定的转子位置信号时,通过其互补输出改变电流方向,从而实现换向。
举例
假设我们正在控制一个无刷直流电机,我们使用 TIM1 来生成 PWM 信号,控制电机的三相绕组。以下是如何使用 TIM1 的触发和换向功能的示例:
-
配置 TIM1 为 PWM 模式:设置 TIM1 的通道为 PWM 模式,以生成控制电机绕组的信号。
-
配置触发输入:如果我们需要 TIM1 的 PWM 输出与另一个定时器(如 TIM2)同步,我们可以配置 TIM1 的触发输入来响应 TIM2 的更新事件。
-
配置换向:使用 TIM1 的互补输出和输入捕获功能来检测转子的位置。根据转子位置信号,通过软件或硬件逻辑来控制 TIM1 的输出,以在正确的时刻改变电流方向。
-
启动 TIM1:使能 TIM1 的更新中断,并启动定时器。在中断服务例程中,根据转子位置更新 TIM1 的捕获比较寄存器,以改变 PWM 输出的占空比,从而控制电机的速度和方向。
定时器知识点
2 个基本定时器(TIM6 和 TIM7)、
10 个通用定时 器(TIM2~TIM5,TIM9~TIM14)、
2 个高级控制定时器(TIM1 和 TIM8)。

用的时候可以自己在it.c或者自己的驱动中去实现声明和定义。
计数中断
初始化,配置重载值、计数值,到点了执行中断。
不同定时器的中断函数不同,不同模式的中断函数也不同。
直接查手册去做。
通用定时器PWM输出
引脚的复用功能一般在GPIO配置中设置,如
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初始化设置中,
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信号来控制电机速度,同时在另一个通道上进行输入捕获来测量旋转编码器的脉冲。
2. 同步操作
在需要同步多个事件的应用中,多个通道可以协调工作。例如,在电机控制中,可能需要同时控制多个相位的电流,每个通道控制一相。
3. 复杂的PWM控制
对于需要复杂PWM控制的应用,如无刷直流电机(BLDC)控制,多个通道可以用于生成相位差的PWM信号,以实现电机的换向。
通用定时器输入捕获
输入捕获模式可以用来测量脉冲宽度或者测量频率。
先配置通道为上升沿触发中断, 触发后开始计时,同时配置下降沿触发中断,触发后计时结束。时间差就是我们测量的脉冲宽度。
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。
/* 从模式:外部触发模式 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也要配置为输入捕获通道。
gpio_init_struct.Alternate = GTIM_TIMX_CNT_CHY_GPIO_AF; /* 复用为捕获TIMx的通道 */HAL_GPIO_Init(GTIM_TIMX_CNT_CHY_GPIO_PORT, &gpio_init_struct);

在实际应用中,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。
使能时钟

复用和初始化GPIO

复用了一个刹车,两个TIM通道
定时器的 PWM初始化,
这里 arr = 1000-1,psc = 180-1。
初始化定时器模式和周期
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互补信号
设置定时器死区时间
带死区互补输出情况
死区时间内,两互补输出不同时导通。
由于开关器件的开关速度和寄生电容的影响,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 波的脉宽和频率等信息。
/*定时器初始化和通道配置*/
gtim_timx_pwm_chy_init(10 - 1, 90 - 1); /* 1Mhz的计数频率, 100Khz的PWM */
atim_timx_pwmin_chy_init(); /* 初始化PWM输入捕获 */
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通道 */
}
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独立的捕获中断服务函数
/* TIM1/TIM8 有独立的捕获中断服务函数,需要单独定义,对于TIM2~5等,则不需要以下定义 */
#define ATIM_TIMX_PWMIN_CC_IRQn TIM8_CC_IRQn
#define ATIM_TIMX_PWMIN_CC_IRQHandler TIM8_CC_IRQHandler
设置优先级,开启TIM中断,
/*启用定时器的更新中断*/
__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);