从零开始学习linux的I2C设备驱动框架——写一个简单的SHT20驱动

0.测试环境说明

Linux的I2C子系统资料遍地都是,但是单看资料是不会明白驱动如何写的,所以选择了一个简单的器件,针对该器件有目的的写一个驱动实现最基本的器件设置和数据读取。

SHT20是一个采用标准I2C协议通信的温湿度传感器,其精度和特性在本次学习中无需关注。驱动基于的开发板环境如下表所示:

环境参数
开发板型号荣品RP-DV300B
芯片型号Hi3516DV300
Kernel Version4.9.37 SMP
SDKHi3516CV500_SDK_V2.0.1.0

1.设备树的修改

linux4.9.37版本使用设备树描述板级连接,在 /linux-4.9.37-smp/arch/arm/boot/dts 目录下的 hi3516dv300-demb.dts 文件中添加节点。

设备树的修改

这里需要注意的是设备I2C地址使用的是不包含读写位的7位地址,I2C的频率应当适配硬件,SHT20支持的最高SCL频率为0.4MHz,这在手册上可以找到。

很多资料都是这样修改设备树的,但是这样修改之后,编译内核时出现如下警告(对设备树不是很了解,所以放任不管),这个警告并不影响器件运作。

内核编译的警告

2.设备驱动框架

SHT20挂接在I2C总线上,但是从设备类型看,依旧属于字符设备。从整体来看,驱动程序使用 i2c_add_driver()i2c_del_driver() 函数实现设备在I2C总线上的挂载和卸载,使用 cdev_init()cdev_add()cdev_del() 函数实现对字符设备的注册和注销。

I2C核心实现设备的匹配,执行 .probe().remove() ,在 .probe() 函数中进行字符设备注册,将文件操作结构体填充并绑定到注册的字符设备上。这样对字符设备的访问便会从文件读写函数,通过 i2c_transfer() 向I2C适配器提交 struct i2c_msg 结构体报文实现。

多说无益,来看伪代码:

static int sht2x_open(struct inode *inode,struct file *filp)
{
    filp->private_data=st_dev.client;
    
    /*此处初始化SHT2x器件(软复位)*/

    return 0;
}

static int sht2x_read(struct file *filp,char __user *buf,size_t count,loff_t *f_pos)
{
    struct i2c_client *client = (struct i2c_client *)filp->private_data;
    
    /*对传感器数据进行读取*/
    
    copy_to_user(...);
    
    /*读回调返回值为驱动读到的有效数据长度*/
    return count;
}

static int sht2x_release(struct inode *inode,struct file *filp)
{
    return 0;
}

static struct file_operations sht2x_fops=
{
    .owner         =THIS_MODULE,
    .read          =sht2x_read,
    .open          =sht2x_open,
    .release       =sht2x_release,
};

static int sht2x_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret;
    
    /*注册字符设备*/
    cdev_init(&st_dev.cdev,&sht2x_fops);
    cdev_add(&st_dev.cdev, ...);
    
    /*创建设备节点*/
    class_create(THIS_MODULE,"sht2xclass");
    device_create(dev_class,NULL,MKDEV(driver_major,0),NULL,"sht2x");
    
    st_dev.client=client;

	return 0;
FAIL:
    return ret;
}

static int sht2x_remove(struct i2c_client *client)
{
    /*删除字符设备*/
    cdev_del(&st_dev.cdev);
    
    /*删除节点*/
    device_unregister(dev_class_device);
    class_destroy(dev_class);
    
    return 0;
}

/*设备树方式*/
static struct of_device_id sht2x_of_match[] = {
    { .compatible = "sensirion,sht2x", .data = NULL },
    {}
};

static struct i2c_driver sht2x_driver =
{
    .driver =
    {
        .name  = "sht2x",
        .owner = THIS_MODULE,
        .of_match_table = sht2x_of_match,
    },
    .probe     = sht2x_probe,
    .remove    = sht2x_remove,
    .id_table  = sht2x_id,
};

static int __init sht2x_init(void)
{
    return i2c_add_driver(&sht2x_driver);
}
module_init(sht2x_init);

static void __exit sht2x_exit(void)
{
    i2c_del_driver(&sht2x_driver);
}
module_exit(sht2x_exit);

代码删除了大部分内容,但是依然可以看出上面所说的过程。这个过程对于学习过字符设备驱动的人来说非常简单。

3.I2C数据传输过程

3.1 struct i2c_msg

每一个 struct i2c_msg 结构体有自己的起始信号,在I2C适配器传输过程中,该结构体的数量取决于一次通信需要多少个起始信号。

I2C终止信号在一次 i2c_transfer() 函数调用完成后发送。在数据发送过程中,使用结构体的 .flag 标志位指定读写标志位。

static uint16_t i2c_read_reg(struct i2c_client *client, uint8_t reg, uint8_t *recv, uint32_t len)
{
	struct i2c_msg msg[2];
	
    /*首先使用IIC写模式传出要读取的寄存器地址*/
    msg[0].addr  = client->addr;
    msg[0].flags = 0;//0表示写
    msg[0].buf   = ®
    msg[0].len   = 1;
    
    /*然后使用IIC读模式读取从机传来的数据,具体长度由器件协议决定*/
    msg[1].addr = client->addr;
    msg[1].flags = I2C_M_RD;
    msg[1].buf = recv;//读取的数据存放的缓冲区指针
    msg[1].len = len;//要读取的长度由操作决定
    
    /*提交适配器执行消息动作*/
    ret = i2c_transfer(client->adapter, msg, 2);
}

3.2 SHT20的数据收发

SHT20的中英文手册网上都有,为了方便在文末提供下载链接。

由于只是实现最简单的温湿度读取功能,所以在驱动中只需要实现以下三个函数:

1.设备打开时进行软复位,软复位之后等待15ms
2.主机模式下的温度读取
3.主机模式下的湿度读取

这是因为用户寄存器复位之后的模式为最高分辨率,本次无需更改即可使用。

SHT20主机模式测量

SHT20软复位

上面两个图是从手册上摘出的。软件中只需要按照对应的命令写入数据即可。驱动程序将在文末【本文资源】中给出下载链接,就不在正文中粘贴了。

编写中对数据进行读取时需要注意两个细节。一是对读出的原始温湿度数据进行的处理,虽然上图中标明了Data位的MSB到LSB之间一共占据了14位,但是对高低位进行合并的时候却是高位对其的,手册中这样写道:

在进行物理换算时,后两位状态位应置‘0’

另一点需要注意的是,linux驱动中对浮点计算有问题,所以传感器输出的温湿度将在用户应用程序中进行计算。

4.I2C适配器超时等待时间的修改

在测试中,使用海思自带的I2C总线控制驱动进行数据的传输,在默认情况下,数据传输将出现如下问题:

iic超时

在测试应用中每一次数据读取会出现这样的提示:

hibvt-i2c 120b2000.i2c: wait rx no empty timeout, RIS: 0x10, SR: 0xa0000

经查,这个提示出现在 linux-4.9.37-smp/drivers/i2c/busses/i2c-hibvt.c 文件中:

i2c超时代码

这表示在等待50us*1024≈51ms没有收到数据后,对本次I2C读取判定超时,这与上面逻辑分析仪中测的的情况相符。由于SHT20在最高分辨率下温度的转换需要66ms(逻辑分析仪同样证实这一点),

SHT20测量时间

所以修复上面的错误可以采用两种方式进行:降低传感器测量分辨率或者增大I2C读超时时间,本文采取后者。

正常对SHT20的温度读取波形如下所示(其中红点表示停止信号,显示过密起始信号隐去了):

读取波形

最终读取的温湿度如图所示:

在这里插入图片描述

本文资源

百度网盘链接: https://pan.baidu.com/s/1iOG4FrrvNUgLUctqSZn1Ag

提取码: aqwq

参考

1.Linux驱动开发(十八):I2C驱动

————2020-6-13 @燕卫博————

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值