前言
和bmp280一样,使用i2c驱动注册。需要注意的是,设备reset需要15ms,可以使用读出来的第三个字节作为CRC8校验码,进行数据校验。另外,温湿度计算公式如果使用小数,可能编译器默认的参数不支持,可以将小数转化为整数进行计算。
另外,htu21d的i2c支持两种模式,hold master模式支持SCL stretching,更加方便我们主机端读取数据,设备会在数据ADC采集完成之前,控制SCL,不让master继续后续操作,再数据完成采集后,再释放SCL,master才进行后续读取操作。另外一个模式,需要我们自己去轮询读取,指导读取到有ACK的字节,才算htu21d数据准备完毕。但是hold master有一个比较严重的问题,如果SCL stretching期间,htu21d掉电的话,master i2c bus会被占用,导致后续都没办法再继续操作了。
CRC-8
需要确定CRC使用的多项式,多项式的计算公式是依据每一项的指数排序,存在指数的项依次用1填充一个字节的一位,没有的项用0填充。比如下面的多项式:
x^8 + x^5 + x^4 + 1
那么x8 应该填充第8位,x5 应该填充第5位,依次类推,最后的+1,应该是 x0 ,最终得到的数是 100110001
,这时得到一个9位数的值,因为只需要一个字节,因此去掉第8位,得到 00110001
(0x31)。
crc值会有一个默认初始值,htu21d要求的crc初始值是0。将crc值依次与数据的每个字节异或得到新的crc。在异或了一个字节得到新crc后,需要将当时的crc值向左移位,当最高位为1时,则将当前移位后的值与上述计算的多项式得到的值进行异或后赋给crc,如果最高位不为1,则将移位后的值赋给crc。一个字节8位都移位完成后,crc又与下一个字节异或,继续上面的操作,直到最后一个字节完成,就得到了最终的crc值。
/* x^8 + x^5 + x^4 + 1: 100110001 (00110001 -> 0x31) */
static u8 crc8_calcu(u8 *data, size_t len)
{
/* init value for htu21d: 0x0 */
u8 crc = 0x0;
int i, j;
for (i = 0; i < len; i++) {
crc ^= data[i];
for (j = 0; j < 8; j++) {
crc = (crc & 0x80) ? ((crc << 1) ^ 0x31) : (crc << 1);
}
}
return crc;
}
设备属性
在提供温湿度接口时,我们这里使用最简单的sysfs接口提供温湿度属性值,或者也可以使用字符设备驱动来完成。。一般温湿度传感器是使用iio子系统或者input子系统来提供用户接口。后续做到相应章节再实现iio和input子系统方式的接口。
总线,设备,驱动,类等内核对象都是拥有属性接口的。可以直接属性定义的宏,定义内核某个对象的属性借口,实现属性的读写功能。读属性为show,写属性为store。设备的属性接口使用以下宏进行定义:
#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
static DEVICE_ATTR(temperature, 0644, bmp280_temperature_show,
bmp280_temperature_store);
定义属性的名字,sysfs文件节点的权限,读写方法。当属性只读或者只写时,建议不要使用这个接口,可能回到只系统报oops。比如我不支持写,那么我给store传参会是NULL。insmod的时候回报一堆错误警告及堆栈信息出来。
定义完成后,使用 device_create_file
函数创建文件节点。
只读或者只写使用以下宏:
#define DEVICE_ATTR_RO(_name) \
struct device_attribute dev_attr_##_name = __ATTR_RO(_name)
#define DEVICE_ATTR_WO(_name) \
struct device_attribute dev_attr_##_name = __ATTR_WO(_name)
在用户空间,将生成 temperature 和 humidity 两个属性节点,权限只读。可以使用cat读取设备节点,将分别读取到传感器的温度数据和湿度数据。如果支持写权限的话,可以使用echo命令进行写入。这里两个节点是以字符串的格式进行输出的,如果属性节点是输出二进制数据的,可以使用xxd命令进行读取。
设备树配置
&i2c4 {
status = "okay";
htu21d: htu21d@40 {
status = "okay";
compatible = "se,htu21d";
reg = <0x40>;
};
};
这里使用驱动和设备的匹配方式使用设备树风格的方式,会和驱动中 of_device_id
列表中的 compatible 字段进行匹配。可以尝试修改设备树中的compatible属性,设备树风格匹配将失败,继而使用 i2c_device_id 方式进行匹配,这个时候 i2c_device_id 匹配将匹配设备节点的名称,即 "htu21d"
。
驱动代码
#include "asm-generic/errno-base.h"
#include "linux/device.h"
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/delay.h>
struct htu21d_dev {
struct i2c_client *client;
s32 temperature;
u32 humidity;
};
/* x^8 + x^5 + x^4 + 1: 100110001 (00110001 -> 0x31) */
static u8 crc8_calcu(u8 *data, size_t len)
{
/* init value for htu21d: 0x0 */
u8 crc = 0x0;
int i, j;
for (i