一、I2C简介
I2C是很常用的总线协议。它使用两条线在主控制器与从机直接进行数据通信。一条是时钟线,一条是数据线,两条数据线都要接4.7K的上拉电阻,空闲的时候处于高电平。I2C总线的标准模式下传输速度可以达到100k/s,快速模式达到400k/s。
常见的I2C驱动从设备有电容触摸屏,CMOS camera ,重力传感器,EEPROM,HDMI。
二、I2C的协议内容
起始位S:数据位电平拉低
停止位P:数据位拉高
ACK1:MASTER发送完地址后,SLIVER将数据位拉低,响应主机。
ACK2:读写完以后,主机拉低数据位,告诉SLIVER进行回应。
写数据:
主机发送起始位S信号,发送i2c从机的地址,发送写位,ACK1,写入从机的寄存器的8位数据,然后循环写数据,最后从机ACK2应答,并且主机发送停止位信号。
读数据:
写入设备地址,写入寄存器地址,读取设备地址,读取寄存器的值。
阶段一:主机发送S,写入I2C设备地址,ACK1从机响应。
阶段二:重发S,发送寄存器的地址,从机ACK1响应。
阶段三:重发S,发送I2C设备地址,读位,ACK1响应。
阶段四:读取数据,ACK2主机拉低信号读完8bit,主机发送P。
三、I2C子系统框架
分为应用层,驱动层与硬件层三层。
其中驱动层可分为三层:i2c_driver,i2c_core,i2c_adaptor。分别是驱动层,内核层以及适配器层。驱动层是设备的IO的驱动程序。内核层是将设备与驱动分离的两个链表,使得设备树信息与驱动程序进行匹配。适配器层是用于提供硬件协议的执行算法,一般由供应商提供。
I2C相关的API:
struct i2c_driver{
int (*probe)(struct i2c_client * , const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
struct device_driver driver;
const struct i2c_device_id *id_table;
};
//注册与注销:
int i2c_add_driver(struct i2c_driver *driver);
void i2c_del_driver(struct i2c_driver *driver);
//发送接收消息
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);
struct i2c_client {
unsigned short addr; //从设备地址,来源于设备树中的reg = <0x68>
char name[I2C_NAME_SIZE]; //用于与i2c_driver进行匹配的compatible信息
struct i2c_adapter *adapter;
struct device dev;
};
//创建i2c_client的函数
struct i2c_client * i2c_new_device(struct i2c_adapter *adap , struct i2c_board_info const *info);
其中i2c_client相当于设备数中的i2c结点的信息,i2c_adapter相当于设备树中i2c总线上挂在的设备的信息。因此我们在后面编写读写程序的时候会将i2c_client中的信息传递给i2c_adapter。
&i2c1 {
//i2c_client
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
mpu6050@68 {
//i2c_adapter
compatible = "mpu6050,INV_MPU6050";
reg = <0x68>;
};
};
I2C读写信息实现:
I2C的收发其实是使用i2c_transfer函数实现的。唯一的不同点是i2c_msg结构体的flags中读是1,写是0。
/**
* i2c_master_send - issue a single I2C message in master transmit mode
* @client: Handle to slave device
* @buf: Data that will be written to the slave
* @count: How many bytes to write, must be less than 64k since msg.len is u16
*
* Returns negative errno, or else the number of bytes written.
*/
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
{
int ret;
struct i2c_adapter *adap = client->adapter;
struct i2c_msg msg;
msg.addr = client->addr;
msg.flags = client->flags & I2C_M_TEN;
msg.len = count;
msg.buf = (char *)buf;
ret = i2c_transfer(adap, &msg, 1);
/*
* If everything went ok (i.e. 1 msg transmitted), return #bytes
* transmitted, else error code.
*/
return (ret == 1) ? count : ret;
}
/**
* i2c_master_recv - issue a single I2C message in master receive mode
* @client: Handle to slave device
* @buf: Where to store data read from slave
* @count: How many bytes to read, must be less than 64k since msg.len is u16
*
* Returns negative errno, or else the number of bytes read.
*/
int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
{
struct i2c_adapter *adap = client->adapter;
struct i2c_msg msg;
int ret;
msg.addr = client->addr;
msg.flags = client->flags & I2C_M_TEN;
msg.flags |= I2C_M_RD;
msg.len = count;
msg.buf = buf;
ret = i2c_transfer(adap, &msg, 1);
/*
* If everything went ok (i.e. 1 msg received), return #bytes received,
* else error code.
*/
return (ret == 1) ? count : ret;
}
在编写I2C驱动中我们一般是要对寄存器的数据进行读操作,因此就需要首先写入从设备地址,读取寄存器的数据。
int i2c_read_reg(const struct i2c_client *client, char *buf, int reg , int count)
{
struct i2c_adapter *adap = client->adapter;
struct i2c_msg msg[2];
char rxbuf[1];
int ret;
//写入寄存器地址
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].len = 1;
msg[0].buf = ®
//往寄存器读取数据
msg[0].addr = client->addr;
msg[0].flags = 1;
msg[0].len = 1;
msg[0].buf = rxbuf;
ret = i2c_transfer(adap, &msg, 2);
/*
* If everything went ok (i.e. 1 msg received), return #bytes received,
* else error code.
*/
return rxbuf[0];
}
四、MPU6050驱动程序
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/ioctl.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/platform_device.h>
#include <linux/compaction.h>
#include <linux/irq.h>
#include <linux/irqreturn.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <linux/fcntl.h>
#include <linux/sh_clk.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include "mpu6050.h"
struct mpu6050_device{
struct i2c_client *mpu6050_client;
struct cdev cdev;
int major;
struct class *class;
struct device *dev;
};
static struct mpu6050_device *mpu6050;
static int mpu6050_read_byte (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 = 1;
msg.len = count;
msg.buf = buf;
ret = i2c_transfer(adapter,&msg,1);//num是消息的个数
return (ret == 1) ? count : ret;
}
static int mpu6050_write_byte (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);//num是消息的个数
return (ret == 1) ? count : ret;
}
static 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];
//先写reg寄存器地址
msg[0].addr = client->addr;
msg[0].flags = 0; //W
msg[0].len = 1;
msg[0].buf = ®
//读取寄存器的数据
msg[1].addr = client->addr;
msg[1].flags = 1; //R
msg[1].len = 1;
msg[1].buf = rxbuf;
ret = i2c_transfer(adapter,msg,2);//num是消息的个数
if(ret < 0){
printk("i2c_transfer read reg error\r\n");
return ret;
}
return rxbuf[0];
}
static int mpu6050_ioctl(struct file *file, unsigned int cmd,unsigned long arg)
{
struct mpu6050_data data;
struct i2c_client *client = mpu6050->mpu6050_client;
switch(cmd) {
case IOC_GET_ACCEL:
printk("IOC_GET_ACCEL \r\n");
data.accel.x = mpu6050_read_reg_byte(client, ACCEL_XOUT_L);
data.accel.x |= mpu6050_read_reg_byte(client, ACCEL_XOUT_H) << 8;
data.accel.y = mpu6050_read_reg_byte(client, ACCEL_YOUT_L);
data.accel.y |= mpu6050_read_reg_byte(client, ACCEL_YOUT_H) << 8;
data.accel.z = mpu6050_read_reg_byte(client, ACCEL_ZOUT_L);
data.accel.z |= mpu6050_read_reg_byte(client, ACCEL_ZOUT_H) << 8;
break;
case IOC_GET_GYRO:
printk("IOC_GET_GYRO \r\n");
data.gyro.x = mpu6050_read_reg_byte(client, GYRO_XOUT_L);
data.gyro.x |= mpu6050_read_reg_byte(client, GYRO_XOUT_H) << 8;
data.gyro.y = mpu6050_read_reg_byte(client, GYRO_YOUT_L);
data.gyro.y |= mpu6050_read_reg_byte(client, GYRO_YOUT_H) << 8;
data.gyro.z = mpu6050_read_reg_byte(client, GYRO_ZOUT_L);
data.gyro.z |= mpu6050_read_reg_byte(client, GYRO_ZOUT_H) << 8;
break;
case IOC_GET_TEMP:
printk("IOC_GET_TEMP \r\n");
data.temp = mpu6050_read_reg_byte(client, TEMP_OUT_L);
data.temp |= mpu6050_read_reg_byte(client, TEMP_OUT_H) << 8;
break;
default:
printk("invalid argument\n");
return -EINVAL;
}
if (copy_to_user((void *)arg, &data, sizeof(data)))
return -EFAULT;
return 0;
}
/*字符设备操作函数集,open函数实现*/
static int mpu6050_open(struct inode *inode, struct file *filp){
printk("--------------mpu6050_open-------------\r\n");
/*向 mpu6050 发送配置数据,让mpu6050处于正常工作状态*/
char buf[2] = {PWR_MGMT_1 , 0x0};
mpu6050_write_byte(mpu6050->mpu6050_client , buf, 2);
char buf1[2] = {SMPLRT_DIV, 0x07};
mpu6050_write_byte(mpu6050->mpu6050_client , buf1, 2);
char buf2[2] = {CONFIG, 0X06};
mpu6050_write_byte(mpu6050->mpu6050_client , buf2, 2);
char buf3[2] = {ACCEL_CONFIG, 0X01};
mpu6050_write_byte(mpu6050->mpu6050_client , buf3, 2);
char buf4[2] = {GYRO_CONFIG, 0X18};
mpu6050_write_byte(mpu6050->mpu6050_client , buf4, 2);
return 0;
}
static int mpu6050_release(struct inode *inode , struct file *filp){
printk("--------------mpu6050_release-------------\r\n");
return 0;
}
static struct file_operations mpu6050_fops={
.owner=THIS_MODULE,
.open = mpu6050_open,
.release = mpu6050_release,
.unlocked_ioctl = mpu6050_ioctl,
};
static int mpu6050_probe(struct i2c_client *client,const struct i2c_device_id *id)
{
dev_t devid;
printk("--------------mpu6050_probe-------------\r\n");
mpu6050=kzalloc(sizeof(struct mpu6050_device),GFP_KERNEL);
mpu6050->mpu6050_client=client;
if (mpu6050->major) {
devid = MKDEV(mpu6050->major, 0);
register_chrdev_region(devid, 1, "mpu6050");
} else {
alloc_chrdev_region(&devid, 0, 1, "mpu6050");
mpu6050->major = MAJOR(devid);
}
cdev_init(&mpu6050->cdev, &mpu6050_fops);
cdev_add(&mpu6050->cdev, devid, 1);
mpu6050->class=class_create(THIS_MODULE,"mpu6050");
mpu6050->dev = device_create(mpu6050->class,NULL,MKDEV(mpu6050->major,0),NULL,"mpu6050");
printk("--------------mpu6050_probe over-------------\r\n");
return 0;
}
static int mpu6050_remove(struct inode *inode, struct file *filp)
{
printk("--------------mpu6050_remove-------------\r\n");
device_destroy(mpu6050->class,MKDEV(mpu6050->major,0));
class_destroy(mpu6050->class);
cdev_del(&mpu6050->cdev);
kfree(mpu6050);
unregister_chrdev_region(MKDEV(mpu6050->major, 0), 1);
return 0;
}
static const struct i2c_device_id mpu6050_id[] = {
{ "mpu6050,INV_MPU6050", 0 },
{ }
};
/*定义设备树匹配表*/
static const struct of_device_id mpu6050_of_match_table[] = {
{.compatible = "mpu6050,INV_MPU6050"},
{/* sentinel */}
};
static struct i2c_driver mpu6050_driver ={
.driver={
.name ="mpu6050",
.owner=THIS_MODULE,
.of_match_table = mpu6050_of_match_table,
},
.probe = mpu6050_probe,
.remove = mpu6050_remove,
.id_table = mpu6050_id,
};
static int mpu6050_driver_init(void)
{
i2c_add_driver(&mpu6050_driver);
return 0;
}
static void mpu6050_driver_exit(void)
{
i2c_del_driver(&mpu6050_driver);
}
module_init(mpu6050_driver_init);
module_exit(mpu6050_driver_exit);
MODULE_LICENSE("GPL");
五、应用测试程序
这个应用程序说来也奇怪,当我使用Ioctl系统调用的时候,如果不进行判错,data就不会更新数据,有大伙知道是为什么的吗?
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "mpu6050.h"
int main(int argc, char * argv []){
struct mpu6050_data *data , *data1;
int fd ;
fd = open("/dev/mpu6050" , O_RDWR);
if(fd < 0){
perror("open");
exit(-1);
}
data = (struct mpu6050_data *)malloc( sizeof( struct mpu6050_data ));
if(!data ){
printf("failed data\r\n");
return -1;
}
while(1){
if(ioctl(fd, IOC_GET_ACCEL,data) < 0)
{
printf("error IOC_GET_ACCEL\r\n");
}
printf("accel data : x = %d,y= %d , z = %d\n",data->accel.x, data->accel.y, data->accel.z );
sleep(1);
if(ioctl(fd, IOC_GET_GYRO,data) < 0)
{
printf("error IOC_GET_ACCEL\r\n");
}
printf("gyro data : x = %d,y= %d , z = %d\n",data->gyro.x, data->gyro.y , data->gyro.z );
sleep(1);
if(ioctl(fd, IOC_GET_TEMP,data) < 0)
{
printf("error IOC_GET_TEMP\r\n");
}
printf("temp data1 : %f\n",data->temp/340.0 + 36.53);
sleep(1);
}
free(data);
close(fd);
return 0;
}
#ifndef __MPU6050_H__
#define __MPU6050_H__
#define SMPLRT_DIV 0x19
#define CONFIG 0x1A
#define GYRO_CONFIG 0x1B
#define ACCEL_CONFIG 0x1C
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H 0x41
#define TEMP_OUT_L 0x42
#define GYRO_XOUT_H 0x43
#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
#define PWR_MGMT_1 0x6B
struct mpu6050_data{
struct{
short x;
short y;
short z;
}accel;
struct{
short x;
short y;
short z;
}gyro;
short temp;
};
#if 0
#define IOC_GET_ACCEL _IOW('M', 0x34, union mpu6050_data)
#define IOC_GET_GYRO _IOW('M', 0x35, union mpu6050_data)
#define IOC_GET_TEMP _IOW('M', 0x36, union mpu6050_data)
#else
#define IOC_GET_ACCEL 0x01
#define IOC_GET_GYRO 0x04
#define IOC_GET_TEMP 0x08
#endif
#endif