当前位置: 首页 > article >正文

利用I2C_bus(I2C总线)为挂接在I2C总线上的设备AP3216C编写驱动程序

前言

关于I2C总线的原理和结构的介绍,请参看我的另一篇博文 https://blog.csdn.net/wenhao_ir/article/details/146405656
在阅读以下内容前,也建议先看一看上面这篇博文。

i2c_driver的实现

完整代码(ap3216c_drv.c)

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/uaccess.h>
#include <linux/fs.h>

static int major = 0;
static struct class *ap3216c_class;
static struct i2c_client *ap3216c_client;

static ssize_t ap3216c_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char kernel_buf[6];
	int val;
	
	if (size != 6)
		return -EINVAL;

	/*注意:下面的函数i2c_smbus_read_word_data并不是由i2c-tools工具集提供的,i2c-tools工具集只提供了用户空间的相关函数。
	  下面的函数i2c_smbus_read_word_data是由Linux内核的I2C子系统提供的,函数定义于Linux内核的i2c-core-smbus.c文件中。
	  配置内核时启用I2C支持,包括 CONFIG_I2C 和 CONFIG_I2C_CHARDEV,那么相关函数就有了。*/	

	val = i2c_smbus_read_word_data(ap3216c_client, 0xA); /* read IR */
	kernel_buf[0] = val & 0xff;
	kernel_buf[1] = (val>>8) & 0xff;
	
	val = i2c_smbus_read_word_data(ap3216c_client, 0xC); /* read 光强 */
	kernel_buf[2] = val & 0xff;
	kernel_buf[3] = (val>>8) & 0xff;

	val = i2c_smbus_read_word_data(ap3216c_client, 0xE); /* read 距离 */
	kernel_buf[4] = val & 0xff;
	kernel_buf[5] = (val>>8) & 0xff;
	
	err = copy_to_user(buf, kernel_buf, size);
	return size;
}


static int ap3216c_open (struct inode *node, struct file *file)
{
	/*注意:下面的函数i2c_smbus_write_byte_data并不是由i2c-tools工具集提供的,i2c-tools工具集只提供了用户空间的相关函数。
	  下面的函数i2c_smbus_write_byte_data是由Linux内核的I2C子系统提供的,函数定义于Linux内核的i2c-core-smbus.c文件中。
	  配置内核时启用I2C支持,包括 CONFIG_I2C 和 CONFIG_I2C_CHARDEV,那么相关函数就有了。*/	

	i2c_smbus_write_byte_data(ap3216c_client, 0, 0x4);
	/* delay for reset */
	mdelay(20);
	i2c_smbus_write_byte_data(ap3216c_client, 0, 0x3);
	mdelay(250);
	return 0;
}


static struct file_operations ap3216c_ops = {
	.owner = THIS_MODULE,
	.open  = ap3216c_open,
	.read  = ap3216c_read,
};

static const struct of_device_id of_match_ids_ap3216c[] = {
	{ .compatible = "lite-on,ap3216c",		.data = NULL },
	{ /* END OF LIST */ },
};

static const struct i2c_device_id ap3216c_ids[] = {
	{ "ap3216c",	(kernel_ulong_t)NULL },
	{ /* END OF LIST */ }
};

static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	ap3216c_client = client;
	
	/* register_chrdev */
	major = register_chrdev(0, "ap3216c", &ap3216c_ops);

	ap3216c_class = class_create(THIS_MODULE, "ap3216c_class");
	device_create(ap3216c_class, NULL, MKDEV(major, 0), NULL, "ap3216c"); /* /dev/ap3216c */

	return 0;
}

static int ap3216c_remove(struct i2c_client *client)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	device_destroy(ap3216c_class, MKDEV(major, 0));
	class_destroy(ap3216c_class);
	
	/* unregister_chrdev */
	unregister_chrdev(major, "ap3216c");

	return 0;
}

static struct i2c_driver i2c_ap3216c_driver = {
	.driver = {
		.name = "ap3216c",
		.of_match_table = of_match_ids_ap3216c,
	},
	.probe = ap3216c_probe,
	.remove = ap3216c_remove,
	.id_table = ap3216c_ids,
};


static int __init i2c_driver_ap3216c_init(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return i2c_add_driver(&i2c_ap3216c_driver);
}
module_init(i2c_driver_ap3216c_init);

static void __exit i2c_driver_ap3216c_exit(void)
{
	i2c_del_driver(&i2c_ap3216c_driver);
}
module_exit(i2c_driver_ap3216c_exit);

MODULE_AUTHOR("suwenhao");
MODULE_LICENSE("GPL");

代码分析说明

这个代码当你看了下面几篇博文的相关内容后就没啥好说的。
https://blog.csdn.net/wenhao_ir/article/details/146405656【这篇博文全部看】

https://blog.csdn.net/wenhao_ir/article/details/146319007 【搜索“使用读写命令行对I2C设备AP3216C进行读写操作”】

https://blog.csdn.net/wenhao_ir/article/details/146361457 【这篇博文全部看】

值得注意的两个问题

第1个值得注意的地方:
代码中的函数i2c_smbus_read_word_datai2c_smbus_write_byte_data 不是i2c-tools 提供的,而是 Linux 内核 I2C 子系统 的一部分,由 内核的 I2C 核心代码 实现。这两个函数的定义位于 Linux 内核i2c-core-smbus.c 文件中,

第2个值得注意的地方:
i2c_driver中并没有指定是哪条I2C总线,那么它怎么知道操作哪条I2C总线?
答案很简单,因为在i2c_client中会含有I2C设备挂接于哪条I2C总线的信息啦。你往这篇博文后面看i2c_client的实现就知道了。配置内核时启用I2C支持,包括 CONFIG_I2C 和 CONFIG_I2C_CHARDEV,那么相关函数就有了。

Makfile文件的编写

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88/

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= ap3216c_drv.o

交叉编译生成ap3216c_drv.ko

将文件ap3216c_drv.cMakefile复制到Ubuntu的相关目录中:
在这里插入图片描述
然后执行make,从而生成ap3216c_drv.ko文件
在这里插入图片描述

ap3216c_drv.ko复制到网络文件目录中

在这里插入图片描述

加载ap3216c_drv.ko

打开串口终端→打开开发板→挂载网络文件系统

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt

把内核打印信息打开:

echo "7 4 1 7" > /proc/sys/kernel/printk

加载模块ap3216c_drv.ko

cd /mnt/i2c_bus_driver/
insmod ap3216c_drv.ko

在这里插入图片描述

[   38.335052] ap3216c_drv: loading out-of-tree module taints kernel.
[   38.346271] /home/book/mycode/i2c_bus_driver/ap3216c_drv.c i2c_driver_ap3216c_init 116

可见,成功加载了模块,对应的内核打印信息语句如下:
在这里插入图片描述

用户空间的测试程序的实现

完整代码(ap3216c_drv_test.c)

这个测试程序就很简单了,代码如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/*
 */
int main(int argc, char **argv)
{
	int fd;
	char buf[6];
	int len;
	

	/* 2. 打开文件 */
	fd = open("/dev/ap3216c", O_RDWR);
	if (fd == -1)
	{
		printf("can not open file /dev/ap3216c\n");
		return -1;
	}

	len = read(fd, buf, 6);		
	printf("APP read : ");
	for (len = 0; len < 6; len++)
		printf("%02x ", buf[len]);
	printf("\n");
	
	close(fd);
	
	return 0;
}

就不多说了,直接复制到Ubuntu中,用下面的交叉编译命令编译就是了:

交叉编译

arm-buildroot-linux-gnueabihf-gcc -o ap3216c_drv_test ap3216c_drv_test.c

在这里插入图片描述

ap3216c_drv_test复制到网络文件系统目录中

在这里插入图片描述

运行测试程序

具体的测试程序的运行见下一个目录``i2c_client的实现

i2c_client的实现和生成

方式一:直接使用echo命令创建

要读懂下面这两条命令,可以先看看我之前写的博文 https://blog.csdn.net/wenhao_ir/article/details/146319007 中对芯片AP3216C的介绍,搜索关键词“使用读写命令行对I2C设备AP3216C进行读写操作”

// 在i2c-0下创建i2c_client
echo ap3216c 0x1e > /sys/bus/i2c/devices/i2c-0/new_device

// 删除i2c_client
 0x1e > /sys/bus/i2c/devices/i2c-0/delete_device

命令中,0x1e是AP3216C的地址。

从上面的命令中,我们可以看到,一个i2c_client需包含三个关键信息,第1个是设备的名字(name)、第2个是I2C设备的地址,第3个是该I2C设备挂在哪条I2C总线上。

我们这里来实际测试一下,按上面i2c_driver实现中记录的方法加载好模块ap3216c_drv.ko.

然后我们用echo命令创建i2c_client,运行下面的命令:

echo ap3216c 0x1e > /sys/bus/i2c/devices/i2c-0/new_device

在这里插入图片描述

[ 1043.137190] /home/book/mycode/i2c_bus_driver/ap3216c_drv.c ap3216c_probe 79
[ 1043.167269] i2c i2c-0: new_device: Instantiated device ap3216c at 0x1e

从画红线的内核打印信息来看,i2c_client生成成功并与i2c_driver匹配成功了,相关的打印输出语句代码如下:
在这里插入图片描述
我们可以用下面的命令看下有没有相关的驱动程序加载于内核中:

cat /proc/devices

有了,如下图所示:
在这里插入图片描述
我们还可以看下有没有设备文件生成:

ls /dev/ap3216*

有了,如下图所示:
在这里插入图片描述
接下来,我们运行上面已经编译生成好的测试程序,看下能否达到预期效果。

cd /mnt/i2c_bus_test/
./ap3216c_drv_test

在这里插入图片描述
然后我们用手电去照射芯片AP3216C,再运行测试程序,看下有没有数据变化:
在这里插入图片描述
可见,光强数据有变化了,具体值的解析就不去赘述了,参考 https://blog.csdn.net/wenhao_ir/article/details/146319007 【搜索“第03条命令-读取光照强度数据”】

然后我再用手指去接近芯片AP3216C,再运行测试程序,看下有没有数据变化:
在这里插入图片描述
可见,距离数据值也有变化了,具体值的解析就不去赘述了,参考 https://blog.csdn.net/wenhao_ir/article/details/146319007 【搜索“第04条命令-读取距离值”】

接下来,我们删除掉刚才用命令行创建的i2c_client,用下面的命令:

echo 0x1e > /sys/bus/i2c/devices/i2c-0/delete_device

在这里插入图片描述
从运行结果来看,删除i2c_client的操作触发了ap3216c_drv.c中的与probe函数相对应的ap3216c_remove的执行,如下图所示:
在这里插入图片描述
此时我们发现相应的驱动程序和设备文件没有了,如下所示:

cat /proc/devices

在这里插入图片描述
major号为245的驱动程序不见了。

ls /dev/ap3216*

在这里插入图片描述

方式二:利用代码实现i2c_client

完整代码

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>

static struct i2c_client *ap3216c_client;

static int __init i2c_client_ap3216c_init(void)
{
	struct i2c_adapter *adapter;

	static struct i2c_board_info board_info = {
	  I2C_BOARD_INFO("ap3216c", 0x1e), // 填写I2C设备的信息,注意I2C_BOARD_INFO是一个宏
	};

	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* register I2C device */
	adapter = i2c_get_adapter(0); //  获取0号I2C适配器(控制器)
	ap3216c_client = i2c_new_device(adapter, &board_info); // 在 I2C-0 总线上创建一个新的 I2C 设备
	i2c_put_adapter(adapter); // 释放掉适配器资源
	return 0;
}


static void __exit i2c_client_ap3216c_exit(void)
{
	i2c_unregister_device(ap3216c_client);
}

module_init(i2c_client_ap3216c_init);

module_exit(i2c_client_ap3216c_exit);

MODULE_AUTHOR("suwenhao");
MODULE_LICENSE("GPL");

代码分析

代码虽然比较简单,但还是要讲解,由于关键代码都在函数i2c_client_ap3216c_init里,所以这里就分析函数i2c_client_ap3216c_init,分析如下:

函数i2c_client_ap3216c_init的代码如下:

static int __init i2c_client_ap3216c_init(void)
{
	struct i2c_adapter *adapter;

	static struct i2c_board_info board_info = {
	  I2C_BOARD_INFO("ap3216c", 0x1e), // 填写I2C设备的信息,注意I2C_BOARD_INFO是一个宏
	};

	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* register I2C device */
	adapter = i2c_get_adapter(0); //  获取0号I2C适配器(控制器)
	ap3216c_client = i2c_new_device(adapter, &board_info); // 在 I2C-0 总线上创建一个新的 I2C 设备
	i2c_put_adapter(adapter); // 释放掉适配器资源
	return 0;
}

这段代码的作用是在 Linux 内核的 I2C 框架 下,手动创建一个 i2c_client 设备,并将其注册到 I2C 总线中。它通常用于 没有设备树(Device Tree)或 ACPI 方式描述 I2C 设备的情况下。

1. 定义 i2c_board_info

static struct i2c_board_info board_info = {
    I2C_BOARD_INFO("ap3216c", 0x1e),
};
  • i2c_board_info 结构体用于描述 I2C 设备的信息,它用于手动注册 I2C 设备。
  • I2C_BOARD_INFO("ap3216c", 0x1e) 宏展开后等效于:
    struct i2c_board_info board_info = {
        .type = "ap3216c",
        .addr = 0x1e,
    };
    
    • "ap3216c":这个设备的 I2C 驱动名称,必须与 i2c_driver 里的 .name 一致,才能正确匹配驱动。
    • 0x1e:I2C 设备的地址(7 位地址)。

2. 获取 I2C 适配器

adapter = i2c_get_adapter(0);
  • i2c_get_adapter(bus_num) 用于 获取 I2C 总线适配器
  • bus_num = 0 代表获取 I2C-0 总线上的适配器
  • 这个适配器代表 I2C 控制器(I2C master)。

3. 创建 i2c_client 设备

ap3216c_client = i2c_new_device(adapter, &board_info);
  • i2c_new_device(adapter, &board_info) 作用:
    1. 在 I2C-0 总线上创建一个新的 I2C 设备(i2c_client
    2. 设备地址是 0x1e,设备名是 "ap3216c"
    3. 这个 i2c_client 结构体会被自动注册,并与 i2c_driver 进行匹配
      • 如果有 ap3216c 这个 i2c_driver,那么 i2c-core 就会调用它的 probe() 函数。

4. 释放 I2C 适配器

i2c_put_adapter(adapter);
  • i2c_put_adapter(adapter) 释放 之前 i2c_get_adapter() 获取的适配器,防止资源泄漏。

Makefile文件的编写

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88/

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= ap3216c_drv.o

交叉编译生成ap3216c_client.ko

复制文件ap3216c_client.c、Makefile到Ubuntu中:
在这里插入图片描述
然后make生成ap3216c_client.ko

make

在这里插入图片描述

ap3216c_client.ko复制到网络文件目录中

在这里插入图片描述

加载生成的ap3216c_client.ko

首先确认i2c_driverap3216c_drv.ko加载好了。

然后加载ap3216c_client.ko

cd /mnt/i2c_bus_client
insmod ap3216c_client.ko

在这里插入图片描述
可见加载后触发了i2c_driver中的probe函数。我们接下来看下major和设备文件被创建没有。

cat /proc/devices

在这里插入图片描述
可见 major被创建了。

ls /dev/ap3216*

在这里插入图片描述
可见设备文件也有了。

运行用户空间的测试程序

接下来,我们运行上面已经编译生成好的测试程序,看下能否达到预期效果。

cd /mnt/i2c_bus_test/
./ap3216c_drv_test

在这里插入图片描述
然后我们用手电去照射芯片AP3216C,再运行测试程序,看下有没有数据变化:
在这里插入图片描述

可见,光强数据有变化了,具体值的解析就不去赘述了,参考 https://blog.csdn.net/wenhao_ir/article/details/146319007 【搜索“第03条命令-读取光照强度数据”】

然后我再用手指去接近芯片AP3216C,再运行测试程序,看下有没有数据变化:
在这里插入图片描述
可见,距离数据值也有变化了,具体值的解析就不去赘述了,参考 https://blog.csdn.net/wenhao_ir/article/details/146319007 【搜索“第04条命令-读取距离值”】

卸载掉i2c_client

rmmod ap3216c_client

在这里插入图片描述
可见,对模块ap3216c_client.ko的卸载会触发i2c_driver中的remove函数,相关的代码如下:
在这里插入图片描述
此时我们发现相应的驱动程序和设备文件没有了,如下所示:

cat /proc/devices

在这里插入图片描述
major号为245的驱动程序不见了。

ls /dev/ap3216*

在这里插入图片描述

提问:如果某条I2C总线上有多个设备需要扫描怎么办?

比如我的I2C总线上外挂有多种测量光照和距离的芯片作为冗余备份,我可不可以进行扫描然后选择一个可用设备,这是可以的,代码如下:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>


static struct i2c_client *ap3216c_client;

/* Addresses to scan */
static const unsigned short normal_i2c[] = {
	0x1e, I2C_CLIENT_END
};

static int __init i2c_client_ap3216c_init(void)
{
	struct i2c_adapter *adapter;
	struct i2c_board_info board_info;
	memset(&board_info, 0, sizeof(struct i2c_board_info));
	strscpy(board_info.type, "ap3216c", sizeof(board_info.type));
	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* register I2C device */
	adapter = i2c_get_adapter(0);
	ap3216c_client = i2c_new_probed_device(adapter, &board_info,
						   normal_i2c, NULL);
	i2c_put_adapter(adapter);
	return 0;
}

static void __exit i2c_client_ap3216c_exit(void)
{
	i2c_unregister_device(ap3216c_client);
}

module_init(i2c_client_ap3216c_init);

module_exit(i2c_client_ap3216c_exit);

MODULE_AUTHOR("suwenhao");
MODULE_LICENSE("GPL");

代码就不去分析测试了,以后要用的时候再来研究。

方式三:利用设备树生成i2c_client

修改设备树文件

我们只需要把I2C设备加到设备树中的I2C控制器节点下面即可。

在博文 https://blog.csdn.net/wenhao_ir/article/details/146393239 中我已经列举出了I2C控制器在Linux的设备树文件中的描述。

现在我们只需要把下面的节点信息:

&i2c1 {
		ap3216c@1e {
			compatible = "lite-on,ap3216c";
			reg = <0x1e>;
		};
};

加到设备树文件100ask_imx6ull-14x14.dts的引用结构&i2c1中去即可,我们先把内核源码中的设备树文件复制到Windows的VScode工程目录中。
设备树文件100ask_imx6ull-14x14.dts在Ubuntu中的路径如下:

/home/book/100ask_imx6ull-sdk/Linux-4.9.88/arch/arm/boot/dts/100ask_imx6ull-14x14.dts

在这里插入图片描述
然后把Ubuntu中的设备树文件100ask_imx6ull-14x14.dts重命名为100ask_imx6ull-14x14.dts.bak2_把AP3216C加入第1个IC2控制器的节点下前的备份
同时把100ask_imx6ull-14x14.dtb也重名为100ask_imx6ull-14x14.dtb.bak2_把AP3216C加入第1个IC2控制器的节点下前的备份
在这里插入图片描述
接着把复制到Windows的VScode工程目录中的设备树文件100ask_imx6ull-14x14.dts打开,然后搜索“&i2c1”
在这里插入图片描述
在它里面加入子节点ap3216c@1e的信息:

&i2c1 {
		ap3216c@1e {
			compatible = "lite-on,ap3216c";
			reg = <0x1e>;
		};
};

加好之下如下图所示:
在这里插入图片描述

编译修改好的设备树文件

然后再把修改好的设备树文件100ask_imx6ull-14x14.dts复制到Ubuntu中:
在这里插入图片描述
然后进入内核编译出新的设备树文件:

cd /home/book/100ask_imx6ull-sdk/Linux-4.9.88
make dtbs

在这里插入图片描述
在这里插入图片描述

更新开发析上的设备树文件

把新生成的dtb文件复制到NFS网络系统目录中:
在这里插入图片描述
打开串口终端→打开开发板→挂载网络文件系统:

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt

然后用下面的命令覆盖掉之前的设备树文件:

cp /mnt/100ask_imx6ull-14x14.dtb /boot/

在这里插入图片描述

重启开发板,看下 i2c_client是否生成

启动之后执行下面的命令查看下有没有i2c_client生成:

cd /sys/bus/i2c/devices/i2c-0/

ls

在这里插入图片描述
可见,有了,目录名字为0-001e,我们进入目录0-001e看下里面有什么:

cd 0-001e/

ls

在这里插入图片描述
我们看下名字为name的文件里的内容:

cat name

在这里插入图片描述
可见,这个 i2c_client的名字为ap3216c

装载驱动程序

装载驱动程序之前还是把内核打印信息打开:

echo "7 4 1 7" > /proc/sys/kernel/printk

然后挂载网络文件系统:

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt

然后加载我们之前编译生成的ap3216c_drv.ko

cd /mnt/i2c_bus_driver/
insmod ap3216c_drv.ko

在这里插入图片描述
可见,在加载ap3216c_drv.ko时,由于内核已经存在能够匹配成功的i2c_client,所以匹配成功,然后调用了probe函数,从而生成了相应的major和设备文件:

cat /proc/devices

在这里插入图片描述

ls /dev/ap3216*

在这里插入图片描述
接下来我们就可以运行测试程序去测试了。

运行用户空间的测试程序

接下来,我们运行上面已经编译生成好的测试程序,看下能否达到预期效果。

cd /mnt/i2c_bus_test/
./ap3216c_drv_test

在这里插入图片描述
然后我们用手电去照射芯片AP3216C,再运行测试程序,看下有没有数据变化:
在这里插入图片描述

可见,光强数据有变化了,具体值的解析就不去赘述了,参考 https://blog.csdn.net/wenhao_ir/article/details/146319007 【搜索“第03条命令-读取光照强度数据”】

然后我再用手指去接近芯片AP3216C,再运行测试程序,看下有没有数据变化:
在这里插入图片描述
可见,距离数据值也有变化了,具体值的解析就不去赘述了,参考 https://blog.csdn.net/wenhao_ir/article/details/146319007 【搜索“第04条命令-读取距离值”】

看能否正常卸载掉ap3216c_drv.ko

rmmod ap3216c_drv

在这里插入图片描述
可见,释放掉i2c_driver结构体是会触发i2c_driver结构体中的成员函数remove,相关的调用触发关系如下图所示:

在这里插入图片描述
此时我们发现相应的驱动程序和设备文件没有了,如下所示:

cat /proc/devices

在这里插入图片描述
major号为245的驱动程序不见了。

ls /dev/ap3216*

在这里插入图片描述

附相关文件

分别编译好的三个工程文件及源码

如下图所示:
在这里插入图片描述
https://pan.baidu.com/s/1_8PmddBf53rtAvLK6rTfVA?pwd=r8sk

没有进行分割的工程源码(包括Makefile文件、设备树源码)

在这里插入图片描述
https://pan.baidu.com/s/1BQQOXvvs5XzE5LzvQQbn6g?pwd=hrac

设备树源码(dts)和编译好的二进制文件(dtb)

在这里插入图片描述
https://pan.baidu.com/s/1oUyO6bkJt8L8GL-9jgfyhg?pwd=bibk


http://www.kler.cn/a/596419.html

相关文章:

  • 大数据环境搭建
  • 利用 QOpenGLWidget 实现 GPU 加速视频帧绘制
  • 138. 随机链表的复制
  • 网络华为HCIA+HCIP IPv6
  • 【工具变量】中国各地级市是否属于“信息惠民国家试点城市”匹配数据(2010-2024年)
  • springmvc中如何自定义入参注解并自动注入值
  • 遨游科普|三防平板是什么?哪些领域能用到?
  • 前端Wind CSS面试题及参考答案
  • c++ XML库用法
  • 基于STC89C51单片机的储缆卷筒控制器及其结构设计
  • CCBCISCN复盘
  • 【Linux系统】—— 进程概念
  • 附——教6
  • Parsing error: Unexpected token, expected “,“
  • 平台与架构:深度解析与开发实践
  • 从零开始使用 Ansible 自动化部署 SpringBoot Web 应用(含 MySQL、Redis、Vue、Nginx)
  • 反转函数的reverse和reversed的区别
  • 虚拟路由与单页应用(SPA):详解
  • centos7安装单机kafka
  • Elasticsearch:可配置的推理 API 端点分块设置