通过之前的学习,了解到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. [ Java面试题 ]Java 开发岗面试知识点解析

    如背景中介绍,作者在一年之内参加过多场面试,应聘岗位均为 Java 开发方向. 在不断的面试中,分类总结了 Java 开发岗位面试中的一些知识点. 主要包括以下几个部分: Java 基础知识点 Jav ...

  2. Guava Cache,Java本地内存缓存使用实践

    Guava Cache,网上介绍很多,我就不赘述了. 分享一篇好的文章: Guava Cache内存缓存使用实践-定时异步刷新及简单抽象封装 Google Guava 3-缓存 在原作者基础上,我做了 ...

  3. Jmeter- 笔记9 - CLI(无图形界面)

    使用CLI模式,减少资源占用 用GUI调试好脚本 在jmeter的bin文件夹运行cmd,然后输入命令:jmeter -n -t [jmx file] -l [results file] -e -o ...

  4. C# HTTP请求对外接口、第三方接口公用类

    /// <summary> /// 网络数据请求公共函数 /// </summary> public class HttpWebRequestCommon { #region ...

  5. 从性能角度帮你理解HTTP协议

    因为做性能测试分析的人来说,HTTP 协议可能是绕不过去的一个槛.在讲 HTTP 之前,我们得先知道一些基本的信息. HTTP(HyperText Transfer Protocol,超文本传输协议) ...

  6. AIFramework框架Jittor特性(上)

    AIFramework框架Jittor特性(上)

  7. HDR sensor 原理介绍

    HDR sensor 原理介绍 一. HDR sensor 原理介绍 1. 什么是sensor的动态范围(dynamic range): sensor的动态范围就是sensor在一幅图像里能够同时体现 ...

  8. Yolov3&Yolov4网络结构与源码分析

    Yolov3&Yolov4网络结构与源码分析 从2018年Yolov3年提出的两年后,在原作者声名放弃更新Yolo算法后,俄罗斯的Alexey大神扛起了Yolov4的大旗. 文章目录 1. 论 ...

  9. 像Swing这种已经不太用的技术,大学还在教,到底要不要学?

    一直以来,写日常问题.前沿技术和架构思考类的文章比较多,今天为什么突然来说说Swing这个陈年老技术呢? 因为在CSDN上看到了这样的一篇文章: 可以看到作者对于学Swing还是挺愤怒的,不过确实Sw ...

  10. 【NX二次开发】多种变换

    变换的种类: uf5942 矩阵乘积变换 uf5943 平移变换 uf5944 缩放变换 uf5945 旋转变换 uf5946 镜像变换 最后使用 uf5947 实现uf5942-uf5946的变换. ...