文章目录
- 前言
- 一、核心属性
- 二、I2C的时序
- 三、I2C寻址体系
- 四、Linux中的I2C子系统
- 4.1 I2C子系统相关概念
- 4.2 I2C子系统结构框架图
- 4.3 I2C子系统中重要的结构体
- 4.4 I2C相关函数
- 五、LM75温度传感器
- 5.1 LM75相关参数
- 5.2 LM75驱动编写
- 5.2.1 设备树节点添加
- 5.2.1 驱动代码编写
- 六、BH1750光照传感器
- 6.1 BH1750传感器参数
- 6.2 BH1750驱动编写
- 6.2.1 设备树节点添加
- 6.2.1 驱动代码编写
前言
I2C全称: Inter-Integrated Circuit(集成电路间总线),口语简写 IIC、标准书写I²C)
1982 年由飞利浦(现 NXP 恩智浦) 发布,专为同一块 PCB 板内芯片短距离低速通信设计,是嵌入式最通用的板级串行总线之一。许多传感器都会采用I2C协议来与开发板进行通信。在本片文章中,将会介绍I2C的相关概念与一些I2C传感器,如LM75、BH1750等。
一、核心属性
通信类型: 同步、半双工、串行(逐位传输);
架构: 多主多从(支持多个主机抢占总线);
物理引脚:仅 2 根信号线 SCL(时钟)、 SDA(数据);
驱动电气:全部设备引脚为开漏输出,总线必须外接4.7kΩ 上拉电阻至 VCC,无法主动推高电平速率。
| 模式 | 速率 | 适用场景 |
|---|---|---|
| Standard 标准 | 100 kbps | 通用传感器、EEPROM |
| Fast 快速 | 400 kbps | 绝大多数消费电子外设 |
| Fast Plus | 1 Mbps | 高速 ADC、显示屏 |
| High Speed | 3.4 Mbps | 高端摄像头、高清外设 |
| Ultra Fast | 5 Mbps | 单向专用场景 |
注:大部分传感器都采用100k的速率。
二、I2C的时序
- 起始信号 START(S)
总线空闲时 SCL、 SDA 均为高; SCL 保持高电平, SDA 由高跳低,宣告一次通信开始,总线进入忙状态。 - 停止信号 STOP(P)
SCL 保持高电平, SDA 由低跳高,通信结束,总线回到空闲高电平状态。 - 重复起始 Repeated START(Sr)
不发送 STOP,直接再次触发 START;常用于先写寄存器地址、再读数据的场景,避免释放总线被其他主机抢占。 - 应答位 ACK / NACK
每传输 8bit(1 字节)后,紧跟第 9 位应答位:
ACK(0):接收方拉低 SDA → 接收成功,允许继续发下一字节;
NACK(1):接收方释放 SDA(高电平) → 接收失败 / 结束传输;
主机发数据:从机回复 ACK/NACK;
从机发数据:主机回复 ACK;读完最后 1 字节主机必须发 NACK 告知结束。
MSB , Most Significant Bit (MSB) first, one byte bit7 send first,在多字节数据先发高字节。
三、I2C寻址体系
- 7 位标准地址(最常用)
START 后先发 8bit:
高位先行;
R/W=0:主机写数据到从机[bit7~bit1:7位从机地址]+[bit0: R/W读写位]
R/W=1:主机读从机数据
总线最多容纳 (2^7=128) 个地址,部分地址为协议保留专用(0x00、 0x01 等),实际可用 112 个左右从设备。 - 10 位扩展地址
兼容 7 位设备,支持更多外设,高位分两次传输,工业多设备场景使用较少。
四、Linux中的I2C子系统
4.1 I2C子系统相关概念
采用传统的字符设备驱动编写方式在处理总线编程时非常复杂,假设有M个控制器,每个控制器上可能接不同的N个外设,那么我们需要编写的驱动将是M*N个,如下图所示:
这样显然是不能被接受的, M*N是一种强耦合的设计方式,所以我们提炼出一个中间层,让M与中间层耦合, N也与中间层耦合,所以就有了如下模型:
那么我们逐渐将主机控制器驱动与外设传感器驱动进行分离,这样主机控制器驱动与外设控制器驱动互不关心,外设可以通过访问核心层的通用API进行数据传输,主机和外设之间可以进行任意组合。
所以后续的I2c子系统框架、 SPI子系统框架、 USB子系统框架等总线驱动都是这种模型。
在linux中, I2c驱动最终分为2个部分。
①、I2C 总线驱动, I2C 总线驱动就是 SOC 的I2C 控制器驱动,也叫做 I2C 适配器驱动。这部分已经有NXP完成,可以直接调用;
②、I2C 设备驱动, I2C 设备驱动就是针对具体的 I2C 设备而编写的驱动。 需要咱们自己编写。
I2C总线驱动主要是i2c的适配驱动, 和platform总线对标,同等级别。
在dtsi ,中可以找到iic的控制器的资源,查找到iic控制器的驱动:
4.2 I2C子系统结构框架图
4.3 I2C子系统中重要的结构体
总线驱动(从属i2c_client)
| 类型 | 描述 | 从属关系 |
|---|---|---|
| i2c_adapter | i2c适配器(i2c总线) | 从属i2c_adapter |
| i2c_algorithm | 描述i2c数据如何收发的 |
设备驱动
| 类型 | 描述 | 从属关系 |
|---|---|---|
| i2c_client | 一个设备,一个这个结构体,包含器件的地址,中断号,设备名.这个结构体包含一个i2c驱动,需要的大部分的资源 | 包含i2c_adapter一个指针 |
| i2c_drive | 类型platform_driver,probe,remove,driver(设备树中,匹配设备资源),需要自己编写的地方 | 使用 i2c_client,作为函数的参数 |
staticinti2c_device_match(structdevice*dev,structdevice_driver*drv)负责设备和驱动的匹配工作4.4 I2C相关函数
| 函数名 | 功能 |
|---|---|
| i2c_add_driver | 向I2c子系统注册驱动,并触发设备匹配流程 |
| i2c_del_driver | 注销I2c驱动,释放资源并解除设备绑定 |
| i2c_transfer | 执行单次或多次I2C消息传输,适用于复杂时序操作 |
| i2c_master_send | 封装单次I2C消息写操作,适用于简单数据发送 |
| i2c_master_recv | 封装单次I2C消息读操作,适用于简单数据读取 |
| i2c_register_driver | 自动封装注册与卸载,替代i2c_add_driver+i2c_del_driver,简化驱动代码。 |
| module_i2c_driver | 底层原始注册函数,i2c_add_driver内部会调用它 |
五、LM75温度传感器
5.1 LM75相关参数
购买传感器时,厂家一般都会附带相应的参考手册,若没有,可在立创商城中搜索,就可以找到相应手册。
| 参数 | 规格 |
|---|---|
| 工作温度 | -55℃ ~ +125℃ |
| 精度 | ±0.4℃(典型值,-25℃ ~ +55℃区间) |
| 分辨率 | 12位(0.0625℃) |
| 供电电压 | 1.4V ~ 5.5V |
| 转换时间 | 26ms |
| 工作电流 | 15μA @12Hz |
| 关断电流 | 0.3μA |
| 数字输出接口 | I2C、SMBus |
| 替代型号 | x75 |
5.2 LM75驱动编写
5.2.1 设备树节点添加
Linux系统中已经有了I2C子系统,我们只需要在I2C下添加对应的LM75传感器的相关信息即可,注意IMX6ULL中只有两个I2C引出:
5.2.1 驱动代码编写
module_i2c_driver注册LM75传感器设备;创建
i2c_driver结构体,填入module_i2c_driver;在probe中, 创建杂项设备;
编写read函数实现温度读取;
完整代码:
/** * @file led_drv.c * @author suaplus * @brief iic 温度传感器 * @version 0.1 * @date 2026-10-16 * * @copyright Copyright (c) 2026 * */#include"asm-generic/errno-base.h"#include"asm-generic/gpio.h"#include"asm/gpio.h"#include"asm/uaccess.h"#include"linux/export.h"#include"linux/fs.h"#include"linux/printk.h"#include"linux/string.h"#include"linux/sysfs.h"#include<linux/init.h>#include<linux/module.h>#include<linux/cdev.h>#include<asm/io.h>#include<linux/device.h>#include<linux/miscdevice.h>#include<linux/kdev_t.h>#include<linux/kernel.h>#include<linux/of.h>#include<linux/of_device.h>#include<linux/of_address.h>#include<linux/of_gpio.h>#include<linux/platform_device.h>#include<linux/i2c.h>/*----------------------设备名------------------------------------------*/#defineDEV_NAME"lm75"/*------------------------------函数声明-----------------------------------*/staticintmyprob(structi2c_client*,conststructi2c_device_id*);staticintmyremove(structi2c_client*);staticintmyopen(structinode*,structfile*);staticintmyrelease(structinode*,structfile*);staticssize_tmyread(structfile*,char__user*,size_t,loff_t*);/*----------------------------文件操作结构体------------------------------*/staticstructfile_operationsfops={.open=myopen,.read=myread,.release=myrelease,};staticstructmiscdevicemisc_dev={.name=DEV_NAME,.minor=MISC_DYNAMIC_MINOR,.fops=&fops,};staticstructi2c_client*pcli;staticstructi2c_device_idid_table[]={{.name="ptlm75"},{}};staticstructof_device_idof_table[]={{.compatible="pt,lm75"},{}};staticstructi2c_driverlm75_drv={.probe=myprob,.remove=myremove,.id_table=id_table,.driver={.name="ptlm75",.owner=THIS_MODULE,.of_match_table=of_table,},};/*---------------------------open函数-----------------------------------------*/staticintmyopen(structinode*pnode,structfile*f){pr_info("open call...\n");return0;}/*---------------------------close函数-----------------------------------------*/staticintmyrelease(structinode*pnode,structfile*f){pr_info("close call...\n");return0;}/*---------------------------read函数-----------------------------------------*/staticssize_tmyread(structfile*f,char__user*data,size_tsize,loff_t*off){//pr_info("open call...\n");charreg_addr=0x00;charbuf[2]={0};intret=0;i2c_master_send(pcli,®_addr,1);i2c_master_recv(pcli,buf,2);ret=copy_to_user(data,buf,2);if(ret){pr_info("copy_to_user error\n");returnret;}returnsizeof(buf);}/*---------------------------probe函数-----------------------------------------*/staticintmyprob(structi2c_client*cli,conststructi2c_device_id*id){intret=0;pcli=cli;ret=misc_register(&misc_dev);if(ret<0){pr_err("misc_register error\n");returnret;}pr_info("lm75 probe ok\n");return0;}/*---------------------------remove函数-----------------------------------------*/staticintmyremove(structi2c_client*pcli){misc_deregister(&misc_dev);return0;}/*---------------------------初始化-----------------------------------------*/module_i2c_driver(lm75_drv);MODULE_LICENSE("GPL");六、BH1750光照传感器
6.1 BH1750传感器参数
I2C从机地址(ADDR引脚决定)
ADDR引脚电平 7位I2C地址 8位写地址 8位读地址 L(接GND) 0x23 0x46 0x47 H(接VCC) 0x5C 0xB8 0xB9 注意:Linux i2c驱动传入7位地址;ADDR引脚不可悬空。
指令集(Opecode,直接I2C发送)
指令名称 操作码(十六进制) 功能说明 Power Down 0x00 掉电模式,无测量,芯片休眠 Power On 0x01 上电,等待测量命令 Reset 0x07 重置数据寄存器,掉电模式下无效 连续高分辨率模式 0x10 1lx分辨率,典型120ms测量时间,连续采集 连续高分辨率模式2 0x11 0.5lx分辨率,典型120ms测量时间,连续采集 连续低分辨率模式 0x13 4lx分辨率,典型16ms测量时间,连续采集 单次高分辨率模式 0x20 1lx,测量完成自动进入掉电 单次高分辨率模式2 0x21 0.5lx,测量完成自动进入掉电 单次低分辨率模式 0x23 4lx,测量完成自动进入掉电 修改测量时间高字节 0x40~0x47 MTreg高3位 修改测量时间低字节 0x60~0x7F MTreg低5位 测量模式参数
测量模式 分辨率 典型测量时间 特点 H‑Resolution Mode 1 lx 120ms 推荐使用,抑制50/60Hz灯光噪声,适合暗光>10lx H‑Resolution Mode2 0.5 lx 120ms 适合暗光小于10lx,最低可测0.11lx L‑Resolution Mode 4 lx 16ms 速度快,噪声抑制弱 I2C读写时序要点
- 写命令格式:起始信号 → 从机地址+写位 → ACK → 指令字节 → ACK → 停止信号
⚠️每发送1条指令必须发送STOP,不支持一条传输多条指令。
- 读数据格式:起始信号 → 从机地址+读位 → ACK →高8字节→ ACK →低8字节→ NACK → STOP
- 光照数据是16bit大端数据:高字节在前,低字节在后。
- 写命令格式:起始信号 → 从机地址+写位 → ACK → 指令字节 → ACK → 停止信号
照度计算公式
- H‑Resolution Mode:
照度(lx) = (原始16bit数值) / 1.2 - H‑Resolution Mode2:
照度(lx) = (原始16bit数值) / 1.2 / 2
原始数值 = (高字节 << 8) | 低字节
- H‑Resolution Mode:
芯片上电状态与状态流转
- VCC、DVI上电后,默认处于Power Down掉电模式。
- 单次测量模式测量结束自动回到Power Down;连续模式持续测量。
- Reset(0x07)指令只清除数据寄存器,Power Down模式下Reset指令无效。
- DVI引脚:I2C逻辑参考电压,同时是异步复位引脚;上电必须保证DVI拉低≥1μs完成内部复位。
等待时间(代码延时必须遵守)
模式 最大等待时间 H‑Resolution / H‑Resolution Mode2 180 ms L‑Resolution Mode 24 ms 发送测量指令后,必须等待对应最大时间,再读取I2C数据,否则读出旧值。
驱动代码编写注意
- DVI引脚:上电需要至少1μs低电平复位;DVI电压范围1.65 ~ VCC。
- 单次模式读完数据芯片自动休眠,下一次测量必须重新发送测量命令。
- Reset命令在Power Down模式下不起作用,需要先Power On。
- I2C最高时钟400KHz。
- ADDR引脚禁止悬空,必须接VCC或者GND。
- 不允许连续发送多条指令而不发送STOP停止信号。
- 读取返回两字节,高字节在前,不要颠倒大小端。
6.2 BH1750驱动编写
6.2.1 设备树节点添加
6.2.1 驱动代码编写
流程与lm75整体是极其相似的,这里就不赘述了。
#include<asm/delay.h>#include<asm/uaccess.h>#include<linux/cdev.h>#include<linux/device.h>#include<linux/fs.h>#include<linux/i2c.h>#include<linux/init.h>#include<linux/miscdevice.h>#include<linux/module.h>#include<linux/of_gpio.h>#include<linux/platform_device.h>#include<linux/time.h>externvoidmsleep(unsignedintmsecs);structi2c_client*pbh1750_client;// 驱动层read函数staticssize_tbh1750_read(structfile*fp,char__user*puser,size_tn,loff_t*off){structi2c_msgmsg;unsignedcharbuff[2]={0};// buff[0]:high buff[1]:lowshortdata=0;intret=0;unsignedlongnret=0;// 1. 发送通电指令buff[0]=0x01;msg.addr=pbh1750_client->addr;msg.flags=0;msg.buf=buff;msg.len=1;ret=pbh1750_client->adapter->algo->master_xfer(pbh1750_client->adapter,&msg,1);if(ret<0){pr_info("[driver]:master_xfer_power_on failed: %d\n",ret);returnret;}// 2. 发送连续高分辨率测量模式buff[0]=0x10;msg.addr=pbh1750_client->addr;msg.flags=0;msg.buf=buff;msg.len=1;ret=pbh1750_client->adapter->algo->master_xfer(pbh1750_client->adapter,&msg,1);if(ret<0){pr_info("[driver]:master_xfer_mode failed: %d\n",ret);returnret;}// 3. 等待测量完成msleep(180);// 4. 读取2个字节光照强度msg.addr=pbh1750_client->addr;msg.flags|=I2C_M_RD;msg.buf=buff;msg.len=2;ret=pbh1750_client->adapter->algo->master_xfer(pbh1750_client->adapter,&msg,1);if(ret<0){pr_info("[driver]:master_xfer_read failed: %d\n",ret);returnret;}// 5. 光强原始数据data=(buff[0]<<8)|buff[1];// 6. 拷贝给用户层nret=copy_to_user(puser,&data,sizeof(data));if(nret){pr_info("[driver]:copy_to_user failed!\n");returnnret;}returnsizeof(data);}// 驱动层操作函数集合staticstructfile_operationsfops={.read=bh1750_read,};// 杂项设备结构体 /dev/misc_bh1750staticstructmiscdevicemisc_bh1750={.minor=MISC_DYNAMIC_MINOR,.name="misc_bh1750",.fops=&fops,};// probe函数staticintbh1750_probe(structi2c_client*pclient,conststructi2c_device_id*pdevice){intret=0;// 1. 注册杂项设备(10 自动生成设备节点)ret=misc_register(&misc_bh1750);if(ret){pr_info("[driver]:misc_register failed!\n");returnret;}// 2. i2c_client赋值pbh1750_client=pclient;// 3. 打印pr_info("bh1750_probe successful!\n");return0;}// remove函数staticintbh1750_remove(structi2c_client*pclient){// 1. 注销杂项设备misc_deregister(&misc_bh1750);// 2. 打印pr_info("bh1750_remove successful!\n");return0;}// idtable匹配structi2c_device_iddevice_table[]={{.name="putebh1750"},{},};// 设备树匹配structof_device_idbh1750_table[]={{.compatible="pute,bh1750"},{},};// i2c_driver结构体staticstructi2c_driverbh1750_driver={.probe=bh1750_probe,.remove=bh1750_remove,.driver={.name="putebh1750",.owner=THIS_MODULE,.of_match_table=bh1750_table,},.id_table=device_table,};module_i2c_driver(bh1750_driver);// i2c_add_driver + i2c_del_driverMODULE_LICENSE("GPL");MODULE_AUTHOR("PUTE");