Linux驱动开发:I2C子系统
目录
1、I2C简介
1.1 两根线
1.2 信号
1.3 写时序
1.4 读时序
1.5 I2C速率
1.6 I2C驱动框架简介
2、I2C设备驱动
2.1 I2C相关API
2.1.1 i2c_driver
2.1.2 注册:i2c_add_driver
2.1.3 注销:i2c_del_driver
2.1.4 module_i2c_driver:一键注册,不需要以上注册注销的过程
2.1.5 i2c_client
2.1.6 i2c_msg
2.1.7 i2c_transfer
3、驱动程序
3.1 写消息封装
3.2 读消息封装
3.3 驱动程序
3.3.1 修改设备树
3.3.2 驱动程序编写
3.4 应用程序
1、I2C简介
1.1 两根线
I2C总线只有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL。
1.2 信号
空闲状态:I2C总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。
起始信号:当SCL为高电平期间,SDA由高到低的跳变;
停止信号:当SCL为高电平期间,SDA由低到高的跳变;
应答信号:在第九个时钟周期的时候,sda上低电平就代表应答
非应答信号:在第九个时候周期的时候,sda维持高电平
1.3 写时序
起始信号+七位从机地址+一位写(写是0 读是1)+ ack + 寄存器地址+数据+ ack +停止位
1.4 读时序
起始信号+七位从机地址+一位写(写是0 读是1)+ ack + 寄存器地址+ ack +
起始信号+七位从机地址+一位读(写实0 读是1)+ ack + 数据+NO ack + 停止位
1.5 I2C速率
100K 低速 400K 全速 3.4M 高速
1.6 I2C驱动框架简介
在Linux 内核中 I2C 的体系结构分为3 个部分:
1、I2C 核心:I2C 核心提供了I2C 总线驱动和设备驱动的注册、注销方法等。
2、I2C 总线驱动:I2C 总线驱动是对I2C 硬件体系结构中适配器端的实现,适配器可由 CPU 控制,甚至可以直接集成在CPU 内部。一般SOC 的 I2C 总线驱动都是由半导体厂商编写的,不需要用户去编写。因此我们不用关心 I2C 总线驱动具体是如何实现的,我们只要专注于 I2C 设备驱动即可。
3、I2C 设备驱动:I2C 设备驱动是对I2C 硬件体系结构中设备端的实现,设备一般挂接在受CPU 控制的I2C 适配器上,通过I2C 适配器与CPU 交换数据。
2、I2C设备驱动
2.1 I2C相关API
2.1.1 i2c_driver
struct i2c_driver { int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);int (*remove)(struct i2c_client *client);struct device_driver driver;
};struct device_driver {const char *name;const struct of_device_id *of_match_table;
};
2.1.2 注册:i2c_add_driver
#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver)
2.1.3 注销:i2c_del_driver
void i2c_del_driver(struct i2c_driver *driver)
2.1.4 module_i2c_driver:一键注册,不需要以上注册注销的过程
定义在 linux/i2c.h 中
#define module_i2c_driver(__i2c_driver) module_driver(__i2c_driver, i2c_add_driver, i2c_del_driver)#define module_driver(__driver, __register, __unregister, ...)
static int __init __driver##_init(void)
{ return __register(&(__driver) , ##__VA_ARGS__);
}
module_init(__driver##_init);
static void __exit __driver##_exit(void)
{ __unregister(&(__driver) , ##__VA_ARGS__);
}
module_exit(__driver##_exit);
module_i2c_driver(myi2c);
->
#define module_i2c_driver(myi2c) module_driver(myi2c, i2c_add_driver, i2c_del_driver)#define module_driver(myi2c, i2c_add_driver, i2c_del_driver)
static int __init myi2c_init(void)
{ return i2c_add_driver(&myi2c);
} static void __exit __driver##_exit(void)
{ i2c_del_driver(&myi2c);
}
module_init(myi2c_init);
module_exit(myi2c_exit);
2.1.5 i2c_client
struct i2c_client { unsigned short flags; // 0写 1读unsigned short addr; //从机地址 char name[I2C_NAME_SIZE]; //驱动的名字struct i2c_adapter *adapter;//控制器驱动的对象struct device dev; //这是设备的对象
};
2.1.6 i2c_msg
有多少起始信号就有多少消息,消息的长度用字节表示
struct i2c_msg {__u16 addr; //从机地址__u16 flags; //读写标志位 0写 1度__u16 len; //消息长度__u8 *buf; //消息首地址
};
2.1.7 i2c_transfer
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
/*
功能:消息的发送函数
参数:@adap:控制器的结构体对象@msgs:消息结构体的首地址@num:消息结构体的个数
返回值:成功返回num,否则就是失败
*/
3、驱动程序
3.1 写消息封装
起始信号+七位从机地址+一位写(写是0 读是1)+ ack + 寄存器地址+数据+ ack +停止位
char w_buf[] = {地址,数据};
struct i2c_msg w_msg = {.addr = client->addr,.flags = 0,.len = 2,.buf = w_buf,
};
3.2 读消息封装
起始信号+七位从机地址+一位写(写是0 读是1)+ ack + 寄存器地址+ ack +
起始信号+七位从机地址+一位读(写实0 读是1)+ ack + 数据+NO ack + 停止位
char r_buf[] = {地址};
char val;
struct i2c_msg r_msg[] = {[0] = {.addr = client->addr,.flags = 0,.len = 1,.buf = r_buf,},[1] = {.addr = client->addr,.flags = 1,.len = 1,.buf = &val;},
};
3.3 驱动程序
3.3.1 修改设备树
&i2c1{pinctrl-names = "default", "sleep";pinctrl-0 = <&i2c1_pins_b>;pinctrl-1 = <&i2c1_sleep_pins_b>;i2c-scl-rising-time-ns = <100>; i2c-scl-falling-time-ns = <7>;status = "okay";/delete-property/dmas;/delete-property/dma-names;si7006@40{compatible = "aaa,si7006";reg = <0x40>;};
};
3.3.2 驱动程序编写
由于 client 在各个函数中都需要用到,所以有两种方法,第一种是直接定义一个全局变量,在probe函数中获取到这个 client ,第二种实际上和第一种几乎一致,就是用一个 private_data 的指针去接住这个client。
#ifndef __SI7006_H__
#define __SI7006_H__#define GET_HUM _IOR('l',0,int)
#define GET_TMP _IOR('l',1,int)#define HUM_ADDR 0xe5
#define TMP_ADDR 0xe3
#endif
#define I2CNAME "si7006"
struct i2c_client* gclient;
int major = 0;
struct class* cls;
struct device* dev;int i2c_read(unsigned char reg)
{// 1.封装消息int ret;unsigned char r_buf[] = { reg };unsigned short val;struct i2c_msg r_msg[] = {[0] = {.addr = gclient->addr,.flags = 0,.len = 1,.buf = r_buf,},[1] = {.addr = gclient->addr,.flags = 1,.len = 2,.buf = (char *)&val,},};// 2.发送消息ret = i2c_transfer(gclient->adapter, r_msg, ARRAY_SIZE(r_msg));if (ret != ARRAY_SIZE(r_msg)) {printk("i2c read hum or temp error\n");return -EAGAIN;}return val >> 8 | val << 8;
}
int si7006_open(struct inode* inode, struct file* file)
{printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);return 0;
}
long si7006_ioctl(struct file* file,unsigned int cmd, unsigned long arg)
{int ret, data;switch (cmd) { case GET_HUM:data = i2c_read(HUM_ADDR);ret = copy_to_user((void*)arg, &data, 4);break;case GET_TMP:data = i2c_read(TMP_ADDR);ret = copy_to_user((void*)arg, &data, 4);break;}return 0;
}
int si7006_close(struct inode* inode, struct file* file)
{printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);return 0;
}
struct file_operations fops = {.open = si7006_open,.unlocked_ioctl = si7006_ioctl,.release = si7006_close,
};
int si7006_probe(struct i2c_client* client, const struct i2c_device_id* id)
{gclient = client;// 1.注册字符设备驱动major = register_chrdev(0, I2CNAME, &fops);// 2.自动创建设备节点cls = class_create(THIS_MODULE, I2CNAME);dev = device_create(cls, &client->dev, MKDEV(major, 0), NULL, I2CNAME);return 0;
}
int si7006_remove(struct i2c_client* client)
{device_destroy(cls, MKDEV(major, 0));class_destroy(cls);unregister_chrdev(major, I2CNAME);return 0;
}struct of_device_id oftable[] = {{.compatible = "aaa,si7006",},{}
};struct i2c_driver si7006 = {.probe = si7006_probe,.remove = si7006_remove,.driver = {.name = "bbb",.of_match_table = oftable,}
};module_i2c_driver(si7006);
3.4 应用程序
#include "si7006.h"int main(int argc, const char* argv[])
{int fd;int data,hum,tmp;if ((fd = open("/dev/si7006", O_RDWR)) == -1)PRINT_ERR("open error");while (1) {ioctl(fd, GET_HUM, &hum);ioctl(fd, GET_TMP, &tmp);usleep(2000);}close(fd);return 0;
}相关文章:
Linux驱动开发:I2C子系统
目录 1、I2C简介 1.1 两根线 1.2 信号 1.3 写时序 1.4 读时序 1.5 I2C速率 1.6 I2C驱动框架简介 2、I2C设备驱动 2.1 I2C相关API 2.1.1 i2c_driver 2.1.2 注册:i2c_add_driver 2.1.3 注销:i2c_del_driver 2.1.4 module_i2c_driverÿ…...
[C++] 动态内存与智能指针
众所周知,C五大内存区:全局数据区(静态区)、代码区、栈区、堆区、常量区。 全局数据区(静态区):存放全局变量,静态数据和常量; 代码区:存放所有类成员函数和非成员函数代码,函数体的二进制代码。…...
多态的原理
有了虚函数,会在类的对象增加一个指针,该指针就是虚函数表指针_vfptr;虚表本质就是函数指针数组,虚表里面存放着该对象的虚函数的地址; 派生类继承有虚函数基类的对象模型 子类继承父类的虚表指针时,是对父类的虚表指针进行了拷…...
RK3588平台开发系列讲解(内存篇)Linux 伙伴系统数据结构
平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、 页二、区三、内存节点沉淀、分享、成长,让自己和他人都能有所收获!😄 📢Linux 系统中,用来管理物理内存页面的伙伴系统,以及负责分配比页更小的内存对象的 SLAB 分配器了。 本篇将介绍伙伴系统相关数据结…...
Windows(MFC/C++)上进程间通讯的几种简单又实用的方法
前段时间,做了一个项目,涉及数据传输。项目实现方式有很多种,但不同的实现方式,对数据的传输方法不同,且各有优缺点。 下文就不同情况来如何选择数据传输(通讯)方式。 先说说需求,模块A获取测试数据&#…...
嘉兴桐乡会计考证培训-备考中级职称有必要报班吗?
备考中级会计职称有必要报班吗?其实,备考报班不能说是必需的,但听课学习确实是节省时间的一种方式,根据同学们的反馈,自学所花费的时间远远多于跟着老师学。上元教育就整理了一些学员报班之前走过的弯路 报班之前 在2…...
java元注解和自定义注解的区别
Java的元注解和自定义注解是两个不同的概念。 元注解是Java内置的一组用于修饰其他注解的注解,包括Retention、Target、Inherited和Documented。它们可以控制被修饰的注解的保留策略、目标范围、是否继承等属性,并且可以在编写自定义注解时使用。 Retent…...
技术到底是什么
背景 我发了朋友圈:做了个奇怪的梦,梦见被离职了,理由竟然是:你技术太菜了 我补充评论:我还没想明白怎么回事,就醒了。有点遗憾的是:想再努力反驳两句,结果没机会了… 很多人评论…...
什么CRM客户管理系统最好?
产业互联网背景下,企业数字化转型日渐深化。毋庸置疑,客户是企业的命脉,企业发展的关键便是以客户为中心,为客户创造价值,并不断实现企业的可持续性增长,而这也是每个企业永不落幕的主题。 一套优秀的CRM客…...
吴军《计算之魂》读后感
前言 断断续续,终于完成了这本书的第一次通读,记录下自己的一些想法。 先说一个小故事。前段时间家里买了一个小鱼缸,问我有没有办法让水自动循环,但不想用电。没有好的想法,去小某书上搜了下,好多案例教…...
CSS进阶
01-复合选择器 定义:由两个或多个基础选择器,通过不同的方式组合而成。 作用:更准确、更高效的选择目标元素(标签)。 后代选择器 后代选择器:选中某元素的后代元素。 选择器写法:父选择器 …...
金兰组织 | 2023金兰解决方案集经营管理篇正式发布
为助力企业创新管理、提质增效,人大金仓携手金兰组织成员单位,于近期发布多项经营管理领域的联合解决方案,共享创新应用成果。 /人大金仓高级副总裁宋瑞/ 人大金仓高级副总裁宋瑞在致辞中表示:“联合解决方案创新是指通过把不同领…...
【python】pytorch包:深度学习(序章)
今日听闻师姐说pytorch实现深度学习要比keras更好用一些,特此记录 Part 0. 机器学习 与 深度学习 的联系与区别 参考B站视频链接 联系 深度学习是机器学习的分支,人工神经网络为基础,对数据的特征进行学习的方法 区别 特征抽取 机器学…...
HTML <acronym> 标签
HTML5 中不支持 <acronym> 标签在 HTML 4 中用于定义首字母缩写词。 实例 标记一个首字母缩写: <acronym title"World Wide Web">WWW</acronym> 浏览器支持 IEFirefoxChromeSafariOpera 所有主流的浏览器均支持 <acronym> …...
python基本数据类型 - 字典集合
引入 在内存中存储的数据可以是不同的数据类型。比如名字可以使用字符串存储,年龄可以使用数字存储,python有6种基本数据类型,用于各种数据的存储,分别是:numbers(数字类型)、string(字符串)、List(列表)、Tuple(元组…...
python数据类型总结
标准数据类型 Python 有以下几种标准数据类型: 整数(int):表示整数值,如 1, -5, 0 等。浮点数(float):表示小数值,如 3.14, -0.01, 1.0 等。字符串(str&…...
TS内置类型总结
typeof 取对象身上的类型 const person {name: ,job: ,age:18 } type p typeof person ->> type p {name: string;job: string;age: number; }keyof取一个类型的属性明作为一个联合类型 const person {name: ,job: ,age: 18 } type p typeof person type k keyof p…...
Spring Cloud Alibaba: Gateway 网关过滤器 GatewayGatewayFilter factory (记录)
目录 AddRequestHeader GatewayFilter factory AddRequestHeadersIfNotPresent GatewayFilter factory AddRequestParameter GatewayFilter Factory AddResponseHeader GatewayFilter Factory CircuitBreaker GatewayFilter factory circuit breaker based on the status…...
Windows Server 2016版本说明
Windows Server 2016 Essentials edition Windows Server 2016 Essentials版是专为小型企业而设计的。它对应于Windows Server的早期版本中的Windows Small Business Server。此版本最多可容纳25个用户和50台设备。它支持两个处理器内核和高达64GB的RAM。它不支持Windows Serve…...
车载红外夜视「升温」
红外夜视赛道,正在升温。 本周,全球车载后视镜头部供应商Gentex宣布,领投以色列热成像技术初创公司ADASKY,后者在B轮融资中拿到了3000万美元。按照计划,Gentex将协助ADASKY将红外夜视技术推向汽车市场。 事实上&#x…...
地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
并发编程 - go版
1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...
作为测试我们应该关注redis哪些方面
1、功能测试 数据结构操作:验证字符串、列表、哈希、集合和有序的基本操作是否正确 持久化:测试aof和aof持久化机制,确保数据在开启后正确恢复。 事务:检查事务的原子性和回滚机制。 发布订阅:确保消息正确传递。 2、性…...
Unity中的transform.up
2025年6月8日,周日下午 在Unity中,transform.up是Transform组件的一个属性,表示游戏对象在世界空间中的“上”方向(Y轴正方向),且会随对象旋转动态变化。以下是关键点解析: 基本定义 transfor…...
ubuntu22.04 安装docker 和docker-compose
首先你要确保没有docker环境或者使用命令删掉docker sudo apt-get remove docker docker-engine docker.io containerd runc安装docker 更新软件环境 sudo apt update sudo apt upgrade下载docker依赖和GPG 密钥 # 依赖 apt-get install ca-certificates curl gnupg lsb-rel…...
