2. PX4定时器驱动初始化:定时器时钟使能、GPIO复用功能配置、定时器参数结构体解析
好,咱们直接进入正题。上一章聊了定时器和PWM的基本概念,这一章我们动手实操——看看PX4里定时器驱动到底是怎么初始化的。
我个人习惯把初始化分成三步走:时钟使能 → GPIO复用配置 → 参数结构体填充。这三步走完,定时器基本就能跑了。咱们一个一个来拆。
2.1 定时器时钟使能——别让定时器“睡着”
定时器本质上是一个计数器,它需要时钟才能工作。在STM32这类MCU上,每个定时器都挂载在特定的时钟总线上。比如TIM1挂在APB2上,TIM2挂在APB1上。你得先把这个时钟打开,定时器才能动起来。
代码上其实很简单,就是调用一个库函数。但我在项目中遇到过一个问题:有人把TIM1的时钟使能了,但忘了配置预分频器,结果定时器跑得飞快,PWM频率完全不对。嗯,这里要注意——时钟使能只是第一步,频率还得靠预分频器来调。
核心代码示例(STM32 HAL库风格):
// 使能TIM1时钟(挂在APB2上)
__HAL_RCC_TIM1_CLK_ENABLE();
// 使能TIM2时钟(挂在APB1上)
__HAL_RCC_TIM2_CLK_ENABLE();
在PX4的代码里,你会在 boards/ 目录下的板级支持包中找到这些使能逻辑。说白了,每个板子都有自己的时钟树配置,你得先搞清楚你的定时器挂在哪个总线上。
小技巧: 我建议你在调试时先打印一下时钟频率,确认定时器时钟确实使能了。有时候时钟树配置错了,定时器根本不会跑。
2.2 GPIO复用功能配置——让引脚“听话”
定时器使能了,但它怎么输出PWM信号呢?答案是通过GPIO引脚。但GPIO引脚默认是普通IO口,你得把它配置成定时器的复用功能。
举个例子:STM32F4上,TIM1的通道1输出引脚是PA8。你要把PA8配置成复用功能AF1(TIM1的复用编号)。
为什么会这样?因为芯片内部有多个外设共享同一个引脚,你得告诉芯片:“这个引脚现在归定时器管,别让GPIO模块抢了。”
// 配置PA8为TIM1_CH1复用功能
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 复用推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF1_TIM1; // 复用为TIM1
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
我在PX4的 src/drivers/dshot/ 驱动里看到过类似的配置。DShot协议用的就是定时器PWM输出,GPIO复用配置错了,电机根本转不起来。我曾经因为复用编号写错,折腾了一下午才找到问题——说白了就是粗心。
避坑指南: 不同芯片的复用编号不一样。比如STM32F4上TIM1是AF1,但到了STM32H7上可能变成AF2。一定要查数据手册,别凭经验硬套。
2.3 定时器参数结构体解析——把配置“装进”结构体
时钟使能了,引脚配好了,接下来就是填充定时器的参数结构体。在HAL库中,这个结构体叫 TIM_HandleTypeDef,在PX4的底层驱动中,我们通常直接操作寄存器,但理解结构体有助于你把握整体逻辑。
核心参数就这几个:
| 参数 | 说明 | 我的经验 |
|---|---|---|
Prescaler |
预分频器,决定计数时钟频率 | 一般设成 时钟频率 / 目标频率 - 1 |
CounterMode |
计数模式:向上、向下、中心对齐 | PWM输出用向上计数就够了 |
Period |
自动重装载值,决定PWM周期 | 这个值配合预分频器算出最终频率 |
ClockDivision |
时钟分频,用于数字滤波器 | 一般设成0,除非你需要滤波 |
RepetitionCounter |
重复计数,用于高级定时器 | 普通PWM用不到,设0就行 |
举个例子,你想输出50Hz的PWM信号(周期20ms),时钟频率是84MHz:
// 目标:50Hz PWM
// 时钟:84MHz
// 预分频:84MHz / 8400 = 10kHz(计数频率)
// 周期:10kHz / 50Hz = 200
TIM_HandleTypeDef htim1;
htim1.Instance = TIM1;
htim1.Init.Prescaler = 8400 - 1; // 预分频到10kHz
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 200 - 1; // 50Hz周期
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
HAL_TIM_PWM_Init(&htim1);
你想想看,这个结构体其实就是把定时器的寄存器配置封装起来了。在PX4里,我们有时候直接操作寄存器,但理解这个结构体有助于你快速上手不同平台。
核心要点: 预分频器和周期值决定了PWM的频率。公式很简单:PWM频率 = 定时器时钟 / (Prescaler + 1) / (Period + 1)。记住这个公式,调频率时心里就有数了。
2.4 知识体系总览
为了让你更直观地理解这三步的关系,我画了一张流程图:
这张图把整个流程串起来了。你从左边开始,一步步走到右边,定时器就能正常工作了。我在PX4里调试新板子时,就是按这个顺序排查问题的——先查时钟有没有使能,再查引脚配置对不对,最后看参数算没算错。
2.5 避坑总结
最后,我把自己踩过的坑总结一下:
- 时钟使能顺序:一定要先使能定时器时钟,再配置GPIO。顺序反了,GPIO复用功能可能配置失败。
- 预分频器范围:预分频器是16位的,最大值65535。如果你要极低频率,可能需要两级分频。
- 周期值范围:同样是16位,最大值65535。配合预分频器,能覆盖从几Hz到几MHz的范围。
- 复用功能编号:不同芯片、不同定时器,复用编号都不一样。查数据手册!查数据手册!查数据手册!
嗯,这一章的内容就到这里。你把这些搞懂了,定时器初始化这块基本就通了。下一章咱们聊聊PWM输出的具体配置——怎么调占空比、怎么改频率,到时候见。