4. GPIO外设驱动
定时器,说白了就是芯片的“心跳”和“节拍器”。没有它,你的程序要么傻等,要么乱跑。GD32V的定时器资源挺丰富,从最简单的SysTick到功能强大的通用定时器,再到高级定时器,一层层递进。这一章,咱们先把最核心的SysTick和通用定时器(TIMERx)啃下来。
4.1 SysTick:系统的心跳
SysTick是个24位的递减计数器。它挂在系统总线上,专门为操作系统(或者裸机延时)提供周期性中断。我刚开始用GD32V时,第一件事就是配好SysTick,不然连个精准的延时都写不出来。
4.1.1 工作原理
SysTick的核心逻辑很简单:从你设定的重装载值开始,每个系统时钟周期减1,减到0时触发中断,然后自动重装,继续下一轮。嗯,就这么简单。
关键寄存器:
- SysTick_CTRL:控制寄存器。使能、选择时钟源、使能中断、读取计数是否到0的标志。
- SysTick_LOAD:重装载值寄存器。写入你要计数的初始值。
- SysTick_VAL:当前值寄存器。读它得到当前计数值,写它会清空计数器。
举个例子,系统时钟是108MHz,你想让SysTick每1ms中断一次。那重装载值就是108000000 / 1000 = 108000。注意,这个值不能超过2^24 - 1(约1677万),108MHz下1ms完全够用。
4.1.2 配置步骤
- 关闭SysTick(CTRL寄存器清零)。
- 设置重装载值到LOAD寄存器。
- 清除当前值(写VAL寄存器任意值)。
- 选择时钟源(AHB时钟或AHB/8)。
- 使能中断(如果需要)。
- 使能SysTick。
void systick_config(uint32_t ticks)
{
// 1. 关闭SysTick
SysTick->CTRL = 0;
// 2. 设置重装载值
SysTick->LOAD = ticks - 1;
// 3. 清除当前值
SysTick->VAL = 0;
// 4. 选择时钟源:AHB时钟
SysTick->CTRL |= SysTick_CTRL_CLKSOURCE_Msk;
// 5. 使能中断
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;
// 6. 使能SysTick
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
}
我的习惯: 我会在系统初始化时调用 systick_config(108000),然后在SysTick_Handler里累加一个全局变量 g_tick_count。这样,任何需要延时的地方,直接读这个变量做比较就行,比死循环精准得多。
4.2 通用定时器TIMERx:结构与时基单元
SysTick虽然好用,但功能太单一。真正干活的是通用定时器(TIMER0~TIMER5)。GD32V的通用定时器是一个16位的自动重装载计数器,支持向上、向下、中央对齐三种计数模式。
4.2.1 核心结构
一个通用定时器,内部由几个关键部分组成:
- 时基单元:包括预分频器(PSC)、计数器(CNT)、自动重装载寄存器(CAR)。这是定时器的“心脏”。
- 捕获/比较通道:用于输入捕获、输出比较、PWM生成。
- 中断/事件控制器:处理更新事件、捕获事件、比较事件等。
你想想看,预分频器把时钟源分频后送给计数器,计数器在时钟驱动下递增或递减,当计数值与重装载值匹配时,产生更新事件。这就是时基单元的基本流程。
4.2.2 三种计数模式
| 模式 | 方向 | 典型应用 |
|---|---|---|
| 向上计数 | 0 → CAR | 普通定时、PWM |
| 向下计数 | CAR → 0 | 对称波形生成 |
| 中央对齐 | 0 → CAR → 0 | 电机控制、无刷驱动 |
向上计数最常用。中央对齐模式在电机控制里很常见,因为它能产生对称的PWM波形,谐波更少。我记得有一次做无刷电机驱动,一开始用了向上计数,电机噪音大得离谱,换成中央对齐后,噪音立刻降下来了。
4.3 PWM模式:让定时器输出波形
PWM(脉冲宽度调制),说白了就是通过调节高低电平的占空比来控制功率。GD32V的通用定时器支持两种PWM模式:PWM模式1和PWM模式2。区别在于输出电平的极性。
4.3.1 配置流程
- 使能定时器时钟。
- 配置GPIO为复用功能输出。
- 设置预分频器和自动重装载值,确定PWM频率。
- 配置捕获/比较通道为PWM模式。
- 设置占空比(写入捕获/比较寄存器)。
- 使能定时器。
void timer_pwm_config(void)
{
// 1. 使能定时器时钟
rcu_periph_clock_enable(RCU_TIMER1);
// 2. 配置GPIO(PA1为TIMER1_CH0)
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_1);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1);
gpio_af_set(GPIOA, GPIO_AF_2, GPIO_PIN_1);
// 3. 设置预分频器和自动重装载值
timer_prescaler_config(TIMER1, 107, TIMER_PSC_RELOAD_NOW); // 108MHz / 108 = 1MHz
timer_autoreload_value_config(TIMER1, 999); // 1MHz / 1000 = 1kHz
// 4. 配置通道0为PWM模式1
timer_channel_output_mode_config(TIMER1, TIMER_CH_0, TIMER_OC_MODE_PWM1);
timer_channel_output_state_config(TIMER1, TIMER_CH_0, TIMER_CCX_ENABLE);
// 5. 设置占空比50%
timer_channel_output_pulse_value_config(TIMER1, TIMER_CH_0, 500);
// 6. 使能定时器
timer_enable(TIMER1);
}
我曾经踩过的坑: 配置GPIO复用功能时,一定要查数据手册确认正确的复用功能号(AF号)。GD32V的引脚复用不像STM32那么直观,同一个引脚可能对应多个外设。我一开始没仔细看,把AF_1写成了AF_2,结果PWM死活没输出,查了半天才发现是引脚复用配错了。
4.3.2 占空比与频率计算
频率公式:F_pwm = F_timer / (PSC + 1) / (CAR + 1)
占空比公式:Duty = CCR / (CAR + 1) * 100%
举个例子,系统时钟108MHz,PSC=107,CAR=999,那么PWM频率就是108MHz / 108 / 1000 = 1kHz。CCR=500时,占空比就是50%。
我的建议: 实际项目中,PWM频率和占空比往往需要动态调整。我会把PSC和CAR的计算封装成一个函数,传入目标频率,自动算出最接近的整数值。这样代码复用性高,也方便调试。
4.4 本章小结
这一章我们聊了SysTick和通用定时器的核心用法。SysTick适合做系统心跳和精准延时,通用定时器则能输出PWM、捕获输入信号。配置定时器时,时钟源、预分频器、自动重装载值这三者决定了定时精度和PWM频率。嗯,这些基础打牢了,后面做更复杂的协议解析和电机控制才能游刃有余。