I2C(Inter-integrated Circuit)总线支持设备之间的短距离通信,用于处理器和一些外围设备之间的接口,它只需要两根信号线来完成信息交换。I2C最早是飞利浦在1982年开发设计并用于自己的芯片上,一开始只允许100kHz、7-bit标准地址。1992年,I2C的第一个公共规范发行,增加了400kHz的快速模式以及10-bit扩展地址。在I2C的基础上,1995年Intel提出了“System Management Bus” (SMBus),用于低速设备通信,SMBus 把时钟频率限制在10kHz100kHz,但I2C可以支持0kHz5MHz的设备:普通模式(100kHz即100kbps)、快速模式(400kHz)、快速模式+(1MHz)、高速模式(3.4MHz)和超高速模式(5MHz)。
起始信号:scl为高电平 sda从高到低跳变
停止信号:scl为高电平 sda从低到高跳变
应答信号:从机将sda设置为低电平,主机读取数据
start+7位从机地址 写+ack+8寄存器地址+ack+data+ack+stop
start+7位从机地址 写+ack+8寄存器地址+start+7位从机地址 读+ack+data+NO ack+stop
1 | 2 |
---|---|
app: open read write close
----------------------------------------------
kernel |设备驱动:驱动工程师实现的
|根据用户的读写需求(字符设备驱动),完成数据的封装和数据的发送
|封装好的数据会发给控制驱动,最终操作硬件
|--------------------------------------
|核心层(i2c-core.c):由内核工程师实现的
|完成设备驱动和总线驱动匹配的过程,提供两者的注册注销的方法
|---------------------------------------
|控制器驱动(总线驱动):由控制器厂商实现的
|完成控制器的初始化,以及提供读写的时序 i2c-s3c2410.c
----------------------------------------------
hardware
I2C核心维护了i2c_bus结构体,提供了I2C总线驱动和设备驱动的注册、注销方法,维护了I2C总线的驱动、设备链表,实现了设备、驱动的匹配探测。此部分代码由Linux内核提供。
I2C总线驱动
I2C总线驱动维护了I2C适配器数据结构(i2c_adapter)和适配器的通信方法数据结构(i2c_algorithm)。所以I2C总线驱动可控制I2C适配器产生start、stop、ACK等。此部分代码由具体的芯片厂商提供,比如Samsung、高通。
I2C设备驱动
I2C设备驱动主要维护两个结构体:i2c_driver和i2c_client,实现和用户交互的文件操作集合fops、cdev等。此部分代码就是驱动开发者需要完成的
用于管理I2C的驱动程序和i2c设备(client)的匹配探测,实现与应用层交互的文件操作集合fops、cdev等。
struct i2c_driver {//表示是一个从设备的驱动对象
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
struct device_driver driver; //继承了父类
|
const struct of_device_id *of_match_table;
const struct i2c_device_id *id_table;//用于做比对,非设备树的情况
}
注册和注销
int i2c_add_driver( struct i2c_driver *driver);
void i2c_del_driver(struct i2c_driver *);
每一个i2c从设备都需要用一个i2c_client结构体来描述,i2c_client对应真实的i2c物理设备device。
struct i2c_client {//描述一个从设备的信息,不需要在代码中创建,因为是由i2c adapter帮我们创建
unsigned short addr; //从设备地址,来自于设备树中
char name[I2C_NAME_SIZE]; //用于和i2c driver进行匹配,来自于设备树中compatible
struct i2c_adapter *adapter;//指向当前从设备所存在的i2c adapter
struct device dev; // 继承了父类
};
创建i2c client的函数
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
I2C总线适配器,即soc中的I2C总线控制器,硬件上每一对I2C总线都对应一个适配器来控制它。在Linux内核代码中,每一个adapter提供了一个描述它的结构(struct
i2c_adapter),再通过i2c core层将i2c设备与i2c
adapter关联起来。主要用来完成i2c总线控制器相关的数据通信,此结构体在芯片厂商提供的代码中维护。
struct i2c_adapter {//描述一个i2c控制器,也不是我们要构建,原厂的代码会帮我们构建
const struct i2c_algorithm *algo; //算法
|
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num);
struct device dev; //继承了父类,也会被加入到i2c bus
int nr; //编号
}
注册和注销:
int i2c_add_adapter(struct i2c_adapter * adapter);
void i2c_del_adapter(struct i2c_adapter * adap);
struct i2c_msg {//描述一个从设备要发送的数据的数据包
__u16 addr; //从设备地址,发送给那个从设备
__u16 flags; //读1还是写0
__u16 len; //发送数据的长度
__u8 *buf; //指向数据的指针
};
//写从设备
int i2c_master_send(const struct i2c_client * client,const char * buf,int count)
//读从设备
int i2c_master_recv(const struct i2c_client * client,char * buf,int count)
以上两个函数都调用了:
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
确保i2c core和i2c adatper层必须编译进内核
make menuconfig
Device Drivers --->
-*- I2C support ---> //编译i2c-core.c
I2C Hardware Bus support --->
<*> S3C2410 I2C Driver // i2c-s3c2410.c
[root@farsight i2c-0]# cd /sys/bus/i2c/devices/i2c-0/
[root@farsight i2c-0]# cat name
s3c2410-i2c
硬件原理图
根据结合芯片手册,芯片原理图,开发板与原理图的查询,将i2c控制器和从设备的硬件描述添加到设备树节点中(node-value)。
(1)控制器对应的设备树模板信息:arch/arm/boot/dts/exynos4.dtsi。参考模板,将描述从设备信息的设备树节点添加到文件 arch/arm/boot/dts/exynos4412-origen.dts
i2c@138B0000 {/*i2c adapter5信息*/
#address-cells = <1>;
#size-cells = <0>;
samsung,i2c-sda-delay = <100>;
samsung,i2c-max-bus-freq = <20000>;
pinctrl-0 = <&i2c5_bus>;
pinctrl-names = "default";
status = "okay";
mpu6050@68 { /*i2c client信息*/
compatible = "invensense,mpu6050";
reg = <0x68>;
};
};
(2) 编译设备树,cp到tftp根文件目录
(3)运行开发板 ,添加设备树节点成功
最终实现读取从设备(mpu6050)的数据,实际上就是对模块的寄存器操作。由于mpu6050模块支持iic总线,主控制器通过读写iic的方式对mpu6050模块进行读写操作。
首先驱动创建并注册从设备驱动对象到核心层链表,通过设备树节点的compatible属性匹配到设备对象client,然后可以调用i2c_transfer接口对从设备进行读写操作,这里先进行初始化mpu6050模块。然后对上(应用层)提供接口ioctl,让应用程序可以通过ioctl获取mpu6050的三轴角速度和加速度的原始数据。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "mpu6050.h"
//设计一个全局的设备对象
typedef struct _mpu_sensor{
int dev_major;
struct device *dev;
struct class *cls;
struct i2c_client *client;//记录probe中client
}MPU_SENSOR_T;
MPU_SENSOR_T *mpu_dev;
int mpu6050_write_bytes(struct i2c_client *client, char *buf, int count)
{
int ret;
struct i2c_adapter *adapter = client->adapter;
struct i2c_msg msg;
msg.addr = client->addr;
msg.flags = 0;
msg.len = count;
msg.buf = buf;
ret = i2c_transfer(adapter, &msg, 1);
return ret==1?count:ret;
}
int mpu6050_read_bytes(struct i2c_client *client, char *buf, int count)
{
int ret;
struct i2c_adapter *adapter = client->adapter;
struct i2c_msg msg;
msg.addr = client->addr;
msg.flags = I2C_M_RD;
msg.len = count;
msg.buf = buf;
ret = i2c_transfer(adapter, &msg, 1);
return ret==1?count:ret;
}
//读取某个特定寄存器的地址,然后返回值
int mpu6050_read_reg_byte(struct i2c_client *client, char reg)
{
// 先写寄存器的地址, 然后在读寄存器的值
int ret;
struct i2c_adapter *adapter = client->adapter;
struct i2c_msg msg[2];
char rxbuf[1];
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].len = 1;
msg[0].buf = ®
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].len = 1;
msg[1].buf = rxbuf;
ret = i2c_transfer(adapter, msg, 2);
if(ret < 0)
{
printk("i2c_transfer read error\n");
return ret;
}
return rxbuf[0];
}
int mpu6050_drv_open(struct inode *inode, struct file *filp)
{
printk("-----%s------------\n", __FUNCTION__);
return 0;
}
int mpu6050_drv_close(struct inode *inode, struct file *filp)
{
printk("-----%s------------\n", __FUNCTION__);
return 0;
}
long mpu6050_drv_ioctl (struct file *filp, unsigned int cmd, unsigned long args)
{
union mpu6050_data data;
switch(cmd)
{
case IOC_GET_ACCEL:
//读数据
data.accel.x = (mpu6050_read_reg_byte(mpu_dev->client, ACCEL_XOUT_H) << 8) | (mpu6050_read_reg_byte(mpu_dev->client, ACCEL_XOUT_L));
data.accel.y = (mpu6050_read_reg_byte(mpu_dev->client, ACCEL_YOUT_H) << 8) | (mpu6050_read_reg_byte(mpu_dev->client, ACCEL_YOUT_L));
data.accel.z = (mpu6050_read_reg_byte(mpu_dev->client, ACCEL_ZOUT_H) << 8) | (mpu6050_read_reg_byte(mpu_dev->client, ACCEL_ZOUT_L));
break;
case IOC_GET_GYRO:
data.gyro.x = (mpu6050_read_reg_byte(mpu_dev->client, GYRO_XOUT_H) << 8) | (mpu6050_read_reg_byte(mpu_dev->client, GYRO_XOUT_L));
data.gyro.y = (mpu6050_read_reg_byte(mpu_dev->client, GYRO_YOUT_H) << 8) |
(mpu6050_read_reg_byte(mpu_dev->client, GYRO_YOUT_L));
data.gyro.z = (mpu6050_read_reg_byte(mpu_dev->client, GYRO_ZOUT_H) << 8) | (mpu6050_read_reg_byte(mpu_dev->client, GYRO_ZOUT_L));
break;
case IOC_GET_TEMP:
data.temp = (mpu6050_read_reg_byte(mpu_dev->client, TEMP_OUT_H) << 8) | ( mpu6050_read_reg_byte(mpu_dev->client, TEMP_OUT_L));
break;
default:
printk("invalid cmd\n");
return -EINVAL;
}
//将所有的数据交给用户
if(copy_to_user((void __user * )args, &data, sizeof(data)) > 0)
return -EFAULT;
}
const struct file_operations mpu6050_fops = {
.open = mpu6050_drv_open,
.release = mpu6050_drv_close,
.unlocked_ioctl = mpu6050_drv_ioctl,
};
int mpu6050_drv_probe(struct i2c_client *client, const struct i2c_device_id * id)
{
/*
申请设备号,实现fops
创建设备文件
通过i2c的接口去初始化i2c从设备
*/
char buf[2] = {0x0, 0x0};
int ret;
printk("-----%s------------\n", __FUNCTION__);
mpu_dev = kzalloc(sizeof(MPU_SENSOR_T), GFP_KERNEL);
if(NULL == mpu_dev)
{
printk(KERN_ERR "malloc error\n");
ret = -ENOMEM;
goto malloc_err;
}
if(client != NULL)
mpu_dev->client = client;
mpu_dev->dev_major = register_chrdev(0,"mpu_drv", &mpu6050_fops);
if(mpu_dev->dev_major < 0)
{
printk(KERN_ERR "register_chrdev error\n");
ret = -ENOMEM;
goto reg_err;
}
else
{
printk("register_chrdev ok\n");
}
mpu_dev->cls = class_create(THIS_MODULE, "mpu_cls");
if(IS_ERR(mpu_dev->cls))
{
printk(KERN_ERR "class_create error\n");
ret = PTR_ERR(mpu_dev->cls);
goto class_err;
}
mpu_dev->dev = device_create(mpu_dev->cls, NULL, MKDEV(mpu_dev->dev_major, 0),
NULL, "mpu_sensor");
if(IS_ERR(mpu_dev->dev))
{
printk(KERN_ERR "device_create error\n");
ret = PTR_ERR(mpu_dev->dev);
goto device_err;
}
buf[0] = PWR_MGMT_1;
buf[1] = 0x0;
mpu6050_write_bytes(mpu_dev->client, buf, 2);
buf[0] = SMPLRT_DIV;
buf[1] = 0x07;
mpu6050_write_bytes(mpu_dev->client, buf, 2);
buf[0] = CONFIG;
buf[1] = 0x06;
mpu6050_write_bytes(mpu_dev->client, buf, 2);
buf[0] = GYRO_CONFIG;
buf[1] = 0x18;
mpu6050_write_bytes(mpu_dev->client, buf, 2);
buf[0] = ACCEL_CONFIG;
buf[1] = 0x01;
mpu6050_write_bytes(mpu_dev->client, buf, 2);
return 0;
device_err:
class_destroy(mpu_dev->cls);
class_err:
unregister_chrdev(mpu_dev->dev_major, "mpu_drv");
reg_err:
kfree(mpu_dev);
malloc_err:
return ret;
}
int mpu5060_drv_remove(struct i2c_client *client)
{
printk("-----%s------------\n", __FUNCTION__);
device_destroy(mpu_dev->cls, MKDEV(mpu_dev->dev_major, 0));
class_destroy(mpu_dev->cls);
unregister_chrdev(mpu_dev->dev_major, "mpu_drv");
kfree(mpu_dev);
}
const struct of_device_id of_mpu6050_id[] = {
{
.compatible = "invensense,mpu6050",
},
{/*northing to be done*/},
};
const struct i2c_device_id mpu_id_table[] = {
{"mpu6050_drv", 0x1111},
{/*northing to be done*/},
};
struct i2c_driver mpu6050_drv = {
.probe = mpu6050_drv_probe,
.remove = mpu5060_drv_remove,
.driver = {
.name = "mpu6050_drv",//随便写,/sys/bus/i2c/driver/mpu6050_drv
.of_match_table = of_match_ptr(of_mpu6050_id),
},
.id_table = mpu_id_table,//非设备树情况下的匹配,在设备树的模式下不需要使用
};
static int __init mpu6050_drv_init(void)
{
printk("-----%s------------\n", __FUNCTION__);
// 1,构建i2c driver,并注册到i2c总线
i2c_add_driver(&mpu6050_drv);
return 0;
}
static void __exit mpu6050_drv_exit(void)
{
printk("-----%s------------\n", __FUNCTION__);
i2c_del_driver(&mpu6050_drv);
}
module_init(mpu6050_drv_init);
module_exit(mpu6050_drv_exit);
MODULE_LICENSE("GPL");
#ifndef __MPU6050_H__
#define __MPU6050_H__
union mpu6050_data
{
struct{
short x;
short y;
short z;
}accel;
struct{
short x;
short y;
short z;
}gyro;
short temp;
};
#define IOC_GET_ACCEL _IOR('M', 0x34,union mpu6050_data)
#define IOC_GET_GYRO _IOR('M', 0x35,union mpu6050_data)
#define IOC_GET_TEMP _IOR('M', 0x36,union mpu6050_data)
#define SMPLRT_DIV 0x19 //采样频率寄存器-25 典型值:0x07(125Hz)
//寄存器集合里的数据根据采样频率更新
#define CONFIG 0x1A //配置寄存器-26-典型值:0x06(5Hz)
//DLPF is disabled(DLPF_CFG=0 or 7)
#define GYRO_CONFIG 0x1B//陀螺仪配置-27,可以配置自检和满量程范围
//典型值:0x18(不自检,2000deg/s)
#define ACCEL_CONFIG 0x1C //加速度配置-28 可以配置自检和满量程范围及高通滤波频率
//典型值:0x01(不自检,2G,5Hz)
#define ACCEL_XOUT_H 0x3B //59-65,加速度计测量值 XOUT_H
#define ACCEL_XOUT_L 0x3C // XOUT_L
#define ACCEL_YOUT_H 0x3D //YOUT_H
#define ACCEL_YOUT_L 0x3E //YOUT_L
#define ACCEL_ZOUT_H 0x3F //ZOUT_H
#define ACCEL_ZOUT_L 0x40 //ZOUT_L---64
#define TEMP_OUT_H 0x41 //温度测量值--65
#define TEMP_OUT_L 0x42
#define GYRO_XOUT_H 0x43 //陀螺仪值--67,采样频率(由寄存器 25 定义)写入到这些寄存器
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47
#define GYRO_ZOUT_L 0x48 //陀螺仪值--72
#define PWR_MGMT_1 0x6B //电源管理 典型值:0x00(正常启用)
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include "mpu6050.h"
int main(int argc, char *argv[])
{
int fd;
union mpu6050_data data;
fd = open("/dev/mpu_sensor", O_RDWR);
if(fd < 0)
{
perror("open");
exit(1);
}
while(1)
{
ioctl(fd, IOC_GET_ACCEL, &data);
printf("accel data : x = %d, y=%d, z=%d\n", data.accel.x, data.accel.y, data.accel.z);
ioctl(fd, IOC_GET_GYRO, &data);
printf("gyro data : x = %d, y=%d, z=%d\n", data.gyro.x, data.gyro.y, data.gyro.z);
sleep(1);
}
close(fd);
return 0;
}
https://blog.csdn.net/feit2417/article/details/84564391