I.MX6ULL内核开发11:使用设备树实现RGB灯驱动

news2024/11/17 1:58:59

目录

一、实验说明

二、硬件原理图分析

2.1 打开原理图,找到rgb部分

2.2 对RGB的R灯进行寄存器设置

2.2.1 时钟配置

2.2.2 引脚复用GPIO

2.2.3 引脚属性设置

2.2.4 输出电平设置

三、实验代码

3.1 编程思路

3.2 代码分析

3.2.1 添加RGB设备节点

3.2.2 编写驱动函数

3.2.3 编写测试应用程序

四、编译驱动程序

4.1 编译设备树

4.2 编译驱动和应用程序

五、程序运行结果

5.1 实验操作


一、实验说明

        本实验使用到ebf6ull-pro开发板上rgb彩灯

二、硬件原理图分析

2.1 打开原理图,找到rgb部分

 

 LED_R的阴极连接到i.MX6ULL芯片上的GPIO1_IO04引脚,LED_G连接到CSI_HSYNC,LED_B连接到CSI_VSYNC。而CSI_HSYNC和CSI_VSYNC作为摄像头的某一功能被复用为GPIO。如下表所示。

LED灯原理图的标号具体引脚名GPIO端口及引脚编号
R灯GPIO_4GPIO1_IO04GPIO1_IO04
G灯CSI_HSYNCCSI_HSYNCGPIO4_IO20
B灯CSI_VSYNCCSI_VSYNCGPIO4_IO19

对于RGB灯的控制进行控制,也就是对上述GPIO的寄存器进行读写操作。可分为以下几个步骤:

  • 使能GPIO时钟
  • 设置引脚复用为GPIO
  • 设置引脚属性(上下拉、速率、驱动能力)
  • 控制GPIO引脚输出高低电平

2.2 对RGB的R灯进行寄存器设置

2.2.1 时钟配置

        跟GPIO相关的时钟主要是CCM_CCGR(0-3)寄存器。查看数据手册可以知道GPIO的第26-27两位控制引脚时钟。

数据手册描述

System clock

 

 两个bit的不同取值,设置GPIO时钟的不同属性

  • 00:所有模式下都关闭时钟
  • 01:在运行模式下开启时钟
  • 10:保留
  • 11:在所有模式下都开启时钟 

CCM_CCGR1地址为0x20C406C。先对CCM_CCGR1寄存器的第26位、27位值清空,再赋值为11。

2.2.2 引脚复用GPIO

引脚复用相关的寄存器位IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO4

关于该寄存器的配置可见下图

寄存器地址为0x20E006C,对该寄存器第0-3位配置为0101时,MUX_MODE为ALT5,也就是该引脚复用为GPIO。

2.2.3 引脚属性设置

寄存器为IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04

 

  • HYS(bit16):用来使能迟滞比较器。
  • PUS(bit15-bit14):用来设置上下拉电阻大小。
  • PUE(bit13):当IO作为输入的时候,这个位用来设置IO上下拉还是状态保持器。
  • PKE(bit12): 用来使能或者禁止上下拉/状态保持器功能。
  • ODE(bit11):IO作为输出的时候,此位用来禁止或者使能开漏输出。
  • SPEED(bit7-bit6):当IO用作输出的时候,此位用来设置IO速度。
  • DSE(bit5-bit3):当IO用作输出的时候用来设置IO的驱动能力。
  • SRE(bit0):设置压摆率

寄存器地址位0x20E02F8,对该寄存器写入0x1F838,也就是二进制1 1111 1000 0011 1000,对比上图了解引脚的属性。

2.2.4 输出电平设置

 -0:输入   -1:输出

硬件原理以及寄存器配置到此为止,更多硬件上的信息可以查看原理图和芯片手册。

三、实验代码

3.1 编程思路

        程序编写的内容主要为添加RGB灯的设备树节点、在驱动程序使用of函数获取设备节点中的属性,编写测试程序。

  • 首先向设备树添加RGB设备节点。
  • 其次编写平台设备驱动框架,主要包含驱动入口函数、驱动注销函数、平台设备结构体定义三部分内容。
  • 实现.probe函数,对rgb进行设备注册和初始化。
  • 实现字符设备操作函数集,这里主要实现.write函数。
  • 编写测试应用程序,对于输入不同的值控制rgb颜色。

3.2 代码分析

3.2.1 添加RGB设备节点

RGB灯实际使用的是一个IO口,控制它所需要的资源几个控制寄存器。

/*---------------------第一部分-----------------------------------------*/
/*
*CCM_CCGR1                         0x020C406C
*IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04  0x020E006C
*IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04  0x020E02F8
*GPIO1_GD                          0x0209C000
*GPIO1_GDIR                        0x0209C004
*/


/*
*CCM_CCGR3                         0x020C4074
*IOMUXC_SW_MUX_CTL_PAD_CSI_HSYNC   0x020E01E0
*IOMUXC_SW_PAD_CTL_PAD_CSI_HSYNC   0x020E046C
*GPIO4_GD                          0x020A8000
*GPIO4_GDIR                        0x020A8004
*/


/*
*CCM_CCGR3                         0x020C4074
*IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC   0x020E01DC
*IOMUXC_SW_PAD_CTL_PAD_CSI_VSYNC   0x020E0468
*GPIO4_GD                          0x020A8000
*GPIO4_GDIR                        0x020A8004
*/

/*---------------------第二部分-----------------------------------------*/
	/*添加led节点*/
	rgb_led{
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "fire,rgb_led";

/*---------------------第三部分-----------------------------------------*/
		/*红灯节点*/
		ranges;
		rgb_led_red@0x020C406C{
			compatible = "fire,rgb_led_red";
			reg = <0x020C406C 0x00000004
			       0x020E006C 0x00000004
			       0x020E02F8 0x00000004
				   0x0209C000 0x00000004
			       0x0209C004 0x00000004>;
			status = "okay";
		};

		/*绿灯节点*/
		rgb_led_green@0x020C4074{
			compatible = "fire,rgb_led_green";
			reg = <0x020C4074 0x00000004
			       0x020E01E0 0x00000004
			       0x020E046C 0x00000004
				   0x020A8000 0x00000004
			       0x020A8004 0x00000004>;
			status = "okay";
		};

		/*蓝灯节点*/
		rgb_led_blue@0x020C4074{
			compatible = "fire,rgb_led_blue";
			reg = <0x020C4074 0x00000004
			       0x020E01DC 0x00000004
			       0x020E0468 0x00000004
				   0x020A8000 0x00000004
			       0x020A8004 0x00000004>;
			status = "okay";
		};
	};	
  • 第一部分:这部分列出了控制RGB灯的三个引脚所使用的寄存器,包含控制时钟寄存器、引脚复用寄存器、引脚属性寄存器、数据输出寄存器(包括引脚是输出还是输入,输出高电平还是低电平)。
  • 第二部分:这部分是RGB灯的设备树节点,节点名为“rgb_led”,由于在根节点下,很明显它的设备树路路径为“/rgb_led”,在驱动程序中我们会用到cells定义它的子节点的reg属性样式。“compatible”属性用于匹配驱动,在驱动我们配置一个和“compatible”一样的参数,这样加载驱动就是自动和这个设备树节点匹配了。
  • 第三部分:reg_led的子节点,RGB灯使用了三个引脚,如上所示,它会用到15个寄存器,为方便管理,每个引脚船舰一个子节点,从上到下依次为红灯控制引脚、绿灯控制引脚、蓝灯控制引脚。在灯的子节点中只定义三个属性,“compatible=“fire,rgb_led_red””表示这是一个红灯子节点。“reg=<...>”定义红灯引脚使用到寄存器。一共有五个,排列顺序于注释中的一致。“status=“okay””定义子节点的状态,要使能这个节点就设置为“okay”。

3.2.2 编写驱动函数

        基于设备树的驱动程序与平台总线驱动非常相似,差别是平台总线驱动中的平台驱动要和平台设备进行匹配,使用设备树后设备树取代“平台设备”的作用,平台驱动只需要和与之对应的设备树节点匹配即可

驱动入口函数

驱动入口函数中注册一个平台驱动,如下所示

/*
*驱动初始化函数
*/
static int __init led_platform_driver_init(void)
{
	int DriverState;
	DriverState = platform_driver_register(&led_platform_driver);
	printk(KERN_EMERG "\tDriverState is %d\n", DriverState);
	return 0;
}

在整个入口函数中仅仅调用了“platform_driver_register”函数注册了一个平台驱动。参数是传入了一个平台设备结构体。

定义平台设备结构体

注册平台驱动时会用到平台设备结构体,在平台设备结构体主要作用时指定平台驱动的.probe函数、指定与平台驱动匹配的平台设备,使用了设备树就是指定与平台驱动匹配的设备树节点。

/*-------------------第一部分-----------------------------*/
static const struct of_device_id rgb_led[] = {
	{.compatible = "fire,rgb_led"},
	{/* sentinel */}};

/*--------------------第二部分-----------------------------*/
/*定义平台设备结构体*/
struct platform_driver led_platform_driver = {
	.probe = led_probe,
	.driver = {
		.name = "rgb-leds-platform",
		.owner = THIS_MODULE,
		.of_match_table = rgb_led,
	}
	};
  • 第一部分:定义了匹配表
  • 第二部分:就是定义的平台设备结构体。其中“.probe=led_probe,”指定.probe函数。.pro函数比较特殊,当平台驱动和设备树节点匹配后会自动执行.probe函数,后面的RGB灯的初始化以及字符设备的注册都在这个函数中为实现(也可以在其他函数中实现)。".driver={...}"定义driver的一些属性,包括名字、所有者等,其中最需要注意的时“.of_match_table”属性,它指定这个驱动的匹配表。这里只定义了一个匹配值“.compatible = "fire,rgb_led"”,这个驱动将会和设备树中“compatible = “fire,reb_led””的相对根节点的子节点匹配。在根节点下定义了rgb_led子节点,并且设置“compatible = fire,reg_led”;所以正常情况下,驱动会和这个子节点匹配。

实现.probe函数

如上文所述,当驱动和设备树节点匹配成功后会自动执行.probe函数,所以在.probe函数中实现一些初始化工作。本实验将RGB初始化以及字符设备的初始化全部放到.probe函数中实现,.probe函数较长,但包含大量的简单、重复性的初始化代码。

.probe函数内容:

/*----------------------------第一部分--------------------------------------*/
/*定义 led 资源结构体,保存获取得到的节点信息以及转换后的虚拟寄存器地址*/
struct led_resource
{
	struct device_node *device_node; //rgb_led_red的设备树节点
	void __iomem *virtual_CCM_CCGR;
	void __iomem *virtual_IOMUXC_SW_MUX_CTL_PAD;
	void __iomem *virtual_IOMUXC_SW_PAD_CTL_PAD;
	void __iomem *virtual_DR;
	void __iomem *virtual_GDIR;
};

/*定义 R G B 三个灯的led_resource 结构体,保存获取得到的节点信息*/
struct led_resource led_red;
struct led_resource led_green;
struct led_resource led_blue;

/*----------------平台驱动函数集-----------------*/
static int led_probe(struct platform_device *pdv)
{

	int ret = -1; //保存错误状态码
	unsigned int register_data = 0;

	printk(KERN_EMERG "\t  match successed  \n");

/*----------------------------第二部分--------------------------------------*/
	/*获取rgb_led的设备树节点*/
	rgb_led_device_node = of_find_node_by_path("/rgb_led");	
	if (rgb_led_device_node == NULL)
	{
		printk(KERN_ERR "\t  get rgb_led failed!  \n");
		return -1;
	}

/*----------------------------第三部分--------------------------------------*/
	/*获取rgb_led节点的红灯子节点*/
	led_red.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_red");
	if (led_red.device_node == NULL)
	{
		printk(KERN_ERR "\n get rgb_led_red_device_node failed ! \n");
		return -1;
	}


	/*获取 reg 属性并转化为虚拟地址*/
	led_red.virtual_CCM_CCGR = of_iomap(led_red.device_node, 0);
	led_red.virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(led_red.device_node, 1);
	led_red.virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(led_red.device_node, 2);
	led_red.virtual_DR = of_iomap(led_red.device_node, 3);
	led_red.virtual_GDIR = of_iomap(led_red.device_node, 4);

	/*初始化红灯*/
	register_data = readl(led_red.virtual_CCM_CCGR);
	register_data |= (0x03 << 26);
	writel(register_data, led_red.virtual_CCM_CCGR); //开启时钟

	register_data = readl(led_red.virtual_IOMUXC_SW_MUX_CTL_PAD);
	register_data &= ~(0xf << 0);
	register_data |= (0x05 << 0);
	writel(register_data, led_red.virtual_IOMUXC_SW_MUX_CTL_PAD); //设置复用功能

	register_data = readl(led_red.virtual_IOMUXC_SW_PAD_CTL_PAD);
	register_data = (0x10B0);
	writel(register_data, led_red.virtual_IOMUXC_SW_PAD_CTL_PAD); //设置PAD 属性

	register_data = readl(led_red.virtual_GDIR);
	register_data |= (0x01 << 4);
	writel(register_data, led_red.virtual_GDIR); //设置GPIO1_04 为输出模式

	register_data = readl(led_red.virtual_DR);
	register_data |= (0x01 << 4);
	writel(register_data, led_red.virtual_DR); //设置 GPIO1_04 默认输出高电平

	/*获取rgb_led节点的绿灯子节点*/
	led_green.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_green");
	if (led_green.device_node == NULL)
	{
		printk(KERN_ERR "\n get rgb_led_green_device_node failed ! \n");
		return -1;
	}

	/*获取 reg 属性并转化为虚拟地址*/
	led_green.virtual_CCM_CCGR = of_iomap(led_green.device_node, 0);
	led_green.virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(led_green.device_node, 1);
	led_green.virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(led_green.device_node, 2);
	led_green.virtual_DR = of_iomap(led_green.device_node, 3);
	led_green.virtual_GDIR = of_iomap(led_green.device_node, 4);

	/*初始化绿灯*/
	register_data = readl(led_green.virtual_CCM_CCGR);
	register_data |= (0x03 << 12);
	writel(register_data, led_green.virtual_CCM_CCGR); //开启时钟

	register_data = readl(led_green.virtual_IOMUXC_SW_MUX_CTL_PAD);
	register_data &= ~(0xf << 0);
	register_data |= (0x05 << 0);
	writel(register_data, led_green.virtual_IOMUXC_SW_MUX_CTL_PAD); //设置复用功能

	register_data = readl(led_green.virtual_IOMUXC_SW_PAD_CTL_PAD);
	register_data = (0x10B0);
	writel(register_data, led_green.virtual_IOMUXC_SW_PAD_CTL_PAD); //设置PAD 属性

	register_data = readl(led_green.virtual_GDIR);
	register_data |= (0x01 << 20);
	writel(register_data, led_green.virtual_GDIR); //设置GPIO4_IO20 为输出模式

	register_data = readl(led_green.virtual_DR);
	register_data |= (0x01 << 20);
	writel(register_data, led_green.virtual_DR); //设置 GPIO4_IO20 默认输出高电平

	/*获取rgb_led节点的蓝灯子节点*/
	led_blue.device_node = of_find_node_by_name(rgb_led_device_node,"rgb_led_blue");
	if (led_blue.device_node == NULL)
	{
		printk(KERN_ERR "\n get rgb_led_blue_device_node failed ! \n");
		return -1;
	}

	/*获取 reg 属性并转化为虚拟地址*/
	led_blue.virtual_CCM_CCGR = of_iomap(led_blue.device_node, 0);
	led_blue.virtual_IOMUXC_SW_MUX_CTL_PAD = of_iomap(led_blue.device_node, 1);
	led_blue.virtual_IOMUXC_SW_PAD_CTL_PAD = of_iomap(led_blue.device_node, 2);
	led_blue.virtual_DR = of_iomap(led_blue.device_node, 3);
	led_blue.virtual_GDIR = of_iomap(led_blue.device_node, 4);

	/*初始化蓝灯*/
	register_data = readl(led_blue.virtual_CCM_CCGR);
	register_data |= (0x03 << 12);
	writel(register_data, led_blue.virtual_CCM_CCGR); //开启时钟

	register_data = readl(led_blue.virtual_IOMUXC_SW_MUX_CTL_PAD);
	register_data &= ~(0xf << 0);
	register_data |= (0x05 << 0);
	writel(register_data, led_blue.virtual_IOMUXC_SW_MUX_CTL_PAD); //设置复用功能

	register_data = readl(led_blue.virtual_IOMUXC_SW_PAD_CTL_PAD);
	register_data = (0x10B0);
	writel(register_data, led_blue.virtual_IOMUXC_SW_PAD_CTL_PAD); //设置PAD 属性

	register_data = readl(led_blue.virtual_GDIR);
	register_data |= (0x01 << 19);
	writel(register_data, led_blue.virtual_GDIR); //设置GPIO4_IO19 为输出模式

	register_data = readl(led_blue.virtual_DR);
	register_data |= (0x01 << 19);
	writel(register_data, led_blue.virtual_DR); //设置 GPIO4_IO19 默认输出高电平

/*----------------------------第四部分--------------------------------------*/
	/*---------------------注册 字符设备部分-----------------*/

	//第一步
	//采用动态分配的方式,获取设备编号,次设备号为0,
	//设备名称为rgb-leds,可通过命令cat  /proc/devices查看
	//DEV_CNT为1,当前只申请一个设备编号
	ret = alloc_chrdev_region(&led_devno, 0, DEV_CNT, DEV_NAME);
	if (ret < 0)
	{
		printk("fail to alloc led_devno\n");
		goto alloc_err;
	}
	//第二步
	//关联字符设备结构体cdev与文件操作结构体file_operations
	led_chr_dev.owner = THIS_MODULE;
	cdev_init(&led_chr_dev, &led_chr_dev_fops);
	//第三步
	//添加设备至cdev_map散列表中
	ret = cdev_add(&led_chr_dev, led_devno, DEV_CNT);
	if (ret < 0)
	{
		printk("fail to add cdev\n");
		goto add_err;
	}

	//第四步
	/*创建类 */
	class_led = class_create(THIS_MODULE, DEV_NAME);

	/*创建设备*/
	device = device_create(class_led, NULL, led_devno, NULL, DEV_NAME);

	return 0;

add_err:
	//添加设备失败时,需要注销设备号
	unregister_chrdev_region(led_devno, DEV_CNT);
	printk("\n error! \n");
alloc_err:

	return -1;
}
  • 第一部分:自定义led资源结构体,用于保存获得到的设备节点信息以及转换后的虚拟寄存器地址。
  • 第二部分:使用of_find_node_by_path函数获取设备树节点“/rgb_led”,获取成功后会返回“rgb_led”节点的“设备节点结构体”后面的代码就可以根据这个“设备节点结构体”访问它的字节点。
  • 第三部分:依次初始化红、绿、蓝灯,这三部分非常相似,这里仅介绍第三部分红灯初始化部分。初始化过程如下:
    • 获取红灯子节点,这里使用函数“of_find_node_by_name”,参数rgb_led_device_node指定从rgb_led节点开始搜索,参数“rgb_led_red”指定要获取那个节点,这里时rgb_led节点下rgb_led_red子节点。
    • 获取并转换reg属性,我们直到reg属性保存的就是寄存器地址(物理地址),这里使用“of_iomap”函数,获取并完成物理地址到虚拟地址的转换。
    • 初始化寄存器,注意的时这里只能用系统提供的API函数(例如这里读写的时32位数据,使用writel和readl),不能像裸机那样直接使用“=”、“&=”、“|=”等等那样直接修改寄存器。
  • 第四部分:注册一个字符设备,字符设备的注册过程与之讲解的字符驱动设备驱动非常相似。
static dev_t led_devno;					 //定义字符设备的设备号
static struct cdev led_chr_dev;			 //定义字符设备结构体chr_dev
struct class *class_led;				 //保存创建的类
struct device *device;					 // 保存创建的设备

/*字符设备操作函数集*/
static struct file_operations led_chr_dev_fops =
	{
		.owner = THIS_MODULE,
		.open = led_chr_dev_open,
		.write = led_chr_dev_write,
};
  • 第四部分:第一步:使用“alloc_chrdev_region”动态申请主设备号,并保存到“led_devno”结构体中。
  • 第四部分:第二步:使用“cdev_init”初始化字符设备。
  • 第四部分:第三步:使用“cdev_add”将字符设备添加到系统。如果需要驱动自动创建设备节点,则还需要创建类和设备。
  • 第四部分:第四步:使用“class_create”函数创建类。
  • 第四部分:第五步:使用“device_create”创建设备,其中参数“DEV_NAME”用于指定设备节点名,这个名字在应用程序中会用到。

如果驱动和设备树节点完成匹配,系统会自动执行.probe函数,从上方代码可知,.probe函数完成了RGB灯的初始化和字符设备的创建。下一步只需要在字符设备的操作函数集中控制RGB灯即可。

实现字符设备操作函数集

这里仅仅实现了字符设备操作函数集中的.write函数,.write函数根据收到的信息控制RGB灯的亮、灭。代码如下:

/*字符设备操作函数集,open函数*/
static int led_chr_dev_open(struct inode *inode, struct file *filp)
{
	printk("\n open form driver \n");
	return 0;
}

/*字符设备操作函数集,write函数*/
static ssize_t led_chr_dev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{

	unsigned int register_data = 0; //暂存读取得到的寄存器数据
	unsigned char write_data; //用于保存接收到的数据

/*-----------------------------第一部分---------------------------------------------*/
	int error = copy_from_user(&write_data, buf, cnt);
	if (error < 0)
	{
		return -1;
	}

/*-----------------------------第二部分---------------------------------------------*/
	/*设置 GPIO1_04 输出电平*/
	if (write_data & 0x04)
	{
		register_data = readl(led_red.virtual_DR);
		register_data &= ~(0x01 << 4);
		writel(register_data, led_red.virtual_DR); // GPIO1_04引脚输出低电平,红灯亮
	}
	else
	{
		register_data = readl(led_red.virtual_DR);
		register_data |= (0x01 << 4);
		writel(register_data, led_red.virtual_DR); // GPIO1_04引脚输出高电平,红灯灭
	}

	/*设置 GPIO4_20 输出电平*/
	if (write_data & 0x02)
	{
		register_data = readl(led_green.virtual_DR);
		register_data &= ~(0x01 << 20);
		writel(register_data, led_green.virtual_DR); // GPIO4_20引脚输出低电平,绿灯亮
	}
	else
	{
		register_data = readl(led_green.virtual_DR);
		register_data |= (0x01 << 20);
		writel(register_data, led_green.virtual_DR); // GPIO4_20引脚输出高电平,绿灯灭
	}

	/*设置 GPIO4_19 输出电平*/
	if (write_data & 0x01)
	{
		register_data = readl(led_blue.virtual_DR);
		register_data &= ~(0x01 << 19);
		writel(register_data, led_blue.virtual_DR); //GPIO4_19引脚输出低电平,蓝灯亮
	}
	else
	{
		register_data = readl(led_blue.virtual_DR);
		register_data |= (0x01 << 19);
		writel(register_data, led_blue.virtual_DR); //GPIO4_19引脚输出高电平,蓝灯灭
	}

	return 0;
}

/*字符设备操作函数集*/
static struct file_operations led_chr_dev_fops =
	{
		.owner = THIS_MODULE,
		.open = led_chr_dev_open,
		.write = led_chr_dev_write,
};

这里仅实现了两个字符设备操作函数,open对应led_chr_dev_open函数这是一个空函数。.write对应led_chr_dev_write函数,这两个函数接收应用程序传回的命令,根据命令控制RGB三个灯的亮、灭。

  • 第一部分:使用copy_from_user函数将用户空间的数据拷贝到内核空间。这里传递的数据是一个无符号整型数据。
  • 第二部分:解析命令,如何解析由我们自己决定。本例中使用数字的后三位从高到低依次对应红灯、绿灯、蓝灯,对应位是1则亮灯否则熄灭。例如0x03表示红灯灭,绿灯和蓝灯亮。0x07表示全亮。

3.2.3 编写测试应用程序

        在驱动程序中采用自动创建设备节点的方式创建了字符设备的设备节点文件,文件名可自定义,写测试应用程序时记得文件名即可。测试程序很简单,源码如下所示。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{
    printf("led_tiny test\n");

/*------------------------------第一部分------------------------------------*/
    /*判断输入的命令是否合法*/
    if(argc != 2)
    {
        printf(" commend error ! \n");
        return -1;
    }

/*------------------------------第二部分------------------------------------*/
    /*打开文件*/
    int fd = open("/dev/rgb_led", O_RDWR);
    if(fd < 0)
    {
		printf("open file : %s failed !\n", argv[0]);
		return -1;
	}

/*------------------------------第三部分------------------------------------*/
    unsigned char commend = atoi(argv[1]);  //将受到的命令值转化为数字;

/*------------------------------第四部分------------------------------------*/
    /*写入命令*/
    int error = write(fd,&commend,sizeof(commend));
    if(error < 0)
    {
        printf("write file error! \n");
        close(fd);
        /*判断是否关闭成功*/
    }
/*------------------------------第五部分------------------------------------*/
    /*关闭文件*/
    error = close(fd);
    if(error < 0)
    {
        printf("close file error! \n");
    }
    
    return 0;
}
  • 第一部分:简单判断输入是否合法,运行本测试应用程序时argc应该位2。它由应用程序文件名和命令组成,例如“/.test_app<命令值>”。
  • 第二部分:打开设备文件。
  • 第三部分:将终端输入的命令值转化位数字,最终使用write函数。
  • 第四部分:关闭设备文件。

四、编译驱动程序

4.1 编译设备树

将rgb_led节点添加到设备树中,并在内核源码目录执行如下命令。

make ARCH = arm CROSS_COMPILE = arm-linux-gnueabihf- npi_v7_defconfig
make ARCH = arm -j4 CROSS_COMPILE = arm-linux-gnueabihf- dtbs
最终会在内核源码/arch/arm/boot/dts下生成imx6ull-mmc-npi.dtb这个设备树文件。

4.2 编译驱动和应用程序

执行make命令,Makefile和前面大致相同。最终会生成rgb_led.ko和test_app应用程序

五、程序运行结果

        在板卡上的部分GPIO可能会被系统占用,在使用前请根据需要修改/boot/uEndv.txt文件,可注释掉某些设备树插件的加载,重启系统,释放相应的GPIO引脚。

如若运行代码时出现“Devices or resources busy”或者运行代码卡死等等现象,请按照上述情况检查并按上述步骤操作。

5.1 实验操作

将设备树、驱动程序和应用程序拷贝到开发板中。替换掉原来的设备树 /usr/lib/linux-image-4.19.35-imx6/imx6ull-mmc-npi.dtb,并输入sudo reboot命令重启开发板。

执行如下命令加载驱动:

命令:

sudo insmod ./rgb_led.ko

驱动加载成功后直接运行应用程序如下所示。

命令:./test_app<命令>

执行结果如下:

与此同时,还会看到LED呈现不同颜色的光。

命令是一个“unsigned char”型数据,只有后三位有效,每一位代表一个灯,从高到低依次代表红、绿、蓝,1代表亮,0代表灭。例如命令=4代表亮红灯,命令=7代表三个灯全亮,命令=0代表三个灯全灭。 

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

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

相关文章

openlayers加载离线地图并实现深色地图

问题背景 我们自己一直使用的openlayergeoserver自己发布的地图&#xff0c;使用的是矢量地图。但是由于政府地图大都使用为天地图&#xff0c;所以需要将geoserver的矢量地图更改为天地图&#xff0c;并且依旧是搭配openlayers来使用。 解决步骤 一&#xff1a;加载离线地图&a…

ubuntu20.04安装nginx一系列问题

当初做一个项目的时候给linux装nginx遇到了很多问题&#xff0c;当初边搞边记录&#xff0c;这两天翻看项目笔记的时候找出来了&#xff0c;就把这一部分分享出来给大家看看 ubuntu20.04 LTS 安装yum无法定位软件包 备份原来的软件源 sudo cp /etc/apt/sources.list /etc/ap…

目前软搭建测试的行业现状和前景

软件测试的发展前景和行业现状 1. 软件测试的工资情况 软件测试的方向&#xff1a;功能>>>接口>>>性能>>>自动化>>>测开>>>人生巅峰 功能测试:曾经互联网缺口和软件测试缺口非常大&#xff0c;因此功能测试越来越多。可是20…

Linux-编写一个自己的命令

前言&#xff08;1&#xff09;在Linux中&#xff0c;我们对文件路径进行操作都需要输入命令。那么&#xff0c;有人可能就会有疑惑了&#xff0c;命令是什么东西&#xff1f;我们是否也可以创造出自己的命令呢&#xff1f;答案是可以的。命令本身其实就是可执行文件。但是与普…

使用docker pull 跨系统架构拉取镜像

使用docker pull 跨系统架构拉取镜像使用docker pull 跨系统架构拉取镜像docker hub上找到相应的镜像在个人电脑中的执行拉取镜像命令&#xff1a;执行查看镜像命令&#xff1a;执行检查镜像命令&#xff1a;执行保存镜像命令&#xff1a;使用docker pull 跨系统架构拉取镜像 …

【C语言编译器】03 Linux GCC 初探

一、准备工作 简单介绍&#xff0c;马上出 GCC 系列。本文非常浅显。 Linux系统常用来用作服务器&#xff0c;其中最常用的发行版是CentOS、Ubuntu、Debian等。 尽管很多C语言IDE都有Linux版本&#xff0c;比如VS、CLion的Linux版。但作为服务器的Linux通常没有GUI界面&…

腾讯TIM实现即时通信 v3+ts实践

目录 初始化sdk 功能描述 初始化 准备 SDKAppID 调用初始化接口 监听事件 发送消息 创建消息 创建文本消息 登录登出 功能描述 登录 登出 销毁 登录设置 获取会话列表 功能描述 获取会话列表 获取全量的会话列表 历史消息 功能描述 拉取消息列表 分页拉取…

自动驾驶自主避障概况

文章目录前言1. 自主避障在自动驾驶系统架构中的位置2. 自主避障算法分类2.1 人工势场法&#xff08;APF&#xff09;2.1.1引力势场的构建2.1.2斥力势场的构建2.1.3人工势场法的改进2.2 TEB&#xff08;Timed-Eastic-Band, 定时弹性带&#xff09;2.3 栅格法2.4 向量场直方图(V…

Linux 之 大数据定制篇-shell 编程

文章目录1 为什么要学习 Shell 编程2 Shell 是什么&#xff1f;3 Shell 脚本的执行方式3.1 脚本格式要求3.2 编写第一个 Shell 脚本3.3 脚本的常用执行方式4 Shell 的变量4.1 Shell 变量介绍4.2 shell 变量的定义4.3 shell 变量的定义5 设置环境变量5.1 基本语法5.2 快速入门6 …

【AI绘画】秒级出图 快速生成大师级画作

最近闲来无事&#xff0c;在网上体验了一下各种AI绘画工具。 根据输入的描述语快速生成自己想要的图片&#xff0c;听着还是很不错的&#xff01;想要啥图片就可以生成啥图片&#xff1f;于是&#xff0c;期待满满的搞起来了~ 可是真当体验了一下之后… 这生成的啥呢&#xf…

广泛运用在工业、轨道交通、监狱的ip对讲终端

ip网络对讲系统是不同于传统广播、调频寻址广播和数控广播的产品&#xff0c;它是基于IP数据网络&#xff0c;将音频信号经过数字编码以数据包形式按TCP\IP协议在局域网或广域网上传送&#xff0c;再由终端解码的纯数字化单向&#xff0c;双向及多向音频扩声系统。 本产品是新一…

多表left join 慢sql问题

作为个人记录&#xff0c;后续再填坑a对p是1对多 ,p对llup 1对多SELECTa.id,p.id,t1.id FROMliv_series_product aINNER JOIN liv_product p ON p.id a.product_idLEFT JOIN ( SELECT llup.id, llup.product_id, llup.room_id FROM liv_live_user_product llup WHERE llup.ro…

超声功率放大器原理(超声功率放大器的作用是什么)

超声功率放大器是电子实验室中比较常见的测量仪器&#xff0c;虽然很多工程师频繁使用&#xff0c;但是对于超声功率放大器的了解却不够。下面就让安泰电子来为大家科普超声功率放大器原理和作用的内容。超声功率放大器是什么&#xff1a;超声功率放大器是一种用于提高超声波能…

requests---(2)session简介与自动写博客

目录&#xff1a;导读 session简介 session登录 自动写博客 获取登录cookies 抓取写博客接口 requests自动写博客 写在最后 http协议是无状态的&#xff0c;也就是每个请求都是独立的。那么登录后的一系列动作&#xff0c;都需要用cookie来验证身份是否是登录状态&#…

C++将派生类赋值给基类(向上转型)

1.将派生类对象赋值给基类对象 #include <iostream> using namespace std;//基类 class A{ public:A(int a); public:void display(); public:int m_a; }; A::A(int a): m_a(a){ } void A::display(){cout<<"Class A: m_a"<<m_a<<endl; }//…

一文解读电压放大器(电压放大器原理)

关于电压放大器的科普知识&#xff0c;之前讲过很多&#xff0c;今天为大家汇总一篇文章来详细的讲解电压放大器&#xff0c;希望大家对于电压放大器能有更清晰的认识。电压放大器是什么&#xff1a;电压放大器是一种常用的电子器件&#xff0c;它的主要作用是把输入信号的振幅…

计算机网络-- 分类、体系结构(day03)

计算机网络的分类 计算机网络的性能指标 速率 数据块&#xff08;文件&#xff09;的大小单位是以2^10(1024)为一个级别递增。 例如&#xff1a; 1MB大小的文件&#xff0c;在网速为1Mbps发送的时间需要多少 文件大小的M是2进制来表示的&#xff0c;网速的M为10进制来表示的 …

zabbix4.0-自定义脚本钉钉告警

目录 1、自定义一个钉钉群组 2、自定义脚本告警 3、创建媒体类型 4、为用户指定媒介类型 5、更改触发器表达式进行测试 1、自定义一个钉钉群组 群组里面添加一个自定义机器人 会有一个webhook地址&#xff0c;这个地址要记住 安全设置暂时选择自定义关键词&#xff0c;定义…

第四阶段02-酷鲨商城项目Mybatis相关的配置

14. 添加与Mybatis相关的配置 在每个项目中&#xff0c;当需要使用Mybatis实现数据库编程时&#xff0c;都需要添加2项一次性配置&#xff1a;配置Mapper接口所在的包&#xff08;package&#xff09;、配置XML文件在哪里。 关于配置Mapper接口所在的包&#xff0c;可以&…

BPMN2.0是什么,BPMN能解决企业流程管理中哪些问题?

一、前言&#xff1a; 在任何行业和企业中&#xff0c;一定存在着各式各样的流程&#xff0c;请假流程、报销流程、入职流程、离职流程、出差流程、合同审批流程、出入库流程等等…… 无论是管理者、技术人员还是业务人员&#xff0c;每天肯定也在使用各种流程&#xff0c;但…