i.MX6ULL(十七) linux LED驱动

嵌入式Linux设备树实战:i.MX6ULL开发板LED驱动适配详解 在嵌入式Linux开发中,设备树(Device Tree)是描述硬件资源配置的核心机制,它通过结构化的文本文件将CPU、内存、外设等硬件信息传递给内核,实现驱动与硬件的动态匹配。其工作原理在于,Bootloader在启动内核时会加载设备树二进制文件(.dtb),内核解析后根据节点中的compatible属性绑定对应驱动,从而完成硬件初始化。这一技术价值在于实现了同一内核镜像对不同硬件平台的广泛支持,极大提升了嵌入式系统的可移植性和维护效率。其典型应用场景包括基于ARM、PowerPC等架构的嵌入式平台开发, 阅读详情

1 设备树 LED 驱动原理

对于MX6ULL 点灯方式较多,直接通过寄存器操作更适合低成本设备,与裸机实验不同的是,在 Linux 下编写驱动要符合 Linux 的驱动框架。

但Linux 下的任何外设驱动,最终都是要配置相应的硬件寄存器,这是本质。实际开发中即使驱动层我们也不需要自定义设备树 led节点 ,芯片厂商BSP 内核设备树会按照pinctrl 及gpio系统框架规则实现所有gpio节点,对gpio操作也将遵循linux下访问gpio通用方式操作而得以简化。

本章实验在四十二章实验的基础上完成,重点是将驱动改为基于设备树的.

2 例程代码

实验流程

①、在 imx6ull-alientek-emmc.dts 文件中创建相应的设备节点。
②、编写驱动程序(在第四十二章实验基础上完成),获取设备树中的相关属性值。
③、使用获取到的有关属性值来初始化 LED 所使用的 GPIO。

2.1 修改设备树文件

在根节点“/”下创建一个名为“alphaled”的子节点,打开  (imx6ull-14x14-evk.dts/imx6ull-14x14-evk-emmc.dts)文件,
在根节点“/”最后面输入如下所示内容:
alphaled {
		#address-cells = <1>;
 		#size-cells = <1>;
 		compatible = "atkalpha-led";
 		status = "okay";
 		reg = < 0X020C406C 0X04 /* CCM_CCGR1_BASE */
 		0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */
 		0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */
		 0X0209C000 0X04 /* GPIO1_DR_BASE */
 		0X0209C004 0X04 >; /* GPIO1_GDIR_BASE */
		 };
第 6~10 行,reg 属性,非常重要!reg 属性设置了驱动里面所要使用的寄存器物理地址,比
如第 6 行的“0X020C406C 0X04”表示 I.MX6ULL 的 CCM_CCGR1 寄存器,其中寄存器首地
址为 0X020C406C,长度为 4 个字节。
设备树修改完成以后输入如下命令重新编译一下 也可只编译当前dts  进入顶层目录 make xx/xx.dtb
make dtbs
编译完成以后得到 imx6ull-14x14-evk-emmc.dtb,使用新的 imx6ull-14x14-evk-emmc.dtb (修改名称为imx6ull-14x14-emmc-4.3-800x480-c.dtb)copy到 tftp目录 
cp arch/arm/boot/dts/imx6ul-14x14-evk-emmc.dtb ~/imx6ull/tftpboot/
mv imx6ul-14x14-evk-emmc.dtb imx6ull-14x14-emmc-4.3-800x480-c.dtb
chmod a+x imx6ull-14x14-emmc-4.3-800x480-c.dtb
为了便于调试 学习 ,通过tftp 远程下载 重启加载设备树

setenv ipaddr 192.168.1.113
setenv ethaddr b8:ae:1d:01:00:00
setenv gatewayip 192.168.1.1
setenv netmask 255.255.255.0
setenv serverip 192.168.1.122
 

setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-14x14-emmc-4.3-800x480-c.dtb; 
bootz 80800000 - 83000000'
saveenv

Linux 内核。Linux 启动成功以后进入到/proc/device-tree/目录中查看是否有“alphaled”这个节
点, 

 节点添加成功

2.2 dtc  反编译dtb ->dts

dtc目录 

scripts/dtc

2.3 LED 灯驱动程序编写

设备树准备好以后就可以编写驱动程序了,本章实验在第四十二章实验驱动文件
newchrled.c 的基础上修改而来。
2.2.1 添加驱动模版基础代码

 创建驱动模版 编写 led_init  led_exit 方法,

init方法中动态 创建注册 设备号 、添加字符设备

创建类,绑定设备操作集‘、创建设备

led_exit 删除设备 注销设备号 、销毁设备、销毁设备类

#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>


/* dtsled设备结构体 */
struct dtsled_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
	struct device_node	*nd; /* 设备节点 */
};

struct dtsled_dev dtsled;	/* led设备 */

#define DTSLED_CNT			1		  	/* 设备号个数 */
#define DTSLED_NAME			"dtsled"	/* 名字 */

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

static int  __init led_init() {

	/* 注册字符设备驱动 */
	/* 1、创建设备号 */

	if(dtsled.major) {
		dtsled.nd=MKDEV(dtsled.major, 0);
		register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME);
	} else {
			alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT, DTSLED_NAME);	/* 申请设备号 */
			dtsled.major = MAJOR(dtsled.devid);	/* 获取分配号的主设备号 */
			dtsled.minor = MINOR(dtsled.devid);	/* 获取分配号的次设备号 */
	}

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

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

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

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

	return 0;

}
static void   __exit  led_exit() {
 /* 注销字符设备驱动 */
	cdev_del(&dtsled.cdev);/*  删除cdev */
	unregister_chrdev_region(dtsled.devid, DTSLED_CNT); /* 注销设备号 */

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

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("t");
2.2.2  添加字符设备操作集

打开时设置私有数据,读写驱动 注意系统调用 实现用户空间和内核转换

/*
 * @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 = &dtsled; /* 设置私有数据 */
	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;

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

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

	if(ledstat == LEDON) {	
		led_switch(LEDON);		/* 打开LED灯 */
	} else if(ledstat == LEDOFF) {
		led_switch(LEDOFF);	/* 关闭LED灯 */
	}
	return 0;
}

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

1 of_find_node_by_path 设备节点查询 2  of_find_property 查询属性内容 3 status 检查

4寄存器获取 of_property_read_u32_array 

/* 获取设备树中的属性数据 */
	/* 1、获取设备节点:alphaled */
	dtsled.nd = of_find_node_by_path("/alphaled");
	if(dtsled.nd == NULL) {
		printk("alphaled node nost find!\r\n");
		return -EINVAL;
	} else {
		printk("alphaled node find!\r\n");
	}

	/* 2、获取compatible属性内容 */
	proper = of_find_property(dtsled.nd, "compatible", NULL);
	if(proper == NULL) {
		printk("compatible property find failed\r\n");
	} else {
		printk("compatible = %s\r\n", (char*)proper->value);
	}

	/* 3、获取status属性内容 */
	ret = of_property_read_string(dtsled.nd, "status", &str);
	if(ret < 0){
		printk("status read failed!\r\n");
	} else {
		printk("status = %s\r\n",str);
	}

	/* 4、获取reg属性内容 */
	ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
	if(ret < 0) {
		printk("reg property read failed!\r\n");
	} else {
		u8 i = 0;
		printk("reg data:\r\n");
		for(i = 0; i < 10; i++)
			printk("%#X ", regdata[i]);
		printk("\r\n");
	}
2.2.4 初始化LED

1 寄存器映射 of_iomap / ioremap 2 设置IO复用 寄存器电器属性 初始化默认值

	/* 初始化LED */

	/* 1、寄存器地址映射 */
		/* 初始化LED */
#if 0
	/* 1、寄存器地址映射 */
	IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);
	SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);
  	SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);
	GPIO1_DR = ioremap(regdata[6], regdata[7]);
	GPIO1_GDIR = ioremap(regdata[8], regdata[9]);
#else
	IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0);
	SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);
  	SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);
	GPIO1_DR = of_iomap(dtsled.nd, 3);
	GPIO1_GDIR = of_iomap(dtsled.nd, 4);
#endif

/* 2、使能GPIO1时钟 */
	val = readl(IMX6U_CCM_CCGR1);
	val &= ~(3 << 26);	/* 清楚以前的设置 */
	val |= (3 << 26);	/* 设置新值 */
	writel(val, IMX6U_CCM_CCGR1);

	/* 3、设置GPIO1_IO03的复用功能,将其复用为
	 *    GPIO1_IO03,最后设置IO属性。
	 */
	writel(5, SW_MUX_GPIO1_IO03);
	
	/*寄存器SW_PAD_GPIO1_IO03设置IO属性
	 *bit 16:0 HYS关闭
	 *bit [15:14]: 00 默认下拉
     *bit [13]: 0 kepper功能
     *bit [12]: 1 pull/keeper使能
     *bit [11]: 0 关闭开路输出
     *bit [7:6]: 10 速度100Mhz
     *bit [5:3]: 110 R0/6驱动能力
     *bit [0]: 0 低转换率
	 */
	writel(0x10B0, SW_PAD_GPIO1_IO03);

	/* 4、设置GPIO1_IO03为输出功能 */
	val = readl(GPIO1_GDIR);
	val &= ~(1 << 3);	/* 清除以前的设置 */
	val |= (1 << 3);	/* 设置为输出 */
	writel(val, GPIO1_GDIR);

	/* 5、默认关闭LED */
	val = readl(GPIO1_DR);
	val |= (1 << 3);	
	writel(val, GPIO1_DR);
2.2.5 完善代码

dtsled.c完整代码

#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: dtsled.c
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: LED驱动文件。
其他	   	: 无
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2019/7/9 左忠凯创建
***************************************************************/
#define DTSLED_CNT			1		  	/* 设备号个数 */
#define DTSLED_NAME			"dtsled"	/* 名字 */
#define LEDOFF 					0			/* 关灯 */
#define LEDON 					1			/* 开灯 */

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

/* dtsled设备结构体 */
struct dtsled_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
	struct device_node	*nd; /* 设备节点 */
};

struct dtsled_dev dtsled;	/* led设备 */

/*
 * @description		: LED打开/关闭
 * @param - sta 	: LEDON(0) 打开LED,LEDOFF(1) 关闭LED
 * @return 			: 无
 */
void led_switch(u8 sta)
{
	u32 val = 0;
	if(sta == LEDON) {
		val = readl(GPIO1_DR);
		val &= ~(1 << 3);	
		writel(val, GPIO1_DR);
	}else if(sta == LEDOFF) {
		val = readl(GPIO1_DR);
		val|= (1 << 3);	
		writel(val, GPIO1_DR);
	}	
}

/*
 * @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 = &dtsled; /* 设置私有数据 */
	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;

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

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

	if(ledstat == LEDON) {	
		led_switch(LEDON);		/* 打开LED灯 */
	} else if(ledstat == LEDOFF) {
		led_switch(LEDOFF);	/* 关闭LED灯 */
	}
	return 0;
}

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

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

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init led_init(void)
{
	u32 val = 0;
	int ret;
	u32 regdata[14];
	const char *str;
	struct property *proper;

	/* 获取设备树中的属性数据 */
	/* 1、获取设备节点:alphaled */
	dtsled.nd = of_find_node_by_path("/alphaled");
	if(dtsled.nd == NULL) {
		printk("alphaled node nost find!\r\n");
		return -EINVAL;
	} else {
		printk("alphaled node find!\r\n");
	}

	/* 2、获取compatible属性内容 */
	proper = of_find_property(dtsled.nd, "compatible", NULL);
	if(proper == NULL) {
		printk("compatible property find failed\r\n");
	} else {
		printk("compatible = %s\r\n", (char*)proper->value);
	}

	/* 3、获取status属性内容 */
	ret = of_property_read_string(dtsled.nd, "status", &str);
	if(ret < 0){
		printk("status read failed!\r\n");
	} else {
		printk("status = %s\r\n",str);
	}

	/* 4、获取reg属性内容 */
	ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
	if(ret < 0) {
		printk("reg property read failed!\r\n");
	} else {
		u8 i = 0;
		printk("reg data:\r\n");
		for(i = 0; i < 10; i++)
			printk("%#X ", regdata[i]);
		printk("\r\n");
	}

	/* 初始化LED */
#if 0
	/* 1、寄存器地址映射 */
	IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);
	SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);
  	SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);
	GPIO1_DR = ioremap(regdata[6], regdata[7]);
	GPIO1_GDIR = ioremap(regdata[8], regdata[9]);
#else
	IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0);
	SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);
  	SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);
	GPIO1_DR = of_iomap(dtsled.nd, 3);
	GPIO1_GDIR = of_iomap(dtsled.nd, 4);
#endif

	/* 2、使能GPIO1时钟 */
	val = readl(IMX6U_CCM_CCGR1);
	val &= ~(3 << 26);	/* 清楚以前的设置 */
	val |= (3 << 26);	/* 设置新值 */
	writel(val, IMX6U_CCM_CCGR1);

	/* 3、设置GPIO1_IO03的复用功能,将其复用为
	 *    GPIO1_IO03,最后设置IO属性。
	 */
	writel(5, SW_MUX_GPIO1_IO03);
	
	/*寄存器SW_PAD_GPIO1_IO03设置IO属性
	 *bit 16:0 HYS关闭
	 *bit [15:14]: 00 默认下拉
     *bit [13]: 0 kepper功能
     *bit [12]: 1 pull/keeper使能
     *bit [11]: 0 关闭开路输出
     *bit [7:6]: 10 速度100Mhz
     *bit [5:3]: 110 R0/6驱动能力
     *bit [0]: 0 低转换率
	 */
	writel(0x10B0, SW_PAD_GPIO1_IO03);

	/* 4、设置GPIO1_IO03为输出功能 */
	val = readl(GPIO1_GDIR);
	val &= ~(1 << 3);	/* 清除以前的设置 */
	val |= (1 << 3);	/* 设置为输出 */
	writel(val, GPIO1_GDIR);

	/* 5、默认关闭LED */
	val = readl(GPIO1_DR);
	val |= (1 << 3);	
	writel(val, GPIO1_DR);

	/* 注册字符设备驱动 */
	/* 1、创建设备号 */
	if (dtsled.major) {		/*  定义了设备号 */
		dtsled.devid = MKDEV(dtsled.major, 0);
		register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME);
	} else {						/* 没有定义设备号 */
		alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT, DTSLED_NAME);	/* 申请设备号 */
		dtsled.major = MAJOR(dtsled.devid);	/* 获取分配号的主设备号 */
		dtsled.minor = MINOR(dtsled.devid);	/* 获取分配号的次设备号 */
	}
	printk("dtsled major=%d,minor=%d\r\n",dtsled.major, dtsled.minor);	
	
	/* 2、初始化cdev */
	dtsled.cdev.owner = THIS_MODULE;
	cdev_init(&dtsled.cdev, &dtsled_fops);
	
	/* 3、添加一个cdev */
	cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);

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

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

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit led_exit(void)
{
	/* 取消映射 */
	iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);

	/* 注销字符设备驱动 */
	cdev_del(&dtsled.cdev);/*  删除cdev */
	unregister_chrdev_region(dtsled.devid, DTSLED_CNT); /* 注销设备号 */

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

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

2.3 编写app操作代码 ledApp.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 © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: ledApp.c
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: chrdevbase驱测试APP。
其他	   	: 无
使用方法	 :./ledtest /dev/led  0 关闭LED
		     ./ledtest /dev/led  1 打开LED		
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2019/1/30 左忠凯创建
***************************************************************/

#define LEDOFF 	0
#define LEDON 	1

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd, retvalue;
	char *filename;
	unsigned char databuf[1];
	
	if(argc != 3){
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];

	/* 打开led驱动 */
	fd = open(filename, O_RDWR);
	if(fd < 0){
		printf("file %s open failed!\r\n", argv[1]);
		return -1;
	}

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

	/* 向/dev/led文件写入数据 */
	retvalue = write(fd, databuf, sizeof(databuf));
	if(retvalue < 0){
		printf("LED Control Failed!\r\n");
		close(fd);
		return -1;
	}

	retvalue = close(fd); /* 关闭文件 */
	if(retvalue < 0){
		printf("file %s close failed!\r\n", argv[1]);
		return -1;
	}
	return 0;
}

2.4 编译运行

修改 Makefile 设置为内核源码目录 注意保持 班子内核和编译代码内核同一版本

KERNELDIR := /home/t/imx6ull/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek 

2.4.1编译 驱动模块及app

 

2.4.2 copy dtsled.ko 及 ledApp到 nfs 目录 通过nfs 传输到开发板

cp dtsled.ko  ledApp /home/t/nfs_rootfs/

开发板

2.4.3 安装 dtsled.ko 

insmod desled.ko 

安装成功后会打印驱动相关信息

查询设备树节点 注册成功

2.4.4 测试 ledApp 

添加执行权限 开关led

root@ATK-IMX6U:/mnt# chmod a+x ledApp
root@ATK-IMX6U:/mnt# ./ledApp /dev/dtsled 0
root@ATK-IMX6U:/mnt# ./ledApp /dev/dtsled 1

测试成功

1.I.MX6ULL介绍以及参考手册 本文介绍了NXP I.MX6ULL处理器架构及GPIO配置方法,重点分析了Cortex-A7架构和I.MX6U的IO命名规则。每个IO对应两个寄存器:复用功能寄存器(MUX)和电气特性寄存器(PAD)。文章详细说明了如何通过寄存器配置GPIO的复用模式、上拉/下拉、开漏输出、驱动强度等参数,并以控制LED为例,演示了从原理图查找引脚到配置寄存器的完整流程。与STM32相比,I.MX6ULL的GPIO配置更为复杂,需要同时设置复用功能和电气特性两个寄存器。 阅读详情

相关推荐

i.MX6ULL嵌入式Linux LED驱动开发与sysfs控制实战

GPIO(通用输入输出)是嵌入式系统中最基础的硬件接口技术,通过配置引脚的电平状态实现对外设的控制。Linux内核的LED子系统采用分层架构设计,将硬件操作与用户接口分离,通过sysfs文件系统向用户空间暴露统一的控制接口。这种设计极大简化了嵌入式开发中的硬件控制复杂度,提升了代码的可移植性。在实际工程中,开发者可以通过命令行直接操作/sys/class/leds/目录下的brightness、trigger等文件,实现LED的亮灭控制、定时闪烁、心跳指示等功能。i.MX6ULL作为典型的ARM Corte

weixin_34331102的博客 417

Linux之LED驱动程序

linux之led驱动

zhen 553

正点原子imx6ull-mini-Linux驱动之pinctrl 和 gpio 子系统(5)

上一小节讲解了 pinctrl 子系统,pinctrl 子系统重点是设置 PIN(有的 SOC 叫做 PAD)的复用 和电气属性,如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系 统了。gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO 为输入输出,读取 GPIO 的值等。

NEWEVA__zzera22的博客 1648

迅为iTOP-i.MX6ULL开发板I2C驱动程序实现 I2C通信

我们数据包的封包的操作,封装了一个 i2c_rdwr_ioctl_data 数据包,才对 i2c 进行读写,同样在驱动里面,在第 67.1 章节学习 i2c 的时候,我们是在应用层操作设备节点对 i2c 设备进行读写的,那么如果我们在。我们对某个可读可写的寄存器进行读写操作,我们打开触摸芯片 ft5x06 的数据手册,打开 2.1 章节,如。如上图所示,我们可以看到读写函数是没问题的,可以对寄存器进行正常的读写操作。/* i2c 驱动的 remove 函数 */// 采用设备树的时候驱动使用的匹配表。

mucheni的博客 568

Linux 驱动开发基础知识——编写LED驱动程序(三)

我们基于 Hello 驱动程序先写出最简单的 LED 驱动程序

嵌入式技术开发 7513

i.MX6ULL驱动开发 | 03-基于字符设备驱动框架点亮LED

一、硬件原理 1. 看原理图确定硬件连接 i.MX6ULL开发板上板载了一个用户LED,如图: LED0连接到GPIO_3,在核心板原理图上查看对应到i.MX6ULL的引脚为GPIO1_IO03。 2. 看芯片手册如何控制引脚 因为i.MX系列的外设原理基本一样,所以在本系列文章中,关于外设原理请阅读之前i.MXRT1062中的详细分析。 i.MX RT开发笔记-04 | 使用 IOMUXC 和 GPIO 点亮LED 2.1. IOMUXC外设选择引脚复用 (1)SW_MUX_CTL_PAD寄存器:用

Mculover666的博客(嵌入式) 1213

Linux驱动开发 - LED驱动

Linux驱动开发 笔记

qq_41470744的博客 465

用Simulink对Carsim车辆进行档位控制

用Simulink对Carsim车辆进行档位控制一、carsim中的档位设置为开环控制二、在Constant目录下面选取一个选项并重新命名为constant :0 Gear三、双击进去设置常量为 0四、输入和输出IO通道五、验证 :输入 为 3 档 ----- 输出 为 3!六、参考 一、carsim中的档位设置为开环控制 二、在Constant目录下面选取一个选项并重新命名为constant :0 Gear 三、双击进去设置常量为 0 四、输入和输出IO通道 五、验证 :输入 为 3 档 -

qq_42027654的博客 2602

05_Linux设备树下的LED驱动

驱动文件中定义有关寄存器物理地址,然后使用io_remap函数进行内存映射,得到对应的虚拟地址,最后操作寄存器对应的虚拟地址完成对GPIO的初始化。第169-174行,通过of_find_property函数获取alphaled节点的compatible属性,返回值为property结构体类型指针变量, property的成员变量value表示属性值。通过of_property_read_u32_array函数获取alphaled节点的reg属性所有值,并且将获取到的值都存放到 regdata 数组中。

Yuanghxb的博客 751

S32K344 Bootloader实战:一个按键实现从Boot到App的无缝跳转(附完整代码)

本文详细介绍了基于NXP S32K344微控制器的Bootloader开发实战,重点解析如何通过一个硬件按键实现从Boot到App的可靠跳转。内容涵盖内存分区规划、中断向量表重映射、外设初始状态管理等核心要素,并提供了完整的代码示例和调试技巧,适用于汽车电子和工业控制等高可靠性应用场景。

weixin_29192365的博客 308

初识Renode

1. 简介 Renode是Antmicro公司发布的一个开发框架,使得开发人员通过模拟物理硬件系统(包括CPU、外围设备、传感器、环境以及节点之间的有线或无线介质),加速物联网和嵌入式系统的开发。它可在PC上通过软件对嵌入式设备进行模拟,可模拟Cortex-M、Cortex-A、RISC-V和其他CPU,并可模拟外设以及各种通信总线和接口。其指令模拟器使用C语言开发,外设模拟器使用C#语言开发,测试及输出层面的内容则用python编写。 Renode官网:Renodehttps://renode.io/

牧羊女 5293

I.MX6ULL Linux 点灯实验理论及汇编点灯

I.MX6ULL Linux C语言开发。

Redhat_yan 1633

AUTOSAR 3万字面试题全面解析(持续更新)

ECU抽象层(EAL)是AUTOSAR架构中的一部分,它位于微控制器抽象层(MCAL)之下,但通常与MCAL一起被提及。EAL的目标是提供一个统一的接口来访问和管理ECU(Electronic Control Unit,电子控制单元)的硬件资源,包括处理器、存储器和外围设备。通过这种抽象,EAL使得上层软件能够以一种标准化的方式与ECU硬件交互,而不需要关心具体的硬件实现细节。EAL解决了硬件异构性带来的问题,即不同ECU可能采用不同类型的处理器和硬件配置。

大模型大数据攻城狮的专栏 5609

i.MX6ULL - 从零开始移植linux内核5.4.70_2.3.0

uboot移植看这里:https://blog.csdn.net/qq153471503/article/details/126587387见此篇博客:https://blog.csdn.net/qq153471503/article/details/126587387下载内核(文件很大且非常慢,建议晚上睡前开始下载,下一晚上): 查看所有分支: 切换到imx_5.4.70_2.3.0分支: 修改顶层Makefile文件,找到,修改为: 初次编译:

^_^ 5999

基于Simulink的发动机管理系统中的自适应增压控制系统设计

通过上述详细设计步骤,我们成功开发了一个基于Simulink的自适应增压控制系统。该系统不仅能够实时监测和预测发动机的运行状态,动态调整增压压力目标值,还具备实时监控、故障诊断和保护功能,确保系统的安全性和可靠性。此外,通过仿真实验和硬件在环测试,我们验证了系统的性能,并对其进行了优化,使其能够更好地满足实际应用需求。这个项目展示了如何利用MATLAB/Simulink的强大功能,结合控制理论和工程实践,开发出高效、可靠的自适应增压控制系统。

MHD0815的博客 221

9、傅里叶变换:连续时间与离散时间信号分析的核心工具

本文深入探讨了傅里叶变换在连续时间和离散时间信号分析中的核心作用,详细介绍了连续时间傅里叶变换(CTFT)和离散时间傅里叶变换(DTFT)的定义、性质及其应用。文章涵盖了傅里叶级数到傅里叶变换的推导过程、常见变换对、系统分析中的关键性质,并讨论了采样后频谱的周期延拓与混叠问题。此外,还展示了傅里叶变换在滤波器设计、通信系统调制解调等实际场景中的应用,最后展望了其在图像处理、生物医学等领域的广阔前景。

jump7的博客 67

I.MX6ULL的蜂鸣器实验-GPIO输出实验

在 I.MX6U-ALPHA 开发板上有一个有源蜂鸣器,通过 IO 输出高低电平即可控制蜂鸣器的开关,本质上也属于 GPIO 的输出控制。蜂鸣器常用于计算机、打印机、报警器、电子玩具等电子产品中,常用的蜂鸣器有两种:有源蜂鸣器和无源蜂鸣器,这里的有“源”不是电源,而是震荡源,有源蜂鸣器内部带有震荡源,所以有源蜂鸣器只要通电就会叫。无源蜂鸣器内部不带震荡源,直接用直流电是驱动不起来的,需要 2K-5K 的方波去驱动。

Redhat_yan 1955

MTEX包在Matlab中实现从EBSD数据生成Abaqus网格与晶粒取向:一种对用户材料旋转矩阵分量实践的全面指南

此教程提供了一种使用MTEX Matlab包处理EBSD数据的方法,并生成Abaqus输入文件,这对于进行多晶体有限元分析具有极大的帮助。但值得注意的是,这只是一个基本的例子,实际应用中可能需要根据您的实际需求进行调整。MTEX包还提供了许多其他的功能,可以进行更复杂的分析和处理,我非常推荐您去探索。希望这篇文章能对您有所帮助,如果您有任何疑问或建议,欢迎留言交流。在接下来的文章中,我将会介绍更多关于MTEX的高级功能,敬请期待。– (完)–

qq_38334677的博客 4437

i.MX 6ULL 驱动开发 五:LED 驱动

内核中描述以及NXP提供设备配置添加。内核中描述以及NXP提供设备配置添加。2、拷贝编译成功的设备树文件。文件被设备源码文件引用。灯可以正常被点亮和关闭。查看设备树解析是否成功。子系统设置为低电平。

OnlyLove_的博客 935

LeRobot 官方论文:一个用于端到端机器人学习的开源库

本文介绍了一个名为LeRobot的开源库,旨在解决机器人学习领域的碎片化问题。该库提供了端到端的技术栈,包括统一的机器人中间件API、标准化的数据集格式(LeRobotDataset)、优化的推理栈以及多种最先进机器人学习算法的实现。LeRobot支持多种低成本开源机器人平台(如SO-10X、ALOHA-2等),通过共享中间件实现便捷的遥操作和数据收集。其数据集格式已广泛应用于社区,截至2025年9月已有16,000多个数据集公开共享。该库还支持流式处理大规模数据,降低了研究门槛。在模型方面,LeRobot

2506_90492529的博客 907

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

I.MX6ULL基于设备树使用pinctrl和gpio子系统驱动LED

I.MX6ULL基于设备树使用pinctrl和gpio子系统驱动LED 一、修改设备树文件 1、添加pinctrl节点 打开imx6ull-alientk-emmc.dts设备树源文件,在iomuxc节点的imx6ul-evk字节点下添加pinctrl_led节点。(注意:节点前缀一定要为“pinctrl_”) 在pinctrl_led节点下添加“fsl,pins”属性,对于I.MX系列的SOC...

qq_38113006的博客 1681

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

【i.MX6ULL】驱动开发4——点亮LED(寄存器版)

上篇文章(【i.MX6ULL】驱动开发3–GPIO寄存器配置原理),介绍了i.MX6ULL芯片的GPIO的工作原理与寄存器配置。 本篇,就要来实际操作一下GPIO,实现板子上LED灯的亮灭控制。 文章目录1 地址映射1.1 MMU介绍1.2 TLB介绍1.3 I/O映射函数ioremap()iounmap()1.4 I/O内存访问函数2 程序编写2.1 LED驱动程序2.1.1 字符设备的基本框架2.1.2 具体完善1)GPIO寄存器宏定义2)GPIO硬件初始化3)字符设备初始化4)LED亮灭控制5)驱动退

码农爱学习的博客 826

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

嵌入式Linux--项目--基于I.MX6ULL的智能家居系统(驱动开发、mqtt、qt、JsonRPC)

基于IMX6ULL,设计的智能家居系统,涉及驱动开发、应用开发(Qt、mqtt远程控制)JsonRPC前后端分离的设计思想;适合刚入门的新手做项目练手,以此为基础想更深层学习打下基础。

张哈哈的博客 9843

嵌入式Linux驱动开发:i.MX6ULL平台设备驱动

本文档详细介绍了在i.MX6ULL平台上开发Linux设备驱动的完整流程。通过实现一个LED控制驱动,阐述了Linux内核中的平台总线模型,包括平台设备(platform_device)和平台驱动(platform_driver)的注册与匹配机制。文章分析了设备树中LED节点的定义,重点讲解了驱动开发的关键环节:获取设备资源、内存映射、硬件初始化(时钟使能、引脚复用、GPIO配置)以及字符设备框架的实现。项目包含三个核心模块:平台设备定义(leddevice.c)、平台驱动实现(leddriver.c)和用

愿青春不负梦想 881

一灯大师,基于imx6ull点亮LED灯

主要介绍基于Linux字符设备驱动框架来实现在imx6ull点亮LED灯

朝气蓬勃 3447

Linux 内核自带的 LED 灯驱动

学习如何使用 Linux 内核自带的 LED 驱动来驱动 I.MX6U-ALPHA 开发板上的 LED0。 LED 灯这样非常基础的设备驱动, Linux 内核已经集成了。 Linux 内核的 LED 灯驱动采用 platform 框架 因此我们只需要按照要求在设备树文件中添加相应的 LED 节点即可 linux 自带LED驱动理解 内核配置: -> Device Drivers -> LED Support (NEW_LEDS [=y]) ->LED Support for GPI

3714
上一篇: FFmpeg时间戳
下一篇: i.MX6ULL(十八) linux pinctrl 子系统
江太翁
博客等级 码龄15年 271粉丝 141原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值