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@10
compatible 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