转眼间,从事嵌入式系统开发已快三年了。回首三年走过的历程,发觉除了增加几行代码,没有留下什么。希望在chinaunix的博客,把"I2C总线驱动"做为第一篇博文,争取每周一篇,把之前和今后工作中的心得体会记录下来,有不当之处,还恳请指正。
$1 I2C总线概述
I2C总线是一种由PHILIPS公司开发的两线式(数据线SDA和时钟线SCL)串行总线,其标准模式下传输速度可以100Kbps,快速模式下可达400Kbps.I2C总线上的每个器件都有一个唯一的地址识别,可以作为总线上的一个发送器件或接收器件。
$1.1 I2C总线的几种信号状态 1. 空闲状态:SDA和SCL都为高电平。
2. 开始条件(S):SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据(由主机产生)。
3. 重复起始条件(Sr):SCL为高电平时,SDA由高电平向低电平跳变,总线处于忙状态(由主机产生)。
4. 结束条件(P):SCL为低电平时,SDA由低电平向高电平跳变,结束传送数据(由主机产生)。
5. 数据有效:在SCL的高电平期间, SDA保持稳定,数据有效。SDA的改变只能发生在SCL的低电平期间。
6. ACK信号: 数据传输的过程中,接收器件每接收一个字节数据要产生一个ACK信号,向发送器件发出特定的低电平脉冲,表示已经收到数据。
$1.2 总线仲裁
主机只能在总线空闲的时候启动传输。当SCL线是高电平时,仲裁在SDA线发生,当有其它主机发送低电平时,发送高电平的主机将断开它的数据输出级,产生仲裁失败。
$1.3 用时钟同步机制作为握手
在字节级的快速传输中,器件可以快速接收字节,但需要更多时间来处理和准备下一个要发送的字节。这时从机以一种握手过程,在接收和响应一个字节后使SCL线保持低电平,迫使主机进入等待状态,直到从机准备好下一个要传输的字节。
$1.4 设备地址
设备地址分7位地址和10位地址,
10位地址的第一字节:11110xx, C语言代码:addr_1st = (0xf0 | ((addr & 0x300) >> 7)) & 0xff;
$1.5 传输时序
标准传输时序见I2C协议手册,开发时需要注意某些从设备的芯片手册上的非标准的传输时序。
$2. I2C总线驱动模板
$2.1 板级注册
1. 预先注册I2C0上的I2C从设备信息
#ifdef CONFIG_I2C0_XXXX static struct i2c_board_info __initdata board_i2c0_devices[] = { #if defined (CONFIG_BATTERY_BQ27510) { .type = "bq27510", .addr = 0x55, .flags = 0, }, #endif #if defined (CONFIG_RTC_HYM8563) { .type = "rtc_hym8563", .addr = 0x51, .flags = 0, }, #endif }; #endif
i2c_register_board_info(0, board_i2c0_devices,中心ARRAY_SIZE(board_i2c0_devices));
$2.2 注册平台设备
static struct resource resources_i2c0[] = { { .start = IRQ_I2C0, .end = IRQ_I2C0, .flags = IORESOURCE_IRQ, }, { .start = XXXX_I2C0_PHYS, .end = XXXX_I2C0_PHYS + SZ_4K - 1, .flags = IORESOURCE_MEM, }, };
struct platform_device xxxx_device_i2c0 = { .name = "xxxx_i2c", .id = 0, .num_resources = ARRAY_SIZE(resources_i2c0), .resource = resources_i2c0, .dev = {
//板级私有结构,总线驱动通过pdev->dev.platform_data获取此结构地址. .platform_data = xxxx_priv_data,
}, };
platform_device_register();
$2.3 总线驱动
//i2c_transfer函数调用master_xfer方法完成底层硬件操作
static int xxxx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
//在函数是需要特别注意对出错情况的处理(如:总线仲裁失败,传输超时,总线忙等等)!
....
}
//返回I2C所支持的协议
static u32 xxxx_i2c_func(struct i2c_adapter *adap) { return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR; }
static const struct i2c_algorithm xxxx_i2c_algorithm = { .master_xfer = xxxx_i2c_xfer, .functionality = xxxx_i2c_func, };
static int xxxx_i2c_probe(struct platform_device *pdev)
{
struct i2c_adapter adap;
....
//注册适配器
adap.owner = THIS_MODULE; adap.algo = &xxxx_i2c_algorithm; //I2C总线通信方法 adap.class = I2C_CLASS_HWMON;
....
i2c_add_numbered_adapter(&adap);
....
}
static int xxxx_i2c_remove(struct platform_device *pdev)
{
....
}
static struct platform_driver xxxx_i2c_driver = { .probe = xxxx_i2c_probe, .remove = xxxx_i2c_remove, .driver = { .owner = THIS_MODULE, .name = "xxxx_i2c", }, };
static int __init xxxx_i2c_adap_init(void) {
return platform_driver_register(&xxxx_i2c_driver); }
static void __exit xxxx_i2c_adap_exit(void) { platform_driver_unregister(&xxxx_i2c_driver); }
subsys_initcall(xxxx_i2c_adap_init); module_exit(xxxx_i2c_adap_exit);
$3. I2C从设备驱动模板
static int __devinit xxxx_probe(struct i2c_client *client,const struct i2c_device_id *id)
{
....
}
static int __devexit xxxx_remove(struct i2c_client *client)
static struct i2c_driver xxxx_driver = { .driver = { .name = "xxxxxxxx", .owner = THIS_MODULE, }, .probe = xxxx_probe, .remove = __devexit_p(xxxx_remove),
};
static int __init xxxx_init(void) { return i2c_add_driver(&xxxx_driver); }
static void __exit xxxx_exit(void) { i2c_del_driver(&xxxx_driver); } module_init(xxxx_init); module_exit(xxxx_exit);
$4. I2C设备数据传输(8位寄存器地址读写例子)
static int xxxx_i2c_write(struct i2c_client *client,char *data,char reg,int len)
{
struct i2c_msg msg;
char buf[len + 1];
buf[0] = reg;
memcpy(buf+1, data, len);
msg.addr = client->addr;
msg.flag = 0;
msg.len = len + 1;
msg.buf = buf;
return i2c_transfer(client->adapter, &msg, 1);
}
static int xxxx_i2c_read(struct i2c_client *client, char *buf, char reg, int len)
{
struct i2c_msg msgs[2];
//发送从设备寄存器地址(有些从设备芯片手册称之为命令号)
msgs[0].addr = client->addr;
msgs[0].flag = 0;
msgs[0].len = len;
msgs[0].buf = ®
//读数据
msgs[1].addr = client->addr;
msgs[1].flag = I2C_M_RD;
msgs[1].len = len;
msgs[1].buf = buf;
return i2c_transfer(client->adapter, msgs, 2);
}
$5. 调试过程中常见的出错原因
1. 总线上拉电阻不够
2. 总线长度太长
注意:如果总线长度超过10cm,则总线线路的配线方式为:
SDA-------------
VDD-------------
VSS-------------
SCL-------------
3. 示波上显示的波形不符合从设备芯片上描述的传输时序(有些厂商家为了绕开PHILIPS的I2C协议,芯片手册上的传输时序跟标准的协议描述有点出入,需要驱动的额外支持)
4. 有些从设备在传输一个字节后会把SCL总线拉低一段时间处理数据,导致主机认为总线仲裁失败.
|