![[单片机框架][bsp层][cx32l003][bsp_tim] TIM定时器配置和使用](http://pic.xiahunao.cn/yaotu/[单片机框架][bsp层][cx32l003][bsp_tim] TIM定时器配置和使用)
文章目录1. TIM1 简介2. TIM1 主要特性3. 硬件平台与工程结构4. 代码实现4.1 头文件 bsp_tim.h4.2 源文件 bsp_tim.c5. 代码说明5.1 初始化流程5.2 占空比动态调节5.2 反初始化流程6. 常见问题与排查6.1 无波形输出6.2 频率不对6.3 占空比不可调6.4 多通道互相干扰7. 总结参考资料附录宏定义配置示例1. TIM1 简介高级控制定时器(TIM1)由一个 16 位的自动装载计数器组成它由一个可编程的预分频器驱动。它适合多种用途包含测量输入信号的脉冲宽度(输入捕获)或者产生输出波形(输出比较、PWM、嵌入死区时间的互补 PWM 等)。使用定时器预分频器和 RCC 时钟控制预分频器可以实现脉冲宽度和波形周期从几个微秒到几个毫秒的调节2. TIM1 主要特性TIM1 定时器的功能包括⚫ 16 位向上、向下、向上/向下自动装载计数器⚫ 16 位可编程(可以实时修改)预分频器计数器时钟频率的分频系数为 165535 之间的任意数值⚫ 多达 4 个独立通道输入捕获输出比较PWM 生成(边缘或中间对齐模式)单脉冲模式输出⚫ 死区时间可编程的互补输出⚫ 使用外部信号控制定时器和定时器互联的同步电路⚫ 允许在指定数目的计数器周期之后更新定时器寄存器的重复计数器⚫ 刹车输入信号可以将定时器输出信号置于复位状态或者一个已知状态⚫ 如下事件发生时产生中断更新计数器向上溢出/向下溢出计数器初始化(通过软件或者内部/外部触发)触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)输入捕获输出比较刹车信号输入⚫ 支持针对定位的增量(正交)编码器和霍尔传感器电路⚫ 触发输入作为外部时钟或者按周期的电流管理3. 硬件平台与工程结构本文示例基于 CX32L003 系列 MCU使用 HAL 库进行开发。工程中通过bsp_tim.c与bsp_tim.h两个文件封装 TIM1 的 PWM 输出功能业务层通过宏定义BS_TIM1_EN、BS_TIM1_PRESCALER、BS_TIM1_PERIOD等配置定时器参数实现代码复用与模块化。4. 代码实现4.1 头文件 bsp_tim.h头文件声明了定时器枚举类型与 PWM 初始化/反初始化接口供业务层调用/******************************************************************************** * file bsp_tim.h * author jianqiang.xue * Version V1.0.0 * Date 2021-04-19 * brief NULL ********************************************************************************/#ifndef__BSP_TIM_H#define__BSP_TIM_H#includestdint.htypedefenum{BSP_TIM_00,BSP_TIM_1}bsp_tim_t;voidbsp_tim_pwm_init(void);voidbsp_tim_pwm_deinit(void);#endif4.2 源文件 bsp_tim.c源文件实现了 TIM1 的 PWM 初始化与反初始化逻辑支持 4 路独立通道输出/******************************************************************************** * file bsp_tim.c * author jianqiang.xue * Version V1.0.0 * Date 2021-04-19 * brief ********************************************************************************//* Private includes ----------------------------------------------------------*/#includestdio.h#includestring.h#includecx32l003_hal.h#includebsp_gpio.h#includebsp_tim.h#includebusiness_gpio.h#includebusiness_function.h/* Private define ------------------------------------------------------------*/#defineTIM1_ENBS_TIM1_EN#defineTIM1_PRESCALERBS_TIM1_PRESCALER#defineTIM1_PERIODBS_TIM1_PERIOD#defineTIM1_CH1_PINBS_TIM1_CH1_PIN#defineTIM1_CH1_GPIO_PORTBS_TIM1_CH1_GPIO_PORT#defineTIM1_CH1_GPIO_CLK_ENABLE()BS_TIM1_CH1_GPIO_CLK_ENABLE()#defineTIM1_CH1_GPIO_CLK_DISABLE()BS_TIM1_CH1_GPIO_CLK_DISABLE()#defineTIM1_CH2_PINBS_TIM1_CH2_PIN#defineTIM1_CH2_GPIO_PORTBS_TIM1_CH2_GPIO_PORT#defineTIM1_CH2_GPIO_CLK_ENABLE()BS_TIM1_CH2_GPIO_CLK_ENABLE()#defineTIM1_CH2_GPIO_CLK_DISABLE()BS_TIM1_CH2_GPIO_CLK_DISABLE()#defineTIM1_CH3_PINBS_TIM1_CH3_PIN#defineTIM1_CH3_GPIO_PORTBS_TIM1_CH3_GPIO_PORT#defineTIM1_CH3_GPIO_CLK_ENABLE()BS_TIM1_CH3_GPIO_CLK_ENABLE()#defineTIM1_CH3_GPIO_CLK_DISABLE()BS_TIM1_CH3_GPIO_CLK_DISABLE()#defineTIM1_CH4_PINBS_TIM1_CH4_PIN#defineTIM1_CH4_GPIO_PORTBS_TIM1_CH4_GPIO_PORT#defineTIM1_CH4_GPIO_CLK_ENABLE()BS_TIM1_CH4_GPIO_CLK_ENABLE()#defineTIM1_CH4_GPIO_CLK_DISABLE()BS_TIM1_CH4_GPIO_CLK_DISABLE()#definePWM0_ENBS_PWM0_EN#definePWM1_ENBS_PWM1_EN#definePWM2_ENBS_PWM2_EN#definePWM3_ENBS_PWM3_EN#ifTIM1_ENTIM_HandleTypeDeftim1_handle_t;TIM_OC_InitTypeDeftim1_oc_init_handle_t;#endif/* Private typedef -----------------------------------------------------------*/voidbsp_tim_pwm_init(void){#ifTIM1_ENmemset(tim1_handle_t,0,sizeof(TIM_HandleTypeDef));memset(tim1_oc_init_handle_t,0,sizeof(TIM_OC_InitTypeDef));__HAL_RCC_TIM1_CLK_ENABLE();/* Set TIMx instance */tim1_handle_t.InstanceTIM1;tim1_handle_t.Init.PeriodTIM1_PERIOD;// 重装载值tim1_handle_t.Init.PrescalerTIM1_PRESCALER;// 预分频tim1_handle_t.Init.ClockDivision0;// CKD 时钟分频因子(Clock division)tim1_handle_t.Init.CounterModeTIM_COUNTERMODE_UP;// 边沿对齐模式 计数器向上计数tim1_handle_t.Init.RepetitionCounter0;// TIM1_RCR 重复计数器的值tim1_handle_t.Init.AutoReloadPreloadTIM_AUTORELOAD_PRELOAD_DISABLE;// 禁止自动重装载HAL_TIM_PWM_Init(tim1_handle_t);#ifPWM0_ENbsp_gpio_init_tim(TIM1_CH1_GPIO_PORT,TIM1_CH1_PIN,GPIO_AF1_TIM1_CH1);tim1_oc_init_handle_t.OCModeTIM_OCMODE_PWM1;// PWM模式1 TIM1_CCMR1tim1_oc_init_handle_t.PulseTIM1_PERIOD;// CCR 捕获/比较通道HAL_TIM_PWM_ConfigChannel(tim1_handle_t,tim1_oc_init_handle_t,TIM_CHANNEL_1);HAL_TIM_PWM_Start(tim1_handle_t,TIM_CHANNEL_1);#endif#ifPWM1_ENbsp_gpio_init_tim(TIM1_CH2_GPIO_PORT,TIM1_CH2_PIN,GPIO_AF1_TIM1_CH2);tim1_oc_init_handle_t.OCModeTIM_OCMODE_PWM1;// PWM模式1 TIM1_CCMR1tim1_oc_init_handle_t.PulseTIM1_PERIOD;// CCR 捕获/比较通道HAL_TIM_PWM_ConfigChannel(tim1_handle_t,tim1_oc_init_handle_t,TIM_CHANNEL_2);HAL_TIM_PWM_Start(tim1_handle_t,TIM_CHANNEL_2);#endif#ifPWM2_ENbsp_gpio_init_tim(TIM1_CH3_GPIO_PORT,TIM1_CH3_PIN,GPIO_AF1_TIM1_CH3);tim1_oc_init_handle_t.OCModeTIM_OCMODE_PWM1;// PWM模式1 TIM1_CCMR1tim1_oc_init_handle_t.PulseTIM1_PERIOD;// CCR 捕获/比较通道HAL_TIM_PWM_ConfigChannel(tim1_handle_t,tim1_oc_init_handle_t,TIM_CHANNEL_3);HAL_TIM_PWM_Start(tim1_handle_t,TIM_CHANNEL_3);#endif#ifPWM3_ENbsp_gpio_init_tim(TIM1_CH4_GPIO_PORT,TIM1_CH4_PIN,GPIO_AF1_TIM1_CH4);tim1_oc_init_handle_t.OCModeTIM_OCMODE_PWM1;// PWM模式1 TIM1_CCMR1tim1_oc_init_handle_t.PulseTIM1_PERIOD;// CCR 捕获/比较通道HAL_TIM_PWM_ConfigChannel(tim1_handle_t,tim1_oc_init_handle_t,TIM_CHANNEL_4);HAL_TIM_PWM_Start(tim1_handle_t,TIM_CHANNEL_4);#endif#endif}voidbsp_tim_pwm_deinit(void){#ifTIM1_EN#ifPWM0_ENHAL_TIM_PWM_Stop(tim1_handle_t,TIM_CHANNEL_1);bsp_gpio_deinit(TIM1_CH1_GPIO_PORT,TIM1_CH1_PIN);#endif#ifPWM1_ENHAL_TIM_PWM_Stop(tim1_handle_t,TIM_CHANNEL_2);bsp_gpio_deinit(TIM1_CH2_GPIO_PORT,TIM1_CH2_PIN);#endif#ifPWM2_ENHAL_TIM_PWM_Stop(tim1_handle_t,TIM_CHANNEL_3);bsp_gpio_deinit(TIM1_CH3_GPIO_PORT,TIM1_CH3_PIN);#endif#ifPWM3_ENHAL_TIM_PWM_Stop(tim1_handle_t,TIM_CHANNEL_4);bsp_gpio_deinit(TIM1_CH4_GPIO_PORT,TIM1_CH4_PIN);#endifHAL_TIM_PWM_DeInit(tim1_handle_t);__HAL_RCC_TIM1_CLK_DISABLE();#endif}5. 代码说明5.1 初始化流程5.2 占空比动态调节在实际应用中经常需要在运行过程中实时改变 PWM 的占空比例如调节 LED 亮度、控制电机转速等。基于 HAL 库只需修改tim1_oc_init_handle_t.Pulse即 CCR 值并重新调用HAL_TIM_PWM_ConfigChannel即可生效。下面以通道 1 为例给出一个动态调节占空比的示例/** * brief 动态设置 TIM1 通道 1 的 PWM 占空比 * param duty_cycle: 目标占空比取值范围 0.0 ~ 1.0例如 0.5 表示 50% * retval 无 */voidbsp_tim_set_duty_cycle(floatduty_cycle){uint32_tpulse_value;/* 1. 将占空比换算为 CCR 值Pulse ARR * duty_cycle */pulse_value(uint32_t)((float)TIM1_PERIOD*duty_cycle);/* 2. 限制 Pulse 取值范围避免超出 [0, TIM1_PERIOD] 导致输出异常 */if(pulse_valueTIM1_PERIOD){pulse_valueTIM1_PERIOD;// 占空比最大为 100%}/* 3. 更新输出比较句柄中的 Pulse 值 */tim1_oc_init_handle_t.Pulsepulse_value;/* 4. 重新配置通道 1使新的占空比立即生效 */HAL_TIM_PWM_ConfigChannel(tim1_handle_t,tim1_oc_init_handle_t,TIM_CHANNEL_1);}调用示例bsp_tim_set_duty_cycle(0.25f);// 占空比设为 25%bsp_tim_set_duty_cycle(0.75f);// 占空比设为 75%说明由于代码中AutoReloadPreload配置为TIM_AUTORELOAD_PRELOAD_DISABLE修改Pulse后调用HAL_TIM_PWM_ConfigChannel会立即更新 CCR 寄存器无需等待更新事件。若需要同时调节多个通道可分别修改对应通道的Pulse并调用HAL_TIM_PWM_ConfigChannel注意传入正确的TIM_CHANNEL_x参数。若占空比需要频繁变化如软件 PWM 调光建议将该函数放在定时器中断或主循环中周期性调用并注意控制调用频率以避免不必要的开销。bsp_tim_pwm_init函数的执行流程如下清零定时器句柄与输出比较句柄使能 TIM1 时钟配置周期、预分频、计数模式等参数并调用HAL_TIM_PWM_Init根据宏开关PWM0_EN~PWM3_EN依次初始化对应通道的 GPIO 复用功能、配置 PWM 模式并启动输出。5.2 反初始化流程bsp_tim_pwm_deinit依次停止各通道 PWM 输出、释放 GPIO、反初始化定时器并关闭时钟用于低功耗或资源释放场景。6. 常见问题与排查在实际使用 TIM1 输出 PWM 时可能会遇到波形异常、频率偏差、占空比不可调或多通道互相干扰等问题。下面针对这些常见问题给出原因分析与排查步骤。6.1 无波形输出现象调用bsp_tim_pwm_init后对应引脚测量不到 PWM 波形。可能原因与排查步骤宏开关未使能检查BS_TIM1_EN以及对应通道的PWM0_EN~PWM3_EN是否定义为 1。若为 0相关代码会被预编译屏蔽不会输出波形。#defineBS_TIM1_EN1#definePWM0_EN1// 使能通道 1#definePWM1_EN0#definePWM2_EN0#definePWM3_EN0GPIO 复用功能配置错误确认引脚已通过bsp_gpio_init_tim配置为 TIM1 的复用功能如GPIO_AF1_TIM1_CH1且引脚号与通道对应正确。若引脚被配置为普通 GPIO 输出则不会输出 PWM。时钟未使能确认__HAL_RCC_TIM1_CLK_ENABLE()已执行。若 TIM1 时钟未打开寄存器配置无效。通道未启动确认已调用HAL_TIM_PWM_Start启动对应通道。只配置通道而不启动不会有输出。引脚被其他外设占用检查该引脚是否同时被 UART、I2C 等其他外设复用导致信号被抢占。6.2 频率不对现象输出的 PWM 频率与预期值不符。可能原因与排查步骤预分频与重装载值配置错误PWM 频率由BS_TIM1_PRESCALER与BS_TIM1_PERIOD共同决定。计算公式为PWM 频率 TIM1 时钟频率 / ((BS_TIM1_PRESCALER 1) * (BS_TIM1_PERIOD 1))检查这两个宏的取值是否符合预期。例如若 TIM1 时钟为 48 MHz期望输出 1 kHz则(PSC 1) * (ARR 1) 48000。计算示例假设 TIM1 时钟频率为 48 MHz期望输出 1 kHz 的 PWM 波形则PWM 频率 48 MHz / ((BS_TIM1_PRESCALER 1) * (BS_TIM1_PERIOD 1)) 1 kHz即(BS_TIM1_PRESCALER 1) * (BS_TIM1_PERIOD 1) 48000。可取BS_TIM1_PRESCALER 47、BS_TIM1_PERIOD 999此时(47 1) * (999 1) 48 * 1000 48000输出频率正好为 1 kHz。对应宏定义如下#defineBS_TIM1_PRESCALER47// 预分频值 PSC实际分频系数为 48#defineBS_TIM1_PERIOD999// 重装载值 ARR实际计数周期为 1000此时若设置Pulse 500则占空比为500 / 1000 50%。时钟源选择错误确认 TIM1 的时钟源内部时钟或外部时钟配置正确。若误选了外部时钟且外部信号异常频率会偏离预期。重复计数器影响RepetitionCounter会影响更新事件的频率但不会改变 PWM 输出频率本身。若配置了非零值需确认其用途是否符合设计。修改后未重新初始化修改BS_TIM1_PRESCALER或BS_TIM1_PERIOD后需要重新调用bsp_tim_pwm_init使配置生效。6.3 占空比不可调现象PWM 波形存在但改变PulseCCR值后占空比不变。可能原因与排查步骤Pulse 值未更新确认修改的是tim1_oc_init_handle_t.Pulse后重新调用HAL_TIM_PWM_ConfigChannel。若只改了变量而未重新配置通道占空比不会更新。tim1_oc_init_handle_t.Pulse500;// 修改占空比HAL_TIM_PWM_ConfigChannel(tim1_handle_t,tim1_oc_init_handle_t,TIM_CHANNEL_1);Pulse 值超出范围Pulse的值应介于 0 与TIM1_PERIOD之间。若Pulse大于TIM1_PERIOD输出将恒为高电平若为 0则恒为低电平。自动重装载预装载未生效代码中AutoReloadPreload配置为TIM_AUTORELOAD_PRELOAD_DISABLE此时修改Pulse会立即生效。若改为ENABLE则需等待更新事件后才会生效需确认是否符合预期。通道配置错误确认修改的是目标通道的Pulse。例如修改通道 1 的占空比时应使用TIM_CHANNEL_1调用HAL_TIM_PWM_ConfigChannel。6.4 多通道互相干扰现象使能多个通道后某个通道的波形异常或与其他通道相互影响。可能原因与排查步骤GPIO 引脚冲突确认 4 个通道使用的引脚互不冲突且每个引脚都正确配置为对应的复用功能GPIO_AF1_TIM1_CH1~GPIO_AF1_TIM1_CH4。若两个通道复用了同一引脚信号会互相干扰。共用句柄被覆盖代码中 4 个通道共用tim1_oc_init_handle_t。初始化通道 2 时会覆盖通道 1 的OCMode和Pulse配置。若通道 1 需要不同的占空比必须在配置通道 2 之前先完成通道 1 的配置并启动或者为每个通道单独保存配置参数。死区时间配置冲突TIM1 支持互补输出和死区时间。若使能了互补输出需确认死区时间配置合理避免互补通道间出现异常电平。刹车信号误触发若刹车输入信号被意外拉低所有 PWM 输出会被强制置于复位状态。检查刹车引脚电平及HAL_TIM_PWM_Init中的刹车配置。中断优先级冲突若多个通道都开启了中断且优先级配置不当可能导致中断丢失或响应异常。建议为 TIM1 中断设置合理的抢占优先级并确保中断服务函数中及时清除标志位。7. 总结参考资料CX32L003 系列数据手册Datasheet包含 TIM1 定时器寄存器描述、引脚复用功能表GPIO_AF1_TIM1_CH1~GPIO_AF1_TIM1_CH4以及电气特性参数。CX32L003 HAL 库用户手册HAL Driver User Manual详细说明HAL_TIM_PWM_Init、HAL_TIM_PWM_ConfigChannel、HAL_TIM_PWM_Start等 API 的用法、参数含义与返回值。CX32L003 参考手册Reference Manual介绍 TIM1 高级定时器的计数模式、预分频器、自动重装载寄存器、重复计数器、死区时间与刹车输入等内部机制。CX32L003 应用笔记Application Note关于定时器 PWM 输出、互补输出与死区配置的典型应用示例与注意事项。附录宏定义配置示例在business_config.h中TIM1 相关的宏定义可按如下方式配置。以下示例使能 TIM1 及通道 1、通道 2输出 1 kHz、50% 占空比的 PWM 波形假设 TIM1 时钟为 48 MHz/* TIM1 总开关 */#defineBS_TIM1_EN1/* TIM1 时钟分频与周期配置48 MHz / ((47 1) * (999 1)) 1 kHz */#defineBS_TIM1_PRESCALER47// 预分频值 PSC实际分频系数为 48#defineBS_TIM1_PERIOD999// 重装载值 ARR实际计数周期为 1000/* 通道使能开关1 使能0 关闭 */#defineBS_PWM0_EN1// 使能通道 1CH1#defineBS_PWM1_EN1// 使能通道 2CH2#defineBS_PWM2_EN0// 关闭通道 3CH3#defineBS_PWM3_EN0// 关闭通道 4CH4/* 通道 1 引脚配置PA0 复用为 TIM1_CH1 */#defineBS_TIM1_CH1_PINGPIO_PIN_0#defineBS_TIM1_CH1_GPIO_PORTGPIOA#defineBS_TIM1_CH1_GPIO_CLK_ENABLE()__HAL_RCC_GPIOA_CLK_ENABLE()#defineBS_TIM1_CH1_GPIO_CLK_DISABLE()__HAL_RCC_GPIOA_CLK_DISABLE()/* 通道 2 引脚配置PA1 复用为 TIM1_CH2 */#defineBS_TIM1_CH2_PINGPIO_PIN_1#defineBS_TIM1_CH2_GPIO_PORTGPIOA#defineBS_TIM1_CH2_GPIO_CLK_ENABLE()__HAL_RCC_GPIOA_CLK_ENABLE()#defineBS_TIM1_CH2_GPIO_CLK_DISABLE()__HAL_RCC_GPIOA_CLK_DISABLE()/* 通道 3 引脚配置未使能可留空或按需填写 */#defineBS_TIM1_CH3_PINGPIO_PIN_2#defineBS_TIM1_CH3_GPIO_PORTGPIOA#defineBS_TIM1_CH3_GPIO_CLK_ENABLE()__HAL_RCC_GPIOA_CLK_ENABLE()#defineBS_TIM1_CH3_GPIO_CLK_DISABLE()__HAL_RCC_GPIOA_CLK_DISABLE()/* 通道 4 引脚配置未使能可留空或按需填写 */#defineBS_TIM1_CH4_PINGPIO_PIN_3#defineBS_TIM1_CH4_GPIO_PORTGPIOA#defineBS_TIM1_CH4_GPIO_CLK_ENABLE()__HAL_RCC_GPIOA_CLK_ENABLE()#defineBS_TIM1_CH4_GPIO_CLK_DISABLE()__HAL_RCC_GPIOA_CLK_DISABLE()配置说明BS_TIM1_EN为总开关置 1 时bsp_tim.c中的 TIM1 相关代码才会被编译。BS_TIM1_PRESCALER与BS_TIM1_PERIOD共同决定 PWM 频率计算公式为PWM 频率 TIM1 时钟 / ((PSC 1) * (ARR 1))。BS_PWM0_EN~BS_PWM3_EN分别控制 4 个通道的使能状态置 1 时对应通道会初始化 GPIO 复用功能并启动 PWM 输出。各通道的引脚宏需根据实际硬件连接填写并确保引脚复用功能与通道号对应如GPIO_AF1_TIM1_CH1对应通道 1。若某通道未使用可将对应使能宏置 0引脚相关宏可保留默认值或留空。本文介绍了 CX32L003 平台上基于 HAL 库的 TIM1 高级定时器 PWM 输出实现通过宏配置实现 4 路通道的灵活开关代码结构清晰、易于移植。开发者可根据实际需求调整BS_TIM1_PERIOD与BS_TIM1_PRESCALER来改变 PWM 频率与占空比。