☰
【I2C设备驱动专题】概念介绍、与LM75、BH1750传感器实现
2026/10/23 4:57:32 网站建设 项目流程

文章目录

    • 前言
    • 一、核心属性
    • 二、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 Plus1 Mbps高速 ADC、显示屏
High Speed3.4 Mbps高端摄像头、高清外设
Ultra Fast5 Mbps单向专用场景

注:大部分传感器都采用100k的速率。

二、I2C的时序

  1. 起始信号 START(S)
    总线空闲时 SCL、 SDA 均为高; SCL 保持高电平, SDA 由高跳低,宣告一次通信开始,总线进入忙状态。
  2. 停止信号 STOP(P)
    SCL 保持高电平, SDA 由低跳高,通信结束,总线回到空闲高电平状态。
  3. 重复起始 Repeated START(Sr)
    不发送 STOP,直接再次触发 START;常用于先写寄存器地址、再读数据的场景,避免释放总线被其他主机抢占。
  4. 应答位 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寻址体系

  1. 7 位标准地址(最常用)
    START 后先发 8bit:
    高位先行;
    [bit7~bit1:7位从机地址]+[bit0: R/W读写位]
    R/W=0:主机写数据到从机
    R/W=1:主机读从机数据
    总线最多容纳 (2^7=128) 个地址,部分地址为协议保留专用(0x00、 0x01 等),实际可用 112 个左右从设备。
  2. 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_adapteri2c适配器(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 驱动代码编写
  1. module_i2c_driver注册LM75传感器设备;

  2. 创建i2c_driver结构体,填入module_i2c_driver;

  3. 在probe中, 创建杂项设备;

  4. 编写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,&reg_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传感器参数

  1. I2C从机地址(ADDR引脚决定)

    ADDR引脚电平7位I2C地址8位写地址8位读地址
    L(接GND)0x230x460x47
    H(接VCC)0x5C0xB80xB9

    注意:Linux i2c驱动传入7位地址;ADDR引脚不可悬空。

  2. 指令集(Opecode,直接I2C发送)

    指令名称操作码(十六进制)功能说明
    Power Down0x00掉电模式,无测量,芯片休眠
    Power On0x01上电,等待测量命令
    Reset0x07重置数据寄存器,掉电模式下无效
    连续高分辨率模式0x101lx分辨率,典型120ms测量时间,连续采集
    连续高分辨率模式20x110.5lx分辨率,典型120ms测量时间,连续采集
    连续低分辨率模式0x134lx分辨率,典型16ms测量时间,连续采集
    单次高分辨率模式0x201lx,测量完成自动进入掉电
    单次高分辨率模式20x210.5lx,测量完成自动进入掉电
    单次低分辨率模式0x234lx,测量完成自动进入掉电
    修改测量时间高字节0x40~0x47MTreg高3位
    修改测量时间低字节0x60~0x7FMTreg低5位
  3. 测量模式参数

    测量模式分辨率典型测量时间特点
    H‑Resolution Mode1 lx120ms推荐使用,抑制50/60Hz灯光噪声,适合暗光>10lx
    H‑Resolution Mode20.5 lx120ms适合暗光小于10lx,最低可测0.11lx
    L‑Resolution Mode4 lx16ms速度快,噪声抑制弱
  4. I2C读写时序要点

    • 写命令格式:起始信号 → 从机地址+写位 → ACK → 指令字节 → ACK → 停止信号

      ⚠️每发送1条指令必须发送STOP,不支持一条传输多条指令。

    • 读数据格式:起始信号 → 从机地址+读位 → ACK →高8字节→ ACK →低8字节→ NACK → STOP
    • 光照数据是16bit大端数据:高字节在前,低字节在后。
  5. 照度计算公式

    • H‑Resolution Mode:
      照度(lx) = (原始16bit数值) / 1.2
    • H‑Resolution Mode2:
      照度(lx) = (原始16bit数值) / 1.2 / 2

    原始数值 = (高字节 << 8) | 低字节

  6. 芯片上电状态与状态流转

    • VCC、DVI上电后,默认处于Power Down掉电模式。
    • 单次测量模式测量结束自动回到Power Down;连续模式持续测量。
    • Reset(0x07)指令只清除数据寄存器,Power Down模式下Reset指令无效。
    • DVI引脚:I2C逻辑参考电压,同时是异步复位引脚;上电必须保证DVI拉低≥1μs完成内部复位。
  7. 等待时间(代码延时必须遵守)

    模式最大等待时间
    H‑Resolution / H‑Resolution Mode2180 ms
    L‑Resolution Mode24 ms

    发送测量指令后,必须等待对应最大时间,再读取I2C数据,否则读出旧值。

  8. 驱动代码编写注意

    • 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");

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询