<Linux开发>驱动开发 -之-基于pinctrl/gpio子系统的LED驱动

news2024/10/6 8:34:28

<Linux开发>驱动开发 -之-基于pinctrl/gpio子系统的LED驱动
交叉编译环境搭建:
<Linux开发> linux开发工具-之-交叉编译环境搭建

uboot移植可参考以下:
<Linux开发> -之-系统移植 uboot移植过程详细记录(第一部分)
<Linux开发> -之-系统移植 uboot移植过程详细记录(第二部分)
<Linux开发> -之-系统移植 uboot移植过程详细记录(第三部分)(uboot移植完结)

Linux内核及设备树移植可参考以下:
<Linux开发>系统移植 -之- linux内核移植过程详细记录(第一部分)
<Linux开发>系统移植 -之- linux内核移植过程详细记录(第二部分完结)

Linux文件系统构建移植参考以下:
<Linux开发>系统移植 -之- linux构建BusyBox根文件系统及移植过程详细记录
<Linux开发>系统移植 -之-使用buildroot构建BusyBox根文件系统

Linux驱动开发参考以下:
<Linux开发>驱动开发 -之-pinctrl子系统

以下实验基于上述uboot和kernel系统移植,以及buildroot构建的BusyBox根文件系统。

一、前言

本文主要讲解基于pinctrl子系统和gpio子系统的前提下,编写LED驱动,并编写测试app测试led的控制。对于led来说,是比较常用的一种设备,不管是做系统运行指示灯,还是人机交互提示灯都是常备的功能。

二、新增led设备节点

参考-pinctrl子系统和gpio子系统的讲解,可以在设备树中添加如下内容;
pinctrl节点添加如下:

路径:arch/arm/boot/dts/imx6ull-water-emmc.dts
pinctrl_led: ledgrp {
			fsl,pins = <
				MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
			>;
		};

在这里插入图片描述
可以看到pinctrl_led节点是在iomuxc节点下的。

gpio节点添加如下:

路径:arch/arm/boot/dts/imx6ull-water-emmc.dts
gpioled {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "water-gpioled";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_led>;
		led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
		status = "okay";
	};

在这里插入图片描述

新增gpioled 节点则是在/节点下的。

添加完上述两个节点后,重新编译设备树“make dtbs”,然后用新生成的dtb文件启动kernel,在设备的/sys/firmware/devicetree/base/目录下有如下:
在这里插入图片描述
新增Led节点完成,后面编写驱动和app来测试验证这个Led设备。

三、编写驱动

3.1 编写led驱动

在kernel本身就存在一些Led的驱动,我们自己的驱动也放到改目录下,这样有利于我们配置使用;
新建文件:drivers/leds/leds-water.c
在leds-water.c输入以下内容:

路径:drivers/leds/leds-water.c
/***************************************************************
Copyright © OneFu Co., Ltd. 2018-2023. All rights reserved.
文件名 : gpioled.c
作者 : water
版本 : V1.0
描述 : 采用 pinctrl 和 gpio 子系统驱动 LED 灯。
其他 : 无
日志 : 初版 V1.0 2023/05/24 water创建
***************************************************************/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define GPIOLED_CNT 1           /* 设备号个数 */
#define GPIOLED_NAME "gpioled"  /* 名字 */
#define LEDOFF 0                /* 关灯 */
#define LEDON 1                 /* 开灯 */

/* gpioled 设备结构体 */
struct gpioled_dev{
    dev_t devid;            /* 设备号 */
    struct cdev cdev;       /* cdev */
    struct class *class;    /* 类 */
    struct device *device;  /* 设备 */
    int major;              /* 主设备号 */
    int minor;              /* 次设备号 */
    struct device_node *nd; /* 设备节点 */
    int led_gpio;           /* led 所使用的 GPIO 编号 */
};
 
struct gpioled_dev gpioled; /* led 设备 */

/*
* @description : 打开设备
* @param – inode : 传递给驱动的 inode
* @param – filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
* 一般在 open 的时候将 private_data 指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &gpioled; /* 设置私有数据 */
    return 0;
}

/*
* @description : 从设备读取数据
* @param – filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param – offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t led_read(struct file *filp, char __user *buf,
                        size_t cnt, loff_t *offt)
{
    return 0;
}
 
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param – offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t led_write(struct file *filp, const char __user *buf,
                            size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;
    struct gpioled_dev *dev = filp->private_data;

    retvalue = copy_from_user(databuf, buf, cnt);
    if(retvalue < 0) {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    ledstat = databuf[0]; /* 获取状态值 */

    if(ledstat == LEDON) { 
        gpio_set_value(dev->led_gpio, 0); /* 打开 LED 灯 */
    } else if(ledstat == LEDOFF) {
        gpio_set_value(dev->led_gpio, 1); /* 关闭 LED 灯 */
    }
    return 0;
}

/*
* @description : 关闭/释放设备
* @param – filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int led_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/* 设备操作函数 */
static struct file_operations gpioled_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    .release = led_release,
};

/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init water_led_init(void)
{
    int ret = 0;

    /* 设置 LED 所使用的 GPIO */
    /* 1、获取设备节点:gpioled */
    gpioled.nd = of_find_node_by_path("/gpioled");
    if(gpioled.nd == NULL) {
        printk("gpioled node cant not found!\r\n");
        return -EINVAL;
    } else {
        printk("gpioled node has been found!\r\n");
    }

    /* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
    if(gpioled.led_gpio < 0) {
        printk("can't get led-gpio");
        return -EINVAL;
    }
    printk("led-gpio num = %d\r\n", gpioled.led_gpio);

    /* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
    ret = gpio_direction_output(gpioled.led_gpio, 1);
    if(ret < 0) {
        printk("can't set gpio!\r\n");
    }

    /* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (gpioled.major) { /* 定义了设备号 */
        gpioled.devid = MKDEV(gpioled.major, 0);
        register_chrdev_region(gpioled.devid, GPIOLED_CNT,
                                    GPIOLED_NAME);
    } else { /* 没有定义设备号 */
        alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, 
                                    GPIOLED_NAME); /* 申请设备号 */
        gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */
        gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */
    }
    printk("gpioled major=%d,minor=%d\r\n",gpioled.major,
                                    gpioled.minor); 

    /* 2、初始化 cdev */
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);

    /* 3、添加一个 cdev */
    cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);

    /* 4、创建类 */
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if (IS_ERR(gpioled.class)) {
    return PTR_ERR(gpioled.class);
    }

    /* 5、创建设备 */
    gpioled.device = device_create(gpioled.class, NULL,
                            gpioled.devid, NULL, GPIOLED_NAME);
    if (IS_ERR(gpioled.device)) {
        return PTR_ERR(gpioled.device);
    }
    return 0;
}

/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit water_led_exit(void)
{
    /* 注销字符设备驱动 */
    cdev_del(&gpioled.cdev); /* 删除 cdev */
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销 */

    device_destroy(gpioled.class, gpioled.devid);
    class_destroy(gpioled.class);
}

module_init(water_led_init);
module_exit(water_led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("water");

3.2 添加makefile编译项

在drivers/leds/leds-water.c同级目录下找到Makefile文件,添加一下内容:

路径:drivers/leds/Makefile
obj-$(CONFIG_LEDS_WATER)			+= leds-water.o

在这里插入图片描述
在62行前面的那些,就是一些其他平台的led驱动,这里我们用我们自己写的驱动,毕竟别人的驱动对应的硬件和我们的不一样。

3.3 添加menuconfig选项

在3.2节中,我们把驱动添加进Kernel了,那怎么样才能编译这个驱动呢?我们添加的Makefile内容中有CONFIG_LEDS_WATER 这个宏,只要这个宏为true那么就会编译leds-water.o所对于的leds-water.c文件了。
在Kconfig文件末尾添加如下内容:

路径:drivers/leds/Kconfig
config LEDS_WATER
	tristate "LED support for water imax6ull board"
	help
	  This option enables support for the imax water led.

添加完上述内容保存后,就可以通过“make menuconfig”来配置使用这个led驱动了。

3.4 使用menuconfig配置驱动

在kernel跟目录运行“make menuconfig”后,如下步骤配置;
(1) 选择 “Device Drivers —>”
在这里插入图片描述
(2) 选择 “LED Support —>”
在这里插入图片描述
(3) 选择 “LED Support for water imax6ull board —>”
在这里插入图片描述
选中之后按“Y”,在行前的尖括号里会出现*,表示编译进内核;然后用键盘左右键选择"Save",保存配置;保存的路径使用默认的路径,如下,然后OK即可。
在这里插入图片描述
配置完驱动编译,重新编译kernel,然后使用新的kernel启动。

四、编写测试led的app

在ubuntu下新建目录:/home/water/imax/soft/led,并新建文件led_water.c,然后输入一下内容:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

/*************************************************************** 
 * Copyright © onefu Co., Ltd. 2019-2021. All rights reserved. 
 * 文件名 : gpioledApp.c 
 * 作者 : water 
 * 版本 : V1.0 
 * 描述 : led 驱测试APP。 
 * 其他 : 使用方法:./gpioledApp /dev/led <1>|<2>
 *                argv[2] 0:关闭LED
 *                argv[2] 1:打开LED
 * 日志 : 初版V1.0 2021/11/11 water创建 
 * ***************************************************************/ 

#define LEDOFF 0
#define LEDON  1

/* 
* @description : main主程序 
* @param - argc : argv数组元素个数 
* @param - argv : 具体参数 
* @return : 0 成功;其他 失败 
*/ 
int main(int argc, char *argv[])
{
    int fd, retvalue;               //fd: 文件描述符 用以对文件操作    retvalue:存放函数操作后的返回值
    char *filename;                 //filename:文件名,有主函数参数传入赋值
    unsigned char databuf[1];       //定义的buf,用来读写数据用 

    if(argc != 3){                  //判断主函数传入的函数的参数的个数
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];              //获取第1个参数,存放的是文件的路径(即要操作的设备文件路径)
    
    fd = open(filename,O_RDWR);                         /*打开驱动文件*/
    if(fd < 0){
        printf("Can't open file %s\r\n",filename);      /*打开失败,输出提示*/
        return -1;
    }

    databuf[0] = atoi(argv[2]);                         /* 要执行的操作:打开或关闭 */

    retvalue = write(fd, databuf, sizeof(databuf));     /*向设备驱动写入数据*/
    if(retvalue < 0){
        printf("LED Control Failed!\r\n",filename);     /*写入错误输出提示*/
    }

    retvalue = close(fd);                               /*关闭文件*/
    if(retvalue < 0){
        printf("Can't close file %s\r\n",filename);     /*关闭错误输出提示*/
        return -1;
    }

    return 0;
}
//编译指令: arm-linux-gnueabihf-gcc gpioledApp.c  -o gpioledApp

编译app:

arm-linux-gnueabihf-gcc gpioledApp.c  -o gpioledApp

在这里插入图片描述

五、运行测试

将编译生成的led_water执行文件拷贝到文件系统的/root/water_soft/led_water/目录下:
在这里插入图片描述
在Securecrt终端运行如下命令:

 cd /root/water_soft/led_water/
 ./led_water  /dev/gpioled  1

在这里插入图片描述
运行之后,即可观看开发板led状态为开。
如果是./led_water /dev/gpioled 0则led为关。
作者实际现象是正常开关的,读者可以自行测试自己的开发板。

六、总结

我们结合pinctrl子系统和gpio子系统,完成了led的驱动开发,后面我们会接着开发beep蜂鸣器的驱动,其原理与led是一样的,都是通过控制gpio引脚输出高低电平来控制设备的。

先通过控制简单的led、beep等设备,熟悉掌握pinctrl子系统和gpio子系统,后面我们在深入开发比较复杂的驱动。

一步一脚印,法路自然成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/570155.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chatgpt赋能python:Python二维码解码-从介绍到结论

Python 二维码解码 - 从介绍到结论 二维码在现代数字化时代的应用越来越普及&#xff0c;它能够快速、准确地扫描并解码大量的信息。Python作为一种流行的通用编程语言&#xff0c;已经成为开发人员首选的工具之一&#xff0c;为我们解码二维码提供了强大的支持。本篇SEO文章将…

chatgpt赋能python:Python主模块:入门指南

Python主模块&#xff1a;入门指南 什么是Python主模块&#xff1f; Python主模块指的是一组基本模块&#xff0c;通常在Python程序中使用最为广泛的模块。这些模块包含了各种功能&#xff0c;如文件操作、系统库、数据类型、数学运算等。通过使用这些模块&#xff0c;Python…

Carla自动驾驶仿真五:opencv绘制运动车辆的boudingbox(代码详解)

文章目录 一、安装opencv二、opencv绘制车辆的boudingbox1、构造相机投影矩阵函数2、定义将Carla世界坐标转换成相机坐标的函数3、设置Carla并生成主车和相机4、使用队列接收相机的数据5、计算相机投影矩阵6、定义顶点创建边的列表7、通过opencv显示相机的画面8、通过opencv绘制…

知识点梳理:ATTO 647N NHS ester,ATTO 647N 琥珀酰亚胺酯,荧光标记用于红色光谱区

ATTO 647N NHS ester&#xff0c;ATTO 647N SE&#xff0c;ATTO 647N 琥珀酰亚胺酯&#xff0c;ATTO 647N NHS酯 激发波长(nm)&#xff1a;646 发射波长(nm)&#xff1a;664 反应图像&#xff1a; 产品规格&#xff1a; 1.CAS号&#xff1a;N/A 2.分子式&#xff1a;N/A 3.分…

Sentinel降级规则

1.降级规则简介 官方文档 熔断降级概述 除了流量控制以外&#xff0c;对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块&#xff0c;可能是另外的一个远程服务、数据库&#xff0c;或者第三方 API 等。例如&#xff0c;支付的…

chatgpt赋能python:Python倒序遍历

Python倒序遍历 Python是一种常用的编程语言&#xff0c;其操作序列化和数据结构的方式使得其在网站开发、科学计算和人工智能领域具有重要作用。本文将重点介绍Python中倒序遍历的基本原则和实现方法。 什么是倒序遍历 倒序遍历是指从后往前迭代序列的过程。Python中提供了…

SSRF之GOPHER协议利用

目录 GOPHER协议 GOPHER协议发起的格式 GOPHER利用工具 案例一&#xff1a;CTFSHOW-359关 GOPHER协议 GOPHER协议是一种比HTTP协议还要古老的协议&#xff0c;默认工作端口70&#xff0c;但是gopher协议在SSRF漏洞利用上比HTTP协议更有优势。GOPHER协议可以以单个URL的形式…

chatgpt赋能python:Python编写接口实践:让API更高效、更可靠

Python编写接口实践&#xff1a;让API更高效、更可靠 随着互联网技术的不断发展&#xff0c;API已经成为了现代应用架构的基石之一。而Python作为一种高效、灵活的语言&#xff0c;也逐渐成为了接口开发的首选。 什么是API接口&#xff1f; API是应用程序接口&#xff08;Ap…

chatgpt赋能python:Python单行循环:提升开发效率的必备技巧

Python单行循环&#xff1a;提升开发效率的必备技巧 在Python编程中&#xff0c;循环是一种非常重要的控制流程&#xff0c;可以让程序执行特定的操作多次。而Python有一种针对短小的循环语句进行简化的技巧&#xff0c;即“单行循环”&#xff0c;也被称为“列表解析”或“生…

数据分析学习

tableau tableau介绍 tableau可以做数据可视化&#xff0c;但可视化只是tableau的基操&#xff0c;数据赋能和数据探索才是tableau的正确打开方式 数据赋能&#xff1a;让业务一线也可以轻松使用最新数据 数据探索&#xff1a;通过统计分析和数据可视化&#xff0c;从数据发现…

从应用层到MCU,看Windows处理键盘输入 [2.a.1.传球手User32.dll]

副标题:精准型消息断点 引言1. 前文作为系列的开篇&#xff0c;我们站在Notepad.exe的视角&#xff0c;看它接过系统传来的消息&#xff0c;交由Notepad的窗口处理函数(WndProc)进行处理的过程。User32.dll!DispatchMessage API是前面"系统传来"4个字中的一环&#…

Kerberos认证原理及相关漏洞

Kerberos认证协议 Kerberos认证协议也称三头犬协议&#xff0c;因为在Kerberos认证过程中&#xff0c;需要有三个角色&#xff1a;Client、Server以及KDC(Key Distribution Center)密钥分发中心。 Kerberos认证协议的目的是为客户端/服务端提供身份验证。最主要的问题是如何证明…

Win11硬盘分区

电脑重装了Win11系统&#xff0c;按WinE打开主文件夹&#xff0c;再点击此电脑&#xff0c;发现&#xff1a; 磁盘只有一个C盘。硬盘的所有空间都在该盘上了&#xff0c;那么我们怎么将其分区呢&#xff1f; Win11硬盘分区步骤&#xff1a; 步骤1&#xff1a; 按WinR输入dis…

数据库中的事务,隔离级别,以及数据展示

想要知道和学习数据库中的锁&#xff0c;要先学习数据库的事务和并发事务所带来的问题&#xff01; 1.数据库中的事务&#xff01; 1.1什么事务 事务是由一组SQL语句组成的逻辑处理单元&#xff08;多个sql进行修改&#xff0c;新增等&#xff09;&#xff0c;这些操作要么同时…

跟踪任何目标(想跟踪什么就跟踪什么)

结果展示 介绍 该项目是一个简单的跟踪工具,可以用于跟踪任何你感兴趣的东西。它提供了一个基于Web的界面,让用户可以轻松地创建和管理跟踪列表,同时也提供了一个RESTful API,可以方便地进行数据交互。 项目的原理是将用户需要跟踪的内容,通过创建跟踪项的方式存储到数据…

Hausdorff 距离

1. 定义 给定欧氏空间中的两点集 A { a 1 , a 2 , . . . } \rm A\left \{a_1, a_2,... \right\} A{a1​,a2​,...} 和 B { b 1 , b 2 , . . . } \rm B\left \{b_1, b_2,... \right\} B{b1​,b2​,...} &#xff0c; H a u s d o r f f {\rm Hausdorff} Hausdorff 距离就是用…

基于SSM的在线考试系统开发与设计-(附源码文档)-毕业设计

文章目录 1.适用人群2.你将收获3.项目介绍4.系统需求分析4.1 需求特性分析4.2 功能需求分析 5.系统设计5.1 系统总体结构设计5.2 数据库设计5.2.1 数据库概念原则设计5.2.2 数据库各部分模块设计5.2.3 数据库表设计 6.系统详细设计6.1 系统各模块功能设计6.1.1 登录模块6.1.2 注…

chatgpt赋能python:如何利用Python加快计算速度

如何利用Python加快计算速度 在大数据时代&#xff0c;计算效率的问题成为了企业和科研机构普遍关注的焦点问题。Python是一种高级编程语言&#xff0c;其具有灵活、易学、语法简洁、运行速度快等优点&#xff0c;因此在数据分析和科学计算领域广泛应用。然而&#xff0c;Pyth…

chatgpt赋能python:Python内部函数介绍

Python内部函数介绍 Python是一门功能强大、易于学习的编程语言&#xff0c;拥有许多内部函数可供使用。本文将介绍Python的内部函数和其用途&#xff0c;以便更好地利用和理解Python。 什么是内部函数&#xff1f; 内部函数是Python提供的一组内置函数&#xff0c;它们可以…

2023/5/25总结

学习CSS list-style:none 去掉无序列表的带有的样式&#xff0c;比如原点。 border-radius:length 设置圆角&#xff0c;也可以写%&#xff0c;不一定需要些半径大小&#xff0c;也可以顺时针写半径大小&#xff0c;就会出现四个顶点不一样的圆角。或者写&#xff1a;borde…