4. GD32V电源管理单元
各位同学,今天我们来啃一块硬骨头——GPIO和中断。说实话,这部分内容在GD32V上玩好了,后面低功耗和FreeRTOS移植才能走得顺。我刚开始做嵌入式那会儿,总觉得GPIO不就是个高低电平嘛,直到有一次产品量产了才发现中断响应慢了一拍,那叫一个惨。好,咱们开始。
4.1 GPIO模式配置:推挽、开漏、上拉、下拉
GD32V的GPIO引脚,说白了就是个多面手。每个引脚可以配置成多种模式,你得根据实际电路需求来选。我个人习惯,先看外设手册,再定模式,省得后面改来改去。
4.1.1 推挽输出 vs 开漏输出
| 模式 | 输出电平能力 | 典型应用 | 注意事项 |
|---|---|---|---|
| 推挽输出 | 强拉高、强拉低 | LED驱动、普通IO | 不能线与,否则短路 |
| 开漏输出 | 只能拉低,高阻需外接上拉 | I2C总线、多设备共享线 | 必须配外部上拉电阻 |
推挽输出,内部PMOS和NMOS交替导通,输出0就是0,输出1就是1,干脆利落。开漏输出呢,只用了NMOS,输出0时拉低,输出1时引脚直接变高阻——你想想看,这时候电平全靠外部上拉电阻决定。
4.1.2 上拉与下拉电阻
GD32V内部集成了可编程的上拉和下拉电阻,大约40kΩ左右。这个值不算大,但对付浮空输入足够了。
- 上拉输入:引脚默认高电平,外部拉低时触发
- 下拉输入:引脚默认低电平,外部拉高时触发
- 浮空输入:啥都不接,电平不确定——千万别用
4.1.3 代码示例:GPIO模式配置
// 配置PA0为推挽输出,50MHz速度
gpio_mode_set(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_0);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0);
// 配置PB1为开漏输出,带上拉
gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_PIN_1);
gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_2MHZ, GPIO_PIN_1);
// 配置PC2为输入,下拉
gpio_mode_set(GPIOC, GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN_2);
嗯,这里要注意:GPIO_OSPEED不是越高越好。50MHz适合高速信号,但功耗也大。低功耗场景下,我一般用2MHz,够用就行。
4.2 外部中断(EXTI)配置
外部中断,就是让GPIO引脚在电平变化时通知CPU。GD32V的EXTI支持16个独立中断线,每个中断线可以映射到任意一个GPIO引脚——但同一中断线不能同时映射多个引脚。
4.2.1 EXTI配置步骤
- 使能GPIO时钟和SYSCFG时钟
- 配置GPIO模式为输入
- 将GPIO引脚连接到EXTI线
- 配置EXTI触发方式(上升沿、下降沿、双边沿)
- 使能EXTI中断
- 配置NVIC中断优先级
- 编写中断服务函数
核心要点:EXTI线0~15对应GPIO的PIN0~PIN15。比如PA0和PB0都映射到EXTI线0,但只能选一个用。我习惯把同一功能的外设放在不同PIN上,避免冲突。
4.2.2 代码示例:按键中断
// 使能时钟
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_SYSCFG);
// 配置PA0为输入,上拉
gpio_mode_set(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, GPIO_PIN_0);
// 连接EXTI线0到PA0
syscfg_exti_line_config(EXTI_SOURCE_GPIOA, EXTI_SOURCE_PIN0);
// 配置EXTI:下降沿触发(按键按下)
exti_init(EXTI_0, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
exti_interrupt_flag_clear(EXTI_0);
exti_interrupt_enable(EXTI_0);
// 配置NVIC优先级
nvic_irq_enable(EXTI0_IRQn, 2, 0);
4.2.3 中断服务函数
void EXTI0_IRQHandler(void)
{
if(exti_interrupt_flag_get(EXTI_0) != RESET)
{
// 处理按键事件
// 注意:中断里别做复杂操作
// 我一般只设一个标志位
key_pressed = 1;
// 清中断标志
exti_interrupt_flag_clear(EXTI_0);
}
}
4.3 中断优先级与嵌套
GD32V的NVIC支持最多16级抢占优先级和16级子优先级。说白了,就是给中断排个队——谁重要谁先上。
4.3.1 优先级分组
| 分组 | 抢占优先级位数 | 子优先级位数 | 说明 |
|---|---|---|---|
| NVIC_PRIGROUP_0 | 0 | 4 | 只有子优先级,无抢占 |
| NVIC_PRIGROUP_1 | 1 | 3 | 2级抢占,8级子优先级 |
| NVIC_PRIGROUP_2 | 2 | 2 | 4级抢占,4级子优先级 |
| NVIC_PRIGROUP_3 | 3 | 1 | 8级抢占,2级子优先级 |
| NVIC_PRIGROUP_4 | 4 | 0 | 16级抢占,无子优先级 |
我个人习惯用NVIC_PRIGROUP_2,4级抢占够用,4级子优先级也能处理一些微调需求。你想想看,如果所有中断都是同一抢占优先级,那就只能排队,高优先级的中断没法打断低优先级的——这在实时系统里很要命。
4.3.2 中断嵌套规则
- 抢占优先级决定能否打断:数字越小优先级越高
- 子优先级决定同时到达时谁先响应
- 相同抢占优先级的中断不能互相打断
4.3.3 代码示例:设置中断优先级
// 设置优先级分组:4级抢占,4级子优先级
nvic_priority_group_set(NVIC_PRIGROUP_2);
// 使能EXTI0中断,抢占优先级0,子优先级0
nvic_irq_enable(EXTI0_IRQn, 0, 0);
// 使能USART0中断,抢占优先级1,子优先级0
nvic_irq_enable(USART0_IRQn, 1, 0);
// 使能TIMER2中断,抢占优先级2,子优先级0
nvic_irq_enable(TIMER2_IRQn, 2, 0);
嗯,这里要注意:中断优先级配置必须在使能中断之前完成。我见过有人先使能中断再配优先级,结果中断一使能就触发,优先级还没配好,乱套了。
4.4 知识体系总览
下面这张图把本章的核心逻辑串起来了,你可以对照着看。
从图上你能看到,GPIO模式配置是基础,EXTI中断是桥梁,NVIC优先级是调度核心。这三层搞明白了,GD32V的中断系统你就拿下了。
本章核心要点:
- 推挽输出强驱动,开漏输出适合总线
- 上拉下拉防浮空,浮空输入慎用
- EXTI配置分六步,标志位清除不能忘
- 抢占优先级决定打断能力,子优先级决定排队顺序
- 中断服务函数要短小精悍,别在里面做复杂运算