IIC设备驱动实例调试
Posted H₂O₂
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 = client; return 0;}
在probe函数中,主要实现:申请设备号、向外提供读写接口(file_operations结构体)和创建class下设备节点。
读写接口实例化:
static int ap3216c_open(struct inode *inode, struct file *file){ /* ap3216c初始化 在probe配置也可以 */ file->private_data = &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_id ap3216c_id[] = { {"100ask,ap3216c", 0}, {}};.id_table = ap3216c_id,
实际效果:
由于我在家调试时,房间开灯,所以数据差值不是太大。具体调试,可自行实验。
总结:
本篇主要记录了iic设备ap3216c的驱动简单实现,对于iic通讯原理并没有做太详细的分析。
在实际学习中,我的经验是:先对iic协议有个详细的了解,然后实际实现一个iic驱动设备,再从头复习iic通讯协议,会对iic通讯有一个更加清晰深刻的认识。
对于使用过单片机调试iic设备的同学,对于iic通讯应该很熟悉了,主要就是对linux驱动注册流程稍加研究即可。
本篇代码下载:关注” 开源519“公众号,聊天窗口回复“ap3216c”,即可获取下载方式。
以上是关于IIC设备驱动实例调试的主要内容,如果未能解决你的问题,请参考以下文章
基于STM8的IIC协议--实例篇--时钟模块(DS3231)读取