I2C(Inter-Integrated Circuit BUS)是 I2C BUS 简称,中文为集成电路总线,是目前应用最广泛的总线之一。和 IMX6ULL 有些相关的是,刚好该总线是 NXP 前身的 PHILIPS 设计。
I2C 协议
概述:
体育老师:可以把球发给学生,也可以把球从学生中接过来。
① 发球:
a) 老师说:注意了(start);
b) 老师对 A 学生说,我要球发给你(A 就是地址);
c) 老师就把球发出去了(传输);
d) A 收到球之后,应该告诉老师一声(回应);
e) 老师说下课(停止)。
② 接球:
a) 老师说注意了(start);
b) 老师说:B 把球发给我(B 是地址);
c) B 就把球发给老师(传输);
d) 老师收到球之后,给 B 说一声,表示收到球了(回应);
e) 老师说下课(停止)。
我们就使用这个简单的例子,来解释一下 IIC 的传输协议:
① 老师说注意了,表示开始信号(start)
② 老师告诉某个学生,表示发送地址(address)
③ 老师发球/接球,表示数据的传输
④ 老师/学生收到球,回应表示:回应信号(ACK)
⑤ 老师说下课,表示 IIC 传输接受(P)
物理层:
➢ 特性 1:半双工(非全双工)
I2C 总线中只使用两条线路:SDA、SCL。
① SDA(串行数据线):
主芯片通过一根 SDA 线既可以把数据发给从设备,也可以从 SDA 上读取数据。在 I2C 设备内部有两个引脚(发送引脚/接受引脚),它们都连接到外部的SDA 线上。
② SCL(串行时钟线):
I2C 主设备发出时钟,从设备接收时钟。
SDA 和 SCL 引脚的内部电路结构一致,引脚的输出驱动与输入缓冲连在一起。其中输出为漏极开路的场效应管、输入缓冲为一只高输入阻抗的同相器。这
样结构有如下特性:
⚫ 由于 SDA、SCL 为漏极开路结构,借助于外部的上拉电阻实现了信号的“线与”逻辑;
⚫ 引脚在输出信号的同时还作用输入信号供内部进行检测,当输出与输入不一致时,就表示有问题发生了。这为 “时钟同步”和“总线仲裁”提供硬件基础。SDA 和 CLK 连接线上连有两个上拉电阻,当总线空闲时,两根线均为高电平。连到总线上的任一器件输出的低电平,都将使总线的信号变低。
物理层连接如下图所示:
➢ 特性 2:地址和角色可配置
每个连接到总线的器件都可以通过唯一的地址和其它器件通信,主机/从机角色和地址可配置,主机可以作为主机发送器和主机接收器。
➢ 特性 3:多主机
IIC 是真正的多主机总线,I2C 设备可以在通讯过程转变成主机。如果两个或更多的主机同时请求总线,可以通过冲突检测和仲裁防止总线数据被破坏。
➢ 特性 4:传输速率
传输速率在标准模式下可以达到 100kb/s,快速模式下可以达到 400kb/s。
➢ 特性 5:负载和距离
节点的最大数量受限于地址空间以及总线电容决定,另外总电容也限制了实际通信距离只有几米。
协议层:
➢ 数据有效性
I2C 协议的数据有效性是靠时钟来保证的,在时钟的高电平周期内,SDA 线上的数据必须保持稳定。数据线仅可以在时钟 SCL 为低电平时改变。
➢ 起始和结束条件
⚫ 起始条件:当 SCL 为高电平的时候,SDA 线上由高到低的跳变被定义为起始条件。
⚫ 结束条件:当 SCL 为高电平的时候,SDA 线上由低到高的跳变被定义为停止条件。
要注意起始和终止信号都是由主机发出的,连接到 I2C 总线上的器件,若具有 I2C 总线的硬件接口,则很容易检测到起始和终止信号。
总线在起始条件之后,视为忙状态,在停止条件之后被视为空闲状态。
➢ 应答
每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,从机应答主机所需要的时钟仍是主机提供的,应答出现在每一次主机完成 8 个数据位传输后紧跟着的时钟周期,低电平 0 表示应答,1 表示非应答。
➢ 数据帧格式
SDA 线上每个字节必须是 8 位长,在每个传输(transfer)中所传输字节数没有限制,每个字节后面必须跟一个 ACK。8 位数据中,先传输最高有效位(MSB)传输。
IMX6ULL的I2C控制器操作与寄存器介绍
本部分在IMX6ULL裸机开发手册里面有详细介绍
I2C 读写标准流程
➢ 写寄存器的标准流程如下图:
a) Master 发起 START
b) Master 发送 I2C addr(7bit)和 w 操作 0(1bit),等待 ACK
c) Slave 发送 ACK
d) Master 发送 reg addr(8bit),等待 ACK
e) Slave 发送 ACK
f) Master 发送 data(8bit),即要写入寄存器中的数据,等待 ACK
g) Slave 发送 ACK
h) 第 6 步和第 7 步可以重复多次,即顺序写多个寄存器
i) Master 发起 STOP
➢ 读寄存器的标准流程如下图:
a) Master 发送 I2C addr(7bit)和 w 操作 1(1bit),等待 ACK
b) Slave 发送 ACK
c) Master 发送 reg addr(8bit),等待 ACK
d) Slave 发送 ACK
e) Master 发起 RESTART
f) Master 发送 I2C addr(7bit)和 r 操作 1(1bit),等待 ACK
g) Slave 发送 ACK
h) Slave 发送 data(8bit),即寄存器里的值
i) Master 发送 ACK
j) 第 8 步和第 9 步可以重复多次,即顺序读多个寄存器
k) Master 发送 NO ACK 表示读取完成,从机也不用发送 ACK
l) Master 发送 STOP
I2C总线驱动模型
I2C设备驱动程序模板
i2c_drv.c
#include "linux/i2c.h"
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
/* 主设备号 */
static int major = 0;
static struct class *my_i2c_class;
/* i2c设备结构体数据指针,指向I2C客户端的结构体 */
struct i2c_client *g_client;
static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);
/* i2c异步通知结构体指针 */
struct fasync_struct *i2c_fasync;
/* 实现对应的open/read/write等函数,填入file_operations结构体 */
static ssize_t i2c_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
int err;
/* 实例i2c_msg, 用于表示I2C通信中的一个消息,可以包含消息的地址、数据方向以及要传输的数据*/
struct i2c_msg msgs[2];
/* 初始化i2c_msg
adapter:是I2C适配器的结构体指针
*/
err = i2c_transfer(g_client->adapter, msgs, 2); //传输2个消息
/* copy_to_user */
return 0;
}
static ssize_t i2c_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int err;
/* copy_from_user */
struct i2c_msg msgs[2];
/* 初始化i2c_msg */
err = i2c_transfer(g_client->adapter, msgs, 2);
return 0;
}
static unsigned int i2c_drv_poll(struct file *fp, poll_table * wait)
{
//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
poll_wait(fp, &gpio_wait, wait);
//return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
return 0;
}
static int i2c_drv_fasync(int fd, struct file *file, int on)
{
if (fasync_helper(fd, file, on, &i2c_fasync) >= 0)
return 0;
else
return -EIO;
}
/* 定义自己的file_operations结构体 */
static struct file_operations i2c_drv_fops = {
.owner = THIS_MODULE,
.read = i2c_drv_read,
.write = i2c_drv_write,
.poll = i2c_drv_poll,
.fasync = i2c_drv_fasync,
};
static int i2c_drv_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
// struct device_node *np = client->dev.of_node;
// struct i2c_adapter *adapter = client->adapter;
/* 记录client */
g_client = client;
/* 注册字符设备 */
/* 注册file_operations */
major = register_chrdev(0, "100ask_i2c", &i2c_drv_fops); /* /dev/gpio_desc */
my_i2c_class = class_create(THIS_MODULE, "100ask_i2c_class");
if (IS_ERR(my_i2c_class)) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "100ask_i2c");
return PTR_ERR(my_i2c_class);
}
device_create(my_i2c_class, NULL, MKDEV(major, 0), NULL, "myi2c"); /* /dev/myi2c */
return 0;
}
static int i2c_drv_remove(struct i2c_client *client)
{
/* 反注册字符设备 */
device_destroy(my_i2c_class, MKDEV(major, 0));
class_destroy(my_i2c_class);
unregister_chrdev(major, "100ask_i2c");
return 0;
}
/* 设备树匹配表 */
static const struct of_device_id myi2c_dt_match[] = {
{ .compatible = "100ask,i2cdev" },
{},
};
/* i2c_driver驱动结构体 */
static struct i2c_driver my_i2c_driver = {
.driver = {
.name = "100ask_i2c_drv",
.owner = THIS_MODULE,
.of_match_table = myi2c_dt_match, //设备树匹配表
},
.probe = i2c_drv_probe,
.remove = i2c_drv_remove,
};
static int __init i2c_drv_init(void)
{
/* 注册i2c_driver */
return i2c_add_driver(&my_i2c_driver);
}
static void __exit i2c_drv_exit(void)
{
/* 删除i2c_driver */
i2c_del_driver(&my_i2c_driver);
}
/* 7. 其他完善:提供设备信息,自动创建设备节点 */
module_init(i2c_drv_init);
module_exit(i2c_drv_exit);
MODULE_LICENSE("GPL");
评论