Linux I2C设备驱动基本规范

2023-10-31

不同于单片机驱动开发,即使是简单的I2C设备驱动程序,如果要在Linux上实现同种功能的驱动程序,事情也会变的复杂起来。对于初学者而言,主要的困难就是不知道如何使用Linux现有的驱动框架,去完成驱动程序的开发。I2C设备驱动,相对来说比较简单,但由于Linux大部分设备驱动框架十分的类似,所以,通过对于I2C驱动框架的学习,可以作为继续深入Linux其他设备驱动框架的基础。

学习一项技术的最好方式就是去应用它,所以为了更为高效的学习,本文最后结合一个i2c设备驱动实例,来分析如何从零实现一个驱动程序。

程序框架

本文主要为了学习如何在Linux上实现一个I2C设备驱动,而对于具体的I2C驱动架构不会详细的展开。一个完整的I2C设备驱动主要包括如下几个部分:

  1. 定义i2c_driver数据结构
  2. 注册i2c_driver
  3. 实现设备访问

具体实现上述几部分时,会用到Linux系统I2C相关的很多数据结构和接口,下面简要介绍一下主要的数据结构和接口。

数据结构

struct i2c_driver

struct i2c_driver代表一个I2C设备驱动实体。其主要的数据成员如下:

struct i2c_driver {
	... 

	/* Standard driver model interfaces */
	int (*probe)(struct i2c_client *client, const struct i2c_device_id *id); <-----------(1)
	int (*remove)(struct i2c_client *client);								 <-----------(2)
	...
	
	struct device_driver driver;											 <-----------(3)
	const struct i2c_device_id *id_table;									 <-----------(4)
};
  • (1) 设备与驱动程序匹配之后,会调用该probe接口完成设备的初始化和注册。
  • (2) 卸载设备驱动时,会调用该接口完成设备注销和相关资源的释放。
  • (3) Linux设备驱动抽象结构。
  • (4) i2c设备id表,驱动程序中根据具体的设备ID来区分不同的设备。依次来达到兼容同种类型,不同型号的设备。

这里还需要进一步说一下struct device_driver结构。

struct device_driver {
		const char	   *name;							<-----------(1)
		const struct of_device_id  *of_match_table;		<-----------(2)
};
  • (1) 设备驱动名称,Linux内核未支持DeviceTree之前,设备和驱动程序需要根据name进行匹配。
  • (2) 设备和驱动匹配类型表,设备驱动程序需要定义其支持的设备类型,并初始化该of_match_table。

struct i2c_device

struct i2c_client代表一个具体的I2C设备实体。其主要数据成员如下:

struct i2c_client {
	unsigned short addr;          <-----------(1)               
	char name[I2C_NAME_SIZE];	  <-----------(2)
	struct i2c_adapter *adapter;  <-----------(3)
};
  • (1) 设备芯片通信地址,默认为7bit,保存在addr的低7bit。
  • (2)设备名称。
  • (3) I2C设备所依赖的I2C适配器,该适配器用于完成具体的I2C物理信号通信。

struct i2c_device_id

struct i2c_device_id代表一种具体的i2c设备类型,设备与驱动匹配之后,会确定具体的设备类型。其数据成员如下:

struct i2c_device_id {
	char name[I2C_NAME_SIZE];                                     <-----------(1)      
	kernel_ulong_t driver_data;	/* Data private to the driver */  <-----------(2)
};
  • (1)设备类型名称。
  • (2)设备私有数据,数据类型为long,可以指向一个具体的整型,也可以指向一个指针。

struct i2c_adapter

struct i2c_adapter代表具体的I2C控制器,其完成I2C的物理信号通信。对于一个设备驱动,一般不会涉及到该数据结构,待到学习I2C架构时,再进行详细的分析。

主要API

要实现I2C设备驱动主要使用到三个API,其中两个probe和remove,在介绍struct i2c_driver时进行了简单的介绍。另外一个module_i2c_driver,其用于向系统注册一个i2c驱动程序。

probe

设备与驱动程序匹配之后,会调用该probe接口完成设备的初始化和注册。

  • 设备初始化

    具体到每个I2C设备芯片,一般都会有一些参数,I2C设备驱动程序会将这些参数封装成结构,然后,在设备初始化阶段完成这些参数的初始化设置。对于设备的初始化配置,一般来源于设备的DTS配置,下面介绍DTS配置时,会具体介绍到。

  • 设备注册

    每个I2C设备最终都会绑定到一种具体的Linux设备上,比如,RTC设备,EEROM设备,IIO设备等。设备注册完成的任务就是将该I2C设备通过具体的设备注册接口注册到系统中。
    这个说的可能比较绕,举个例子,比如,我们编写的这个I2C驱动,用于驱动一个RTC设备,那我们就需要调用devm_rtc_device_register接口进行设备注册,又比如,我们编写一个基于IIO的adc设备驱动,那我们就需要调用iio_device_register接口进行设备注册。

remove

卸载设备驱动时,系统会调用该接口完成设备的注销和相关资源的释放。

module_i2c_drvier

#define module_i2c_driver(__i2c_driver) \
module_driver(__i2c_driver, i2c_add_driver, \
	i2c_del_driver)

module_i2c_driver用于将I2C设备驱动注册到系统,其中,i2c_add_driver和i2c_del_register用于完成驱动的添加和删除。

通信API

I2C设备驱动与设备进行通信时,有两种方式可供选择:(1)基于i2c_msg方式;(2)基于SMbus方式。

i2c_msg

i2c_msg可以作为I2C传输的一个单元进行使用,通过将通信数据封装到i2c_msg中,之后再通过i2c_transfer完成驱动程序与设备的I2C通信。

struct i2c_msg的定义如下:

struct i2c_msg {
__u16 addr;	/* slave address			*/
__u16 flags;
#define I2C_M_RD		0x0001	/* read data, from slave to master */
					/* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN		0x0010	/* this is a ten bit chip address */
#define I2C_M_DMA_SAFE		0x0200	/* the buffer of this message is DMA safe */
					/* makes only sense in kernelspace */
					/* userspace buffers are copied anyway */
#define I2C_M_RECV_LEN		0x0400	/* length will be first received byte */
#define I2C_M_NO_RD_ACK		0x0800	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK	0x1000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR	0x2000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART		0x4000	/* if I2C_FUNC_NOSTART */
#define I2C_M_STOP		0x8000	/* if I2C_FUNC_PROTOCOL_MANGLING */
	__u16 len;		/* msg length				*/
	__u8 *buf;		/* pointer to msg data			*/
};

下面展示了一个读取寄存reg1上数据的示例。

struct i2c_msg msg[2];

msg[0].addr = client->addr;
msg[0].flags = 0;//写
msg[0].len = 1;
msg[0].buf = &reg1;

msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;//读
msg[1].len   = sizeof(buf);
msg[1].buf   = &buf[0];

i2c_transfer(client->adapeter, msg, 2);

上面定义了两个msg,第一个msg定义了将要读取的设备寄存器,第二个msg用于读取该寄存器中的数据。

SMbus

SMbus是Intel基于I2C推出的一种通用的通信协议(System Management Bus),其可以认为是I2C的通信子集,其定义了一套I2C主-从设备之间通信的时序。SMbus与I2C的关系,可以类比与网络通信中的HTTP和TCP的关系,I2C提供的基本的通信规则,其上可以跑的是裸数据,而SMbus规定了数据的格式。Linux系统的I2C通信架构提供了关于SMbus的支持,在支持SMbus的适配器和I2C设备之间可以使用SMbus协议进行通信。具体的SMbus协议可以参考Linux内核文档。

SMbus提供丰富的通信接口,用于传输单字节、双字节、字节数据数组等数据单元。

  1. 单字节数据传输:

     extern s32 i2c_smbus_read_byte(const struct i2c_client *client);
     extern s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value);
     extern s32 i2c_smbus_read_byte_data(const struct i2c_client *client,
     				    u8 command);
     extern s32 i2c_smbus_write_byte_data(const struct i2c_client *client,
     				     u8 command, u8 value);
    
  2. 双字节数据传输:

     extern s32 i2c_smbus_read_word_data(const struct i2c_client *client,
     				    u8 command);
     extern s32 i2c_smbus_write_word_data(const struct i2c_client *client,
     				     u8 command, u16 value);
    
  3. 字节数组数据传输:

     extern s32 i2c_smbus_read_block_data(const struct i2c_client *client,
     		     u8 command, u8 *values);
     extern s32 i2c_smbus_write_block_data(const struct i2c_client *client,
     				      u8 command, u8 length, const u8 *values);
     /* Returns the number of read bytes */
     extern s32 i2c_smbus_read_i2c_block_data(const struct i2c_client *client,
     					 u8 command, u8 length, u8 *values);
     extern s32 i2c_smbus_write_i2c_block_data(const struct i2c_client *client,
     					  u8 command, u8 length,
     					  const u8 *values);
    

几点注意事项:

  1. command代表具体的设备寄存器。

  2. Linux推荐尽可能的使用SMbus协议与设备进行I2C通信。

  3. 在使用SMbus进行通信之前,需要检查当前的适配器是否支持需要的SMbus操作,比如:

     if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA
     	| I2C_FUNC_SMBUS_I2C_BLOCK)) {
     	dev_err(&adapter->dev, "doesn't support required functionality\n");
     	return -EIO;
     }
    

    上面这段代码用于检查当前的adapter是否支持:I2C_FUNC_SMBUS_BYTE_DATA和I2C_FUNC_SMBUS_I2C_BLOCK这两项操作,如果不支持,返回EIO错误。

设备访问

用户空间访问I2C设备的方式各不相同,具体需要依据I2C设备的类型而定,比如,RTC设备通过rtc_class_ops 接口访问,ads1015通过sensor_device_attribute访问。

每个I2C适配器在/dev目录下都有一个对应的设备文件,我们通过这个设备文件直接访问挂接在该适配器之下的设备。

DTS配置

设备驱动程序编写完成之后,具体设备需要在DTS中定义其挂接的适配器,并配置设备的通信地址等信息,下面就是一个典型的I2C设备配置信息。

&i2c1 {                                                                                                                                                                                                             
        clock-frequency = <100000>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_i2c1>;
        status = "okay";
  
    rx8010:rtc@32 {
           compatible = "epson,rx8010";
           status = "okay";
           reg = <0x32>;
   };
};
  • rx8010设备挂接在i2c1适配器下。
  • rx8010的通信地址为0x32。
  • rx8010的驱动程序兼容字段为“epson,rx8010”,对应到上面所讲的of_match_table中的设备兼容性。
  • clock-frequency表示I2C通信时钟为100KHz

实例

rx8010为EPSON公司的一款RTC芯片,其使用I2C进行通信,其驱动源码可以参考这里。下面简要分析一下这个驱动程序。

定义 struct i2c_driver

首先,其作为一个I2C设备,必须实现struct i2c_driver结构。

static const struct i2c_device_id rx8010_id[] = {
	{ "rx8010", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, rx8010_id);

static const struct of_device_id rx8010_of_match[] = {
	{ .compatible = "epson,rx8010" },
	{ }
};
MODULE_DEVICE_TABLE(of, rx8010_of_match);


static struct i2c_driver rx8010_driver = {
	.driver = {
		.name = "rtc-rx8010",
		.of_match_table = of_match_ptr(rx8010_of_match),
	},
	.probe		= rx8010_probe,
	.id_table	= rx8010_id,
};

之后通过module_i2c_driver(rx8010_driver)注册驱动程序。

实现probe接口

实现prome接口完成设备的初始化和反初始化操作。

static int rx8010_probe(struct i2c_client *client,
			const struct i2c_device_id *id)
{
	struct i2c_adapter *adapter = client->adapter;
	struct rx8010_data *rx8010;
	int err = 0;

	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA         <-------------(1)
		| I2C_FUNC_SMBUS_I2C_BLOCK)) {
		dev_err(&adapter->dev, "doesn't support required functionality\n");
		return -EIO;
	}

	rx8010 = devm_kzalloc(&client->dev, sizeof(struct rx8010_data),        <-------------(2)
		| I2C_FUNC_SMBUS_I2C_BLOCK)) {
			      GFP_KERNEL);
	if (!rx8010)
		return -ENOMEM;

	rx8010->client = client;
	i2c_set_clientdata(client, rx8010);

	err = rx8010_init_client(client);
	if (err)
		return err;

	.... ... 

	rx8010->rtc = devm_rtc_device_register(&client->dev, client->name,    <-------------(3)
		&rx8010_rtc_ops, THIS_MODULE);

	if (IS_ERR(rx8010->rtc)) {
		dev_err(&client->dev, "unable to register the class device\n");
		return PTR_ERR(rx8010->rtc);
	}

	return 0;
}
  • (1)可以看到rx8010使用的是SMbus通信协议,这里进行了适配器功能检测。
  • (2)分配设备私有数据,并进行初始化。
  • (3)rx8010为RTC设备,所以最终调用devm_rtc_device_register,将设备注册成为RTC设备。

设备通信

rx8010_get_time和rx8010_set_time实现RTC时间的读取和设置操作,具体实现中,与设备通信时使用到的就是SMbus协议。比如,读取RTC时间的操作

	err = i2c_smbus_read_i2c_block_data(rx8010->client, RX8010_SEC,
				    7, date);
	if (err != 7)
		return err < 0 ? err : -EIO;

通过i2c_smbus_read_i2c_block_data连续读取了开始寄存器RX8010_SEC的7个字节,并将其存储到date数组中。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux I2C设备驱动基本规范 的相关文章

  • Linux C++ 错误:未定义对“dlopen”的引用

    我在 Linux 上使用 C Eclipse 工作 并且想要使用一个库 Eclipse 向我显示一个错误 undefined reference to dlopen 你知道解决办法吗 这是我的代码 include
  • Linux shell 根据第二列对文件进行排序?

    我有一个这样的文件 FirstName FamilyName Address PhoneNumber 如何按 FamilyName 排序 如果这是 UNIX sort k 2 file txt 您可以使用多个 k用于对多列进行排序的标志 例
  • linux新手关于嵌入式linux设备驱动的问题

    最近在研究linux驱动 正如我读过的那些文章所说 设备驱动程序模块很可能会根据内核的需要自动加载 因此我想知道内核如何确定为特定设备 声卡 I2C spi 设备 等 我也无法彻底想象内核如何在启动时检测每个硬件设备 与嵌入式linux相关
  • Xvfb 冻结初始化 GLX 扩展

    我正在尝试运行无头 Xvfb 服务器来捕获 Amazon EC2 micro 上的屏幕截图 但它在 GLX 上陷入了困境 我使用此脚本安装了 GLX Xvfb 和所有库 https gist github com joekiller 414
  • 我们如何在使用循环时调用 ansible playbook 中的变量

    我有两个文件 其中这些文件包含server names and server IP s 我想更改 替换一些特定的server names and IP addressees根据要求在两个文件中 这与这篇文章 因为它被要求开设一个新职位 ht
  • 应用程序中两个不同版本的库

    考虑一个场景 其中有两个不同版本的共享库 考虑 A 1 so 链接到 B so A 2 so 链接到 C so 现在 B so 和 C so 都链接到 d exe 当 B so 想要调用 A 1 so 中的函数时 它最终会调用 A 2 so
  • 我想在 Red Hat Linux 服务器中执行 .ps1 powershell 脚本

    我有一个在窗口中执行的 ps1 powershell 脚本 但我的整个数据都在 Linux 服务器中 有什么可能的方法可以让我在红帽服务器中执行 powershell 脚本 powershell脚本是 Clear Host path D D
  • 如何将一个文本文件拆分为多个 *.txt 文件?

    我有一个文本文件file txt 12 MB 包含 something1 something2 something3 something4 有没有办法分开file txt分成 12 个 txt 文件 比方说file2 txt file3 t
  • 如何获取与 shell 中的文件名模式匹配的所有文件的总文件大小?

    我正在尝试仅使用 shell 来计算与文件名模式匹配的所有文件 在目录树中 的总大小 以字节为单位 这是我到目前为止所拥有的 find name undo exec stat c s awk 总计 1 END 打印总计 有没有更简单的方法来
  • Vagrant 遇到问题 - “404 - 未找到”

    我正在尝试使用 Vagrant 制作一个 LAMP 盒子 有人告诉我它使用起来非常简单 我对网络和虚拟机完全陌生 对 Linux Ubuntu 的经验也很少 我目前已尝试按照官方文档页面上的教程进行操作 http docs vagrantu
  • 使用 ioctl 在 C++ 中以编程方式添加路由

    我编写了简单的 C 函数 添加了新路线 void addRoute int fd socket PF INET SOCK DGRAM IPPROTO IP struct rtentry route memset route 0 sizeof
  • 在 x86 汇编语言中获取文件大小的简单方法

    假设我已经在汇编中打开了一个文件 并且在寄存器 eax 中有该文件的文件句柄 我将如何获取文件的大小 以便为其分配足够的缓冲区空间 我在这里研究了另一个讨论 建议使用sys fstat 28 系统调用来获取文件统计信息但无法实现它 My a
  • LINUX:如何锁定内存中进程的页面

    我有一个 LINUX 服务器 运行一个具有大量内存占用的进程 某种数据库引擎 该进程分配的内存太大 需要将其中一部分换出 换出 我想做的是将所有其他进程 或正在运行的进程的子集 的内存页面锁定在内存中 以便只有数据库进程的页面被换出 例如
  • 如何在线程创建和退出时调用函数?

    include
  • 如何才能将 TCP 连接返回到同一端口?

    机器是 RHEL 5 3 内核 2 6 18 有时我在 netstat 中注意到我的应用程序有连接 建立了 TCP 连接本地地址 and 国外地址是一样的 其他人也报告了同样的问题 症状与链接中描述的相同 客户端连接到本地运行的服务器的端口
  • ssh 连接超时

    我无法在 git 中 ssh 到 github bitbucket 或 gitlab 我通常会收到以下错误消息 如何避免它 输出 ssh T email protected cdn cgi l email protection i ssh
  • ioctl 命令的用户权限检查

    我正在实现 char 驱动程序 Linux 并且我的驱动程序中有某些 IOCTL 命令仅需要由 ADMIN 执行 我的问题是如何在 ioctl 命令实现下检查用户权限并限制非特权用户访问 IOCTL 您可以使用bool capable in
  • 仅使用containerd(不使用Docker)修剪容器镜像

    如果我刚刚containerd安装在 Linux 系统上 即 Docker 是not安装 如何删除未使用的容器映像以节省磁盘空间 Docker 就是这么方便docker system prune https docs docker com
  • 相当于Linux中的导入库

    在 Windows C 中 当您想要链接 DLL 时 您必须提供导入库 但是在 GNU 构建系统中 当您想要链接 so 文件 相当于 dll 时 您就不需要链接 为什么是这样 是否有等效的 Windows 导入库 注意 我不会谈论在 Win
  • 如何使用 JSch 将多行命令输出存储到变量中

    所以 我有一段很好的代码 我很难理解 它允许我向我的服务器发送命令 并获得一行响应 该代码有效 但我想从服务器返回多行 主要类是 JSch jSch new JSch MyUserInfo ui new MyUserInfo String

随机推荐

  • Web应用防火墙--规则防护

    一 什么是Web应用防火墙 Web应用防火墙对网站 APP的业务流量安全及合规性保护 对业务流量的识别恶意特征提取 分析识别出恶意流量并进行处理 将正常安全的流量回源到业务服务器 保护网站核心业务和数据安全 京东云Web应用防火墙的产品架构
  • 深度学习C语言——结构体

    不起眼 前言 结构体 结构体的声明 结构体变量的定义和初始化 结构体大小计算 枚举 联合 总结 前言 自定义类型连续剧 结构体 结构是一些值的集合 这些值称为成员变量 结构的每个成员是不同类型的变量 为什么要有结构体 比如说 描述一个学生时
  • Learnning Dlib(五) Dlib face landmark detection

    官方例子 人脸模型68点绘制 非常非常慢 需要优化 下载模型 下载后放入lib 目录下 代码如下 interface ViewController shape predictor sp NSString imagePath void vie
  • Python Web Flask源码解读(三)——模板渲染过程

    关于我 编程界的一名小小程序猿 目前在一个创业团队任team lead 技术栈涉及Android Python Java和Go 这个也是我们团队的主要技术栈 Github github com hylinux1024 微信公众号 angry
  • 【数据分析】数据分析方法(二):逻辑树分析方法

    数据分析方法 二 逻辑树分析方法 逻辑树分析方法是把复杂问题拆解成若干个简单的子问题 然后像树枝那样逐步展开 1 工作计划分解 不管是在实际生活中还是工作中 我们经常会使用逻辑树分析方法来分析问题 比如 现在你想给自己做一个年度计划 但是要
  • 基石

    本文是Checkpoint系列非源码最后一篇文章 必会 关于SparkStreaming checkpoint那些事儿 flink超越Spark的Checkpoint机制 前面两篇 一篇是spark的driver的Checkpoint细节及
  • 工作中git遇到的问题

    一开始我提交代码总是提交到另一个同事的git里 代码 Windows PowerShell 版权所有 C Microsoft Corporation 保留所有权利 尝试新的跨平台 PowerShell https aka ms pscore
  • 使用springboot搭建swing桌面程序(二)

    概述 桌面应用是个人兴趣 但不是很擅长 这里接着上一篇的内容 上一篇主要是springboot jpa swing集成到一起 启动是否正常 这一篇主要是应用的具体实现 页面编写 基本的todo的添加 完成 展示 页面的布局 设计自己的组件
  • Element UI Table排序顺序错乱处理

    1 a b gt return a total money b total money a b gt 0表示a大于b a b 0表示a等于b a b lt 0表示a小于b
  • java面向对象简述

    1 面向对象编程的基本特征 java面向对象编程的三个基本特征是封装 继承和多态 这三个特征是面向对象编程更加灵活 高效 2 类和对象 在java中 所有的代码都必须放在类中 类是种模板 它确定了对象的属性和行为 对象是类的实例化 可以调用
  • 解决TypeError: string indices must be integers, not str

    遇到问题 ExtendValue area 1 info year 2014 a 12 b 3 c 5 trip country CN 在按照字典访问的时候 报错 TypeError string indices must be integ
  • ubuntu 16.04 gedit 配置

    ubuntu 16 04 gedit 配置 1 功能说明 说明 1 配置使用gedit调用python工具 调用终端显示python运行结果 2 配置使用gedit调用终端 显示shell运行结果 3 配置使用gedit编辑markdown
  • Java常见面试题(2)

    1 表单提交重复 怎么设置接口的幂等性 场景 当用户在下单的时候 他已经支付过了 再返回支付结果的时候 出现网络抖动的问题 出现了一些异常 那这个时候用户已经消费过了 如果用户在点击这个按钮 就会二次消费 这就是因为没有实现幂等性 解决 1
  • State Manager

    stateProvider工作的方式与Angular s v1 router相近 但是他更加注重状态 状态对应于应用程序中某个位置 整体的UI和导航A state corresponds to a place in the applicat
  • mybatis条件判断/ 动态sql

    1 if标签 单条件判断 判断完第一个条件 对下一个条件进行判断 看是否能满足条件 满足则执行
  • 开发中用到的数据库查询案例

    目录 1 查询过去12个月的数据 并统计每个月数据的数量 2 查询过去12个月的数据 并统计每个月数据的数量 如果某个月数据没有 也展示出为0 1 查询过去12个月的数据 并统计每个月数据的数量 select date format cas
  • Qt入门-界面多语言国际化的实现

    Qt为国际化的实现提供了简便的方法 下面使用Qt Linguist示例一个中文语言界面的生成 我使用以前的实例 http blog csdn net xgbing article details 7778856 它是一个英文界面 步骤如下
  • 什么场景该用 MongoDB

    摘要 前段时间 MongoDB源码团队 在云栖社区上发起了一个 MongoDB 使用场景及运维管理问题交流探讨 的技术话题 有近五千人关注了该话题讨论 很多人比较关心 MongoDB的适用场景 也有用户在话题里分享了自己的业务场景 这里就
  • 【题解】poj2689(LibreOJ10197) 线性筛

    题目链接 筛出2到sqrt u 的所有质数 再标记 l u 中是质数p倍数的数 最后枚举相邻质数 部分代码实现参考了大佬题解 题目描述 给定两个整数 L R L R L R 求闭区间 L
  • Linux I2C设备驱动基本规范

    不同于单片机驱动开发 即使是简单的I2C设备驱动程序 如果要在Linux上实现同种功能的驱动程序 事情也会变的复杂起来 对于初学者而言 主要的困难就是不知道如何使用Linux现有的驱动框架 去完成驱动程序的开发 I2C设备驱动 相对来说比较