模拟IIC.docx
《模拟IIC.docx》由会员分享,可在线阅读,更多相关《模拟IIC.docx(16页珍藏版)》请在冰豆网上搜索。
![模拟IIC.docx](https://file1.bdocx.com/fileroot1/2023-1/7/5f6dcce8-a274-403d-b8cc-3da9a451f906/5f6dcce8-a274-403d-b8cc-3da9a451f9061.gif)
模拟IIC
IIC总线
1、IIC总线简介
IIC总线是Philips推出的一种串行总线方式,它的全称是IntelIntegrateCircuitBus,它通过SDA(串行数据线)和SCL(串行时钟线)两根线在连到总线上的器件之间传送信息,并通过软件寻址识别每个器件,而不需要片选线(结合图1说明)。
IIC总线接口均为开漏或开集电极输出,因此需要为总线增加上拉电阻Rp。
总线速率越高,总线上拉电阻就越小,对于100Kbit/s总线速率,通常使用5.1K欧姆的上拉电阻。
IIC总线上的通信通常发生在两个器件之间,其中一个是主机另一个是从机,从机和主机均能读和写,ads1110只能作为从机。
2、数据方式
IIC总线有三种数据速率工作方式:
标准方式:
允许最高100KHz的时钟频率;
快速方式:
允许最高400KHz的时钟频率;
高速方式:
允许最高3.4MHz的时钟频率。
3、数据传送
一条IIC总线有两条线路构成:
SDA线和SCL线,SDA线传送数据,SCL提供时钟,所用数据以8位为一组在总线上传送。
在数据传送过程中,必须确认数据传送的开始和结束,这通过起始和结束信号识别,开始信号的条件是当时钟为高电平时,数据线从高到低的跳变;结束信号的条件是当时钟为高电平时,数据线从低到高的跳变。
(结合图2说明)
IIC的完整时序图,具体可参考ads1110的datasheet
发送起始信号后传送的第一字节数据具有特别的意义,其中前七位为从机地址,最后一位为读写方向位(0表示写,1表示读)。
IIC总线数据传送时,每传送一个字节数据后都必须有应答信号(A)。
主控器接收数据时,如果要结束通信时,将在停止位之前发送非应答信号(!
A)。
IIC总线是双向的:
SDA用来发送数据和接收数据,当主机从从机中读取数据时,从机驱动数据线,当主机向从机发送数据时,主机驱动数据线,主机总是驱动时钟线。
具有IIC接口的器件在通信中可以配置为主控器也可以作为被控器,那么它就具有4种操作模式:
主发送模式、主接收模式、从发送模式和从接收模式。
(结合图3说明)
多数时候总线是空闲的,不发生通信,两条线上均是高电平。
只有主机才能发起一次通信。
为了开始通信,主机必须发起一个开始信号,通常在SCL为低的时候数据才允许改变,否则在SCL线为高电平的时候数据线改变了状态,则形成一个开始条件或结束条件。
4对于没有IIC接口的器件,比如MCS-51单片机,若想使用IIC进行串行通信,硬件上可以外扩IIC接口芯片,软件上可以模拟IIC总线时序进行通讯,在此我们采用第二种方法,这就需要软件模拟IIC的读、写时序,模拟在作为接收器时的应答信号和非应答信号。
5、IIC程序构成及代码
IIC程序一共分成7个子程序块:
开始条件,结束条件,应答信号,发送连续读信号,发送不连续读信号,发送一个字节,接收一个字节。
5.1开始条件
当时钟信号SCL处于高电平期间数据线SDA有个由高到低的跳变则表示进入IIC通信。
代码段:
voidstart(void)//开始
{
SDA=1;
_nop_();
SCL=1;
_nop_();
SDA=0;
_nop_();
SCL=0;
_nop_();
}
5.2结束条件
当时钟信号SCL处于高电平期间数据信号SDA有个由低到高的跳变表示IIC通信结束。
代码段:
voidstop(void)//结束
{
SDA=0;
_nop_();;
SCL=1;
_nop_();
SDA=1;
_nop_();
SCL=0;
_nop_();
}
5.3应答信号
一个时钟周期内如果数据信号为低则产生一个应答信号,数据为高则没有应答。
程序最后有返回值。
注意:
在检查应答信号之前一定要先释放SDA的控制权:
SDA=1;否则程序会出错。
代码段:
unsignedcharcheck_ack(void)//检查应答信号
{
SDA=1;//检测从机应答信号之前应释放对SDA线的控制权
SCL=0;
SCL=1;
_nop_();
if(SDA)
{
ACK=1;
}
else
{
ACK=0;
}
SCL=0;
return(ACK);
}
5.4发送连续读信号,即主机工作在主接收方式下,接收完一个字节数据后发送应答信号
在读周期中,字节是8位为一组在总线上传送的,这就涉及到连续数据的传输。
注意:
主机发送完应答信号后要释放SDA。
代码段:
voidwrite_ack(void)//发送连续读信号
{
SCL=0;
SDA=0;
SCL=1;
_nop_();;
SCL=0;
SDA=1;//主发送机发送完应答信号后应释放SDA总线,否则
//只能读出第一个字节
}
5.5发送非应答信号(也可作为不连续读信号),在要结束IIC通信时可发一个非应答信号
在一个时钟周期内,数据线SDA始终为高,表示发送非应答信号。
代码段:
voidwrite_nack(void)//发送不连续读信号
{
SCL=0;
SDA=1;
SCL=1;
_nop_();
SCL=0;
}
5.6发送一个字节
以8位一个字节为一组进行发送。
代码段:
voidwrite_byte(unsignedchardate)//发送一个字节
{
unsignedchari=8;//发送8位
do
{
SCL=0;
if((date&0x80)==0x80)
{
SDA=1;//写1
}
else
{
SDA=0;//写0
}
SCL=1;
_nop_();
SCL=0;
date=date<<1;
}while(--i);
SDA=1;//发送完一个字节数据后应在接收器发送应答信号前释放总线
}
5.7接收一个字节:
有返回值。
代码段:
unsignedcharread_byte(void)//接收一个字节
{
unsignedchardate=0;
unsignedchari=8;
do
{
SCL=0;
SCL=1;
_nop_();
if(SDA)//读1
{
date=date|0x01;
}
SCL=0;
if(i-1)
{
date=date<<1;
}
}while(--i);
return(date);
}
6、典型应用
我们以16位A/D转换芯片ADS1110为例,说明其具体操作过程。
ADS1110的写操作是对配置寄存器进行的,首先要对ADS1110寻址,然后写入一个字节。
这个字节是写入配置寄存器。
输出寄存器不能被写入。
ADS1110的读操作首先也要对ADS1110寻址,然后从器件中读取三个字节,前两个是输出寄存器内容,第三个为配置寄存器内容。
ADS1110允许读少于三个的字节,但是多于三个字节是无效的。
从第四个以后所有的字节均为FF。
16位的输出寄存器包含了上次转换的结果,8位的配置寄存器是用来控制ADS1110的工作方式,数据速率和PGA的设置。
默认设置为8C。
具体信息参考datasheet中寄存器的说明部分。
写操作过程:
开始条件,对器件寻址,检查主机应答信号,有应答则继续。
写入一个字节到配置寄存器,检查应答信号,有应答则继续,最后结束IIC的写操作。
程序:
start();//开始一次通信
write_byte(0x90);//写器件地址(写)
if(check_ack())//检测应答信号
{
return(0);
}
write_byte(8c);//写配置字,8c为ads1110工作时的设置
if(check_ack())//检测应答信号
{
return(0);
}
stop();
读操作过程:
(ads1110是16位ad,转换结果分两次读出)
开始条件,读器件地址,检查主机应答信号,有应答则继续。
读高8位数据,主机发送连续读信号,读低8位数据,主机发送连续读信号,读配置寄存器状态,主机发送不连续读信号,结束IIC的读操作。
程序:
start();
write_byte(0x91);//写器件地址(读)
if(check_ack())//检测应答信号
{
return(0);
}
temp=read_byte();//读高8位
write_ack();//连续读
tempL=read_byte();//读低8位
write_ack();//连续读
config_s=read_byte();//读配置寄存器的状态
write_nack();
stop();//结束读
temp=(temp<<8)+tempL;//对两次读取的结果进行处理
整个过程:
开始--->>写从机地址(写操作)--->>检测应答信号(若有应答信号)--->>写配置字,设定从机工作方式--->>检测应答信号(若有应答信号)--->>写操作结束;
开始--->>写从机地址(读操作)--->>检测应答信号(若有应答信号)--->>读高8位转换数据--->>发送应答信号即连续读信号--->>读低8位转换数据--->>发送应答信号即连续读信号--->>读配置寄存器(可选)--->>发送非应答信号信号准备结束IIC通信--->>结束读操作,可对读到的两个8位数据联接成一个16位的数据;
一次读取转换数据结束。
注意:
在以下三个地方单片机要释放对SDA总线的控制权
1)单片机作为主发送机检查从机的应答信号之前要释放SDA总线
2)单片机作为主接收机发送应答信号之后要释放SDA总线
3)单片机作为主发送机发送完一个字节数据之后要释放SDA总线
他们有一个共同的特点,即在对从机进行读操作之前释放SDA总线
7、附ads1110完整程序清单如下:
//ADS1110的驱动程序,采用IIC通讯方式
sbitSDA=P1^3;//根据硬件实际连接作相应改动
sbitSCL=P1^2;
unsignedcharACK;
voidNOP()
{
_nop_();
_nop_();
}
*******************************************************************************
voidstart(void)//开始
{
SDA=1;
NOP();
SCL=1;
NOP();
SDA=0;
NOP();
SCL=0;
NOP();
}
**************************************************************************
voidstop(void)//结束
{
SDA=0;
NOP();
SCL=1;
NOP();
SDA=1;
NOP();
SCL=0;
NOP();
}
*************************************************************************
unsignedcharcheck_ack(void)//检查应答信号
{
SDA=1;//检测应答信号前应释放对SDA线的控制权
SCL=0;
SCL=1;
NOP();
if(SDA)
{
ACK=1;
}
else
{
ACK=0;
}
SCL=0;
return(ACK);
}
*********************************************************************
voidwrite_ack(void)//发送连续读信号
{
SCL=0;
SDA=0;
SCL=1;
NOP();
SCL=0;
SDA=1;//主发送机发送完应答信号后应释放SDA总线,否则只能读出第一个字节
}
***************************************************************************
voidwrite_nack(void)//发送不连续读信号
{
SCL=0;
SDA=1;
SCL=1;
NOP();
SCL=0;
}
**********************************************************************
voidwrite_byte(unsignedchardate)//发送一个字节
{
unsignedchari=8;//发送8位
do
{
SCL=0;
if((date&0x80))
{
SDA=1;//写1
}
else
{
SDA=0;//写0
}
SCL=1;
NOP();
SCL=0;
date=date<<1;
}while(--i);
SDA=1;//发送完一个字节数据后应在接收器发送应答信号前释放总线
}
******************************************************************************
unsignedcharread_byte(void)//接收一个字节
{
unsignedchardate=0;
unsignedchari=8;
do
{
SCL=0;//在时钟大于4u秒期间读数据
SCL=1;
NOP();
if(SDA)//读1
{
date=date|0x01;
}
SCL=0;
if(i-1)
{
date=date<<1;
}
}while(--i);
return(date);
}
**************************************************************
floatads1110(unsignedcharconfig)
{
unsignedintconfig_s;//配置字状态
unsignedinttemp;
unsignedinttempL;
floatadc_value;
start();
//开始写
write_byte(0x90);//写器件地址(写)
if(check_ack())//检测应答信号
{
return(0);
}
write_byte(config);//写配置字
if(check_ack())//检测应答信号
{
return(0);
}
stop();
start();
write_byte(0x91);//写器件地址(读)
if(check_ack())//检测应答信号
{
return(0);
}
temp=read_byte();//读高8位
write_ack();//连续读
tempL=read_byte();//读低8位
write_ack();//连续读
config_s=read_byte();//读配置寄存器的状态
write_nack();
stop();//结束读
temp=(temp<<8)+tempL;
x=56;
y=4;
printf("%d",temp);
adc_value=temp;
adc_value=adc_value*2.048/32768;
return(adc_value);
}