项目:I2C 传感器驱动
编写一个完整的 I2C 设备驱动,读写传感器。
一、目标
- 在 Linux I2C 子系统下注册设备驱动
- 读 / 写 I2C 设备寄存器
- 用户态通过 sysfs / ioctl 访问
- 示例:温湿度传感器(如 HTU21D / SHT30)
二、硬件:HTU21D
- I2C 接口,地址 0x40
- 温度命令 0xF3(hold master)、0x78(no hold)
- 湿度命令 0xF5(hold master)、0x79(no hold)
- 14-bit 精度
三、设备树
&i2c1 {
clock-frequency = <400000>;
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
htu21d: htu21d@40 {
compatible = "myvendor,htu21d";
reg = <0x40>;
};
};
四、驱动
// drivers/i2c/chips/htu21d.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/delay.h>
struct htu21d_data {
struct i2c_client *client;
struct mutex lock;
};
static int htu21d_read(struct i2c_client *client, u8 cmd, u16 *value)
{
int ret;
u8 buf[3];
ret = i2c_smbus_write_byte(client, cmd);
if (ret < 0) return ret;
msleep(50); // 等待转换
ret = i2c_master_recv(client, buf, 3);
if (ret < 0) return ret;
if (buf[2] & 0x02) {
// CRC 错误(简化:忽略)
}
*value = ((u16)buf[0] << 8) | buf[1];
*value &= 0xFFFC; // 状态位清零
return 0;
}
static int htu21d_read_temperature(struct htu21d_data *data, int *temp)
{
u16 raw;
int ret = htu21d_read(data->client, 0xF3, &raw);
if (ret) return ret;
// T = -46.85 + 175.72 * (raw / 65536)
*temp = (-46850 + 175720 * raw / 65536); // 0.01°C
return 0;
}
static int htu21d_read_humidity(struct htu21d_data *data, int *hum)
{
u16 raw;
int ret = htu21d_read(data->client, 0xF5, &raw);
if (ret) return ret;
// RH = -6 + 125 * (raw / 65536)
*hum = (-600 + 125000 * raw / 65536); // 0.01%
return 0;
}
// sysfs 接口
static ssize_t temp_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
struct htu21d_data *data = i2c_get_clientdata(client);
int t;
mutex_lock(&data->lock);
htu21d_read_temperature(data, &t);
mutex_unlock(&data->lock);
return sprintf(buf, "%d.%02d\n", t / 100, abs(t % 100));
}
static ssize_t hum_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
struct htu21d_data *data = i2c_get_clientdata(client);
int h;
mutex_lock(&data->lock);
htu21d_read_humidity(data, &h);
mutex_unlock(&data->lock);
return sprintf(buf, "%d.%02d\n", h / 100, abs(h % 100));
}
static DEVICE_ATTR_RO(temp);
static DEVICE_ATTR_RO(hum);
static int htu21d_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct htu21d_data *data;
int ret;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
return -ENODEV;
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
if (!data) return -ENOMEM;
data->client = client;
mutex_init(&data->lock);
i2c_set_clientdata(client, data);
// 软复位
i2c_smbus_write_byte(client, 0xFE);
msleep(20);
// 创建设备属性
ret = device_create_file(&client->dev, &dev_attr_temp);
if (ret) return ret;
ret = device_create_file(&client->dev, &dev_attr_hum);
if (ret) { device_remove_file(&client->dev, &dev_attr_temp); return ret; }
dev_info(&client->dev, "HTU21D probed\n");
return 0;
}
static int htu21d_remove(struct i2c_client *client)
{
device_remove_file(&client->dev, &dev_attr_temp);
device_remove_file(&client->dev, &dev_attr_hum);
return 0;
}
static const struct i2c_device_id htu21d_id[] = {
{ "htu21d", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, htu21d_id);
static const struct of_device_id htu21d_of[] = {
{ .compatible = "myvendor,htu21d" },
{ }
};
MODULE_DEVICE_TABLE(of, htu21d_of);
static struct i2c_driver htu21d_driver = {
.driver = {
.name = "htu21d",
.of_match_table = htu21d_of,
},
.probe = htu21d_probe,
.remove = htu21d_remove,
.id_table = htu21d_id,
};
module_i2c_driver(htu21d_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("HTU21D temperature/humidity sensor driver");
五、Makefile
obj-m += htu21d.o
六、用户态测试
# 加载驱动
insmod htu21d.ko
# 看 sysfs
ls /sys/bus/i2c/devices/1-0040/
# 读温度
cat /sys/bus/i2c/devices/1-0040/temp
# 输出:23.45
# 读湿度
cat /sys/bus/i2c/devices/1-0040/hum
# 输出:56.78
七、IIO 子系统(更专业)
实际产品中建议用 IIO(Industrial I/O)框架:
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
static const struct iio_chan_spec htu21d_channels[] = {
{
.type = IIO_TEMP,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
.output = 0,
.indexed = 1,
.channel = 0,
},
{
.type = IIO_HUMIDITYRELATIVE,
.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
.indexed = 1,
.channel = 0,
},
};
static int htu21d_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct htu21d_data *data = iio_priv(indio_dev);
int tmp;
switch (mask) {
case IIO_CHAN_INFO_PROCESSED:
switch (chan->type) {
case IIO_TEMP:
htu21d_read_temperature(data, &tmp);
*val = tmp;
*val2 = 10000;
return IIO_VAL_INT_PLUS_MICRO;
case IIO_HUMIDITYRELATIVE:
htu21d_read_humidity(data, &tmp);
*val = tmp;
*val2 = 10000;
return IIO_VAL_INT_PLUS_MICRO;
}
break;
}
return -EINVAL;
}
static const struct iio_info htu21d_info = {
.read_raw = htu21d_read_raw,
};
用户态:
# IIO 工具
iio_info
iio_readdev -u local:htu21d -s
# libiio / iio-tools