前言
GPIO(General Purpose Input/Output,通用输入输出)是嵌入式开发中最基础的外设。本文将详细讲解 i.MX6ULL 的 GPIO 体系结构,包括引脚复用(IOMUX)、电气特性配置、以及如何驱动LED和读取按键。
一、i.MX6ULL GPIO 体系结构
1.1 GPIO 模块结构
i.MX6ULL 的 GPIO 由两部分组成:
引脚(Pad)
│
├── IOMUX(引脚复用选择)──→ 选择该引脚用作GPIO/UART/SPI等功能
│
└── GPIO模块
├── GDIR(方向寄存器)──→ 输入/输出
├── DR(数据寄存器)──→ 读/写电平
├── PSR(引脚状态寄存器)──→ 读取实际引脚电平
├── ICR1/ICR2(中断配置寄存器)──→ 中断触发方式
└── IMR(中断屏蔽寄存器)──→ 使能/屏蔽中断
1.2 GPIO 端口与引脚编号
i.MX6ULL 有 GPIO1 ~ GPIO5 共5组GPIO,每组最多32个引脚:
- GPIO1_IO00 ~ GPIO1_IO31
- GPIO2_IO00 ~ GPIO2_IO31
- GPIO3_IO00 ~ GPIO3_IO31
- GPIO4_IO00 ~ GPIO4_IO31
- GPIO5_IO00 ~ GPIO5_IO31
二、引脚复用配置(IOMUX)
2.1 为什么需要引脚复用?
i.MX6ULL 的引脚数量有限(通常只有几百个),但功能很多(UART、SPI、I2C、GPIO等)。为了节省引脚,一个物理引脚可以复用为多种功能。
例如,UART1_TX_DATA 引脚可以配置为:
- 功能1:UART1发送
- 功能2:GPIO1_IO16
- 功能3:其他外设功能
2.2 引脚复用配置代码
以LED引脚(GPIO1_IO03)为例:
cs
void led_init(void)
{
// 1. 配置引脚复用:将GPIO1_IO03配置为GPIO功能
IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0);
// 2. 配置引脚电气特性
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0x1079);
// 3. 配置GPIO方向:输出
GPIO1->GDIR |= (1 << 3);
// 4. 初始输出高电平(LED熄灭)
GPIO1->DR |= (1 << 3);
}
2.3 IOMUXC_SetPinMux 函数
IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0);
参数说明:
- 第一个参数:引脚复用选择宏,定义在
fsl_iomuxc.h中IOMUXC_GPIO1_IO03_GPIO1_IO03表示将 GPIO1_IO03 引脚配置为 GPIO 功能IOMUXC_UART1_TX_DATA_UART1_TX表示将该引脚配置为 UART1 发送功能
- 第二个参数:输入模式配置(0表示使用默认配置)
内部实现: 该函数会配置 IOMUXC_SW_MUX_CTL_PAD_xxx 寄存器,选择引脚的复用功能。
2.4 IOMUXC_SetPinConfig 函数
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0x1079);
参数 0x1079 的含义: 配置 IOMUXC_SW_PAD_CTL_PAD_xxx 寄存器,控制引脚的电气特性。
0x1079 = 0001 0000 0111 1001
位[16] = 0 : HYS(迟滞)= 禁用
位[15:14] = 00 : PUS(上拉/下拉选择)= 100KΩ下拉
位[13] = 0 : PUE(上下拉/保持器选择)= 使用上下拉
位[12] = 1 : PKE(上下拉/保持器使能)= 使能
位[11] = 0 : ODE(开漏使能)= 禁用
位[10:8] = 000: 保留
位[7:6] = 01 : SPEED(速度)= 100MHz
位[5:3] = 111: DSE(驱动强度)= R0/6(最大驱动能力)
位[2:1] = 00 : SRE(压摆率)= 快速
位[0] = 1 : 保留
常用电气特性配置值:
| 配置值 | 说明 | 适用场景 |
|---|---|---|
| 0x1079 | 100MHz速度,最大驱动强度,上拉 | GPIO输出(LED) |
| 0x5000 | 禁用上下拉,输入模式 | GPIO输入(按键) |
| 0x10B0 | UART引脚配置 | UART TX/RX |
三、GPIO 通用驱动 (gpio.c)
3.1 GPIO 方向配置
cs
void gpio_init(GPIO_Type * gpio, int n, dir_t dir)
{
if(GPIO_INPUT == dir)
{
gpio->GDIR &= ~(1 << n); // 清除第n位:配置为输入
}
else
{
gpio->GDIR |= (1 << n); // 设置第n位:配置为输出
gpio->DR |= (0 << n); // 初始输出低电平
}
}
GDIR寄存器(GPIO Direction Register):
- 位n = 0:第n个引脚配置为输入
- 位n = 1:第n个引脚配置为输出
3.2 GPIO 输出控制
cs
void gpio_set_value(GPIO_Type * gpio, int n, int value)
{
if(value)
{
gpio->DR |= (1 << n); // 输出高电平
}
else
{
gpio->DR &= ~(1 << n); // 输出低电平
}
}
DR寄存器(GPIO Data Register):
- 写操作:设置输出电平
- 读操作:读取输出锁存器的值(不一定等于实际引脚电平)
3.3 GPIO 输入读取
cs
int gpio_get_value(GPIO_Type * gpio, int n)
{
return ((gpio->DR) & (1 << n)) ? 1 : 0;
}
注意: 这里读取的是DR寄存器,实际上应该读取PSR(Pin Status Register)来获取引脚的实际电平。但在输入模式下,DR寄存器会反映引脚的实际状态。
四、LED 驱动 (led.c)
4.1 LED 初始化
cs
void led_init(void)
{
// 1. 引脚复用:GPIO1_IO03 作为 GPIO 功能
IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0);
// 2. 电气特性:100MHz速度,最大驱动强度
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0x1079);
// 3. 方向:输出
GPIO1->GDIR |= (1 << 3);
// 4. 初始值:高电平(LED熄灭,假设低电平点亮)
GPIO1->DR |= (1 << 3);
}
4.2 LED 控制函数
cs
void led_on(void)
{
GPIO1->DR &= ~(1 << 3); // 输出低电平 → LED点亮
}
void led_off(void)
{
GPIO1->DR |= (1 << 3); // 输出高电平 → LED熄灭
}
void led_nr(void)
{
GPIO1->DR ^= (1 << 3); // 翻转电平 → LED状态取反
}
电路原理:
- LED 阳极接 3.3V,阴极通过限流电阻接 GPIO
- GPIO 输出低电平时,电流从 3.3V 流过 LED 到 GPIO,LED 点亮
- GPIO 输出高电平时,LED 两端无电压差,LED 熄灭
五、按键驱动 (key.c)
5.1 按键初始化
cs
void key_init(void)
{
// 1. 引脚复用:UART1_CTS_B 引脚配置为 GPIO 功能
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);
// 2. 电气特性:禁用上下拉(按键外部已有上下拉电阻)
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0x5000);
// 3. 方向:输入
gpio_init(GPIO1, 18, GPIO_INPUT);
// 4. 配置中断:双边沿触发(按下和释放都触发中断)
GPIO1->ICR2 |= (0x3 << 4); // GPIO1_IO18 的中断配置
GPIO1->IMR |= (1 << 18); // 使能 GPIO1_IO18 的中断
}
5.2 引脚复用说明
cs
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);
这行代码将 UART1_CTS_B (UART1的流控引脚)复用为 GPIO1_IO18。
- 这是一个引脚复用的例子:原本用于UART流控的引脚,现在用作GPIO输入
- 在实际产品中,如果不使用UART流控功能,这个引脚可以复用为GPIO
5.3 电气特性配置
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0x5000);
0x5000 的含义:
0x5000 = 0101 0000 0000 0000
位[16] = 0 : HYS(迟滞)= 禁用
位[15:14] = 01 : PUS(上拉/下拉选择)= 100KΩ上拉
位[13] = 0 : PUE = 使用上下拉
位[12] = 1 : PKE = 使能上下拉
位[11] = 0 : ODE = 禁用开漏
为什么按键引脚要配置上拉?
- 按键未按下时,GPIO读到高电平(通过上拉电阻)
- 按键按下时,GPIO读到低电平(按键将引脚接地)
- 这样可以通过读取GPIO电平判断按键状态
5.4 中断配置
// 配置中断触发方式
GPIO1->ICR2 |= (0x3 << 4); // GPIO1_IO18:双边沿触发
// 使能中断
GPIO1->IMR |= (1 << 18); // 使能 GPIO1_IO18 的中断屏蔽
ICR2寄存器(Interrupt Configuration Register 2):
- 每个引脚占2位,配置中断触发方式
00= 低电平触发01= 高电平触发10= 上升沿触发11= 下降沿触发
IMR寄存器(Interrupt Mask Register):
- 位n = 1:使能第n个引脚的中断
- 位n = 0:屏蔽第n个引脚的中断
六、GPIO 中断状态寄存器
6.1 ISR寄存器(Interrupt Status Register)
当中断发生时,对应的ISR位会被置1。必须手动清除该位,否则中断会持续触发。
// 在中断处理函数中清除中断标志
GPIO1->ISR |= (1 << 18); // 清除 GPIO1_IO18 的中断标志
注意: 写1清除(W1C),不是写0清除。
七、总结
| 功能 | 寄存器 | 操作 |
|---|---|---|
| 引脚复用 | IOMUXC_SW_MUX_CTL_PAD_xxx | SetPinMux() |
| 电气特性 | IOMUXC_SW_PAD_CTL_PAD_xxx | SetPinConfig() |
| 方向控制 | GDIR | 0=输入,1=输出 |
| 输出控制 | DR | 写0/写1控制电平 |
| 输入读取 | DR/PSR | 读取引脚电平 |
| 中断触发方式 | ICR1/ICR2 | 低/高电平,上升/下降沿 |
| 中断使能 | IMR | 使能/屏蔽中断 |
| 中断状态 | ISR | W1C清除中断标志 |
GPIO 是所有外设的基础,理解了GPIO的配置方法,后面的UART、SPI、I2C等外设的引脚配置都是同样的套路。