IIC设备驱动实例调试

逆水行舟用力撑,一篙松劲退千寻。这篇文章主要讲述IIC设备驱动实例调试相关的知识,希望能为你提供帮助。
简介:

      在应用到linux的设备(特别是手机)中,大部分硬件设备与主芯片都是通过iic通讯的,譬如TP、加速度传感器、温湿度传感器等等。记录一次自己调试linux开发板iic器件(ap3216c光敏设备)。


概述:

      iic通讯线一般只有两条,一条用于时钟控制,一条用于数据通讯。当然也存在单总线通讯,像单片机经常用到的ds18b20。简单介绍两者的区别:
单总线通讯:省略了时钟控制线,其数据格式是时钟+数据。即最先发射时钟,然后再将数据传输出去。从机设备收到时钟时,就会响应主机。
双线通讯:时钟线和数据线。时钟线是固定频率的方波,数据线则在时钟线低电平时传输数据。
    iic通讯协议网上总结的相当到位,有时间会整理一下。本篇对iic通讯协议的介绍到此为止,主要是对iic实际设备的代码总结。


撸码:

1详细信息:

平台:imx6ull开发板
linux版本:4.9.88
开发编辑器:gediit
2概要:

    在linux源码中,已经实现了iic主机通讯协议传输的各种传输接口。在实际编码时,只需要调用这些接口实现对特殊iic设备的读写,并向外提供读写接口即可。
3流程:

初始化函数:

static struct i2c_driver ap3216c_device_driver = {.probe= ap3216c_probe,.remove = ap3216c_remove,.driver = {.name = PLATFORM_NAME,.owner = THIS_MODULE,.of_match_table = ap3216c_table,},.id_table = ap3216c_id,};
static int __init ap3216c_init(void){int ret = 0; printk("%s:%d: Entry %s \\r\\n", __FILE__, __LINE__, __func__);
ret = i2c_add_driver(& ap3216c_device_driver); return ret; }
static void __exit ap3216c_exit(void){printk("%s:%d: Entry %s \\r\\n", __FILE__, __LINE__, __func__);
i2c_del_driver(& ap3216c_device_driver); }
module_init(ap3216c_init); module_exit(ap3216c_exit);



跟普通字符驱动的注册没什么区别,只不过iic注册和卸载驱动的API:

i2c_add_driver(); i2c_del_driver();



本质是将iic驱动加载到系统的iic的链表中。在系统初始化时,会遍历iic驱动链表将本驱动注册。具体原理可查阅代码include/linux/i2c.h。
设备树:

& i2c1 {clock-frequency = < 100000> ; pinctrl-names = "default"; pinctrl-0 = < & pinctrl_i2c1> ; status = "okay";
ap3216c: ap3216c@1e {compatible = "100ask,ap3216c"; reg = < 0x1e> ; status = "okay"; }; };



因为板上的ap3216c挂在了iic1总线上,所以在设备树配置中,只需要在i2c1下添加上ap3216c设备节点即可,根据手册查阅本设备iic地址为0x1e。
入口函数:
    本驱动采用了platform总线架构。在platform总线驱动注册完成,驱动.of_match_table成员字符串会与设备树通过compatible匹配,匹配成功后,会进入驱动probe入口函数中。

static int register_driver(void){/* 1. 设置设备号
* 主设备号已知, 静态注册;未知, 动态注册。
*/if (ap3216c_dev.major){ap3216c_dev.devid = MKDEV(ap3216c_dev.major, 0); register_chrdev_region(ap3216c_dev.devid, AP3216C_NUM, AP3216C_NAME); } else {alloc_chrdev_region(& ap3216c_dev.devid, 0, AP3216C_NUM, AP3216C_NAME); ap3216c_dev.major = MAJOR(ap3216c_dev.devid); }
/* 2. 注册驱动结构体 */ap3216c_dev.cdev.owner = THIS_MODULE; cdev_init(& ap3216c_dev.cdev, & ap3216c_fops); cdev_add(& ap3216c_dev.cdev, ap3216c_dev.devid, AP3216C_NUM);
/* 3. 创建类 */ap3216c_dev.class = class_create(THIS_MODULE, AP3216C_CLASS_NAME); if(IS_ERR(ap3216c_dev.class)) {printk("Failed:%s:%d: %s under class created failed! \\r\\n",__func__, __LINE__, AP3216C_DEVICE_NAME); return ERROR; }/* 4.创建设备 */ap3216c_dev.device = device_create(ap3216c_dev.class, NULL,ap3216c_dev.devid, NULL, AP3216C_DEVICE_NAME); if(NULL == ap3216c_dev.device) {printk("Failed:%s:%d: %s device created failed! \\r\\n",__func__, __LINE__,AP3216C_DEVICE_NAME); return ERROR; }return OK; }
static int ap3216c_probe(struct i2c_client * client, const struct i2c_device_id *id){int ret = -1 ; printk("%s:%d: Entry %s \\r\\n", __FILE__, __LINE__, __func__); ret = register_driver(); if(ERROR == ret ) {printk("Failed:%s:%d: driver register error! \\r\\n", __func__, __LINE__); }else {printk("%s:%d: driver register successfully \\r\\n", __func__, __LINE__); }ap3216c_dev.private_data = https://www.songbingjia.com/android/client; return 0; }



在probe函数中,主要实现:申请设备号、向外提供读写接口(file_operations结构体)和创建class下设备节点。
读写接口实例化:

static int ap3216c_open(struct inode *inode, struct file *file){/* ap3216c初始化 在probe配置也可以 */file-> private_data = https://www.songbingjia.com/android/& ap3216c_dev; ap3216c_write_reg(& ap3216c_dev, AP3216C_SYSTEMCONG, 0x04); mdelay(50); ap3216c_write_reg(& ap3216c_dev, AP3216C_SYSTEMCONG, 0x03);
return 0; }
static ssize_t ap3216c_read(struct file *file, char __user *buf, size_t cnt, loff_t *off){/* 读取I2C设备数据 */unsigned short data[3]; int ret = 0; struct sap3216c_dev *dev = (struct sap3216c_dev *)file-> private_data; ap3216c_readdata(dev); data[0] = dev-> ir; data[1] = dev-> als; data[2] = dev-> ps; ret = copy_to_user(buf, data, sizeof(data)); return 0; }
static int ap3216c_release(struct inode *inode, struct file *file){return 0; }
/* 驱动结构体 */static struct file_operations ap3216c_fops = {.owner = THIS_MODULE,.open= ap3216c_open,.read= ap3216c_read,.release = ap3216c_release,};



由于ap3216c对于上层来讲,只需要读取其检测的3个光感参数值,因此这里只需要提供读取接口即可。具体对ap3216c硬件的如何读写过程单独封装,便于程序多次调用。


问题排查:

在驱动写完以后,发现注册模块时,并不能进入到probe入口函数中。
首先排查设备树与驱动的compatible值,发现一致,找不出问题。
最后经过网上搜索,发现匹配不上的原因,是因为driver中.id_table成员也需要赋值。是因为内核在注册iic设备时,会检测id_table成员是否赋值,若没有赋值则不予注册。因此添加上:

static struct i2c_device_idap3216c_id[] = {{"100ask,ap3216c", 0},{}}; .id_table = ap3216c_id,



实际效果:

IIC设备驱动实例调试

文章图片



由于我在家调试时,房间开灯,所以数据差值不是太大。具体调试,可自行实验。


总结:

    本篇主要记录了iic设备ap3216c的驱动简单实现,对于iic通讯原理并没有做太详细的分析。
    在实际学习中,我的经验是:先对iic协议有个详细的了解,然后实际实现一个iic驱动设备,再从头复习iic通讯协议,会对iic通讯有一个更加清晰深刻的认识。
    对于使用过单片机调试iic设备的同学,对于iic通讯应该很熟悉了,主要就是对linux驱动注册流程稍加研究即可。
本篇代码下载:关注”  开源519“公众号,聊天窗口回复“ap3216c”,即可获取下载方式。
【IIC设备驱动实例调试】


    推荐阅读