3. U-Boot环境变量与启动参数配置
任务说明
本案例基于内核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
验证方法与判定标准
- LED闪烁验证:加载驱动后,观察LED以约1Hz频率(每500ms翻转一次)持续闪烁。若LED不亮或常亮不闪,检查GPIO编号是否正确、硬件连接是否可靠。
- 按键读取验证:按下按键后执行
dmesg | tail -3,应看到类似Key pressed! GPIO value = 0的日志(按下为0,释放为1)。随后执行cat /dev/gpio_key,应输出与日志一致的数值。若中断未触发,检查按键GPIO是否被其他驱动占用,或中断号是否正确。 - 卸载验证:执行
rmmod gpio_demo后,LED停止闪烁,dmesg显示GPIO demo exited,且无内核报错。
关键实现说明
- 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设备均在初始化时申请,退出时按相反顺序释放,避免资源泄漏。