2025-03-09
i2c是嵌入式开发的常用协议,下面我们学习通过Linux驱动来读写i2c设备的数据。通过设备树来获取i2c的信息,使用Linux内核的i2c子系统提供的接口来控制读写数据。
raspberry pi3b
Linux raspberrypi 6.1.21-v8+ #1642 SMP PREEMPT Mon Apr  3 17:24:16 BST 2023 aarch64 GNU/Linux
// testoverlay.dts
/dts-v1/;
/plugin/;
/ {
    compatible = "raspberrypi,3-model-bbrcm,bcm2837";
    fragment@0 {
        target = <&i2c1>;
        __overlay__ {
            #address-cells = <1>;
            #size-cells = <0>;
            my_adc: my_adc@10 {
                compatible = "brightlight,myadc";
                reg = <0x10>;
                status = "okay";
            };
        };
    };
};
// simple.c
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/proc_fs.h>
#include <linux/property.h>
static struct i2c_client *adc_client = NULL;
static struct proc_dir_entry *proc_file = NULL;
static struct of_device_id my_driver_ids[] = {
    {
        .compatible = "brightlight,myadc",
    },
    {},
};
MODULE_DEVICE_TABLE(of, my_driver_ids);
static struct i2c_device_id my_adc[] = {
    {"my_adc", 0},
    {},
};
MODULE_DEVICE_TABLE(i2c, my_adc);
static ssize_t _read(struct file *file, char __user *userbuf, size_t size,
                     loff_t *offset) {
    pr_info("simple_read\n");
    char buf[32];
    int value = i2c_smbus_read_byte(adc_client);
    int len = snprintf(buf, sizeof(buf), "%d\n", value);
    if (copy_to_user(userbuf, buf, len)) {
        return -EFAULT;
    }
    return len;
}
static ssize_t _write(struct file *file, const char __user *userbuf,
                      size_t size, loff_t *offset) {
    pr_info("simple_write\n");
    long val = 0;
    if (0 == kstrtol(userbuf, 0, &val)) {
        i2c_smbus_write_byte(adc_client, (unsigned char)val);
    }
    return size;
}
static struct proc_ops fops = {
    .proc_read = _read,
    .proc_write = _write,
};
// 要求i2c设备真实存在,且匹配成功才会调用该函数
static int _dt_probe(struct i2c_client *client, const struct i2c_device_id *id) {
    pr_info("simple_dt_probe\n");
    if (client->addr != 0x10) {
        pr_err("error: client addr != 10\n");
        return -1;
    }
    adc_client = client;
    proc_file = proc_create("my-adc", 0666, NULL, &fops);
    if (!proc_file) {
        pr_err("proc_create /proc/my-adc file failed\n");
        return -ENOMEM;
    }
    return 0;
}
static void _dt_remove(struct i2c_client *client) {
    pr_info("simple_dt_remove\n");
    proc_remove(proc_file);
}
static struct i2c_driver my_dirver = {
    .probe = _dt_probe,
    .remove = _dt_remove,
    .id_table = my_adc,
    .driver =
        {
            .name = "my_adc",
            .of_match_table = my_driver_ids,
        },
};
// 这里会调用init和exit函数
module_i2c_driver(my_dirver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("heng30");
MODULE_VERSION("v0.0.1");
MODULE_DESCRIPTION("A simple kernel module");
#!/bin/sh
top-dir = $(shell pwd)
kernel-version = $(shell uname -r)
kernel-dir ?= /lib/modules/$(kernel-version)/build
obj-m += simple.o
all: dt driver
driver:
        make -C $(kernel-dir) modules M=$(top-dir)
dt:
        dtc -@ -I dts -O dtb -o testoverlay.dtbo testoverlay.dts
clean:
        rm -f *.o *.ko *.mod *.mod.c *.order *.symvers *.dtbo
        make -C $(kernel-dir) clean m=$(top-dir)
安装i2c-tools:sudo apt install i2c-tools
查看所有i2c总线:i2cdetect -l
查看总线1上的i2c设备:i2cdetect -y 1
编译程序:make
安装设备树节点:sudo dtoverlay testoverlay.dtbo
查看设备树节点信息
find /proc/device-tree/ -name "*adc*"/proc/device-tree/soc/i2c@7e804000/my_adc@10
ls /proc/device-tree/soc/i2c@7e804000/my_adc@10compatible  name  phandle  reg  status
安装驱动:insmod simple.ko
写入数据:echo 1 > /proc/my-adc
读取数据:head -n 1 /proc/my-adc
移除驱动:rmmod simple.ko
移除设备树节点:sudo dtoverlay -r testoverlay