Linux驱动---LED

news/2025/2/11 19:51:13/文章来源:https://www.cnblogs.com/Xin-Code9/p/18709520

目录
  • 一、pinctrl子系统
  • 二、GPIO子系统
  • 三、GPIO操作步骤
    • 3.1、获取GPIO描述符
    • 3.2、设置方向
    • 3.3、读写值
  • 四、编写LED驱动
    • 4.1、硬件原理图
    • 4.2、修改设备树
    • 4.3、编写驱动程序
    • 4.4、安装驱动测试
    • 4.5、应用程序测试

一、pinctrl子系统

现代 SoC(系统级芯片)的引脚功能高度灵活,单个物理引脚可能被多个外设(如 GPIO、I2C、SPI 等)复用,且需要配置电气属性(如上拉/下拉、驱动强度、电压等),画个草图方便大家理解:


Pin_A引脚可以被用于普通的 GPIO 功能,也可以被复用为 I2C 功能,具体用作哪个功能,通过芯片的IOMUXC控制器来实现配置,配置的过程本质就是操作引脚相关的寄存器。但是一个芯片几百个引脚,都这样直接操作寄存器显然工程量很大,所以很多厂商在中间抽象了 pinctrl 子系统。pinctrl 子系统统一管理引脚复用、配置和状态切换,程序员调用pinctrl子系统提供的接口即可,无需关注底层硬件差异。至于 pinctrl 子系统如何运行、如何设置 pin 复用和电气属性,最终肯定仍要初始化寄存器,这部分已由厂商写好。

我们在上篇文章学过了,硬件信息都应该写在设备树文件中,那么 pinctrl 子系统是如何从设备树文件中获取关于 PIN 的信息呢?其实,pinctrl 子系统根据 iomuxc 节点中的fsl,pins的属性值来获取 PIN 配置信息的。


关于MX6UL_PAD_NAND_DQS__GPIO4_IO16的定义在arch/arm/boot/dts/imx6ul-pinfunc.h中:

#define MX6UL_PAD_NAND_DQS__RAWNAND_DQS         0x01b8 0x0444 0x0000 0 0
#define MX6UL_PAD_NAND_DQS__CSI_FIELD           0x01b8 0x0444 0x0530 1 1
#define MX6UL_PAD_NAND_DQS__QSPI_A_SS0_B        0x01b8 0x0444 0x0000 2 0
#define MX6UL_PAD_NAND_DQS__PWM5_OUT            0x01b8 0x0444 0x0000 3 0
#define MX6UL_PAD_NAND_DQS__EIM_WAIT            0x01b8 0x0444 0x0000 4 0
#define MX6UL_PAD_NAND_DQS__GPIO4_IO16          0x01b8 0x0444 0x0000 5 0
#define MX6UL_PAD_NAND_DQS__SDMA_EXT_EVENT01    0x01b8 0x0444 0x0614 6 1
#define MX6UL_PAD_NAND_DQS__SPDIF_EXT_CLK       0x01b8 0x0444 0x061c 8 1

二、GPIO子系统

如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系统。gpio 子系统用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO 为输入输出,读取 GPIO 的值等。

我们需要做的工作就是在设备树根节点下添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API 函数来操作 GPIO。那我们如何在根节点下添加 gpio 相关信息呢?具体来说,我们首先要在根节点下创建设备节点,然后添加 pinctrl 信息描述 pin 复用及电气属性,最后添加 GPIO 属性信息描述使用的 gpio 引脚。


接下来,我们的驱动程序就可以通过函数对GPIO进行操作了。

三、GPIO操作步骤

3.1、获取GPIO描述符

1.struct gpio_desc *gpiod_get(struct device *dev, const char *con_id, enum gpiod_flags flags);
2.struct gpio_desc *__must_check devm_gpiod_get_index(struct device *dev, const char *con_id, unsigned int index, enum gpiod_flags flags);
3.struct gpio_desc *gpiod_get_index(struct device *dev, const char *con_id, unsigned int index, enum gpiod_flags flags);
//dev:指向设备结构体的指针,表示 GPIO 消费者设备;
//con_id:GPIO 的功能标识符,通常在设备树中定义。如果为 NULL,表示该 GPIO 没有特定的功能名称;
//index:GPIO 的索引,用于从设备中获取特定的 GPIO 引脚。例如,如果一个设备有多个 GPIO 引脚,可以通过索引区分它们
//flags:GPIO 初始化标志,用于指定 GPIO 的初始状态和方向。常见的标志包括:(GPIOD_ASIS:不初始化 GPIO,保持其当前状态;GPIOD_IN:将 GPIO 初始化为输入模式;GPIOD_OUT_LOW:将 GPIO 初始化为输出模式,并设置为低电平;GPIOD_OUT_HIGH:将 GPIO 初始化为输出模式,并设置为高电平)
//成功时返回一个有效的 GPIO 描述符(struct gpio_desc)。失败时返回一个错误码,可以通过 IS_ERR() 检查。

3.2、设置方向

1.int gpiod_direction_output(struct gpio_desc *desc, int value);
2.int gpiod_direction_input(struct gpio_desc *desc);
//desc:指向 GPIO 描述符的指针,表示要操作的 GPIO 引脚;
//value:要设置的初始输出值,可以是 0(低电平)或 1(高电平);
//成功时返回 0。失败时返回负的错误码;

3.3、读写值

1.void gpiod_set_value(struct gpio_desc *desc, int value);
2.int gpiod_get_value(struct gpio_desc *desc);
//desc:指向 GPIO 描述符的指针,表示要操作的 GPIO 引脚;
//value:要设置的电平值;

四、编写LED驱动

4.1、硬件原理图

我这里使用的是一款共阴极三色灯,原理图及引脚连接如图所示,通过原理图我们可以看出,该三色灯为高电平有效

4.2、修改设备树

创建一个dts文件夹,并将设备树文件拷贝过来(arch/arm/boot/dts/igkboard-imx6ull.dts),在根节点处添加:

    rgbled {compatible = "rgb,leds";pinctrl-names = "default";pinctrl-0 = <&pinctrl_gpio_rgbleds>;status = "okay";gpios = <&gpio1 23 GPIO_ACTIVE_HIGH  /* 33# */&gpio5 1  GPIO_ACTIVE_HIGH  /* 35# */&gpio5 8  GPIO_ACTIVE_HIGH  /* 37# */>;};


在下面的iomxuc节点中添加:

    pinctrl_gpio_rgbleds: rgb-leds {fsl,pins = <MX6UL_PAD_UART2_RTS_B__GPIO1_IO23       0x17059 /* RGB Led red   */MX6UL_PAD_SNVS_TAMPER1__GPIO5_IO01      0x17059 /* RGB Led green */MX6UL_PAD_SNVS_TAMPER8__GPIO5_IO08      0x17059 /* RGB Led blue  */>;};


编写一个Makefile文件用于编译DTS:

ARCH ?= arm
KERNAL_DIR ?= ${HOME}/igkboard-imx6ull/bsp/kernel/linux-imxCPP_CFLAGS=-Wp,-MD,.x.pre.tmp -nostdinc -undef -D__DTS__ -x assembler-with-cpp
CPP_CFLAGS+= -I ${KERNAL_DIR}/arch/${ARCH}/boot/dts -I ${KERNAL_DIR}/include/DTC=${KERNAL_DIR}/scripts/dtc/dtc
DTC_FLAGS=-q -@ -I dts -O dtbDTS_NAME=igkboard-imx6ullall:@cpp ${CPP_CFLAGS} ${DTS_NAME}.dts -o .${DTS_NAME}.dts.tmp${DTC} ${DTC_FLAGS} .${DTS_NAME}.dts.tmp -o ${DTS_NAME}.dtb@rm -f .*.tmpdecompile:${DTC} -q -I dtb -O dts ${DTS_NAME}.dtb -o decompile.dtsclean:rm -f *.dtb decompile.dts


接下来我们可以使用 make 命令编译 igkboard-imx6ull.dts 生成 igkboard-imx6ull.dtb 文件

make

4.3、编写驱动程序

根据上面所学,进行编写程序,代码如下:

点击查看代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/version.h>
#include <linux/ioctl.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>#define LED_IOC_MAGIC   'l'
#define LED_IOC_SET     _IOW(LED_IOC_MAGIC, 1, int)struct led_desc
{struct cdev			cdev;struct device		*dev;struct gpio_desc 	*gpio;
};struct led_priv
{struct class		*class;dev_t				devt;int					nleds;struct led_desc		*leds;
};struct led_priv 		*priv;static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{int					led_id;int					brightness;led_id = iminor(file->f_inode);switch(cmd){case LED_IOC_SET:brightness = (int)arg;gpiod_set_value_cansleep(priv->leds[led_id].gpio, brightness?1:0);break;default:return -ENOTTY;}return 0;
}static const struct file_operations		led_fops = {.owner = THIS_MODULE,.unlocked_ioctl = led_ioctl,
};static int led_probe(struct platform_device *pdev)
{struct device	*dev = &pdev->dev;int				ret, i;priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);if( !priv ){return -ENOMEM;}priv->nleds = gpiod_count(dev, NULL);if(priv->nleds < 1){dev_err(dev, "Failed to read leds gpio from device tree\n");return -EINVAL;}dev_info(dev, "led driver probe for %d leds from device tree\n", priv->nleds);priv->leds = devm_kzalloc(dev,priv->nleds*sizeof(*priv->leds), GFP_KERNEL);if( !priv->leds ){return -ENOMEM;}for(i=0; i<priv->nleds; i++){priv->leds[i].gpio = devm_gpiod_get_index(dev, NULL, i, GPIOD_ASIS);if(IS_ERR(priv->leds[i].gpio))return PTR_ERR(priv->leds[i].gpio);gpiod_direction_output(priv->leds[i].gpio, 0);}ret = alloc_chrdev_region(&priv->devt, 0, priv->nleds, "myled");if(ret){dev_err(dev, "Failed to allocate char dev region\n");return ret;}#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0)priv->class = class_create("myled");
#elsepriv->class = class_create(THIS_MODULE, "myled");
#endifif(IS_ERR(priv->class)){ret = PTR_ERR(priv->class);goto failed_unregister_chrdev;}for(i=0; i<priv->nleds; i++){cdev_init(&priv->leds[i].cdev, &led_fops);priv->leds[i].cdev.owner = THIS_MODULE;ret = cdev_add(&priv->leds[i].cdev, MKDEV(MAJOR(priv->devt), i), 1);if(ret){dev_err(&pdev->dev, "Failed to add cdev for led%d\n", i);while(--i >= 0){cdev_del(&priv->leds[i].cdev);}goto failed_destroy_class;}priv->leds[i].dev = device_create(priv->class, &pdev->dev, MKDEV(MAJOR(priv->devt), i), NULL, "led%d", i);if (IS_ERR(priv->leds[i].dev)) {ret = PTR_ERR(priv->leds[i].dev);dev_err(&pdev->dev, "Failed to create device node for led%d\n", i);while (--i >= 0) {device_destroy(priv->class, MKDEV(MAJOR(priv->devt), i));cdev_del(&priv->leds[i].cdev);}goto failed_destroy_class;}}platform_set_drvdata(pdev, priv);return 0;failed_destroy_class:class_destroy(priv->class);failed_unregister_chrdev:unregister_chrdev_region(priv->devt, priv->nleds);return ret;
}static int led_remove(struct platform_device *pdev)
{struct led_priv 	*priv = platform_get_drvdata(pdev);int					i;for(i=0; i<priv->nleds; i++){device_destroy(priv->class, MKDEV(MAJOR(priv->devt), i));cdev_del(&priv->leds[i].cdev);}class_destroy(priv->class);unregister_chrdev_region(priv->devt, priv->nleds);dev_info(&pdev->dev, "led driver removed.\n");return 0;
}static const struct of_device_id led_of_match[] = {{.compatible = "rgb,leds",},{},
};MODULE_DEVICE_TABLE(of, led_of_match);static struct platform_driver led_driver = {.probe = led_probe,.remove = led_remove,.driver = {.name = "leds",.of_match_table = led_of_match,},
};module_platform_driver(led_driver);MODULE_LICENSE("GPL");

对应的Makefile文件代码如下:

点击查看代码
ARCH ?= arm
CROSS_COMPILE ?= /opt/gcc-aarch32-10.3-2021.07/bin/arm-none-linux-gnueabihf-
KERNAL_DIR ?= ~/igkboard-imx6ull/bsp/kernel/linux-imx/PWD :=$(shell pwd)obj-m += ledv1.omodules:$(MAKE) ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE} -C $(KERNAL_DIR) M=$(PWD) modules@make clearclear:@rm -f *.o *.cmd *.mod *.mod.c@rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f@rm -f .*ko.cmd .*.o.cmd .*.o.d@rm -f *.unsignedclean:@rm -f *.ko

4.4、安装驱动测试

接下来我们可以将新的设备树文件下载到开发板上,并重启生效。

mount /dev/mmcblk1p1 /media/
ls /media/config.txt  igkboard-imx6ull.dtb  overlays  zImage
//我这里通过rz、sz命令将新的设备树文件下载到开发板上替换掉原文件,使用scp命令也可以
sync && reboot


使用insmod命令安装驱动模块文件,如果安装失败,可以通过dmesg查看驱动加载的信息,如果报错是XXX already requested by XXX,那就是RGB的引脚已经被复用其他功能了,关闭该功能就不会再报错了。

insmod ledv1.ko


成功安装后,我们可以在/dev路径下看到三个设备文件了。

ls -l /dev/led*
crw------- 1 root root 243, 0 Jan  2 03:02 /dev/led0
crw------- 1 root root 243, 1 Jan  2 03:02 /dev/led1
crw------- 1 root root 243, 2 Jan  2 03:02 /dev/led2

4.5、应用程序测试

为了测试我们的驱动程序,编写一个应用程序,代码如下:

点击查看代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <signal.h>#define DEVNAME             "/dev/led"
#define MAX_LED             10#define RGBLED_IOC_MAGIC    'l'
#define RGBLED_IOC_SET      _IOW(RGBLED_IOC_MAGIC, 1, int)int g_stop = 0;void sig_handler(int signum)
{switch(signum){case SIGINT:case SIGTERM:g_stop = 1;break;default:break;}return;
}int main (int argc, char **argv)
{char       devname[32];int        fd[MAX_LED];int        led_num = 0;int        i;/* open all the led device */for(i=0; i<MAX_LED; i++){snprintf(devname, sizeof(devname), "%s%d", DEVNAME, i);/* check device node exist or not */if( access(devname, F_OK)<0 )break; /* can not found any more */fd[i] = open(devname, O_RDWR);if( fd < 0 ){printf("Open device %s failed: %s\n", devname, strerror(errno));return 1;}led_num ++;printf("open %s -> fd[%d]\n", devname, fd[i]);}/* install signal to turn led off when program exit */signal(SIGINT, sig_handler);signal(SIGTERM, sig_handler);/* blink the RGB leds */i = 0;while( !g_stop ){ioctl(fd[i], RGBLED_IOC_SET, 1);usleep(300000);ioctl(fd[i], RGBLED_IOC_SET, 0);usleep(300000);i = (i+1)%led_num;}cleanup:/* turn all the leds off and close the fd */for(i=0; i<led_num; i++){printf("close %s -> fd[%d]\n", devname, fd[i]);ioctl(fd[i], RGBLED_IOC_SET, 0);close(fd[i]);}return 0;
}

在编写一个对应的Makefile文件,代码如下:

点击查看代码
BUILD_ARCH=$(shell uname -m)
ifeq ($(findstring "x86_64" "i386", $(BUILD_ARCH)),)CROSS_COMPILE?=/opt/gcc-aarch32-10.3-2021.07/bin/arm-none-linux-gnueabihf-
endifCC=${CROSS_COMPILE}gccSRCFILES = $(wildcard *.c)
BINARIES=$(SRCFILES:%.c=%)all: ${BINARIES}%: %.c$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)clean:rm -f ${BINARIES}

编译完成后,运行./app_led,我们便可以观察到三色灯的变化

open /dev/led0 -> fd[3]
open /dev/led1 -> fd[4]
open /dev/led2 -> fd[5]
^Cclose /dev/led3 -> fd[3]
close /dev/led3 -> fd[4]
close /dev/led3 -> fd[5]

卸载驱动后,设备文件也会随之消失。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PVE 服务器通过脚本进行优化

此处使用的优化脚本为pve_source.tar.gz,需要的可以自己从网上下载‌pve_source‌是一款用于Proxmox VE(PVE)的辅助脚本,主要用于一键换源、更新系统、升级系统、开启直通等功能,能够方便地进行PVE虚拟机的配置和管理,提高工作效率。‌ 一、上传并运行优化脚本 1、首先我…

018 Module的语法

历史上,JavaScript一直没有模块(module)体系,无法将一个大程序拆分成相互依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如Ruby的require、Python的import,甚至就连CSS都有@import,但是JavaScript任何方面的支持都没有,这对开发大型的、复杂的项目形成…

另辟新径实现 Blazor/MAUI 本机交互(三)

新建一个Maui blazor工程, 下面是工程关键文件解析. MainPage.xaml.cs 构造函数:通过 FindByName 方法查找名为 webView 的 WebView 控件,并将其赋值给 wvBrowser 变量。 创建 NativeBridge 的实例 api,并将 wvBrowser 传递给它。 使用 api.AddTarget 方法添加一个名为 dial…

nodejs如何处理Token?一文深入浅出JWT签名验签

关于token的那些事儿,一文深入浅出JWT签名验签前端开发中关于Token的那些事儿:深入浅出JWT签名验签 作为前端也要懂JWT,首先了解两个概念JWK JWT JWK JWK(RSA JSON Web Key)是一种用于表示 RSA 公钥或私钥的 JSON 对象,JWK 是 JSON Web Token (JWT) 和 JSON Web Encrypti…

前端开发中关于Token的那些事儿:深入浅出JWT签名验签

关于token的那些事儿,一文深入浅出JWT签名验签前端开发中关于Token的那些事儿:深入浅出JWT签名验签 作为前端也要懂JWT,首先了解两个概念JWK JWT JWK JWK(RSA JSON Web Key)是一种用于表示 RSA 公钥或私钥的 JSON 对象,JWK 是 JSON Web Token (JWT) 和 JSON Web Encrypti…

Eddystone 与 iBeacon

Eddystone 与 iBeacon 蓝牙信标 (Beacons) 是一种单向通讯方式,所以一般的用途就是发送提醒。 Beacons 是指使用蓝牙4.0(BLE)技术发射信号的小设备。 目前存活的 Beacons 标准有两个,分别是 Google 的 Eddystone 和 Apple 的 iBeacon. Eddystone Eddystone 是谷歌基于 Beac…

OrangePi 5 编译 Android12 源码

OrangePi 5 编译 Android12 源码 材料准备 源码下载地址 Orange Pi - Orangepi官方教程编译环境 在以下环境的 Ubuntu 虚拟机编译通过,基于 VMware Workstation 17 Pro.系统版本:ubuntu-18.04.6-lts-desktop-amd64; CPU:i5-8400,为 VM 分配 4 核; 内存:8G RAM + 16G swap…

Maui 基础 - Preferences 存储和检索应用程序的首选项

Maui 基础 Preferences 是 .NET MAUI 提供的一个静态类,用于存储和检索应用程序的首选项(即设置或配置)。它提供了一种简单的键值对存储机制,可以跨平台使用。每个平台使用其本地的存储机制来实现这些功能,例如:iOS 使用 NSUserDefaults Android 使用 SharedPreferences …

另辟新径实现 Blazor/MAUI 本机交互(一)

本系列由浅入深逐个文件解析工作原理 目录:WebViewNativeApi.cs NativeApi.cs MainPage.xaml.cs 实战 串口 小票机 蓝牙WebViewNativeApi.cs WebViewNativeApi.cs 文件中的代码实现了一个 NativeBridge 类,用于在 .NET MAUI 应用程序中的 WebView 和本地代码之间进行通信。以下…

AI 如何重塑劳动力市场:基于 Claude 数据的深度分析

前言 本文翻译自 Anthropic 今天发布的 The Anthropic Economic Index ,经济指数报告,这份报告基于 Claude 的数据对目前的 AI 使用情况做了汇总。 引言 在未来的几年里,人工智能系统将对人们的工作方式产生重大影响。因此,我们推出了 Anthropic Economic Index,这是一个旨…

Nacos Python SDK 强势来袭,动态管理大模型 Prompt!

Nacos 从 0.8.0 版本开始就一直参与 Python 生态建设,努力作为 Python 生态中分布式微服务发现和配置管理的解决方案一直往前演进。目前随着 AI 领域的发展,Nacos 社区的 Python 开发者用户越来越多,因此这次我们迭代了 Python 的 GA 稳定版本,对不少历史问题做了修复以及易…

踩坑记录-二分搜索的不同情况

二分搜索的不同情况 二分搜索可以用来查找满足条件的值,但是满足条件的值可能只有1个,也可能有多个。比如查找1的索引,对于【1,1,2,2】来说,就有2个。一般要求的就是:满足条件最大值/满足条件最小值。 二分搜索详细介绍可以参考:https://programmercarl.com/0704.二分…