嵌入式Linux LED驱动开发
嵌入式Linux LED驱动开发
一、LED驱动概述
本笔记基于IMX6ULL处理器的LED驱动开发,详细介绍了字符设备驱动开发的基本流程。该驱动实现了对LED的基本控制功能,通过字符设备接口供用户空间程序调用。
二、LED驱动核心概念
1. 寄存器地址定义
本驱动涉及多个硬件寄存器的配置:
#define CCM_CGR1_BASE 0x020C406C // 时钟控制寄存器
#define SW_MUX_GPIO1_IO03_BASE 0x020E0068 // 引脚复用配置
#define SW_PAD_GPIO1_IO03_BASE 0x020E02F4 // 引脚电气特性配置
#define GPIO1_DR_BASE 0x0209C000 // 数据寄存器
#define GPIO1_GDIR_BASE 0x0209C004 // 方向寄存器
2. 寄存器操作方法
ioremap
:将物理地址映射到内核虚拟地址空间readl
:读取寄存器值writel
:写入寄存器值iounmap
:解除地址映射
3. LED状态定义
#define LEDOFF 0 // LED关闭
#define LEDON 1 // LED开启
三、LED驱动开发步骤
1. 驱动模块初始化与卸载
static int __init led_init(void) {// 初始化代码
}static void __exit led_exit(void) {// 清理代码
}module_init(led_init);
module_exit(led_exit);
2. 硬件初始化
- 地址映射
CCM_CGR1 = ioremap(CCM_CGR1_BASE, 4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
- 时钟使能
val = readl(CCM_CGR1);
val &= ~(3 << 26); // 清除旧设置
val |= (3 << 26); // 启用GPIO1时钟
writel(val, CCM_CGR1);
- 引脚配置
writel(0x5, SW_MUX_GPIO1_IO03); // 设置为GPIO模式
writel(0x10B0, SW_PAD_GPIO1_IO03); // 配置电气特性
- GPIO方向设置
val = readl(GPIO1_GDIR);
val |= (1 << 3); // 设置GPIO1_IO03为输出
writel(val, GPIO1_GDIR);
3. 设备操作函数实现
- LED开关函数
static void led_switch(unsigned char state) {unsigned int val = 0;val = readl(GPIO1_DR);if (state == LEDON) {val &= ~(1 << 3); // 拉低电压点亮LED} else if (state == LEDOFF) {val |= (1 << 3); // 拉高电压关闭LED}writel(val, GPIO1_DR);
}
- 文件操作结构体
static const struct file_operations led_fops = {.owner = THIS_MODULE,.write = led_write,.open = led_open,.release = led_release,
};
- open和release函数
static int led_open(struct inode *inode, struct file *file) {printk("LED device opened\n");return 0;
}static int led_release(struct inode *inode, struct file *file) {printk("LED device closed\n");return 0;
}
- write函数
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) {int ret = 0;unsigned char databuf[1];ret = copy_from_user(databuf, buf, count);if (ret < 0) {printk("Failed to copy data from user space\n");return -EIO;}led_switch(databuf[0]);return 0;
}
4. 模块注册与注销
static int __init led_init(void) {// ...ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);// ...
}static void __exit led_exit(void) {// ...unregister_chrdev(LED_MAJOR, LED_NAME);// ...
}
四、用户空间测试程序
1. 测试程序功能
int main(int argc, char *argv[]) {if (argc != 3) {fprintf(stderr, "Usage: %s <led_device> <0|1>\n", argv[0]);return -1;}char* filename;unsigned char databuf[1];filename = argv[1];databuf[0] = atoi(argv[2]);int fd = 0;int ret = 0;fd = open(filename, O_RDWR);if (fd < 0) {perror("open led device error");return -1;}ret = write(fd, databuf, 1);if (ret < 0) {perror("write led device error");close(fd);return -1;}close(fd);return 0;
}
2. 测试方法
# 编译测试程序
arm-linux-gnueabihf-gcc ledAPP.c -o ledAPP# 加载驱动
sudo insmod led.ko# 创建设备节点
sudo mknod /dev/led c 200 0# 点亮LED
sudo ./ledAPP /dev/led 1# 关闭LED
sudo ./ledAPP /dev/led 0
五、Makefile说明
KERNERDIR := /home/ubuntu2004/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENTDIR := $(shell pwd)obj-m := led.obuild : kernel_moduleskernel_modules:$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) modulesclean:$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) clean
六、驱动开发关键知识点
1. 地址映射与硬件访问
- 使用
ioremap
将物理地址映射到内核地址空间 - 使用
readl
和writel
进行寄存器读写 - 使用
iounmap
解除地址映射
2. 内核空间与用户空间交互
- 使用
copy_from_user
从用户空间复制数据 - 使用
printk
输出内核日志信息
3. 字符设备驱动注册
- 使用
register_chrdev
注册字符设备 - 使用
unregister_chrdev
注销字符设备
4. GPIO操作
- 配置GPIO为输出模式
- 通过修改DR寄存器控制LED状态
- 注意GPIO电平的含义(低电平点亮LED)
5. 驱动调试
- 使用
dmesg
查看内核日志 - 使用
insmod
加载驱动 - 使用
mknod
创建设备节点 - 使用测试程序验证驱动功能
七、LED驱动工作流程
-
驱动加载
- 执行
insmod led.ko
加载驱动 - 执行
dmesg
查看"LED module initialized"确认加载成功
- 执行
-
设备节点创建
- 执行
mknod /dev/led c 200 0
创建设备节点
- 执行
-
LED控制
- 执行
./ledAPP /dev/led 1
点亮LED - 执行
./ledAPP /dev/led 0
关闭LED
- 执行
-
驱动卸载
- 执行
rmmod led.ko
卸载驱动 - 执行
dmesg
查看"LED module exited"确认卸载成功
- 执行
八、常见问题与解决
-
LED不工作
- 检查地址映射是否正确
- 使用
printk
调试输出确认驱动加载 - 检查GPIO方向是否设置为输出
- 验证硬件连接
-
驱动加载失败
- 检查设备号是否冲突
- 查看
dmesg
日志定位错误 - 确认内核版本与编译环境匹配
-
用户程序无法访问
- 检查设备节点权限
- 确认设备节点主设备号匹配
- 检查open和write函数实现
九、驱动开发总结
本驱动实现了对IMX6ULL处理器GPIO接口的LED控制,展示了字符设备驱动开发的基本流程:
- 硬件寄存器地址定义和初始化
- 地址映射和硬件配置
- 字符设备注册
- 文件操作函数实现
- 用户空间接口设计
- 驱动模块加载和卸载
- 测试程序开发
Gitee 源码仓库