3. GPIO子系统操作

GPIO子系统操作 学习卡片

任务说明

本案例基于内核GPIO子系统,在Linux驱动中完成两项核心操作:通过GPIO控制LED周期性闪烁,以及读取按键输入状态。驱动加载后自动启动LED闪烁,按键按下时通过内核日志输出当前电平状态。目标平台为i.MX6ULL开发板,GPIO编号采用内核子系统统一管理的全局编号方式。

硬件连接与GPIO编号

  • LED正极经220Ω电阻接GPIO1_IO04(内核编号:4),阴极接地
  • 按键一端接GPIO1_IO05(内核编号:5),另一端接地,内部使能上拉电阻

驱动代码实现


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>

#define LED_GPIO        4
#define KEY_GPIO        5
#define KEY_IRQ_NAME    "gpio_key_irq"

static int led_state;
static int key_value = -1;

/* 按键中断处理函数 */
static irqreturn_t key_irq_handler(int irq, void *dev_id)
{
    key_value = gpio_get_value(KEY_GPIO);
    pr_info("Key pressed! GPIO value = %d\n", key_value);
    return IRQ_HANDLED;
}

/* 读取按键状态 */
static ssize_t key_read(struct file *file, char __user *buf,
                        size_t count, loff_t *ppos)
{
    char kbuf[8];
    int len;

    if (key_value < 0)
        return 0;

    len = snprintf(kbuf, sizeof(kbuf), "%d\n", key_value);
    if (copy_to_user(buf, kbuf, len))
        return -EFAULT;

    key_value = -1;
    return len;
}

static const struct file_operations key_fops = {
    .owner  = THIS_MODULE,
    .read   = key_read,
};

static struct miscdevice key_miscdev = {
    .minor  = MISC_DYNAMIC_MINOR,
    .name   = "gpio_key",
    .fops   = &key_fops,
};

/* LED闪烁工作队列 */
static struct work_struct led_work;
static void led_blink_work(struct work_struct *work)
{
    led_state = !led_state;
    gpio_set_value(LED_GPIO, led_state);
    schedule_delayed_work(to_delayed_work(work), HZ / 2);
}

static DECLARE_DELAYED_WORK(led_work, led_blink_work);

static int __init gpio_demo_init(void)
{
    int ret;

    /* 请求LED GPIO */
    ret = gpio_request(LED_GPIO, "led");
    if (ret) {
        pr_err("Failed to request LED GPIO %d\n", LED_GPIO);
        return ret;
    }
    gpio_direction_output(LED_GPIO, 0);

    /* 请求按键GPIO */
    ret = gpio_request(KEY_GPIO, "key");
    if (ret) {
        pr_err("Failed to request KEY GPIO %d\n", KEY_GPIO);
        gpio_free(LED_GPIO);
        return ret;
    }
    gpio_direction_input(KEY_GPIO);

    /* 注册按键中断(双边沿触发) */
    ret = request_irq(gpio_to_irq(KEY_GPIO), key_irq_handler,
                      IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
                      KEY_IRQ_NAME, NULL);
    if (ret) {
        pr_err("Failed to request IRQ\n");
        gpio_free(KEY_GPIO);
        gpio_free(LED_GPIO);
        return ret;
    }

    /* 注册misc设备 */
    ret = misc_register(&key_miscdev);
    if (ret) {
        pr_err("Failed to register misc device\n");
        free_irq(gpio_to_irq(KEY_GPIO), NULL);
        gpio_free(KEY_GPIO);
        gpio_free(LED_GPIO);
        return ret;
    }

    /* 启动LED闪烁 */
    schedule_delayed_work(&led_work, HZ / 2);

    pr_info("GPIO demo initialized. LED GPIO=%d, KEY GPIO=%d\n",
            LED_GPIO, KEY_GPIO);
    return 0;
}

static void __exit gpio_demo_exit(void)
{
    cancel_delayed_work_sync(&led_work);
    misc_deregister(&key_miscdev);
    free_irq(gpio_to_irq(KEY_GPIO), NULL);
    gpio_free(KEY_GPIO);
    gpio_free(LED_GPIO);
    pr_info("GPIO demo exited\n");
}

module_init(gpio_demo_init);
module_exit(gpio_demo_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("BSP Course");
MODULE_DESCRIPTION("GPIO Subsystem LED Blink and Key Read Demo");

Makefile


obj-m := gpio_demo.o
KDIR := /home/user/linux-imx-rel_imx_4.1.15_2.1.0_ga
PWD := $(shell pwd)

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean

将KDIR路径替换为实际内核源码目录。

编译与加载


# 编译
make

# 拷贝到目标板后加载
insmod gpio_demo.ko

# 查看内核日志确认初始化
dmesg | tail -5

# 读取按键状态(需先按下按键触发中断)
cat /dev/gpio_key

验证方法与判定标准

  1. LED闪烁验证:加载驱动后,观察LED以约1Hz频率(每500ms翻转一次)持续闪烁。若LED不亮或常亮不闪,检查GPIO编号是否正确、硬件连接是否可靠。
  2. 按键读取验证:按下按键后执行dmesg | tail -3,应看到类似Key pressed! GPIO value = 0的日志(按下为0,释放为1)。随后执行cat /dev/gpio_key,应输出与日志一致的数值。若中断未触发,检查按键GPIO是否被其他驱动占用,或中断号是否正确。
  3. 卸载验证:执行rmmod gpio_demo后,LED停止闪烁,dmesg显示GPIO demo exited,且无内核报错。
GPIO子系统驱动架构与数据流 用户空间 cat /dev/gpio_key 内核空间 misc设备 gpio_key 中断处理函数 key_irq_handler LED工作队列 led_blink_work 内核GPIO子系统核心API gpio_request | gpio_direction_output | gpio_direction_input | gpio_get_value | gpio_set_value | gpio_to_irq GPIO1_IO04 (LED) — GPIO1_IO05 (KEY) read() gpio_get_value() gpio_set_value() 数据流向 按键中断 → 读取电平 定时工作队列 → 翻转LED

关键实现说明

  • LED闪烁机制:使用schedule_delayed_work每500ms调度一次工作队列,在工作函数中翻转GPIO电平,实现1Hz闪烁。卸载时通过cancel_delayed_work_sync确保队列清理。
  • 按键中断读取:按键配置为双边沿触发中断,中断服务函数中直接读取GPIO值并保存。用户通过cat /dev/gpio_key读取时返回最近一次按键状态。
  • GPIO编号说明:i.MX6ULL的GPIO1_IO04对应内核GPIO编号4,GPIO1_IO05对应编号5。不同平台编号映射不同,需根据实际BSP调整。
  • 资源管理:所有GPIO、中断、misc设备均在初始化时申请,退出时按相反顺序释放,避免资源泄漏。