第4章 GPIO编程实战:GPIO模式配置、寄存器操作与LED闪烁实验
各位同学,欢迎来到第四章。前面我们聊了开发环境搭建和C语言基础,今天终于要上手操作硬件了。
GPIO,全称是General Purpose Input Output,通用输入输出口。说白了,它就是芯片和外界沟通的“手脚”。你想让LED亮?GPIO输出高电平。你想检测按键是否按下?GPIO读取电平状态。你想让外部事件通知CPU?GPIO中断。
我个人习惯把GPIO编程分成三个层次:模式配置、电平操作、中断响应。今天我们就一个一个来啃。
4.1 GPIO模式配置:输入、输出与中断
MTK平台的GPIO,每个引脚都可以独立配置模式。你想想看,一个引脚既能当输入,又能当输出,还能当中断源,靠的就是寄存器里的模式位。
常见的模式有这几种:
| 模式 | 用途 | 典型场景 |
|---|---|---|
| GPIO_MODE_OUTPUT | 输出模式 | 控制LED、蜂鸣器 |
| GPIO_MODE_INPUT | 输入模式 | 读取按键、传感器状态 |
| GPIO_MODE_INT | 中断模式 | 检测外部事件触发 |
| GPIO_MODE_AF | 复用功能 | UART、I2C等外设 |
嗯,这里要注意:配置模式之前,一定要先使能GPIO时钟。我在项目中遇到过好几次,明明代码写对了,LED就是不亮,查了半天才发现是时钟没开。这个坑,你们一定要记住。
4.2 寄存器操作:直接操控硬件
MTK的GPIO寄存器,通常分为几大类:
- GPIO_DIR:方向寄存器,0是输入,1是输出
- GPIO_DOUT:数据输出寄存器,写1高电平,写0低电平
- GPIO_DIN:数据输入寄存器,读取引脚当前电平
- GPIO_PULLEN:上下拉使能寄存器
- GPIO_PULLSEL:上下拉选择寄存器
直接操作寄存器,说白了就是往特定内存地址写值。MTK平台通常把寄存器映射到某个基地址上,然后通过偏移量访问。
举个例子,假设GPIO基地址是0x10001000,GPIO0的DIR寄存器偏移是0x00,DOUT偏移是0x04:
#define GPIO_BASE 0x10001000
#define GPIO_DIR (*(volatile unsigned int *)(GPIO_BASE + 0x00))
#define GPIO_DOUT (*(volatile unsigned int *)(GPIO_BASE + 0x04))
// 设置GPIO0为输出模式
GPIO_DIR |= (1 << 0);
// GPIO0输出高电平
GPIO_DOUT |= (1 << 0);
// GPIO0输出低电平
GPIO_DOUT &= ~(1 << 0);
volatile 关键字。否则编译器优化后,你的写操作可能被“吃掉”。我曾经因为这个bug,整整调了两天。
4.3 LED闪烁实验:从理论到实践
好了,理论讲完了,我们来做一个经典的LED闪烁实验。这个实验虽然简单,但包含了GPIO编程的完整流程。
实验目标:让板载LED以1秒为周期闪烁。
硬件连接:LED正极接GPIO0,负极接地(串联一个330Ω电阻)。
代码实现:
#include "mtk_gpio.h"
// 延时函数,粗略延时
void delay_ms(unsigned int ms)
{
volatile unsigned int i, j;
for(i = 0; i < ms; i++)
for(j = 0; j < 1000; j++);
}
int main(void)
{
// 1. 使能GPIO时钟
mtk_gpio_clock_enable(GPIO0);
// 2. 配置GPIO0为输出模式
mtk_gpio_set_mode(GPIO0, GPIO_MODE_OUTPUT);
// 3. 主循环:LED闪烁
while(1)
{
mtk_gpio_set_high(GPIO0); // LED亮
delay_ms(500); // 延时500ms
mtk_gpio_set_low(GPIO0); // LED灭
delay_ms(500); // 延时500ms
}
return 0;
}
4.4 中断模式配置:让GPIO“主动”通知你
轮询方式读取GPIO,CPU一直在“问”引脚状态,效率很低。中断模式就不一样了——引脚电平变化时,硬件自动触发中断,CPU再去处理。
配置中断的步骤:
- 配置GPIO为输入模式
- 设置中断触发方式(上升沿、下降沿、双边沿、高电平、低电平)
- 使能GPIO中断
- 编写中断服务函数(ISR)
代码示例:
// 配置GPIO1为下降沿触发中断
mtk_gpio_set_mode(GPIO1, GPIO_MODE_INPUT);
mtk_gpio_set_int_type(GPIO1, GPIO_INT_FALLING_EDGE);
mtk_gpio_enable_int(GPIO1);
// 中断服务函数
void GPIO1_IRQHandler(void)
{
// 清除中断标志
mtk_gpio_clear_int(GPIO1);
// 处理中断事件
// 比如:记录按键按下次数
key_press_count++;
}
重点:中断服务函数里,一定要先清除中断标志,再处理业务逻辑。否则会反复进入中断,造成死循环。我刚开始做中断时,就犯过这个错误,CPU直接卡死。
4.5 避坑指南:我踩过的那些GPIO坑
做GPIO开发这么多年,我总结了一些常见问题,分享给你们:
- 上下拉配置不对:输入模式下,如果引脚浮空,电平会不稳定。记得配置上拉或下拉。
- 中断优先级:多个中断同时触发时,优先级低的可能被“饿死”。合理分配优先级。
- 去抖处理:机械按键按下时,会有抖动。硬件加电容,软件加延时去抖。
- 驱动能力:GPIO输出电流有限,驱动大负载要加三极管或MOS管。
我曾经在一个项目中,GPIO直接驱动继电器,结果芯片烧了。后来才知道,继电器线圈需要几百毫安电流,GPIO根本扛不住。嗯,这个教训很深刻。
4.6 本章小结
今天我们做了三件事:
- 学会了GPIO三种模式的配置方法
- 掌握了直接操作寄存器的技巧
- 完成了LED闪烁实验,并了解了中断配置
下一章,我们会深入定时器编程。到时候,LED闪烁就不用空循环延时了,用硬件定时器精确控制。敬请期待。
记住:纸上得来终觉浅,绝知此事要躬行。 拿起你的开发板,把今天的代码跑一遍。遇到问题,欢迎在课程群里交流。