Linux的I2C设备驱动框架学习之写一个SHT20驱动

Linux的I2C设备驱动框架学习之写一个SHT20驱动 mini菜 2023-06-15 18:05:11 869

测试环境说明

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** 文件中添加节点。 ![设备树的修改](https://ebaina.oss-cn-hangzhou.aliyuncs.com/res/article/202306/12/6486bf4d411ea44795.png) 这里需要注意的是设备I2C地址使用的是不包含读写位的7位地址,I2C的频率应当适配硬件,SHT20支持的最高SCL频率为0.4MHz,这在手册上可以找到。 很多资料都是这样修改设备树的,但是这样修改之后,编译内核时出现如下警告(对设备树不是很了解,所以放任不管),这个警告并不影响器件运作。 ![内核编译的警告](https://ebaina.oss-cn-hangzhou.aliyuncs.com/res/article/202306/12/6486bf4dafb83542.png) #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主机模式测量](https://ebaina.oss-cn-hangzhou.aliyuncs.com/res/article/202306/12/6486bf4e3cc4f97015.png) ![SHT20软复位](https://ebaina.oss-cn-hangzhou.aliyuncs.com/res/article/202306/12/6486bf4ea5d9a38908.png) 上面两个图是从手册上摘出的。软件中只需要按照对应的命令写入数据即可。驱动程序将在文末【本文资源】中给出下载链接,就不在正文中粘贴了。 编写中对数据进行读取时需要注意两个细节。一是对读出的原始温湿度数据进行的处理,虽然上图中标明了Data位的MSB到LSB之间一共占据了14位,但是对高低位进行合并的时候却是高位对其的,手册中这样写道: > 在进行物理换算时,后两位状态位应置‘0’ 另一点需要注意的是,linux驱动中对浮点计算有问题,所以传感器输出的温湿度将在用户应用程序中进行计算。 #4.I2C适配器超时等待时间的修改 在测试中,使用海思自带的I2C总线控制驱动进行数据的传输,在默认情况下,数据传输将出现如下问题: ![iic超时](https://ebaina.oss-cn-hangzhou.aliyuncs.com/res/article/202306/12/6486bf4f06b5d72778.png) 在测试应用中每一次数据读取会出现这样的提示: > 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超时代码](https://ebaina.oss-cn-hangzhou.aliyuncs.com/res/article/202306/12/6486bf4f6516826642.png) 这表示在等待50us\*1024≈51ms没有收到数据后,对本次I2C读取判定超时,这与上面逻辑分析仪中测的的情况相符。由于SHT20在最高分辨率下温度的转换需要66ms(逻辑分析仪同样证实这一点), ![SHT20测量时间](https://ebaina.oss-cn-hangzhou.aliyuncs.com/res/article/202306/12/6486bf4fbd15996231.png) 所以修复上面的错误可以采用两种方式进行:降低传感器测量分辨率或者增大I2C读超时时间,本文采取后者。 正常对SHT20的温度读取波形如下所示(其中红点表示停止信号,显示过密起始信号隐去了): ![读取波形](https://ebaina.oss-cn-hangzhou.aliyuncs.com/res/article/202306/12/6486bf505207467629.png) 最终读取的温湿度如图所示: ![在这里插入图片描述](https://ebaina.oss-cn-hangzhou.aliyuncs.com/res/article/202306/12/6486bf50d79c324524.png) #本文资源 **百度网盘链接:** **提取码:** aqwq #参考 1.[Linux驱动开发(十八):I2C驱动](https://blog.csdn.net/a568713197/article/details/103280647)
声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
mini菜
红包 点赞 1 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
mini菜
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区