
这里是个例子,有问题欢迎一起讨论
/*****************************************************
/* 文件名: I2C.h
/* 描述 : I2C.c的头文件
/* 编写环境 : Keil uVision 3 V3.51
/* 作者 : XX
/* 学校 : 广东XX大学
/* Email : lanhaospider@163.com
/* 版本 : V1.0
/* 编写日期 : 2008-3-30
/* 仅供学习参考
/* 芯片 : MCS-51 AT89S52
/* 晶振 : 11.0592MHz
/* 功能描述 : 模拟I2C总线的接口程序库,主机的程序
/* 应用 : 发送n个字节: 起始位->发送控制字节(类型标识符4位->
片选3位->读写位最后1位)->应答位->数据->应答...........应答->终止位
高位先到达,低位后到达
/****************************************************/
#include "reg51.h"/*根据不同主控芯片型号改写该套入*/
#include "intrins.h"
sbit SCL = P1^6 /*定义SCL线所在口,根据实践需要改写该定义*/
sbit SDA = P1^7 /*定义SDA线所在口,根据实践需要改写该定义*/
unsigned char idata error/*错误提示,全局变量*/
extern void Start_I2C(void)
extern void Stop_I2C(void)
extern void Ack_I2C(void)
extern void Send_Ack(void)
extern void Send_Not_Ack(void)
extern void Send_I2C(unsigned char send_byte)
extern unsigned char Receive_I2C(void)
/*****************************************************/
/* 文件名: I2C.c
/* 描述 : I2C通信程序
/* 编写环境 : Keil uVision 3 V3.51
/* 作者 : XX
/* 学校 : 广东XX大学
/* Email : lanhaospider@163.com
/* 版本 : V1.0
/* 编写日期 : 2008-3-30
/* 仅供学习参考
/* 芯片 : MCS-51 AT89S52
/* 晶振 : 11.0592MHz
/* 功能描述 : 模拟I2C总线的接口程序库,为主机的程序
/*****************************************************/
#include "I2C.h"
/**************************************************
调用方式 : void Start_I2C(void)
函数说明: 启动I2C总线
**************************************************/
void Start_I2C(void)
{
EA = 0 /*关总中断*/
SDA = 1 /*发送启动总线的数据信号*/
SCL = 1 /*发送启动总线的时钟信号*/
_nop_() /*保持数据线高,延时*/
_nop_()
_nop_()
_nop_()
_nop_()
SDA = 0 /*发送起始信号*/
_nop_()
_nop_()
_nop_()
_nop_()
_nop_()
SCL = 0 /*时钟线高低跳变一次,I2C通信开始*/
}
/**************************************************
调用方式 : void Stop_I2C(void)
函数说明: 关闭I2C总线
**************************************************/
void Stop_I2C(void)
{
SCL = 0/*发送关闭总线的时钟信号*/
SDA = 0/*发送关闭总线的数据信号*/
_nop_()
_nop_()
_nop_() /*保持数据线低,延时*/
_nop_()
_nop_()
SCL = 1 /*时钟线一次低高跳变,I2C通信停止*/
_nop_()
_nop_()
_nop_()
_nop_()
_nop_()
SDA = 1 /*发送I2C总线停止数据信号*/
_nop_()
_nop_()
_nop_()
_nop_()
_nop_()
EA = 1 /*开总中断*/
}
/**************************************************
调用方式 : void Ask_I2C(void)
函数说明: 主控程序等待从器件接收方式应答
**************************************************/
void Ack_I2C(void)
{
unsigned char errtimes = 0xFF
SDA = 1
SCL = 1
error = 0x10
while(SDA)
{
errtimes--
if(!errtimes)
{
Stop_I2C()
error = 0x11
return
}
}
SCL = 0
}
/**************************************************
调用方式 : void Send_Ask(void)
函数说明: 主控程序为接收方,从器件为发送方时,从
器件等待主器件应答
**************************************************/
void Send_Ack(void)
{
SDA = 0 /*保持数据线低,时钟线发生一次高低跳变 发送一个应答信号*/
_nop_()
_nop_()
_nop_()
_nop_()
_nop_()
SCL = 1 /*时钟线保持低电平*/
_nop_()
_nop_()
_nop_()
_nop_()
_nop_()
SCL = 0
}
/**************************************************
调用方式 : void Send_Not_Ask(void)
函数说明: 主控程序为接收方,从器件为发送方时,非应答信号
**************************************************/
void Send_Not_Ack(void)
{
SDA = 1 /*保持数据线高,时钟线发生一次高低跳变 没有应答*/
_nop_()
_nop_()
_nop_()
_nop_()
_nop_()
SCL = 1 /*时钟线保持高电平*/
_nop_()
_nop_()
_nop_()
_nop_()
_nop_()
SCL = 0
}
/**************************************************
调用方式 : void Send_I2C(unsigned char send_byte)
函数说明: 总线发送一个字节
**************************************************/
void Send_I2C(unsigned char send_byte)
{
unsigned char send_bit
for(send_bit = 8send_bit <= 0send_bit--)
{
SCL = 0
_nop_()
SDA = (send_byte &0x80)
send_byte<<=1
_nop_()
_nop_()
_nop_()
_nop_()
_nop_()
SCL = 1
_nop_()
_nop_()
_nop_()
_nop_()
_nop_()
}
SCL = 0
}
/**************************************************
调用方式 : unsigned char Receive_I2C(void)
函数说明: 从I2C总线上接收一个字节
**************************************************/
unsigned char Receive_I2C(void)
{
unsigned char receive_bit , receive_byte = 0
for(receive_bit = 8receive_bit <= 0receive_bit--)
{
SCL = 0
_nop_()
_nop_()
_nop_()
_nop_()
_nop_()
_nop_()
SCL = 1
_nop_()
_nop_()
_nop_()
_nop_()
_nop_()
receive_byte <<=1
receive_byte |= SDA
}
SCL = 0
return receive_byte
}
I2C总线是各种总线中使用信号线最少,并具有自动寻址、多主机时钟同步和仲裁等功能的总线。因此,使用I2C总线设计计算机系统十分方便灵活,体积也小,因而在各类实际应用中得到广泛应用。下面举二个应用示例。
I2C的运用比如在铁电存储器中,用铁电存储数据就是用的I2C总线协议。 目前,51、96系列的单片机应用很广,但是由于它们都没有I2C总线接口,从而限制了在这些系统中使用具有I2C总线接口的器件。通过对I2C总线时序的分析,可以用51单片机的两根I/O线来实现I2C总线的功能。接I2C总线规定:SCL线和SDA线是各设备对应输出状态相“与”的结果,任一设备都可以用输出低电平的方法来延长SCL的低电平时间,以迫使高速设备进入等待状态,从而实现不同速度设备间的时钟同步。因此,即使时钟脉冲的高、低电平时间长短不一,也能实现数据的可靠传送,可以用软件控制I/O口做I2C接口。下面就是用GMS97C2051的通用I/O口来作为I2C总线接口,并由软件控制实现数据传送的例子,图6为其连线图。
在单主控器的系统中,时钟线仅由主控器驱动,因此可以用51系列的一根I/O线作为SCL的信号线,将其设置为输出方式,并由软件控制来产生串行时钟信号。在实际系统中使用了P1.3。另一根I/O线P1.2作为I2C总线的串行数据线,可在软件控制下在时钟的低电平期间读取或输出数据。系统传输数据的过程如下:先由单片机发出一个启始数据信号,接着送出要访问器件的7位地址数据,并等待被控器件的应答信号。当收到应答信号后,根据访问要求进行相应的 *** 作。如果是读入数据,则数据线可一直设为输入方式,中间不需要改变SDA线的工作方式,每读入一个字节均应依次检测应答信号;如果是输出数据,则首先将SDA设置为输出方式,当发送完一个字节后,需要改变SDA线为输入方式,此时读入被控器件的应答信号就完成了一个字节的传送。当所有数据传输完毕后,应向SDA发出一个停止信号,以结束该次数据传输。 下面给出51系列用汇编语言实现启始、停止、读、写、应答的程序,读者也可以根据I2C总线时序在96系列或其它单片机上实现I2C总线接口。
a.启动位程序
ACK:CLR P1.3
NOP
NOP
SETB P1.2
NOP
NOP
NOP
CPL P1.3 ;P1.3=1
NOP
NOP
NOP
DENGDAI:JB P1.2,DENGDAI
RET
b.读数据程序
读字节可以在当前地址读(CURRENT
READ),也可以随机读(RANDOM READ),读出数据的最后一个字节后不用加应答信号。
READ:PUSH 0EH
CLR P1.4
LCALL BSTART ;START
MOV A,#0A0H ;SEND THE CNOTROL BYTE
LCALL SENDBYTE
LCALL ACK
MOV A,R1 ;SEND THE ADDRESS
LCALL SENDBYTE
LCALL ACK
LCALL BSTART ;START
MOV A,#0A1H ;SEND THE CNOTROL BYTE
LCALL SENDBYTE
LCALL ACK
LCALL READBYTE
LCALL BSTOP
POP 0EH
RET
送字节程序:
SENDBYTE:PUSH 0EH
PUSH 00H
MOV R0,#08H
LOOP1:CLR P1.3
NOP
NOP
RLC A
MOV P1.2,C
CPL P1.3 P1.3=1
NOP
NOP
DJNZ R0,LOOP1
POP 00H
POP 0EH
RET
读字节子程序:
READBYTE:PUSH 0EH
PUSH 00H
MOV R0,#08H;READ THE CONTENT
CLR A
LOOP4:CLR P1.3
NOP
NOP
NOP
SETB P1.3 ;P1.3=1
MOV C,P1.2
RLC A
DJNZ R0,LOOP4
MOV R2,A
POP 00H
POP 0EH
RET
c.写数据程序:
WRITE:PUSH 0EH
CLR P1.4
LCALL BSTART
MOV A,#0A0H
CLALL SENDBYTE ;SEND THE CONTROL BYTE
LCALL ACK
MOV A,R1 ;SEND THE ADDRESS
LCALL SENDBYTE
LCALL ACK
MOV A,R2 ;WRITE THE CONTENT
LCALL SENDBYTE
LCALL ACK
LCALL BSTOP
POP 0EH
RET
连续写的两个字节之间最好是有10ms的延时。当然,也可以进行页写(PAGE
WRITE),即一次性连续写8个字节,但采用页写方式时每个字节后要有一个应答信号。
d.停止位程序:
BSTOP:CLR P1.3
NOP
NOP
CLR P1.2
NOP
NOP
NOP
SETB P1.3
NOP
NOP
NOP
SETB P1.2
RET // IIC开始
void Start()
{
SDA=1SCL=1NOP4()SDA=0NOP4()SCL=0
}
// IIC 结束
void Stop()
{
SDA=0SCL=0NOP4()SCL=1NOP4()SDA=1
}
// IIC 读取应答
void RACK()
{
SDA=1NOP4()SCL=1NOP4()SCL=0
}
// IIC 发送非应答
void NO_ACK()
{
SDA=1SCL=1NOP4()SCL=0SDA=0
}
// IIC向从设备写入一字节数据
void Write_A_Byte(uchar b)
{
uchar i
for(i=0i<8i++)
{
b<<=1SDA=CY_nop_()SCL=1NOP4()SCL=0
}
RACK()
}
// IIC 向从设备的指定地址写入数据
void Write_IIC(uchar addr,uchar dat)
{
Start()
Write_A_Byte(0xa0)
Write_A_Byte(addr)
Write_A_Byte(dat)
Stop()
DelayMS(10)
}
// IIC 从从设备读取数据
uchar Read_A_Byte()
{
uchar i,b
for(i=0i<8i++)
{
SCL=1b<<=1B|=SDASCL=0
}
return b
}
// IIC 从从设备的当前地址读取数据
uchar Read_Current()
{
uchar d
Start()
Write_A_Byte(0xa1)
d=Read_A_Byte()
NO_ACK()
Stop()
return d
}
// IIC 从从设备的任意地址读取数据
uchar Random_Read(uchar addr)
{
Start()
Write_A_Byte(0xa0)
Write_A_Byte(addr)
Stop()
return Read_Current()
}
假设手上有一块从淘宝上买来的开发板,我要在开发板的I2C总线上增加一个从设备(如at24c08),那么我要怎样写这个“I2C设备驱动”,让
应用程序可以访问at24c08呢?
先来看一个最简单的i2c设备驱动:
static struct i2c_board_info at24cxx_info = { //所支持的i2c设备的列表
I2C_BOARD_INFO("at24c08", 0x50), //一项代表一个支持的设备,它的名字叫做“at24c08”,器件地址是0x50
}
static struct i2c_client *at24cxx_client
static int at24cxx_dev_init(void)
{
struct i2c_adapter *i2c_adap //分配一个适配器的指针
i2c_adap = i2c_get_adapter(0) //调用core层的函数,获得一个i2c总线。这里我们已经知道新增的器件挂接在编号为0的i2c总线上
at24cxx_client = i2c_new_device(i2c_adap, &at24cxx_info) // 把i2c适配器和新增的I2C器件关联起来,这个用了i2c总线0,地址是0x50。这就组成了一个客户端
at24cxx_client i2c_put_adapter(i2c_adap)
return 0
}
static void at24cxx_dev_exit(void)
{
i2c_unregister_device(at24cxx_client)
}
module_init(at24cxx_dev_init)
module_exit(at24cxx_dev_exit)
从上面的程序可以看到,写一个i2c设备驱动程序,与写普通的字符驱动基本一样。特别之处是它调用了i2c的core层的函数,以获得对i2c总线的控制。因为用的是开发板,板上的与soc芯片(一般来说就是arm的芯片)i2c总线驱动一般都做好了,直接调用core层的函数就可以控制soc的i2c模块了。也就是说,写i2c设备驱动不需要关注arm内部的i2c模块的寄存器,我们需要关注的是设备(at24c08)的寄存器以及它的datasheet对时序的要求。
其实,添加i2c设备的方法很灵活。根据Linux的官方文档《linux-3.4.2\Documentation\i2c\instantiating-devices》,添加i2c设备的方法总结有4种:
1. i2c_register_board_info:根据总线编号、设备名字(“at24c08”)、设备地址(0x50)注册一个字符驱动。这种方法最简单、最粗暴,最贴近平时在开片机上开发i2c器件的。
2. i2c_new_device:根据i2c总线的编号,声明一个i2c设备:这种方法就是上面例子用的方法。这种方法也简单,但是需要事先知道器件挂接在哪条总线上。对于设备,还实现知道了设备地址0x50,总线适配器也支持名字为“at24c08”的设备
3. i2c_new_probed_device:
4.从用户空间实例化一个器件:这个方法相当智能快速,如下输入指令,即可增加一个i2c设备,同时增加了对应的设备文件。
# echo eeprom 0x50 >/sys/bus/i2c/devices/i2c-3/new_device
根据英文文档的标题,添加i2c设备有称之为“i2c设备的实例化”。
从上述可以知道,在实例化一个i2c设备之前,除了有对应的驱动支持总线外(这里是总线0),还需要有一个驱动使用了总线0发送时序,支持名字为"at24c08"的器件。这个驱动用总线驱动的函数,配置了at24c08的寄存器。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)