一、时序图
1.1 时序图基础
方波
上升沿与下降沿
上升箭头即在上升沿触发,反之则是下降沿触发
Either Or信号
非此即彼信号表示,既有可能是高也可能是低。
灰色区域
灰色区域表示三态:低电平,高电平,高阻态。
F省略
类似f的图形表示省略符号
二、I2C协议
IIC(Inter-Integrated Circuit)是一种具有两线传输的串行通信总线,使用多主从架构,由飞利浦公司在1980年为了让主板、嵌入式系统或手机连接低速周边设备而提出,适用于数据量不大且传输距离短的场合。IIC串行总线由两根信号线组成,一根是双向的数据线SDA,另一根是单向的时钟线SCL,在空闲状态时,SDA和SCL线都置’1‘,为高电平。
IIC为同步的半双工通信方式
常见的传输速率有:100kb/s、300kb/s、3.4Mkb/s
2.1.传输协议
IIC由两根通信信号线组成,SCL是由主模块输入的时钟信号,是单向的信号,而SDA是由主机或从机控制的数据信号,是双向信号。
2.1.1 基本信号介绍
写操作时序
在进行写操作时,SCL一直保持时钟的信号,SDA线的传输以8位为一个单位,在进行第一个8bit的传输后,若从设备接收到传输信号,则会返回一个应答信号ack,然后拉低SDA线,进行下一步的数据写入,写周期时序图如下图所示 ,twr 为进行数据写入的时钟周期。
数据有效性
SCL保持高电平时,SDA数据必须稳定。SCL为低电平时,SDA才可以改变数据。认为SCL高电平时,数据有效。
开始结束信号
当SCL为高电平时,SDA由高电平降为低电平代表START信号,SDA由低电平升为高电平代表STOP信号。
从机应答信号
检测到start信号后,在随后的8个时钟周期,SDA线进行一次8bit的数据传输,若接收到相应的8bit信号,则在拉低SDA信号,并视为一次ack应答信号。
2.1.2 数据格式
进行一次IIC传输时,由指示当前数据传输开始,由指示当前的数据传输结束,IIC传输格式每一次传输都是以8bit为一个基本传输单位,一次完整的IIC传输包含
Trans_data = Start + n * Bytes+ n * ack + Stop
当检测到Start信号时,**主机输出8bit的信号,其中前7bit表示从机的地址,为选中的从机信息,第8bit表示当前进行的读写操作,为’1’表示读操作,为’0’表示写操作,然后第9位为从机的应答ack信号,**表示指定从机已接收到地址信号,以进行后续的传输;后续的传输以 8 bit + ack 的重复,即 n * Bytes + n * ack 信号来进行数据的传输,最后主机发出Stop信号,即当前的一次IIC传输结束
2.2 Linux下的I2C驱动
2.2.1 I2C的几个对象
1. I2C总线
I2C总线对应着/bus下的一条总线,这个i2c总线结构体维护着两个链表(I2C驱动、I2C设备),管理着i2c设备与I2C驱动的匹配,删除等操作,I2C总线会调用i2c_device_match函数看I2C设备和I2C驱动是否匹配,如果匹配就调用i2c_device_probe函数,进而调用I2C驱动的probe函数。
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.pm = &i2c_device_pm_ops,
};
2. I2C驱动
对应I2C设备的驱动程序
struct i2c_driver {
int (*probe)(struct i2c_client *, const struct i2c_device_id *); //probe函数
struct device_driver driver; //表明这是一个驱动
const struct i2c_device_id *id_table; //要匹配的从设备信息(名称)
int (*detect)(struct i2c_client *, struct i2c_board_info *); //设备探测函数
const unsigned short *address_list; //设备地址
struct list_head clients; //设备链表
};
3. I2C设备
是具体硬件设备的一个抽象
struct i2c_client {
unsigned short addr; //设备地址
char name[I2C_NAME_SIZE]; //设备名称
struct i2c_adapter *adapter; //设配器,值I2C控制器
struct i2c_driver *driver; //设备对应的驱动
struct device dev; //表明这是一个设备
int irq; //中断号
struct list_head detected; //节点
};
4. I2C适配器
经过上面的介绍,知道有I2C驱动和I2C设备,我们需要通过I2C驱动去和I2C设备通讯,这其中就需要一个I2C适配器,I2C适配器对应的就是SOC上的I2C控制器。
struct i2c_adapter {
unsigned int id; //设备器的编号
const struct i2c_algorithm *algo; //算法,发送时序
struct device dev; //表明这是一个设备
};
struct i2c_algorithm {
/* 作为主设备时的发送函数 */
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
/* 作为从设备时的发送函数 */
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
};
5. 总线,设备,驱动之间关系
总线代表同类设备需要共同遵守的时序,不同总线硬件的通信时序也是不同的
设备代表真实存在的物理器件,每个器件有自己不同的通信时序
驱动代表操作设备的方式和流程,以应用来说,在程序open设备时,接着read这个这个设备,驱动就是实现应用访问的具体过程。驱动就是一个通信官和翻译官,一是通过对soc的控制寄存器编程,按总线要求输出相应时序的命令,与设备交互,一是对得到数据进行处理,给上层提供特定格式数据。
设备与驱动匹配上后。驱动程序需要通过i2c_transfer来与设备通讯,此时就需要指定i2c适配器,故驱动程序是通过i2c适配器向设备发送数据的!
以注册I2C驱动为例,简单讲解I2C总线的运行机制(I2C设备道理相同) 1、注册I2C驱动
2、将I2C驱动添加到I2C总线的驱动链表中
3、遍历I2C总线上的设备链表,根据i2c_device_match函数进行匹配,如果匹配调i2c_device_probe函数
4、i2c_device_probe函数会调用I2C驱动的probe函数
6. adapter,driver(驱动),client(设备),algorithm四个结构体关系介绍
(1)adapter与algorithm之间关系
adapter对应一个适配器,而algorithm对应的则是一套通讯方法。适配器需要通信函数来控制适配器产生合适的访问周期。否则适配器什么也做不了。
(2)driver与client
client对应的是真实的物理设备,每个物理设备都需要一个client来描述。而一个驱动可以对应多个client,即一个driver可以对应多个同类型的client。
(3)adapter与client
client依附于adapter。一个适配器可以连多个设备
2.2.2 Linux I2C体系结构
I2C体系结构分为三个部分:I2C核心,I2C设备驱动,I2C总线(适配器)驱动。
1. 核心层
提供一组不依赖硬件平台的接口函数,一般不需要被修改。
/*增加/删除i2c_adapter*/
int i2c_add_adapter(struct i2c_adapter *adap);
void i2c_del_adapter(struct i2c_adapter *adap);
/*增加/删除i2c_driver*/
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
void i2c_del_driver(struct i2c_driver *driver);
/*I2C传输发送与接受: master中包含了transfer*/
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
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);
i2c_transfer() 函数本身不具备驱动适配器物理硬件以完成消息交互的能力,它只是寻找到与 i2c_adapter 对应的 i2c_algorithm, 并使用 i2c_algorithm 的 master_xfer() 函数真正驱动硬件流程。
2. I2C适配器驱动层
对应Soc的I2C控制器,把I2C控制器看做一个设备,实现I2C控制器的驱动代码,和具体的Soc相关;
适配器驱动资源的初始化与注册
由于IIC总线控制器通常是在内存上的,所有它本身也连接在platform总线上,要通过paltform_driver 和paltform_device 的匹配来执行。在该paltform_driver 的probe函数中,通常完成两个工作:(1)初始化I2C适配器所使用的硬件资源,如申请i/o地址、中断号、时钟等。(2)通过i2c_add_adapter接口向I2C总线注册了一个适配器。
I2C总线的通信方法
为特定的I2C适配器提供通信方法,主要是实现i2c_algorithm的functionality()函数与master_xfer()函数。
static int i2c_adapter_xxx_xfer(struct i2c_adapter * adap, struct i2c_msg *
msgs, int num)
{
......
for (i = 0; i < num; i++) {
i2c_adapter_xxx_start(); /* 产生开始位 */
/* 是读消息 */
if (msgs[i]->flags & I2C_M_RD) {
i2c_adapter_xxx_setaddr((msgs->addr << 1) | 1); /* 发送从设备地址 */
i2c_adapter_xxx_wait_ack(); /* 获得从设备的ack */
i2c_adapter_xxx_readbytes((msgs[i]->buf, msgs[i]->len));/* 读取len长度的数据 */
} else { /* 是写消息 */
i2c_adapter_xxx_setaddr(msgs->addr << 1); /* 发送从设备写地址 */
i2c_adapter_xxx_wait_ack(); /* 获得从设备的ack */
i2c_adapter_xxx_writebytes(msgs[i]->buf, msgs[i]->len); /* 写入len长度的数据 */
}
}
i2c_adapter_xxx_stop(); /* 产生停止位 */
}
上述代码实际上给出了一个 master_xfer() 函数处理 I2C 消息数据的流程,对于数组中的每个消息,都是按照该流程跑(硬件的时序),模板中调用的各个函数,用于完成适配器的底层硬件操作,与 I2C 适配器和 CPU 的具体硬件直接相关,根据芯片手册实现。
3.I2C设备驱动层
和具体I2C接口的外设相关,每种外设都有自己的专属I2C驱动代码;
static struct i2c_driver yyy_driver = {
.driver = {
.name = "yyy",
},
.probe = yyy_probe,
.remove = yyy_remove,
.id_table = yyy_id,
};
I2C设备驱动的模块加载与卸载
static int __init yyy_init(void)
{
return i2c_add_driver(&yyy_driver);
}
module_initcall(yyy_init);
static void __exit yyy_exit(void)
{
i2c_del_driver(&yyy_driver);
}
module_exit(yyy_exit);
I2C设备驱动的数据传输
struct i2c_msg msgs[2];
/* 第一条消息是写消息 */
msgs[0].addr = client->addr;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = &offs;
/* 第二条消息是读消息 */
msgs[1].addr = client->addr;
msgs[1].flags = I2C_M_RD;
msgs[1].len = sizeof(buf);
msgs[1].buf = &buf[0];
i2c_transfer(client->adapter, msg, 2);
2.3 GPIO口模拟I2C总线协议(代码)
/*GPIO口模拟I2C总线协议 */
#define SDA 254 //定义SDA所对应的GPIO接口编号
#define SCL 255 //定义SCL所对应的GPIO接口编号
#define OUTP 1 //表示GPIO接口方向为输出
#define INP 0 //表示GPIO接口方向为输入
/* I2C起始条件 —————— S信号:SCL保持高电平,SDA由高到低*/
int i2c_start()
{
//初始化GPIO口
set_gpio_direction(SDA, OUTP); //设置SDA方向为输出
set_gpio_direction (SCL, OUTP); //设置SCL方向为输出
set_gpio_value(SDA, 1); //设置SDA为高电平
set_gpio_value(SCL, 1); //设置SCL为高电平
delay(); //延时
//起始条件
set_gpio_value(SDA, 0); //SCL为高电平时,SDA由高变低
delay();
}
/* I2C终止条件 ————— P信号:SCL保持高电平,SDA由低到高*/
void i2c_stop()
{
set_gpio_value(SCL, 1);
set_gpio_direction(SDA, OUTP);
set_gpio_value(SDA, 0);
delay();
set_gpio_value(SDA, 1); //SCL高电平时,SDA由低变高
}
/*
*并非每传输8位数据之后,都会有ACK信号,有以下3种例外
*1.当从机不能响应从机地址时(例如它正忙于其他事而无法响应IIC总线的操作,或者这个地址没有对应的从机),在第9个SCL周期内SDA线没有拉低,即没有ACK信号。这时,主机发出一个P信号终止传输或者重新发出一个S信号开始新的传输。
*2.如果从机接收器在传输过程中不能接收更多的数据时,它不会发出ACK信号。这样,主机就可以意识到这点,从而发出一个P信号终止传输或者重新发出一个S信号开始新的传输。
*3.主机接收器在接收到最后一个字节后,也不会发出ACK信号。于是,从机发送器释放SDA线,以允许主机发出P信号结束传输。
*/
/*
*应答信号ACK
*发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。
*应答信号低电平为有效应答位,高电平为非应答位
*对于反馈有效应答ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA拉低,并且确保在该时钟的高电平期间为稳定的低电平
*/
/*
I2C读取ACK信号(写数据时使用)
返回值 :0表示ACK信号有效;非0表示ACK信号无效
*/
unsigned char i2c_read_ack()
{
unsigned char r;
set_gpio_direction(SDA, INP); //设置SDA方向为输入
set_gpio_value(SCL,0); // SCL变低
r = get_gpio_value(SDA); //读取ACK信号
delay();
set_gpio_value(SCL,1); // SCL变高
delay();
return r;
}
/* I2C发出ACK信号(读数据时使用) */
int i2c_send_ack()
{
set_gpio_direction(SDA, OUTP); //设置SDA方向为输出
set_gpio_value(SCL,0); // SCL变低
set_gpio_value(SDA, 0); //发出ACK信号
delay();
set_gpio_value(SCL,1); // SCL变高
delay();
}
/* I2C字节写 */
void i2c_write_byte(unsigned char b)
{
int i;
set_gpio_direction(SDA, OUTP); //设置SDA方向为输出
for (i = 7; i >= 0; i--) {
set_gpio_value(SCL, 0); // SCL变低
delay();
set_gpio_value(SDA, b & (1<<i)); //从高位到低位依次准备数据进行发送
set_gpio_value(SCL, 1); // SCL变高
delay();
}
i2c_read_ack(); //检查目标设备的ACK信号
}
/* I2C字节读 */
unsigned char i2c_read_byte()
{
int i;
unsigned char r = 0;
set_gpio_direction(SDA, INP); //设置SDA方向为输入
for (i=7; i>=0; i--) {
set_gpio_value(SCL, 0); // SCL变低
delay();
r = (r <<1) | get_gpio_value(SDA); //从高位到低位依次准备数据进行读取
set_gpio_value(SCL, 1); // SCL变高
delay();
}
i2c_send_ack(); //向目标设备发送ACK信号
return r;
}
/*
I2C读操作
addr:目标设备地址
buf:读缓冲区
len:读入字节的长度
*/
void i2c_read(unsigned char addr, unsigned char* buf, int len)
{
int i;
unsigned char t;
i2c_start(); //起始条件,开始数据通信
//发送地址和数据读写方向
t = (addr << 1) | 1; //低位为1,表示读数据
i2c_write_byte(t);
//读入数据
for (i=0; i<len; i++)
buf[i] = i2c_read_byte();
i2c_stop(); //终止条件,结束数据通信
}
/*
I2C写操作
addr:目标设备地址
buf:写缓冲区
len:写入字节的长度
*/
void i2c_write (unsigned char addr, unsigned char* buf, int len)
{
int i;
unsigned char t;
i2c_start(); //起始条件,开始数据通信
//发送地址和数据读写方向
t = (addr << 1) | 0; //低位为0,表示写数据
i2c_write_byte(t);
//写入数据
for (i=0; i<len; i++)
i2c_write_byte(buf[i]);
i2c_stop(); //终止条件,结束数据通信
}