Linux驱动---LED
一、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-imx
CPP_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 dtb
DTS_NAME=igkboard-imx6ull
all:
@cpp ${CPP_CFLAGS} ${DTS_NAME}.dts -o .${DTS_NAME}.dts.tmp
${DTC} ${DTC_FLAGS} .${DTS_NAME}.dts.tmp -o ${DTS_NAME}.dtb
@rm -f .*.tmp
decompile:
${DTC} -q -I dtb -O dts ${DTS_NAME}.dtb -o decompile.dts
clean:
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");
#else
priv->class = class_create(THIS_MODULE, "myled");
#endif
if(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.o
modules:
$(MAKE) ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE} -C $(KERNAL_DIR) M=$(PWD) modules
@make clear
clear:
@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 *.unsigned
clean:
@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-
endif
CC=${CROSS_COMPILE}gcc
SRCFILES = $(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]
卸载驱动后,设备文件也会随之消失。
Linux驱动---LED的更多相关文章
- Linux 驱动——Led驱动2
led_drv.c驱动文件: #include <linux/module.h>#include <linux/kernel.h>#include <linux/init ...
- Linux 驱动——LED(驱动分离分层)
led_dev.c文件: #include <linux/module.h>#include <linux/version.h> #include <linux/init ...
- Linux 驱动——Led驱动1
led_drv.c驱动文件: #include <linux/module.h>#include <linux/kernel.h>#include <linux/init ...
- arm Linux 驱动LED子系统 测试
Linux内核在3.0以上引入了设备树概念(具体哪个版本不清楚)在编译内核后需要将与之对应的dtb文件也下载人板子上才能使内核与硬件关联起来. dtb文件是有dts文件编译后生成的:例如 /* * C ...
- 嵌入式linux驱动开发之点亮led(驱动编程思想之初体验)
这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的 ...
- 驱动编程思想之初体验 --------------- 嵌入式linux驱动开发之点亮LED
这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的 ...
- (笔记)linux设备驱动--LED驱动
linux设备驱动--LED驱动 最近正在学习设备驱动开发,因此打算写一个系列博客,即是对自己学习的一个总结,也是对自己的一个督促,有不对,不足,需要改正的地方还望大家指出,而且希望结识志同道合的朋友 ...
- 超简单易用的 “在 pcduino 开发板上写 Linux 驱动控制板载 LED 的闪烁”
版权声明:本文为博主原创文章,未经博主同意不得转载.转载联系 QQ 30952589,加好友请注明来意. https://blog.csdn.net/sleks/article/details/251 ...
- 嵌入式Linux学习笔记(三) 字符型设备驱动--LED的驱动开发
在成功构建了一个能够运行在开发板平台的系统后,下一步就要正式开始应用的开发(这里前提是有一定的C语言基础,对ARM体系的软/硬件,这部分有疑问可能要参考其它教程),根据需求仔细分解任务,可以发现包含的 ...
- linux驱动之LED驱动
通过之前的学习,了解到linux驱动编写的流程是:先通过注册函数注册我们编写的入口函数,然后在入口函数中获取设备号->注册字符设备->自动创建设备节点->获取设备树信息,最后通过销毁 ...
随机推荐
- Python实验:Socket编程
实验六 Socket 编程 一.实验目标: 了解TCP协议原理.标准库socket 的用法.熟悉Socket 编程. 1.TCP协议原理: TCP(Transmission Control Proto ...
- Java并发 —— 线程并发(二)
Java 锁 Java 中的锁是在多线程环境下,保证共享资源健康,线程安全的一种手段 线程操作某个共享资源之前,先对资源加一层锁,保证操作期间没有其他线程访问资源,操作完成后再释放锁 保持数据一致 ...
- vue开发一个简单的组件
首先在项目中新建一个js文件 在文件内创建一个对象,对象内创建install方法,将对象用export default暴漏出去 export default{ install(){ console.l ...
- nrm安装后无法使用
前情 在使用node.js的过程中,经常会时不是遇到有些包下载安装慢或者失败,有时可以尝试切换源来解决这类问题 坑 通过npm install nrm -g安装完nrm后运行nrm一直报错 Why? ...
- C++ builder 10.2 x64程序使用typeid获取vcl类名时异常
C++ builder 10.2 x64程序使用typeid获取vcl类名时异常 比如: const std::type_info &t= typeid(TForm1); 那么t的name() ...
- flutter安装过程中 flutter doctor 出现错误 cmdline-tools component is missing
进入Android studio的settings添加tool工具
- 2.mysql授权认证
权限系统介绍 ● 什么是权限系统 权限系统是授予来自某个主机的某个用户可以查询.插入.修改.删除等数据库操作的权限 不能明确的指定拒接某个用户的连接 权限控制(授权与收回)的执行语句包括 create ...
- metasploit扫描mysql空密码
靶机IP 192.168.255.100 攻击机IP 192.168.255.200 流程开始 查找mysql登录模块 msf5 > search mysql_login 加载这个模块 msf5 ...
- Spirng Data JPA 之Specification中and、or的使用
项目中,有的地方存值以逗号分隔的方式来存储,但查询的时候是需要满足单个值或者多个值条件查询,因此用到Specification中and.or组合使用,此文用来记录,以防后用. 描述:parentCod ...
- 开源数字人直播DH_live web整合包免训练使用教程
资源导航首页 项目地址 基于开源项目:DH_live做的web交互系统 主要实现:免训练数字人视频制作和实时语音数字人 可搭配一些直播场控软件的语音 驱动数字人进行直播 整合包下载 「数字人( ...