ARM裸机开发(4)GPIO与LED/按键驱动详解——引脚复用与电气特性配置

前言

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等外设的引脚配置都是同样的套路。

相关推荐
Awh-2 小时前
ARM裸机开发 Day01 学习笔记
arm开发·arm·arm裸机
an12321215 小时前
ARM汇编学习笔记 Day02
arm开发·arm
xx~t6 小时前
嵌入式——ARM——汇编1
linux·汇编·arm开发·嵌入式硬件·arm
ouynagda6 小时前
嵌入式与ARM内核基础体系梳理
arm开发
PiaoKe___6 小时前
从端云解耦到可编程 Android 实例:云手机架构解析与 Python 批量管控实战
arm开发·python·架构·自动化
Horn Still Sounds17 小时前
ARM嵌入式基础|内核、寄存器、架构、存储、汇编移位全面梳理
arm开发·单片机·嵌入式硬件·硬件架构
从零开始的嵌入式之旅19 小时前
day44
arm开发·经验分享·笔记·嵌入式硬件
2401_862880821 天前
ARM 嵌入式开发 ---知识点
arm开发·嵌入式硬件·物联网
AAA代码批发商1 天前
DAY43 ARM 体系结构学习笔记|嵌入式入门,从硬件组件到流水线、内存与栈帧
arm开发·笔记·学习