1.IIC总线的介绍
1.1总线简介
1> I2C总线是PHLIPS公司在八十年代初推出的一种同步串行的半双工总线,主要用于连接整体电路。
连接整体电路 :同一个PCB板之上,两个芯片直接使用IIC总线进行连接核通信
2> I2C总线为两线制,只有两根双向信号线。一根是数据线SDA,另一根是 时钟线SCL。
3> I2C硬件结构简单,接口连接方便,成本较低。因此在各个领域得到了广泛的应用。
I2C总线的使用场合:数字温湿度传感器,脉搏传感器,环境光接近传感器,触摸屏,EEPROM,6轴加速度传感器,磁力计。
4> IIC总线上需要接两个上拉电阻,保证总线在空闲他处于高电平的状态,防止信号的衰减,
上拉电阻的阻值一般为4.7K-10K之间。
5> i2c总线速率(控制器)
常见的I2C总线以传输速率的不同分为不同的模式:标准模式(100Kbit/s)、低速模式(10Kbit/s)、快速模式(400Kbit/s)、高速模式(3.4Mbit/s),时钟频率可以被下降到零,即暂停通信。
6> 典型的电压基准为:+3.3V或+5V。
1.2 通讯特征
串行、同步、非差分、低速率
1> 串行通信,所有的数据以位为单位在SDA线上串行传输
2> 同步通信,即双方工作在同一个时钟下,一般是通信的A方通过一根CLK信号线,将A设备的时钟传输到B
设备,B设备在A设备传输的时钟下工作。同步通信的特征是:通信线中有CLK。
3> 非差分,I2C通信速率不高,且通信距离近,使用电平信号通信。
4> 低速率,I2C一般是同一个板子上的两个IC芯片间通信,数据量不大,速率低。速率:几百KHz,速率可能
不同,不能超过IC的最高速率。
2.IIC总线的硬件连接
1> I2C是具备多主机多从机系统所需的包括总线裁决功能的高性能串行总线。
在实际的开发中,一般常见的是单主机多从机。
2> 主机:能够主动发起通信的叫做主机,一般为CPU, 时钟信号一般由主机产生。
3> 从机:只能被动收发数据的叫做从机。
4> IIC总线上的多个设备之间,在同一时刻只能有一个主机和一个从机进行通信。
5> 每个接到I2C总线上的器件都有唯一的地址。主机与其它器件进行数据传送时总线上发送数据的器件为发送器,总线上接收数据的器件则为接收器。
3.IIC总线的时序图分析
3.1 起始信号和终止信号的时序图
1> SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;
2> SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号;
3> 起始和终止信号都是由主机发出,起始信号产生后,总线就处于占用的态;
终止信号产生后,总线就处于空闲态。
3.2 数据信号的时序图
1> 在时钟为高电平期间,必须保持数据线上的数据稳定,此时接收器可以从数据线上读取数据。
2> 在时钟为低电平期间,数据线上的数据允许发生变化,此时发送器向数据线上写入数据。
3> 在时钟为低电平期间,发送器向数据线上写入要发生的数据,在时钟为高电平期间接收从数据线上读取数据,
一个时钟周期收发一个bit位的数据。
3.3 应答信号和非应答信号的时序图
1> 每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。
2> 在一帧数据的第9个时钟周期的低电平期间,接收器向数据线上写入数据,在第九个时钟周期的高电平期间发送器从数据线上读取数据,如果读到的第低电平则认为是应答信号,如果读到的是高电平则认为是非应答信号。
3.4 从机地址
1> I2C总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。
2> 主机在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/W),用“0”表示主机发送数据(W),“1”表示主机接收数据(R)。总线上的每个从机都将这7位地址码与自己的地址进行比较,如果相同,则认为自己被主机寻址,根据R/W位将自己定为发送器或接收器。
4.IIC总线的通信协议
4.1写时序
4.2 读时序
4.3 协议细节
-
如何在SDA上实现双向传输?
主芯片通过一根SDA线既可以把数据发给从设备,也可以从SDA上读取数据,连接SDA线的引脚里面必然有两个引脚(发送引脚/接受引脚)。主、从设备都可以通过SDA发送数据,肯定不能同时发送数据,怎么错开时间?在9个时钟里,
前8个时钟由主设备发送数据的话,第9个时钟就由从设备发送数据;前8个时钟由从设备发送数据的话,第9个时钟就由主设备发送数据。 -
为什么要接上拉电阻?
5.i2c总线驱动
5.1 i2c子系统框架
//注:在使用i2c子系统前,需要将核心层和控制器驱动层配置到内核中
//核心层:
obj-$(CONFIG_I2C) += i2c-core.o
i2c-core-objs := i2c-core-base.o i2c-core-smbus.o
//控制器驱动层:
i2c-stm32f7-drv-objs := i2c-stm32f7.o i2c-stm32.o
obj-$(CONFIG_I2C_STM32F7) += i2c-stm32f7-drv.o
//核心层选配:
控制器层选配:
5.2 i2c设备驱动的API
1.分配并初始化对象
struct i2c_driver {
int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
int (*remove)(struct i2c_client *client);
struct device_driver driver;
//const struct i2c_device_id *id_table;
}
struct device_driver {
const char *name;
const struct of_device_id *of_match_table;
}
//i2c的匹配方式只有两种:1.idtable,2.设备树
2.注册、注销
#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver) //注册
void i2c_del_driver(struct i2c_driver *driver); //注销
3.一键注册的函数
module_i2c_driver(变量名);
5.3编写i2c驱动实例
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/module.h>
int i2c_probe(struct i2c_client* client, const struct i2c_device_id* id)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
int i2c_remove(struct i2c_client* client)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
const struct of_device_id oftable[] = {
{.compatible = "st,si7006",},
{/*end*/}
};
MODULE_DEVICE_TABLE(of,oftable);
struct i2c_driver si7006 = {
.probe = i2c_probe,
.remove = i2c_remove,
.driver = {
.name = "holle",
.of_match_table = oftable,
}
};
module_i2c_driver(i2c);
MODULE_LICENSE("GPL");
5.4 i2c_client结构体
当总线驱动和设备驱动匹配成功之后,就会创建一个i2c_client结构体
这个结构体是用来记录信息。记录那个总线驱动(i2c_adapter)和那个
设备驱动(i2c_driver)匹配成功的
//i2c 7bit 8bit 10bit寻址
struct i2c_client {
unsigned short flags; // I2C_CLIENT_TEN 10位从机地址 标志位 7位,8位
unsigned short addr; //从机地址,从设备树中获取的 解析设备树后的到的地址,也就是reg
char name[I2C_NAME_SIZE]; //名字
struct i2c_adapter *adapter;//总线驱动对象 发消息要发给这个
struct list_head detected; //构成链表
};
5.5 i2c消息的封装和发送过程
5.5.1消息的封装
设备驱动和总线驱动进行数据交互的时候,是通过i2c_msg结构体完成的。
有多少个起始信号就有多少个消息,消息的长度是以字节来表示的。
struct i2c_msg {
__u16 addr; //从机地址
__u16 flags; //读写的标志位 0写 1读
__u16 len; //消息的长度,单位是字节,
__u8 *buf; //消息的首地址
};
写的消息:
**start+ (从机地址 7bit + 0(写) 1bit) + ack + 寄存器地址(8bit|16bit)+ack **
+ data(8bit) + ack + stop
(从机地址 7bit + 0(写) 1bit) + 寄存器地址(8bit|16bit)+ data(8bit)
char w_buf[] = {reg,val};//将寄存器地址命名位reg,data命名为val;
struct i2c_msg w_msg = {
.addr = client->addr,//从机地址
.flags = 0,
.len = 2,//描述的是buf中字节
.buf = w_buf,
};
读的消息:
**start+ (从机地址 7bit + 0(写) 1bit) + ack + 寄存器地址(8bit|16bit)+ack **
start+ (从机地址 7bit + 1(读) 1bit) + ack + data(8bit 从机给主机) +NO ack+stop
(从机地址 7bit + 0(写) 1bit) + 寄存器地址(8bit|16bit)
(从机地址 7bit + 1(读) 1bit) + data(8bit 从机给主机)
char r_buf[] = {reg};
unsigned char data;
struct i2c_msg r_msg[] = {
[0] = {//第一个消息的地址
.addr = client->addr,//从机地址
.flags = 0,//写
.len = 1,
.buf = r_buf,//寄存的地址
},
[1] = {//第二个消息的地址
.addr = client->addr,//从机地址
.flags = 1,//读
.len = 1,
.buf = &data,//这个表示最终读到的数据,读到char一个字节
},
};
5.5.2消息的发送
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
功能:消息的发送
参数:
@adap:总线驱动的对象 (client->adapter)
@msgs:消息的首地址
@num:消息个数
返回值:成功返回num,否则就是失败//也就是发送成功消息的个数