linux驱动之LED驱动
通过之前的学习,了解到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驱动的更多相关文章
- Linux驱动之LED驱动编写
从上到下,一个软件系统可以分为:应用程序.操作系统(内核).驱动程序.结构图如下:我们需要做的就是写出open.read.write等驱动层的函数.一个LED驱动的步骤如下: 1.查看原理图,确定需要 ...
- 【Linux 驱动】简单字符设备驱动架构(LED驱动)
本文基于icool210开发板,内核版本:linux2.6.35: 驱动代码: (1)头文件:led.h #ifndef __LED_H__ #define __LED_H__ #define LED ...
- linux驱动之LED驱动_1
步骤: 1.框架 2.完好硬件的操作: a.看原理图.引脚 b.看2440手冊 c.写代码: IO口须要用ioremap映射 我的板子电路例如以下所看到的 1.配置GPBCON 寄存器,配置输出 ...
- 字符设备驱动之Led驱动学习记录
一.概述 Linux内核就是由各种驱动组成的,内核源码中大约有85%的各种渠道程序的代码.一般来说,编写Linux设备驱动大致流程如下: 1.查看原理图,数据手册,了解设备的操作方法. 2.在内核中找 ...
- 字符设备驱动之LED驱动
实现 ①编写驱动框架 ②编写硬件实现代码 (在Linux系统下操作硬件,需要操作虚拟地址,因此需要先把物理地址转换为虚拟地址 ioremap()) 如何实现单个灯的操作: 实现方法之一--操作次设备号 ...
- Linux驱动之按键驱动编写(中断方式)
在Linux驱动之按键驱动编写(查询方式)已经写了一个查询方式的按键驱动,但是查询方式太占用CPU,接下来利用中断方式编写一个驱动程序,使得CPU占有率降低,在按键空闲时调用read系统调用的进程可以 ...
- Linux驱动之按键驱动编写(查询方式)
在Linux驱动之LED驱动编写已经详细介绍了一个驱动的编写过程,接着来写一个按键驱动程序,主要是在file_operations结构中添加了一个read函数.还是分以下几步说明 1.查看原理图,确定 ...
- linux设备驱动归纳总结(五):4.写个简单的LED驱动【转】
本文转载自:http://blog.chinaunix.net/uid-25014876-id-84693.html linux设备驱动归纳总结(五):4.写个简单的LED驱动 xxxxxxxxxxx ...
- Linux下实现流水灯等功能的LED驱动代码及测试实例
驱动代码: #include <linux/errno.h> #include <linux/kernel.h> #include <linux/module.h> ...
随机推荐
- libevent中的bufferevent原理
以前的文章看过缓冲区buffer了,libevent用bufferevent来负责管理缓冲区与buffer读写事件. 今天就带大家看下evbuffer.c,使用bufferevent处理事 ...
- nginx的模块化体系结构
nginx的模块化体系结构 nginx的内部结构是由核心部分和一系列的功能模块所组成.这样划分是为了使得每个模块的功能相对简单,便于开发,同时也便于对系统进行功能扩展.为了便于描述,下文中我们将使用n ...
- rman备份出现ORA-19625
[oracle@hear adump]$ rman target / Recovery Manager: Release 11.2.0.4.0 - Production on Mon Jun 17 0 ...
- Steam游戏《Northgard(北境之地)》修改器制作
日期:2021.06.07 博客期:181 星期一 [温馨提示]: 我现在把资源先放到开头,不想研究学习的就直接取用.如果修改器失效了,你们可以在博客园本页直接评论,也可以给我发邮件告诉我,就是不要到 ...
- 使用adb命令查看APP包名 和 包入口方法
方法一: 1.查看 前台应用 包名,使用此命令注意:手机只启动要查看包名的app adb shell dumpsys activity | find "mFocusedActivity&qu ...
- httprunner_安装及利用脚手架工具快速创建项目
一.安装httprunner 笔者自己安装的版本为2.5.7 安装命令: pip install httprunner==2.5.7 二.快速创建目录 hrun --startproject dem ...
- mybatis——解决属性名和数据库字段名不一致问题
首先说一下,我的数据库名字叫mybatis,里边有一张user表,表中有三个字段,id.name.pwd:然后实体类中对应三个属性id.name.password(id和name跟数据库字段名一致,但 ...
- JMeter执行方式
JMeter执行方式有2种,一种是GUI模式,一种是NO-GUI模式. GUI模式就是界面模式,如下: NO-GUI模式就是命令行模式. 界面模式主要用来编写和调试脚本用的,项目的真正执行最好是采用命 ...
- 理解Spring:IOC的原理及手动实现
Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架.也是几乎所有Java工作者必须要掌握的框架之一,其优秀的设计思想以及其代码实现上的艺术也是我们需要掌握的. ...
- 「是时候升级Java11了」 JDK11优势和JDK选择
Java8 商用收费 从2019年1月份开始,Oracle JDK 开始对 Java SE 8 之后的版本开始进行商用收费,确切的说是 8u201/202 之后的版本.如果你用 Java 开发的功能如 ...