一、TIMER概述
定时器分类:基本定时器、通用定时器(输出比较、输入捕获)、高级定时器(PWM互补输出、死区及刹车功能); 时基单元: 计数器:16位计数器,从0计数累加到自动重装载计数值产生溢出,从0重新开始计数; 预分频器:对时钟源进行分频,降低计数频率; 自动重装载寄存器:定时器计数周期。 输出比较: 强制输出模式:通用定时器输出直接由软件将输出比较信号强制设置为有效电平、无效电平或输出不变; 比较输出模式:根据捕获/比较寄存器和计数器比较结果输出为有效电平、无效电平或者电平翻转; PWM输出模式:根据捕获/比较寄存器和计数器比较结果输出为有效电平、无效电平。 输入捕获:设置捕获寄存器,通过边沿中断判断状态值; |
二、HWTIMER定时回调
1、CubeMX驱动生成
根据使用定时器选择TIMX: |
2、使用HWTIMER设备驱动程序
注意:支持哪些定时器需要查看drivers/include/config/tim_config.h,查找设备时根据.name匹配: |
确保HAL库hal_conf.h功能模块使能(CubeMX会自动解除注释): #define HAL_TIM_MODULE_ENABLED 添加tim.c硬件驱动到drivers/board.c: void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle) { if(tim_baseHandle->Instance==TIM3) { /* USER CODE BEGIN TIM3_MspInit 0 */ /* USER CODE END TIM3_MspInit 0 */ /* TIM3 clock enable */ __HAL_RCC_TIM3_CLK_ENABLE(); /* USER CODE BEGIN TIM3_MspInit 1 */ /* USER CODE END TIM3_MspInit 1 */ } } 修改drivers/board.h: #define BSP_USING_TIM #ifdef BSP_USING_TIM #define BSP_USING_TIM3 /*#define BSP_USING_TIM15*/ /*#define BSP_USING_TIM16*/ /*#define BSP_USING_TIM17*/ #endif |
3、使用示例
#include <rtthread.h>
#include <rtdevice.h>
static rt_err_t timeoutCallBack(rt_device_t dev, rt_size_t size);
bool timerInit(char *devName, uint32_t sec, uint32_t usec)
{
// 查找定时器设备
rt_device_t devTimer = rt_device_find(devName);
if (devTimer == RT_NULL) {
rt_kprintf("rt_device_find[%s] failed!\n", devName);
return false;
}
// 打开定时器设备
rt_err_t ret = rt_device_open(devTimer, RT_DEVICE_OFLAG_RDWR);
if (ret != RT_EOK) {
rt_kprintf("rt_device_open[%s] failed!\n", devName);
return false;
}
// 设置超时回调函数
rt_device_set_rx_indicate(devTimer, timeoutCallBack);
// 设置计数频率(默认1Mhz或支持的最小计数频率)
rt_uint32_t freq = 10000;
ret = rt_device_control(devTimer, HWTIMER_CTRL_FREQ_SET, &freq);
if (ret != RT_EOK) {
rt_kprintf("rt_device_control[%s] freq failed!\n", devName);
return false;
}
// 设置模式为周期性定时器
rt_hwtimer_mode_t mode = HWTIMER_MODE_PERIOD;
ret = rt_device_control(devTimer, HWTIMER_CTRL_MODE_SET, &mode);
if (ret != RT_EOK) {
rt_kprintf("rt_device_control[%s] mode failed!\n", devName);
return false;
}
// 设置定时器超时值并启动定时器
rt_hwtimerval_t timeout_s;
timeout_s.sec = sec;
timeout_s.usec = usec;
if (rt_device_write(devTimer, 0, &timeout_s, sizeof(timeout_s)) != sizeof(timeout_s)) {
rt_kprintf("rt_device_write[%s] failed!\n", devName);
return false;
}
rt_kprintf("timer init ok!\n");
return true;
}
static rt_err_t timeoutCallBack(rt_device_t dev, rt_size_t size)
{
return RT_EOK;
}
三、HWTIMER输出比较
1、输出比较种类
强制输出模式: 强制输出模式是指通用定时器的输出直接由软件将输出比较信号(OxRef和OCx)强制设置为有效电平、无效电平或者输出不变。无需考虑捕获/比较寄存器(CCRx)和计数器(CNT)之间的任何比较结果; 比较输出模式: 比较输出模式的输出结果和捕获/比较寄存器(CCRx)和计数器(CNT)之间比较结果相关,当这两个值相等的时候,输出结果为有效电平、无效电平或者电平翻转; PWM输出模式: PWM输出模式是输出比较的一个特例,输出结果和捕获/比较寄存器(CCRx)和计数器(CNT)之间的比较值影响,输出结果为有效电平、无效电平。 |
注:有效电平和无效电平都可以是高电平也可以是低电平,但有效电平和无效电平两者一定是相反值。 |
2、输出比较框图
![]() |
比较输出模式下,捕获/比较寄存器(CCRx)的值由用户自行设置,如果计数器(CNT)的值大于或等于捕获/比较寄存器(CCRx)中的值时,会产生对应的标志或者中断。 |
3、PWM输出模式概述
PWM是脉冲宽度调制的简称,在输出频率不变的情况下,通过调节其占空比(高电平占整个脉冲周期的比值),从而达到调节输出的目的。 通用定时器PWM模式可以产生一个信号,这个信号的频率周期是由自动重装载寄存器中的值决定,信号的占空比则由捕获/比较寄存器中的值决定。 |
控制灯光:七彩灯(RGB灯)、灯光亮度; 控制电机:直流电机、步进电机、无刷电机; 红外遥控:红外发射; 电池充电:控制充电电流。 |
4、CubeMX驱动生成
查找手册查看GPIO支持复用定时器输出比较通道: or: 注意:支持哪些定时器需要查看drivers/include/config/pwm_config.h,查找设备时根据.name匹配,如果默认没有则自定义添加: 自定义添加示例: #ifdef BSP_USING_PWM14 #ifndef PWM14_CONFIG #define PWM14_CONFIG \ { \ .tim_handle.Instance = TIM14, \ .name = "pwm14", \ .channel = 0 \ } #endif /* PWM14_CONFIG */ #endif /* BSP_USING_PWM14 */ 如果.channel不指定且drivers/drv_pwm.c中未预定义通道,则不起作用(注意位移不要搞错): |
5、使用PWM设备驱动程序
![]() |
确保HAL库hal_conf.h功能模块使能(CubeMX会自动解除注释): #define HAL_TIM_MODULE_ENABLED 添加tim.c硬件驱动到drivers/board.c: void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle) { if(tim_baseHandle->Instance==TIM3) { /* USER CODE BEGIN TIM3_MspInit 0 */ /* USER CODE END TIM3_MspInit 0 */ /* TIM3 clock enable */ __HAL_RCC_TIM3_CLK_ENABLE(); /* USER CODE BEGIN TIM3_MspInit 1 */ /* USER CODE END TIM3_MspInit 1 */ } else if(tim_baseHandle->Instance==TIM14) { /* USER CODE BEGIN TIM14_MspInit 0 */ /* USER CODE END TIM14_MspInit 0 */ /* TIM14 clock enable */ __HAL_RCC_TIM14_CLK_ENABLE(); /* USER CODE BEGIN TIM14_MspInit 1 */ /* USER CODE END TIM14_MspInit 1 */ } } void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(timHandle->Instance==TIM3) { /* USER CODE BEGIN TIM3_MspPostInit 0 */ /* USER CODE END TIM3_MspPostInit 0 */ __HAL_RCC_GPIOB_CLK_ENABLE(); /**TIM3 GPIO Configuration PB0 ------> TIM3_CH3 */ GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; GPIO_InitStruct.Alternate = GPIO_AF2_TIM3; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); /* USER CODE BEGIN TIM3_MspPostInit 1 */ /* USER CODE END TIM3_MspPostInit 1 */ } ... ... } 修改drivers/board.h: /*#define BSP_USING_PWM1*/ /*#define BSP_USING_PWM2*/ /*#define BSP_USING_PWM3*/ #define BSP_USING_PWM14 #define BSP_USING_PWM14_CH1 |
6、使用示例
#include <rtthread.h>
#include <rtdevice.h>
struct rt_device_pwm *pwmdev;
bool pwmInit(char *devName, int channel, rt_uint32_t period, rt_uint32_t pulse)
{
pwmdev = (struct rt_device_pwm *)rt_device_find(devName);
if (pwmdev == RT_NULL) {
rt_kprintf("rt_device_find[%s] failed!\n", devName);
return false;
}
rt_pwm_set(pwmdev, channel, period, pulse);
rt_pwm_enable(pwmdev, channel);
return true;
}
四、HWTIMER输入捕获
1、输入捕获概述
通用定时器输入捕获用于对输入信号脉冲宽度测量(测量时钟脉冲信号的周期以及时钟脉冲信号的占空比)。 |
|
总结:通过检测TIMx_CHx边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)时,将当前定时器的计数值(TIMx_CNT)保存到对应通道的捕获/比较寄存器,完成一次捕获。 |
2、CubeMX驱动生成
定时器通道配置: 中断使能: |
3、 使用示例
#include <rtthread.h>
#include <rtdevice.h>
#include "tim.h"
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
// 中断服务函数
// 默认生成到cubemx/Src/stm32f4xx_it.c,由于该文件未添加到SConscript,所以单独摘出来
void TIM3_IRQHandler(void)
{
/* USER CODE BEGIN TIM3_IRQn 0 */
/* USER CODE END TIM3_IRQn 0 */
HAL_TIM_IRQHandler(&htim3);
/* USER CODE BEGIN TIM3_IRQn 1 */
/* USER CODE END TIM3_IRQn 1 */
}
// 输入捕获中断回调函数
uint16_t last_capture = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
// 周期计算 - 100Hz的方波意味着每个周期的时间应该是10ms(10000微秒)
if (htim->Instance == TIM3 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
uint16_t capture = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
uint16_t diff = (capture >= last_capture) ? (capture - last_capture) : ((0xFFFF - last_capture) + capture + 1);
last_capture = capture;
rt_kprintf("Capture: %d, Diff: %d\n", capture, diff);
}
}
int main(void)
{
// TIM3初始化
MX_TIM3_Init();
// TIM3_CH1中断使能
if (HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1) != HAL_OK) {
rt_kprintf("HAL_TIM_IC_Start_IT error!\n");
// 启动错误处理
Error_Handler();
}
while (1) {
rt_kprintf("status: %d\n", 1);
rt_thread_mdelay(1000);
}
return RT_EOK;
}