BH1750光照采集(I2C子系统 + platform框架 + 字符设备驱动)

BH1750 的基本信息

  • 工作电压:3V - 5V
  • I2C 地址:默认 0x23 或 0x5C(具体取决于地址引脚配置)
  • 测量范围:1 - 65535 Lux(高分辨率模式)
  • 精度:高分辨率模式 1 Lux,低分辨率模式 4 Lux
  • 响应时间:高分辨率模式 120ms,低分辨率模式 16ms
  • 工作模式
    • 连续高分辨率模式:每次读取数据,分辨率为 1 Lux,周期为 120ms
    • 一次性高分辨率模式:用户触发读取一次数据,分辨率为 1 Lux,周期为 120ms
    • 连续低分辨率模式:每次读取数据,分辨率为 4 Lux,周期为 16ms
    • 关机模式:节省功耗时,传感器关闭

BH1750 数据转换

BH1750 返回的光照值是一个 16 位无符号整数,表示传感器测量的光照强度。其高 8 位和低 8 位分别传输。

  • I2C 数据大端格式传输的。即高位字节在前,低位字节在后。

    • 高字节(16 位数据的高 8 位)
    • 低字节(16 位数据的低 8 位)
数据转换

BH1750 输出的原始数据需要进行字节序转换(因为 BH1750 是大端格式,RK3568是小端,然后计算实际的 Lux 值。

  • Lux 计算公式lux = (0x03 << 8) | 0xE8 = 0x03E8 = 1000 Lux

模式选择和数据更新
  • 高分辨率模式:数据的更新周期为 120ms,每次读取返回 1 Lux 精度的值。
  • 低分辨率模式:数据的更新周期为 16ms,每次读取返回 4 Lux 精度的值。

BH1750 驱动全流程

1. 确定硬件接口

BH1750 通过 I2C 接口 与主机通信,主要寄存器:

  • 0x23 或 0x5C(I2C 地址)
  • 命令寄存器(常用命令):
    • 0x10连续高分辨率模式(1lx 分辨率,120ms)
    • 0x20一次性高分辨率模式(1lx 分辨率,120ms)
    • 0x13连续低分辨率模式(4lx 分辨率,16ms)

2. 实现 BH1750 驱动

(1) BH1750设备结构体
#define BH1750_ADDR  0x23   // BH1750 I2C 地址
#define BH1750_POWER_ON  0x01   // 开启 BH1750
#define BH1750_CONT_H_RES_MODE  0x10  // 连续高分辨率模式

// 设备结构体
struct bh1750_dev {
    struct i2c_client *client;
    struct cdev cdev;
    dev_t devt;
    struct class *class;
};

(2) I2C 读写函数
// 发送命令到 BH1750
static int bh1750_write_cmd(struct i2c_client *client, u8 cmd)
{
    return i2c_smbus_write_byte(client, cmd);
}

// 读取光照值(16bit)
static int bh1750_read_lux(struct i2c_client *client)
{
    int lux = i2c_smbus_read_word_data(client, BH1750_CONT_H_RES_MODE);
    if (lux < 0)
        return lux;

    // BH1750 数据是 Big-Endian 需要交换字节序
    return ((lux & 0xFF) << 8) | (lux >> 8);
}

(3) 字符设备 read() 实现
static ssize_t bh1750_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    struct bh1750_dev *dev = filp->private_data;
    int lux;
    char kbuf[6];  // 存放字符串

    lux = bh1750_read_lux(dev->client);
    if (lux < 0)
        return lux;

    snprintf(kbuf, sizeof(kbuf), "%d\n", lux);
    if (copy_to_user(buf, kbuf, strlen(kbuf)))
        return -EFAULT;

    return strlen(kbuf);
}

(4) 文件操作集
static const struct file_operations bh1750_fops = {
    .owner = THIS_MODULE,
    .read = bh1750_read,
    .open = simple_open,
};

(5) Probe函数
static int bh1750_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    struct bh1750_dev *dev;
    int ret;

    dev = devm_kzalloc(&client->dev, sizeof(struct bh1750_dev), GFP_KERNEL);
    if (!dev)
        return -ENOMEM;

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

    // 发送开机指令
    ret = bh1750_write_cmd(client, BH1750_POWER_ON);
    if (ret < 0) {
        dev_err(&client->dev, "Failed to power on BH1750\n");
        return ret;
    }

    // 注册字符设备
    ret = alloc_chrdev_region(&dev->devt, 0, 1, "bh1750");
    if (ret < 0)
        return ret;

    cdev_init(&dev->cdev, &bh1750_fops);
    ret = cdev_add(&dev->cdev, dev->devt, 1);
    if (ret < 0)
        goto unregister_chrdev;

    dev->class = class_create(THIS_MODULE, "bh1750");
    device_create(dev->class, NULL, dev->devt, NULL, "bh1750");

    return 0;

unregister_chrdev:
    unregister_chrdev_region(dev->devt, 1);
    return ret;
}

(6) Remove函数
static int bh1750_remove(struct i2c_client *client)
{
    struct bh1750_dev *dev = i2c_get_clientdata(client);

    device_destroy(dev->class, dev->devt);
    class_destroy(dev->class);
    cdev_del(&dev->cdev);
    unregister_chrdev_region(dev->devt, 1);

    return 0;
}

(7) I2C 设备 ID 及驱动结构体
static const struct i2c_device_id bh1750_id[] = {
    { "bh1750", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, bh1750_id);

static struct i2c_driver bh1750_driver = {
    .driver = {
        .name = "bh1750",
    },
    .probe = bh1750_probe,
    .remove = bh1750_remove,
    .id_table = bh1750_id,
};

3. 动态编译、加载驱动

(1) 添加到 Makefile

obj-m += bh1750.o

(2) 编译驱动

make modules

make modules 命令会编译内核源代码树中的所有内核模块,并生成 .ko 文件

(3) 加载驱动

insmod bh1750.ko

(4) 创建设备节点

mknod /dev/bh1750 c 240 0 # (假设主设备号 240)

(5) 修改设备树

对于 BH1750 光照传感器这样的 I2C 设备,你需要在设备树中指定它的 I2C 地址引脚驱动信息等内容,确保内核能够识别并正确加载相应的驱动。

BH1750 设备树修改流程

1. 修改设备树文件

arch/arm64/boot/dts/ 目录下,

&i2c1 {
    status = "okay";
    
    bh1750@23 {
        compatible = "bh1750";
        reg = <0x23>;  // BH1750 默认的 I2C 地址
        status = "okay";
    };
};
2. 编译设备树

修改完成后,需要重新编译设备树,并将新的设备树文件加载到系统中。

make dtbs # 编译设备树文件

将编译生成的 .dtb 文件放置到NFS,并确保启动时加载新的设备树(bootagers)。

3. 驱动和设备树的关联

设备树中的 compatible 字段会与驱动程序的 of_match_table 进行匹配,以便加载相应的驱动程序。

为什么 I2C 不能直接使用中断读取数据

  1. 同步性与轮询机制

    • I2C 是同步通信协议,这意味着数据的发送和接收是由时钟信号(SCL)同步的,数据传输是由主设备控制的,从设备只能响应主设备的请求。
    • 因为从设备在 I2C 协议中没有主动推送数据的机制,不能像 SPIUART 等协议一样,主动向主设备发送数据并触发中断。换句话说,从设备不能在没有主设备请求的情况下主动中断并发送数据。
  2. 中断与异步通信的区别

    • 中断 是一种 异步 通信机制,它不依赖于特定的时序或控制信号,设备在某个事件发生时可以通过中断机制通知处理器进行相应操作。
    • 然而,I2C 设备无法主动发起中断,因为它的工作模式是 主设备驱动的同步请求-响应模型。只有当主设备发出读取命令时,从设备才会返回数据。因此,I2C 通信的模式与中断式的异步通信是完全不兼容的。

可优化点

  • 如何减少 CPU 开销? → 可以使用 I2C DMA 模式批量读取数据,避免 CPU 轮询。
  • 如何让 BH1750 更省电? → 进入 0x00 关机模式,只有在需要时才 POWER_ON
  • 如果数据不稳定,如何优化? → 读取多次数据,滤波处理数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值