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 rn", __FILE__, __LINE__, __func__);
ret = i2c_add_driver(&ap3216c_device_driver);
return ret;
}
static void __exit ap3216c_exit(void)
{
printk("%s:%d: Entry %s rn", __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! rn",
__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! rn",
__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 rn", __FILE__, __LINE__, __func__);
ret = register_driver();
if(ERROR == ret ) {
printk("Failed:%s:%d: driver register error! rn", __func__, __LINE__);
} else {
printk("%s:%d: driver register successfully rn", __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驱动注册流程稍加研究即可。
- 关于pl/sql中的绑定变量(r3笔记第73天)
- python爬取网易云音乐并分析:用户有什么样的音乐偏好?
- shell基础学习总结(二) (r3笔记第72天)
- 对IMP-00013问题的思考(r3笔记第71天)
- 搭建LSTM(深度学习模型)做文本情感分类的代码
- 【大牛经验】高吞吐低延迟Java应用的垃圾回收优化
- shell的图形化实现简单示例(r3笔记第70天)
- 关于查询转换的一些简单分析(三) (r3笔记第69天)
- 【大牛经验】Web服务器的工作原理
- 使用shell定制addm脚本(r3笔记第88天)
- 【专业技术第十三讲】指针和内存泄露
- 【Java案例】余弦函数
- MySQL数据类型(r3笔记第87天)
- NLP真实项目:利用这个模型能够通过商品评论去预测一个商品的销量
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- leetcode链表之找出倒数第k个节点
- MAC下安装nginx的正确姿势 实践笔记
- 关于 ThreadLocal 你需要知道的几点
- 关于Guava ForwardingMap
- http post Request header is too large 开发环境和线上版本解决方案
- 像java一样使用js contains 数组包含方法 实践笔记
- 服务治理之重试篇
- 一次排查线上接口偶发异常耗时引起的思考!
- 台阶很高,青蛙跳不跳?
- 从零开始认识堆排序
- Redis SDS 深入一点,看到更多!
- Centos7下Oracle 11g r2 本地/远程 用exp/imp命令快速导入导出数据 实践笔记
- js常用函数大全107个
- xtraReprot 动态绑定数据 数据列动态
- 线上问题分析之java dump文件生成