嵌入式Linux LED驱动开发

嵌入式Linux LED驱动开发

一、LED驱动概述

本笔记基于IMX6ULL处理器的LED驱动开发,详细介绍了字符设备驱动开发的基本流程。该驱动实现了对LED的基本控制功能,通过字符设备接口供用户空间程序调用。

二、LED驱动核心概念

1. 寄存器地址定义

本驱动涉及多个硬件寄存器的配置:

c 复制代码
#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状态定义

c 复制代码
#define LEDOFF 0 // LED关闭
#define LEDON  1 // LED开启

三、LED驱动开发步骤

1. 驱动模块初始化与卸载

c 复制代码
static int __init led_init(void) {
    // 初始化代码
}

static void __exit led_exit(void) {
    // 清理代码
}

module_init(led_init);
module_exit(led_exit);

2. 硬件初始化

  1. 地址映射
c 复制代码
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);
  1. 时钟使能
c 复制代码
val = readl(CCM_CGR1);
val &= ~(3 << 26); // 清除旧设置
val |= (3 << 26);  // 启用GPIO1时钟
writel(val, CCM_CGR1);
  1. 引脚配置
c 复制代码
writel(0x5, SW_MUX_GPIO1_IO03); // 设置为GPIO模式
writel(0x10B0, SW_PAD_GPIO1_IO03); // 配置电气特性
  1. GPIO方向设置
c 复制代码
val = readl(GPIO1_GDIR);
val |= (1 << 3); // 设置GPIO1_IO03为输出
writel(val, GPIO1_GDIR);

3. 设备操作函数实现

  1. LED开关函数
c 复制代码
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);
}
  1. 文件操作结构体
c 复制代码
static const struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .write = led_write,
    .open = led_open,
    .release = led_release,
};
  1. open和release函数
c 复制代码
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;
}
  1. write函数
c 复制代码
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. 模块注册与注销

c 复制代码
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. 测试程序功能

c 复制代码
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. 测试方法

bash 复制代码
# 编译测试程序
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说明

makefile 复制代码
KERNERDIR := /home/ubuntu2004/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENTDIR := $(shell pwd)

obj-m := led.o

build : kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) modules

clean:
	$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) clean

六、驱动开发关键知识点

1. 地址映射与硬件访问

  • 使用ioremap将物理地址映射到内核地址空间
  • 使用readlwritel进行寄存器读写
  • 使用iounmap解除地址映射

2. 内核空间与用户空间交互

  • 使用copy_from_user从用户空间复制数据
  • 使用printk输出内核日志信息

3. 字符设备驱动注册

  • 使用register_chrdev注册字符设备
  • 使用unregister_chrdev注销字符设备

4. GPIO操作

  • 配置GPIO为输出模式
  • 通过修改DR寄存器控制LED状态
  • 注意GPIO电平的含义(低电平点亮LED)

5. 驱动调试

  • 使用dmesg查看内核日志
  • 使用insmod加载驱动
  • 使用mknod创建设备节点
  • 使用测试程序验证驱动功能

七、LED驱动工作流程

  1. 驱动加载

    • 执行insmod led.ko加载驱动
    • 执行dmesg查看"LED module initialized"确认加载成功
  2. 设备节点创建

    • 执行mknod /dev/led c 200 0创建设备节点
  3. LED控制

    • 执行./ledAPP /dev/led 1点亮LED
    • 执行./ledAPP /dev/led 0关闭LED
  4. 驱动卸载

    • 执行rmmod led.ko卸载驱动
    • 执行dmesg查看"LED module exited"确认卸载成功

八、常见问题与解决

  1. LED不工作

    • 检查地址映射是否正确
    • 使用printk调试输出确认驱动加载
    • 检查GPIO方向是否设置为输出
    • 验证硬件连接
  2. 驱动加载失败

    • 检查设备号是否冲突
    • 查看dmesg日志定位错误
    • 确认内核版本与编译环境匹配
  3. 用户程序无法访问

    • 检查设备节点权限
    • 确认设备节点主设备号匹配
    • 检查open和write函数实现

九、驱动开发总结

本驱动实现了对IMX6ULL处理器GPIO接口的LED控制,展示了字符设备驱动开发的基本流程:

  1. 硬件寄存器地址定义和初始化
  2. 地址映射和硬件配置
  3. 字符设备注册
  4. 文件操作函数实现
  5. 用户空间接口设计
  6. 驱动模块加载和卸载
  7. 测试程序开发

Gitee 源码仓库