通过之前的学习,了解到linux驱动编写的流程是:先通过注册函数注册我们编写的入口函数,然后在入口函数中获取设备号->注册字符设备->自动创建设备节点->获取设备树信息,最后通过销毁函数将出口函数中需要释放的资源进行释放,想知道具实现的小伙伴可以查看我之前的文章。完成之前的学习,这篇文章所涉及的知识就比较简单了,现在我们开始led驱动的学习。

一、准备材料

开发环境:VMware

操作系统:ubuntu

开发版:湃兔i2S-6UB

库文件:linux开发板或ubuntu的内核源码

二、GPIO原理图

我用的是i2C-6ULX-B开发版,想要了解更多开发版的信息可以查看i2C-6ULX-B开发套件,外观如下图所示:



通过湃兔官方提供的原理图,可以知道开发板上的两个LED,有一个是电源指示灯通电就亮,所以我们能使用的只有一个,具体如下图所示:



从原理图中可知led是低电平亮,高电平熄,然后接着查看湃兔核心板的引脚图,如下图所示:



最后在查看湃兔官方提供的引脚定义,具体如下图所以:



现在不用我多说小伙伴们都知道i2C-6ULX-B开发版上的led灯接的是芯片的gpio5.IO[5],在Linux中的GPIO计算方法是,GPIO_num = (<imx6ul_gpio_port> - 1) * 32 + <imx6ul_gpio_pin>,所以湃兔i2C-6ULX-B开发板的led接的是133引脚。

三、GPIO配置

了解led的硬件原理后,需要在设备树中进行配置,有需要的小伙伴可以了解湃兔官方的GPIO配置教程,好吧说得比较简单,没有学习过设备树的小伙伴可能看不懂,需要的可以百度搜索下相关教程。在配置之前我们还需要了解一下什么是GPIO子系统和pinctrl子系统,需要的朋友可以了解一下gpio子系统和pinctrl子系统(上)

好吧,赶紧回来扯远了,看不明白不要紧,我们主要是先实践再理论,只学理论知识可能很让人绝望啊,等用多了,再回头学习自然就明白了。现在我们开始在设备树中配置gpio,打开'arch/arm/boot/dts'目录下的'i2c6ulxb-i2s6ull-emmc.dtsi'文件,在跟节点中添加如下信息

dtsled{
compatible = "i2som,gpioled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_dtsled>;
led-gpios = <&gpio5 5 GPIO_ACTIVE_LOW>;
status = "okay";
};

如下图所示:



细心的小伙伴可以已经看出来了,去注释了一行信息,因为湃兔的这个开发板只有一个led灯,然后被系统用于心跳灯使用,为了更好的验证,所以我们把系统使用的心跳灯给注释了,如下图所示:



最后在'iomuxc_snvs'这个节点中添加如下信息:

pinctrl_dtsled: dtsled {
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER5__GPIO5_IO05 0x1b0b0
>;
};

如下图所示:



到此我们的设备树已经更改完成了,接下来编写驱动程序。

四、led驱动程序

其他的函数我就不过多介绍了,有需要的小伙伴可以查看我直接的文章,我使用led的驱动函数是如下所示

/* 获取GPIO */
int of_get_named_gpio(struct device_node *np, const char *propname, int index)
/* 检查gpio number是否合法 */
int gpio_to_irq(unsigned gpio)
/* 申请IO */
int gpio_request(unsigned gpio, const char *label)
/* 释放IO */
void gpio_free(unsigned gpio)
/* 设置gpio 为输入*/
int gpio_direction_input(unsigned gpio)
/* 设置IO为输出模式 */
int gpio_direction_output(unsigned gpio, int value)
/* 设置IO输出电平 */
gpio_set_value(unsigned gpio, int value)
/* 设置gpio的消抖时间 */
int gpio_set_debounce(unsigned gpio, unsigned debounce)
/* 获取gpio对应的中断线路 */
int gpio_to_irq(unsigned gpio)
/* gpio中断 */
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * name, void * dev)

相信这些函数都不用过多的介绍,小伙伴们应该都知道怎么使用了,如想了解具体的参数含义可以查看Linux 驱动学习笔记 - gpio 子系统 (八)这篇文章,接下来开始编写源码。

五、程序源码

驱动dtsled.c文件

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h> #define DTSLED_NAME "dtsled"
#define DTSLED_COUNT 1
#define LEDOFF 0
#define LEDON 1 /*设备结构体*/
struct dtsled_dev{
dev_t devid; /* 设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct cdev cdev; /* 字符设备 */
struct class *class; /* 类结构体 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
int gpio_number; /* gpio的编号 */
}; 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)
{
return 0;
} static ssize_t dtsled_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
int ret = 0;
unsigned char databuf[1];
struct dtsled_dev *dev = filp->private_data; ret = copy_from_user(databuf, buf, count);
if (ret < 0) {
return -EINVAL;
} if (databuf[0] == LEDON) {
gpio_set_value(dev->gpio_number, 0);
} else if (databuf[0] == LEDOFF) {
gpio_set_value(dev->gpio_number, 1);
} return 0;
} /*
* 字符设备操作集合
*/
static const 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; printk("dtsled_init\r\n"); /* 申请设备号 */
dtsled.major = 0; /* 设置设备号由内存分配 */
if (dtsled.major){
dtsled.devid = MKDEV(dtsled.major, 0);
ret = register_chrdev_region(dtsled.devid, DTSLED_COUNT, DTSLED_NAME);
} else {
ret = alloc_chrdev_region(&dtsled.devid, 0, DTSLED_COUNT, DTSLED_NAME);
dtsled.major = MAJOR(dtsled.devid);
dtsled.minor = MINOR(dtsled.devid);
}
if (ret < 0) {
printk("dtsled chrdev_region err!\r\n");
goto fail_devid;
} /* 注册字符设备 */
dtsled.cdev.owner = dtsled_fops.owner;
cdev_init(&dtsled.cdev, &dtsled_fops);
ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_COUNT);
if (ret < 0) {
goto fail_cdev;
} /* 自动创建设备节点 */
dtsled.class = class_create(dtsled_fops.owner, DTSLED_NAME);
if (IS_ERR(dtsled.class)) {
ret = PTR_ERR(dtsled.class);
goto fail_class;
} dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);
if (IS_ERR(dtsled.device)) {
ret = PTR_ERR(dtsled.device);
goto fail_device;
} /* 获取设备树的属性内容 */
dtsled.nd = of_find_node_by_path("/dtsled");
if (dtsled.nd == NULL) {
ret = -EINVAL;
goto fail_findnd;
} /* 获取GPIO */
dtsled.gpio_number = of_get_named_gpio(dtsled.nd, "led-gpios", 0);
if (dtsled.gpio_number < 0) {
printk("can't find led gpio");
ret = -EINVAL;
goto fail_rs;
} else {
printk("led gpio num = %d\r\n", dtsled.gpio_number);
} /* 申请IO */
ret = gpio_request(dtsled.gpio_number, "led-gpio");
if (ret < 0) {
printk("failde to request the led gpio\r\n");
ret = -EINVAL;
goto fail_rs;
} /* 设置IO为输出模式 */
ret = gpio_direction_output(dtsled.gpio_number, 1);
if (ret < 0) {
ret = -EINVAL;
goto fail_setoutput;
} /* 设置IO默认输出低电平 */
gpio_set_value(dtsled.gpio_number, 0); return 0; fail_setoutput:
gpio_free(dtsled.gpio_number);
fail_rs:
fail_findnd:
device_destroy(dtsled.class, dtsled.devid);
fail_device:
class_destroy(dtsled.class);
fail_class:
cdev_del(&dtsled.cdev);
fail_cdev:
unregister_chrdev_region(dtsled.devid, DTSLED_COUNT);
fail_devid:
return ret; } /*
* 模块出口
*/
static void __exit dtsled_exit(void)
{
printk("dtsled_exit\r\n"); /* 关闭led */
gpio_set_value(dtsled.gpio_number, 1); /* 删除字符设备 */
cdev_del(&dtsled.cdev); /* 释放字符设号 */
unregister_chrdev_region(dtsled.devid, DTSLED_COUNT); /* 摧毁设备 */
device_destroy(dtsled.class, dtsled.devid); /* 摧毁类 */
class_destroy(dtsled.class); /* 释放IO */
gpio_free(dtsled.gpio_number);
} /*
* 模块注册入口
*/
module_init(dtsled_init);
/*
* 模块注册出口
*/
module_exit(dtsled_exit); MODULE_LICENSE("GPL");
MODULE_AUTHOR("jiaozhu");

Makefile文件

KERNELDIR := /home/xfg/linux/imx_7ull/i2x_6ub/system_file/i2SOM-iMX-Linux

CURRENT_PATH := $(shell pwd)
obj-m := dtsled.o build: kernel_modules kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

ledApp.c应用测试文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h> /*
*argc:应用程序参数个数
*argv[]:具体的参数内容,字符串形式
*./ledApp <filename> <0:1> 0表示关,1表示开
*./ledApp /dev/dtsled 0 关闭led灯
*./ledApp /dev/dtsled 1 打开led灯
* */ #define LEDOFF 0
#define LEDON 1 int main(int argc, char *argv[])
{
int ret = 0;
int fd = 0;
char *filename;
unsigned char databuf[1]; if(argc !=3) {
printf("Instruction usage error!!!\r\n");
printf("./ledApp <filename> <0:1> 0表示关,1表示开\r\n");
printf("./ledApp ./dev/dtsled 1 \r\n");
return -1;
} filename = argv[1]; fd = open(filename, O_RDWR);
if(fd < 0) {
printf("open file %s failed\r\n", filename);
} databuf[0] = atoi(argv[2]);
ret = write(fd, databuf, sizeof(databuf));
if(ret < 0) {
printf("write file %s failed\r\n", filename);
} ret =close(fd);
if(ret <0) {
printf("close file %s falied!\r\n", filename);
} return 0;
}

六、测试

1.设备树测试

回到内核源码的根目录下重新编译设备树文件,编译命令

make dtbs

编译完成后将/arch/arm/boot/dts目录下的i2c6ulxb-i2s6ull-emmc.dtb拷贝至tftp服务器下,然后重启开发版即可,我使用的是nfs挂载根文件系统的方式进行测试的,当然也可以直接更新开发板中的设备树,只是这样比较麻烦,开发中不建议这么操作。

重新启动后,进入/proc/device-tree/目录下查看我们更改的节点信息是否存在,如下图所示:



可知我们添加的desled节点信息是确定的,接下来测试驱动。

1.驱动测试

将应用文件和驱动文件编译后拷贝到开发版的/lib/modules/4.1.43/目录下,挂载led驱动,如下图所示:



挂载成功后可以看到我们申请的gpio引脚编号是133,最后通过应用测试led是否能打开和关闭,如下图所示:



如果能正常打开和关闭led灯,说明我们的驱动和应用程序没有问题,若有写得不好的地方,望各位大佬指出。

参考文献

gpio子系统和pinctrl子系统(上):https://www.cnblogs.com/rongpmcu/p/7662751.html

Linux 驱动学习笔记 - gpio 子系统 (八):https://blog.csdn.net/tyustli/article/details/105484666

linux驱动之LED驱动的更多相关文章

  1. Linux驱动之LED驱动编写

    从上到下,一个软件系统可以分为:应用程序.操作系统(内核).驱动程序.结构图如下:我们需要做的就是写出open.read.write等驱动层的函数.一个LED驱动的步骤如下: 1.查看原理图,确定需要 ...

  2. 【Linux 驱动】简单字符设备驱动架构(LED驱动)

    本文基于icool210开发板,内核版本:linux2.6.35: 驱动代码: (1)头文件:led.h #ifndef __LED_H__ #define __LED_H__ #define LED ...

  3. linux驱动之LED驱动_1

    步骤: 1.框架 2.完好硬件的操作: a.看原理图.引脚 b.看2440手冊 c.写代码: IO口须要用ioremap映射 我的板子电路例如以下所看到的 1.配置GPBCON 寄存器,配置输出   ...

  4. 字符设备驱动之Led驱动学习记录

    一.概述 Linux内核就是由各种驱动组成的,内核源码中大约有85%的各种渠道程序的代码.一般来说,编写Linux设备驱动大致流程如下: 1.查看原理图,数据手册,了解设备的操作方法. 2.在内核中找 ...

  5. 字符设备驱动之LED驱动

    实现 ①编写驱动框架 ②编写硬件实现代码 (在Linux系统下操作硬件,需要操作虚拟地址,因此需要先把物理地址转换为虚拟地址 ioremap()) 如何实现单个灯的操作: 实现方法之一--操作次设备号 ...

  6. Linux驱动之按键驱动编写(中断方式)

    在Linux驱动之按键驱动编写(查询方式)已经写了一个查询方式的按键驱动,但是查询方式太占用CPU,接下来利用中断方式编写一个驱动程序,使得CPU占有率降低,在按键空闲时调用read系统调用的进程可以 ...

  7. Linux驱动之按键驱动编写(查询方式)

    在Linux驱动之LED驱动编写已经详细介绍了一个驱动的编写过程,接着来写一个按键驱动程序,主要是在file_operations结构中添加了一个read函数.还是分以下几步说明 1.查看原理图,确定 ...

  8. linux设备驱动归纳总结(五):4.写个简单的LED驱动【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-84693.html linux设备驱动归纳总结(五):4.写个简单的LED驱动 xxxxxxxxxxx ...

  9. Linux下实现流水灯等功能的LED驱动代码及测试实例

    驱动代码: #include <linux/errno.h> #include <linux/kernel.h> #include <linux/module.h> ...

随机推荐

  1. C#异常处理18条最佳实践

    首先,异常处理应该是系统设计规约的一部分出现在系统设计文档中,而不仅仅是一种技术实现. 作为设计文档的一部分,异常处理应该着眼于系统容错性和稳定性(正如楼主提到的那样).然后在根据这个规约,再来具体讨 ...

  2. V $ BACKUP_DATAFILE

    V$BACKUP_DATAFILE 从控制文件显示有关备份集中的控制文件和数据文件的信息. 柱 数据类型 描述 RECID NUMBER 备份数据文件记录ID STAMP NUMBER 备份数据文件记 ...

  3. 同一个Controller里的同一个Service实例,在当前的Controller里的不同方法中状态不一致

    直接上代码如下: @Controller@RequestMapping("/views/information")public class PubContentController ...

  4. css——圣杯布局

    圣杯布局要求 header和footer各自占领屏幕所有宽度,高度固定 中间dontainer部分为左中右三栏式布局 三栏布局中左右两侧宽度固定,中间部分自动填充 实现方式 1.浮动 先定义heade ...

  5. 北汽极狐ARCFOX与华为合作

    北汽极狐ARCFOX与华为合作 全球首款激光雷达量产车 2021年,是激光雷达"上车"的元年. 曾经价格高不可攀,只能用于Robotaxi.无人车测试的激光雷达,终于彻底具备商业化 ...

  6. 稀疏性如何为AI推理增加难度

    稀疏性如何为AI推理增加难度 NVIDIA Ampere架构使数学运算加倍,以加速对各种神经网络的处理. 如果曾经玩过游戏Jenga,那么将有一些AI稀疏感. 玩家将木制积木交叉成一列.然后,每个玩家 ...

  7. 深入理解ES8的新特性SharedArrayBuffer

    简介 ES8引入了SharedArrayBuffer和Atomics,通过共享内存来提升workers之间或者worker和主线程之间的消息传递速度. 本文将会详细的讲解SharedArrayBuff ...

  8. HTML——超链接<a>

    一.超链接的一般格式: <a href="路径" target="目标窗口的位置">链接文本或图像<a/> 1.常用参数说明: href ...

  9. DOS命令行(5)——Windows系统的配置与管理(下)

    whoami --查看当前有效用户 这个工具可以用来获取本地系统上当前用户(访问令牌)的用户名和组信息,以及相应的安全标识符(SID).声明.本地系统上当前用户的权限.登录标识符(登录 ID).例如, ...

  10. JAVA设计模式(6:单例模式详解)

    单例模式作为一种创建型模式,在日常开发中用处极广,我们先来看一一段代码: // 构造函数 protected Calendar(TimeZone var1, Locale var2) { this.l ...