当前位置: 首页 > news >正文

嵌入式Linux驱动开发 - DTS LED驱动

嵌入式Linux驱动开发 - DTS LED驱动

一、项目概述

本项目实现了基于设备树(Device Tree)的LED驱动程序,包含内核模块和用户空间测试程序。驱动程序通过设备树获取硬件信息,实现了LED的开关控制功能。

二、开发环境

  • 开发板:i.MX6ULL阿尔法开发板
  • 内核版本:Linux 4.1.15
  • 开发工具链:交叉编译工具链
  • 硬件平台:NXP i.MX6ULL处理器

三、代码结构

dtsled/
├── dtsled.c          // 内核模块驱动代码
├── dtsledAPP.c       // 用户空间测试程序
├── Makefile          // 编译规则
└── imx6ull-alientek-emmc.dts  // 设备树文件

四、核心组件详解

1. Makefile分析

KERNERDIR := /home/ubuntu2004/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENTDIR := $(shell pwd)obj-m := dtsled.o
build : kernel_moduleskernel_modules:$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) modulesclean:$(MAKE) -C $(KERNERDIR) M=$(CURRENTDIR) clean
  • KERNERDIR:内核源码路径
  • CURRENTDIR:当前工作目录
  • obj-m:声明编译成内核模块
  • kernel_modules:编译内核模块的目标规则
  • clean:清理编译生成的文件

2. 设备树配置

&iomuxc {...pinctrl_gpioled: ledgrp {fsl,pins = <MX6UL_PAD_GPIO1_IO03__GPIO1_IO03	0x10b0>;};
};dtsled{compatible = "gpio-leds";led0{label = "red";pinctrl-names = "default";pinctrl-0 = <&pinctrl_gpioled>;gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;default-state = "on";linux,default-trigger = "heartbeat";};
};
设备树关键配置:
  • pinctrl_gpioled:定义LED使用的GPIO引脚配置
  • dtsled节点
    • compatible:匹配驱动的兼容字符串
    • label:LED标签
    • gpios:指定GPIO引脚和激活电平
    • default-state:默认状态
    • linux,default-trigger:默认触发器

3. 内核模块代码分析 (dtsled.c)

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/slab.h>#define DRIVER_NAME "dtsled"
#define DTSLED_CNT 1
#define LEDON 1
#define LEDOFF 0/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;void led_switch(u8 sta)
{u32 val = 0;if (sta == LEDON){val = readl(GPIO1_DR);val &= ~(1 << 3);writel(val, GPIO1_DR);}else if (sta == LEDOFF){val = readl(GPIO1_DR);val |= (1 << 3);writel(val, GPIO1_DR);}
}struct dtsled_dev
{dev_t devid;int major;int minor;struct cdev cdev;struct class *class;struct device *device;struct device_node *nd;
};
static struct dtsled_dev dtsled;static int dtsled_open(struct inode *inode, struct file *filp)
{filp->private_data = &dtsled;return 0;
}static int dtsled_release(struct inode *inode, struct file *filp)
{struct dtsled_dev *dev = (struct dtsled_dev *)filp->private_data;return 0;
}static ssize_t dtsled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{struct dtsled_dev *dev = (struct dtsled_dev *)filp->private_data;int retvalue;unsigned char databuf[1];unsigned char ledstat;retvalue = copy_from_user(databuf, buf, cnt);if (retvalue < 0){printk("kernel write failed!\r\n");return -EFAULT;}led_switch(buf[0]);return 0;
}struct file_operations dtsled_fops ={.owner = THIS_MODULE,.open = dtsled_open,.release = dtsled_release,.write = dtsled_write,
};static int __init dtsled_init(void)
{int ret = 0;const char *str;u32 regdata[10], val;u8 i = 0;dtsled.major = 0; // Dynamic major numberif (dtsled.major){dtsled.devid = MKDEV(dtsled.major, 0);ret = register_chrdev_region(dtsled.devid, DTSLED_CNT, DRIVER_NAME);}else{ret = alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT, DRIVER_NAME);dtsled.major = MAJOR(dtsled.devid);dtsled.minor = MINOR(dtsled.devid);}if (ret < 0){goto fail_devid;}printk("major: %d, minor: %d\r\n", dtsled.major, dtsled.minor);dtsled.cdev.owner = THIS_MODULE;cdev_init(&dtsled.cdev, &dtsled_fops);ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);if (ret < 0){goto fail_cdev;}dtsled.class = class_create(dtsled.cdev.owner, DRIVER_NAME);if (IS_ERR(dtsled.class)){ret = PTR_RET(dtsled.class);goto fail_create_class;}dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DRIVER_NAME);if (IS_ERR(dtsled.device)){ret = PTR_RET(dtsled.device);goto faid_device_create;}dtsled.nd = of_find_node_by_path("/alphaled");if (dtsled.nd == NULL){ret = -EIO;goto fail_findnd;}ret = of_property_read_string(dtsled.nd, "status", &str);if (ret < 0){goto fail_rs;}else{printk("status=%s", str);}ret = of_property_read_string(dtsled.nd, "compatible", &str);if (ret < 0){goto fail_rs;}else{printk("compatible=%s", str);}ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);if (ret < 0){goto fail_rs;}else{printk("\r\n");for (i = 0; i < 10; i++){printk("%#x ", regdata[i]);}printk("\r\n");}IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0);SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);GPIO1_DR = of_iomap(dtsled.nd, 3);GPIO1_GDIR = of_iomap(dtsled.nd, 4);/* 2、使能GPIO1时钟 */val = readl(IMX6U_CCM_CCGR1);val &= ~(3 << 26); /* 清楚以前的设置 */val |= (3 << 26);  /* 设置新值 */writel(val, IMX6U_CCM_CCGR1);/* 3、设置GPIO1_IO03的复用功能,将其复用为*    GPIO1_IO03,最后设置IO属性。*/writel(5, SW_MUX_GPIO1_IO03);writel(0x10B0, SW_PAD_GPIO1_IO03);/* 4、设置GPIO1_IO03为输出功能 */val = readl(GPIO1_GDIR);val &= ~(1 << 3); /* 清除以前的设置 */val |= (1 << 3);  /* 设置为输出 */writel(val, GPIO1_GDIR);/* 5、默认关闭LED */val = readl(GPIO1_DR);val |= (1 << 3);writel(val, GPIO1_DR);return 0;
fail_rs:
fail_findnd:device_destroy(dtsled.class, dtsled.devid);
faid_device_create:class_destroy(dtsled.class);
fail_create_class:cdev_del(&dtsled.cdev);
fail_cdev:unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
fail_devid:return ret;
}static void __exit dtsled_exit(void)
{u32 val = readl(GPIO1_DR);val |= (1 << 3);writel(val, GPIO1_DR);/* 取消映射 */iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);device_destroy(dtsled.class, dtsled.devid);class_destroy(dtsled.class);cdev_del(&dtsled.cdev);unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
}module_init(dtsled_init);
module_exit(dtsled_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alientek");
模块初始化流程:
  1. 字符设备注册

    • 动态分配主设备号
    • 初始化并添加字符设备
    • 创建设备类和设备文件
  2. 设备树解析

    • 查找设备树节点/alphaled
    • 读取属性:status, compatible, reg
    • 获取寄存器地址并进行IO映射
  3. GPIO初始化

    • 使能GPIO1时钟
    • 配置GPIO1_IO03为GPIO功能
    • 设置GPIO1_IO03为输出模式
    • 默认关闭LED
  4. 文件操作接口

    • open:简单的文件打开处理
    • release:资源释放
    • write:接收用户空间的LED控制命令

4. 用户空间测试程序 (dtsledAPP.c)

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main(int argc, char *argv[])
{if (argc != 3)  // Expecting the program name and one argument{fprintf(stderr, "Usage: %s <led_device> <0|1>\n", argv[0]);return -1;}char* fileanme;unsigned char databuf[1];fileanme = argv[1];databuf[0] = atoi(argv[2]);int fd = 0;int ret = 0;fd = open(fileanme, 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;
}
使用说明:
# 编译
arm-linux-gnueabi-gcc -o dtsledAPP dtsledAPP.c# 运行示例 - 打开LED
./dtsledAPP /dev/dtsled 1# 运行示例 - 关闭LED
./dtsledAPP /dev/dtsled 0

五、驱动工作原理

1. 设备树机制

  • 使用设备树传递硬件信息,避免硬编码GPIO地址
  • 通过of_find_node_by_path获取设备树节点
  • 使用of_property_read_*系列函数读取节点属性
  • 通过of_iomap进行寄存器地址映射

2. 字符设备驱动框架

  • 分配和注册设备号
  • 初始化字符设备结构体
  • 创建设备类和设备文件
  • 实现文件操作接口

3. GPIO控制流程

  1. 使能GPIO时钟
  2. 配置引脚复用功能
  3. 设置引脚电气属性
  4. 配置为输出模式
  5. 通过写DR寄存器控制LED状态

4. 用户空间通信

  • 通过write系统调用传递LED状态
  • 内核空间接收数据后调用led_switch函数
  • 直接操作GPIO寄存器实现LED控制

六、编译与测试流程

1. 编译驱动

make -C /path/to/kernel/source M=$(PWD) modules

2. 加载驱动

insmod dtsled.ko

3. 测试LED

# 打开LED
./dtsledAPP /dev/dtsled 1# 关闭LED
./dtsledAPP /dev/dtsled 0

七、调试技巧

1. 内核日志查看

dmesg

2. 设备节点检查

ls -l /dev/dtsled

3. 寄存器读写验证

  • 使用devmem2工具直接读写寄存器验证配置

4. 错误处理

  • 检查模块加载日志
  • 验证设备树配置
  • 检查GPIO引脚配置
  • 查看文件权限设置

八、扩展与优化

1. 支持多个LED

  • 修改设备树配置多个LED子节点
  • 修改驱动支持多个GPIO控制

2. 支持亮度调节

  • 添加PWM控制功能
  • 在设备树中配置PWM相关属性

3. 添加sysfs接口

  • 创建sysfs节点提供更友好的用户接口

4. 支持设备树热插拔

  • 实现of_device的probe和remove函数
  • 支持设备树动态更新

九、常见问题与解决

1. 模块加载失败

  • 检查内核版本匹配
  • 验证交叉编译工具链
  • 检查内核配置是否支持模块

2. 设备节点未创建

  • 检查class和device创建是否成功
  • 查看dmesg日志中的错误信息

3. LED不响应

  • 验证设备树配置是否正确
  • 检查GPIO引脚是否被其他功能占用
  • 使用devmem2直接操作寄存器测试

4. 权限问题

  • 使用chmod修改设备节点权限
  • 或者使用root权限运行测试程序

十、总结

本项目完整实现了基于设备树的LED驱动程序,展示了嵌入式Linux驱动开发的核心技术:

  • 设备树的使用与配置
  • 字符设备驱动框架
  • GPIO操作与寄存器访问
  • 用户空间与内核空间通信
  • 模块化开发与调试技巧

Gitee 源码仓库

http://www.dtcms.com/a/356669.html

相关文章:

  • 拼多多商品信息批量获取及开放API接口调用指南
  • 【面试场景题】dubbo可以使用自定义的序列化协议吗
  • 音视频学习(五十九):H264中的SPS
  • Kubernetes: 解构Karpenter NodePool, 云原生时代的弹性节点管理艺术
  • 【K8s】整体认识K8s之集群的pod之间的通信
  • LSM6DS3姿态芯片和LIS2MD磁力计芯片数据读取(stm32)
  • 千年智造,一触即发 耐达讯自动化Profibus集线器如何让HMI触摸屏在工业4.0中“点石成金“?
  • 嵌入式Linux驱动开发 - 并发控制机制
  • 【视频讲解】R语言海七鳃鳗性别比分析:JAGS贝叶斯分层逻辑回归MCMC采样模型应用
  • 嵌入式Linux驱动开发 - 新字符设备LED驱动
  • Go Vendor 和 Go Modules:管理和扩展依赖的最佳实践
  • Redis vs Elasticsearch:核心区别深度解析
  • Vue 项目首屏加载速度优化
  • Mysql系列--11、使用c/c++访问mysql服务
  • ViennaCL并行异构库介绍和使用
  • Pinterest自动化 “Pin“得高效
  • SpringMvc下
  • Oracle SQL 性能调优的基石:深入解读与驾驭执行计划
  • 商家协同生态下的复购革命:跨店收益如何激活12%增量消费
  • 【新启航】3D 逆向抄数的工具技术与核心能力:基于点云处理的扫描设备操作及模型重建方法论
  • 【活动回顾】“智驱未来,智领安全” AI+汽车质量与安全论坛
  • OpenEuler部署LoganaLyzer
  • 【开题答辩全过程】以 基于SpringBootVue的智能敬老院管理系统为例,包含答辩的问题和答案
  • 黑芝麻智能与云深处科技达成战略合作,共推具身智能平台全球市场应用
  • 基于互补素数与最小素因子性质的哥德巴赫猜想证明-陈墨仙
  • ROS2学习打卡表
  • 外卖大战之后,再看美团的护城河
  • vue3获取子组件
  • PostgreSQL15——子查询
  • 采用机器学习的苗期棉株点云器官分割与表型信息提取