由于工作需要,用计算机来读写AT24C01A,用VC语言实现并口模拟I2C 。
(一)试验前的准备知识:
一、I2C总线:i2c总线是 Philips 公司首先推出的一种两线制串行传输总线。它由一根数据线(SDA)和一根
时钟线(SDL)组成。i2c总线的数据传输过程如图3所示,基本过程为:
1、主机发出开始信号。
2、主机接着送出1字节的从机地址信息,其中最低位为读写控制码(1为读、0为写),高7位为从机器件地址
代码。
3、从机发出认可信号。
4、主机开始发送信息,每发完一字节后,从机发出认可信号给主机。
5、主机发出停止信号。
I2C总线上各信号的具体说明:
开始信号:在时钟线(SCL)为高电平其间,数据线(SDA)由高变低,将产生一个开始信号。
停止信号:在时钟线(SCL)为高电平其间,数据线(SDA)由低变高,将产生一个停止信号。
应答信号:既认可信号,主机写从机时每写完一字节,如果正确从机将在下一个时钟周期将数据线(SDA)拉
低,以告诉主机操作有效。在主机读从机时正确读完一字节后,主机在下一个时钟周期同样也要将数据线(S
DA)拉低,发出认可信号,告诉从机所发数据已经收妥。(注:读从机时主机在最后1字节数据接收完以后不
发应答,直接发停止信号)。
注意:在I2C通信过程中,所有的数据改变都必须在时钟线SCL为低电平时改变,在时钟线SCL为高电平时必须保 持数据SDA信号的稳定,任何在时钟线为高电平时数据线上的电平改变都被认为是起始或停止信号。
下面以AT24C01A为例,对几个主要工作时序做详细说明。
(1)AT24LC01A的控制字(节)格式:发送时紧跟开始信号后的4位是器件选择位,通常为‘1010',它和后面的3位器件地址码(由AT24C01的A0、A1、A2上的电平决定)共同构成了7位的从机地址。从机地址后紧跟1位读/写控制位,该位为1表示读,为0表示写。最后1位是应答位,这里它由从机给出。
(2)AT24LC01A写时序:主机发送开始信号,接着发出从机地址和写控制码,主机接收从机发出的应答,主发送1字节的地址信息,主机接收应答,主机写1字节数据到从机,主机接收应答,主机发出停止信号。写操作 完成,1字节数据被写入AT24C01内指定地址。AT24C01提供一种页写的方式,每次最多可连续写入8字节数据再发送停止信号,当写入数据多时可采用这种方式以加快速度。
(3)AT24CO1随机读时序:主机发送开始信号,接着发送从机地址和写控制码,主机接收应答,主机发送1字节的的地址信息,主机接收应答(注意:前面的时序为写操作,目的把起始地址写入AT24C01A缓冲中,以告知随后的读操作从哪个地址开始,这个步骤在读时序中有时被称为"伪写"),主机发送开始信号,主机发送从机地址和读控制码,主机接收应答,主机读取1字节数据,主机不发应答,主机发送停止信号。完成上面步骤,主机已从AT24CO1中读出指定地址内1字节数据。
(4)AT24LC01读时序:与随机读时序相比,主机没有给从机写入起始地址,所以这种方式用于读取当前地址内的数据。另,AT24C01也可以采用连续读的方式,这样每次最多可以读取8字节。注意:连续读时每读完1字节后主机要发应答给主机,但在最后1字节后(即停止信号前)主机不发应答。
数据线(SDA)上的信号:读时,从机在SCL的上升沿将数据放到SDA上,写时,遇到SCL的上升沿,从机将接收SDA上的数据。
二、并行口:它包含了一批输入/输出端口,在PC机上它是一个25针的 D 型插口,一般用于连接打印机,因此
有时也称为打印口。
并行口信号:以打印机为例,并口I/0信号中有些是专门用来把数据传送给打印机的,有些则是用来对传
送过程给以控制的,还有将打印机的各种工作状态信息发送给CPU的。详细如表1所示。表中所有信号用低电平
(0V)表示逻辑0,用高电平(5V)表示逻辑1(电压都是相对于18-25脚上的接地电势而言),凡是前缀用符号‘-'表示的信号均指低电平为现役信号。
可以看出,编号为2-9针脚上的信号是传递实际数的信号,而其它线上的信号则是用在对打印机进行初始化处理和对打印机动作进行同步上。下面简单介绍一下打印过程以加深对并口的理解,CPU通过并口中16和17脚上
的信号来选择打印机,并给以初始化处理。且用13脚上的信号给以响应。在打印机已准备好接收数据时,就将
11脚置为低电平(表示可以接收),CPU把数据放到并口的数据线(2-9)上,并通过1脚上的选通信号对打印机的数据进行选通。打印机在收到选通信号时将忙信号(11)置为高电平,表示正在接收数据。数据接收完毕后,打印机在短时间内把现役的确认信号(10脚低电平)发送出去,然后再把忙信号(11)置成低电平(既非现役)并准备好接收更多数据。
并行口硬件:并口行现在通常被集成在系统板上,25针插口上的信号可通过数据锁存器、打印状态和打印
机控制三个寄存器(也就是三个输入/输出端口)进行程序设计和控制。计算机系统中通常有多个并行端口,
表2列出了它们在输入/输出系统中的地址。需要注意的是这些地址是由系统 BIOS 给出的,并不是硬件的物理
地址,所以可以通过设置 BIOS 来改变当前端口的配置。
端口寄存器:表3列出了并行端口寄存器各位的意义。这些信号也是在外部插头上出现的主要信号。不过
寄存器中有些信号的极性与插头上相应信号的极性正好相反。比如,选通信号在插头上为低电平时,信号是现
役的,而在打印机控制寄存器中则为高电平是现役的。
通过上面的准备知识,应有以下理解,1、可以把并口的25个针脚理解为三个寄存器对外的映射,除了传
送8位实际数据的引脚外,还有用于控制打印机和取得打印机当前状态的引脚,这些引脚有的为输入,有的为
输出,因此可以像用单片机I/O一样灵活的运用它们。2、I2C总线在通讯过程中,数据线(SDA)上的信号流
动方向是不断变化的,如:主机正在写AT24C01时,SDA的方向为主机到从机,SDA为输出,写完一字节后,要接收应答时,SDA的方向变为从机到主机,SDA为输入(对于主机)。3、并口模拟I2C总线,其实是用软件控制并口的 I/O 来输入输出 I2C 总线需要的高、低电平信号,从而产生I2C总线的各种时序。
(二)制作试验电路:
实验用电路
相对于并口,P1的13脚接SDA 的输入,P1的3脚接SDA的输出,P1的15脚接SCL的输入,P1的5脚接SCL的输出。试验用的电路,分析如后:P1的5脚接IC1的SCL端,用做I2C总线的串行时钟信号输出。因I2C总线中数据线(SDA)在不同的时间可能是输入也可能是输出,所以接在IC1 SDA端上的信号也有两路,输出时,P13脚输出低电平T1导通,SDA被置为低电平,P13 脚输出高电平T1截止,因 R1的作用SDA被置为高电平。输入时,P1 通过判断 13 脚上的电平高低,来读取SDA上的数据。要注意的是用于输入时T1必须是截止的,以免SDA被箝位。
这个电路具有通用性,AT24C01、AT24CO2、24LC64等24系列的I2C EEPROM 均可按这个电路与并口连接,所以 不妨把它当作实用工具来认真制作。先找一条并口电缆,看电缆插头的形式,找一个与之配套的25针插座,购买一个拨动式的IC插座,将IC插座按图中IC1的连接方法与找来的并口插座相连,然后按图将T1、R1、C1、直接焊在IC插座或并口插座上,要尽量作的紧凑些。最后将电路固定在一个合适的小塑料盒内,好了,现在它是我们的试验器材,等看过后面的内容,你会发现只要为其配上软件,它就是一个用于读写I2C EEPROM 的好工具。
(三)试验程序编写:
和其它高级语言相比,C 更适合于对硬件编程。但是由于要求工作界面要美观,目前实验用到的是VC++。但是在VC条件编制并口程序需要相关的I/O库进行配置。
(四)编程:通过上面分析,要用并口来模拟I2C总线来读写 AT24C01 ,程序需有以下几部分:
发送I2C开始信号:SCL和 SDA都为高电平,延时一段时间后,向378H写入"0XFD"(其它脚状态不变,只是将位 1 置为低电平),使SDA由高电平变为低电平,即产生了I2C的开始信号。最后将在378H中写入"0XFC"(即其它脚不变,将位0和位1置为低电平)使SCL为低电平,以完成一个时钟,也为后面的读写作准备。
发送I2C停止信号:I2C的停止信号是在SCL为高时,SDA由低变高。程序可按下面步骤来写,用写端口函数
向378H写入"0XFC",使SCL和SDA为低电平,延时一段时间,向378H写入"0XFD",使SCL变为高电平,SDA为低电平,延时,向378H写入"0XFF"SCL保持不变,使SDA由原来的低电平变为高电平,即产生了一个停止信号。延时一段时间,最后向378H写入"0XFE",使SCL为低电平,以完成一个时钟。
发送数据:先把要发送的数据放在一个变量里,然后按位发送。方法为,通过位运算求得欲发送位的值(
1或0),然后用写端口函数模拟出SCL和SDA,并按I2C的写时序将一位数据发送出去,程序中可用while循环语句来控制发送的位数和字节数。
主机(并口)发送应答:I2C总线,主机发送应答用在连续读时序中,每读取一字节(8位)后,主机使SDA
保持一个时钟周期的低电平。可以用写端口函数先将SDA、SCL置为 0,然后将SCL变高,SDA保持低电平,一个应答信号既被发送,最后将SCL置低,完成一个时钟。
接收数据:并口读取I2C总线的数据时,必须让 T1截止,使用并口的13脚来接收SDA上的数据。可按下面步
骤操作,先用写端口函数使SCL为低电平,同时在并口3脚输出高电平使 T1 截止。然后用写端口函数单独将SCL置1,其它位保持不变,模拟出时钟上升沿,IC1 将把一位数据放到数据线SDA上,用读端口函数 读
取‘打印机状态'寄存器379H当前的值,将结果赋值给一个变量,然后对这个变量进行先右移4位,再左移7位
的运算(用以获得13 脚电平状态,即打印机状态寄存器的位 4 的值),判断该变量是否为0,最后将判断结果
移入另外的一个用于存放‘已读取数据'的变量中,完成读取一位数据的操作,用写端口函数使SCL为低电平,
在下一个SCL的上升沿,同样用上面的方法将一位数据加入‘已读取数据'变量中。可用while循环控制要读的
位数和字节数。注意以上过程都是在 T1 为截止态时进行的。
主机(并口)接收应答:接收应答用于写 I2C 时,每写一字节数据到从机后,如果操作成功,从机在下一
个时钟内使 SDA 为低。主机查询应答可以加强操作的可靠性。接收应答和上面说的接收数据大致相同,只是仅 接收一位数据并且不存储,直接判断其值是否为 0,不为 0 时(即没有收到应答)转错误处理程序,为 0则继
续后面的操作。在实际编程时将这个步骤合并到写I2C的操作中。
有关延时:I2C器件对SDA和SCL上的高、低电平信号需保持的时间是有规定的。如:开始信号的高、低电平
要保持多长时间,数据信号的高、低电平最低要保持多长时间等。不同的器件对这个时间有不同的规定。查找
24LO02的数据手册,可以知道,它在不同的电压下对各信号要保持的时间分别在几百纳秒到几微秒之间。这个
时间也体现了I2C器件的读写速度。因为计算机的速度不同,要用计算机并口来模拟I2C很难将这个时间精确到
微秒。为了能够在不同的计算机上可靠的操作I2C总线,试验程序用了C语言的延时函数delay();这个函数能产
生的最小延时为1毫秒。虽然这样做降低了I2C的读写速度,但可以保证操作的可靠性。
程序代码:
/***************************************/
//控制的地址是0x378
//SDA信号: PIN13 模拟作为主机的输入;
// PIN3 模拟作为主机的输出;
//SCL信号: PIN15 模拟作为主机的输入;
// PIN5 模拟作为主机的输出;
/***************************************/
/*************************************/
//函数名称:IIC start 函数
//开始信号:在时钟线(SCL)为高电平其间,
//数据线(SDA)由高变低,将产生一个开始信号。
/*************************************/
void port2iicbase::i2c_start()
{
SetPortVal(PORT1,0x07,1);/*scl 0, sda 1*/
delay(1);
SetPortVal(PORT1,0x0f,1);/*scl 1, sda 1*/
delay(1);
SetPortVal(PORT1,0x0d,1);/*scl 1, sda 0*/
delay(1);
SetPortVal(PORT1,0x05,1);/*scl 0, sda 0*/
return;
}
/*************************************/
//函数名称:IIC stop 函数
//停止信号:在时钟线(SCL)为高电平其间,
//数据线(SDA)由低变高,将产生一个停止信号
/*************************************/
void port2iicbase::i2c_stop()
{
SetPortVal(PORT1,0x07,1);/*scl 0, sda 1*/
delay(1);
SetPortVal(PORT1,0x0d,1);/*scl 1, sda 0*/
delay(1);/***/
SetPortVal(PORT1,0x0f,1);/*scl 1, sda 1*/
delay(1);/**/
SetPortVal(PORT1,0x05,1);/*scl 0, sda 0*/
}
/*************************************/
//函数名称:IIC 写一个字节函数
/*************************************/
int port2iicbase::i2c_writebyte(char c)
{
short int count=7;
char temp;
DWORD dwPortVal;
char e;
while(count>=0)
{
temp=c>>count;
temp=temp<<7; //确定传输的字节
if (temp=='\x80') //传输"1"
{
SetPortVal(PORT1,0x05,1);/*scl 0, sda 0*/
delay(1);
SetPortVal(PORT1,0x07,1);/*scl 0, sda 1*/
delay(1);
SetPortVal(PORT1,0x0f,1);/*scl 1, sda 1*/
delay(1);
SetPortVal(PORT1,0x07,1);/*scl 0, sda 1*/
delay(1);
}
else //传输"0"
{
SetPortVal(PORT1,0x05,1);/*scl 0, sda 0*/
delay(1);
SetPortVal(PORT1,0x0d,1);/*scl 1, sda 0*/
delay(1);
SetPortVal(PORT1,0x05,1);/*scl 0, sda 0*/
delay(1);
}
count--;
}
/**ask**/
SetPortVal(PORT1,0x07,1);/*scl 0, sda 1*/
delay(1);/***/
SetPortVal(PORT1,0x0f,1);/*scl 1, sda 1*/
delay(1);
// SetPortVal(PORT1,0x05,1);/*scl 0, sda 0*/
// delay(1);/***/
GetPortVal(PORT2, &dwPortVal, 1);
e = (char)dwPortVal;
temp=e>>4;
temp=temp<<7;
if (temp=='\x0')
return 0;
else
MessageBox(NULL,"Not Acknowledge!!","发送错误",MB_OKCANCEL);
return 1;
}
/*************************************/
//函数名称:IIC 读一个字符函数
/*************************************/
char port2iicbase::i2c_readbyte()
{
unsigned short count=8;
char d,e,f='\x0';
DWORD dwPortVal;
while(count>0)
{
SetPortVal(PORT1,0x07,1);/*scl 0, sda 1*/
delay(1);/***/
SetPortVal(PORT1,0x0f,1);/*scl 1, sda 1*/
delay(1);
GetPortVal(PORT2, &dwPortVal, 1);
e = (char)dwPortVal;
d=e>>4;
d=d<<7;
if(d=='\x80')
d='\x1';
f=f<<1;
f=(f+d); //组合成字节
count--;
}
return f;
}
/*************************************/
//函数名称:主机(并口)的发送应答
/*************************************/
void port2iicbase::i2c_ask()
{
SetPortVal(PORT1,0x05,1);/*scl 0, sda 0*/
delay(1);/**/
SetPortVal(PORT1,0x0d,1);/*scl 1, sda 0*/
delay(1);
SetPortVal(PORT1,0x05,1);/*scl 0, sda 0*/
delay(1);
}
//////////////////////////////////
//发送数据按钮
/////////////////////////////////
void Cport2iicdlg1::OnWriteok()
{
// TOD Add your control notification handler code here
port2iicbase port2iic;
char s[100];
char temp[3];
int len;
int i;
char m_cValue;
unsigned int WriValue=0;
int time = 0;
unsigned short c;
char d,e;
#ifdef _PORT2IIC
SetDlgItemText(IDC_WRITE_STATUS,"正在发送....");
GetDlgItem(IDC_EDIT_WRITE)->GetWindowText(s, 100);
len = strlen(s);
c = len/2;
e='\x0';
d='\x0';
i = 0;
c = 5;
//写一页
//启动开始信号;
port2iic.i2c_start();
//发送控制信息
//"\xa0" = 1010(A2)(A1)(A0)(R/W) 其中(R/w) = 0 写
//"\xa1" = 1010(A2)(A1)(A0)(R/W) 其中(R/w) = 1 读
port2iic.i2c_writebyte('\xa0');
//发送地址
port2iic.i2c_writebyte(e);
while(c>0)
{
temp[0] = s[i];
temp[1] = s[i+1];
temp[2] = 0x0;
sscanf(temp,"%x",&WriValue);
m_cValue = (char)WriValue;
port2iic.i2c_writebyte(m_cValue);
c = c-1;
i+=2;
}
port2iic.i2c_stop();
#if 0
//24c01 写128个字节,调试写16个字;
c=20;
e='\x0';
d='\x0';
while(c>0)
{
//启动开始信号;
port2iic.i2c_start();
//发送控制信息
//"\xa0" = 1010(A2)(A1)(A0)(R/W) 其中(R/w) = 0 写
//"\xa1" = 1010(A2)(A1)(A0)(R/W) 其中(R/w) = 1 读
port2iic.i2c_writebyte('\xa0');
//发送地址
port2iic.i2c_writebyte(e);
port2iic.i2c_writebyte(d);
port2iic.i2c_stop();
d = c/5;
c = c-1;
e = e+1;
}
Sleep(50);
//任意读;
//读写入的数据
b = 0;
i = 0;
a = 20;
while(a > 0)
{
//任意读;
port2iic.i2c_start();
port2iic.i2c_writebyte('\xa0');
d = char(b);
port2iic.i2c_writebyte(d);
port2iic.i2c_start();
port2iic.i2c_writebyte('\xa1');
d = port2iic.i2c_readbyte();
port2iic.i2c_stop();
buff[i] = d;
i++;
b= b+1;
a = a - 1;
}
#endif
#else
len = strlen(s);
for(i = 0; i < len; i+=2)
{
temp[0] = s[i];
temp[1] = s[i+1];
temp[2] = 0x0;
sscanf(temp,"%x",&WriValue);
m_nValue = (DWORD)WriValue;
SetPortVal(0x378,m_nValue,1);
}
#endif
SetDlgItemText(IDC_WRITE_STATUS,"发送结束");
return;
}
//////////////////////////////////
//在定时器里接收数据按钮
/////////////////////////////////
void Cport2iicdlg1::OnTimer(UINT nIDEvent)
{
// TOD Add your message handler code here and/or call default
port2iicbase port2iic;
DWORD m_nValue = 0;
CString temp1,temp2;
CEdit* pReadEdit;
unsigned short buff[100];
unsigned short a,b;
char d;
int i;
pReadEdit = (CEdit*) GetDlgItem(IDC_EDIT_READ);
memset(buff,0x0,100);
#ifdef _PORT2IIC
//任意读;
#if 0
//读写入的数据
b = 0;
a = 5;
i = 0;
while(a > 0)
{
//任意读;
port2iic.i2c_start();
port2iic.i2c_writebyte('\xa0');
d = char(b);
port2iic.i2c_writebyte(d);
port2iic.i2c_start();
port2iic.i2c_writebyte('\xa1');
d = port2iic.i2c_readbyte();
port2iic.i2c_stop();
b= b+1;
a = a - 1;
buff[i] = (unsigned short)d;
buff[i] = buff [i] & 0x00ff;
temp1 = " ";
temp2.Format(_T("%.2x"),buff[i]);
if(m_read != _T(""))
m_read = m_read + temp1;
m_read = m_read + temp2;
i++;
SetDlgItemText(IDC_EDIT_READ,m_read);
DWORD dwSel = pReadEdit->GetSel();
pReadEdit->SetSel(HIWORD(dwSel), -1);
}
#endif
#if 1
//顺序读
//读写入的数据
b = 0;
a = 5;
i = 0;
port2iic.i2c_start();
port2iic.i2c_writebyte('\xa0');
d = char(b);
port2iic.i2c_writebyte(d);
port2iic.i2c_start();
port2iic.i2c_writebyte('\xa1');
while(a > 0)
{
d = port2iic.i2c_readbyte();
a = a - 1;
if(a > 0)
port2iic.i2c_ask();
buff[i] = (unsigned short)d;
buff[i] = buff [i] & 0x00ff;
temp1 = " ";
temp2.Format(_T("%.2x"),buff[i]);
if(m_read != _T(""))
m_read = m_read + temp1;
m_read = m_read + temp2;
i++;
SetDlgItemText(IDC_EDIT_READ,m_read);
DWORD dwSel = pReadEdit->GetSel();
pReadEdit->SetSel(HIWORD(dwSel), -1);
}
port2iic.i2c_stop();
#endif
#else
GetPortVal(0x378, &m_nValue,1);
m_nValue = m_nValue & 0x000000ff;
temp1 = " ";
temp2.Format(_T("%.2x"),m_nValue);
if(m_read != _T(""))
m_read = m_read + temp1;
m_read = m_read + temp2;
SetDlgItemText(IDC_EDIT_READ,m_read);
DWORD dwSel = pReadEdit->GetSel();
pReadEdit->SetSel(HIWORD(dwSel), -1);
#endif
CDialog::OnTimer(nIDEvent);
return;
}
以上程序已经经过了验证能够正确运行。
评论
查看更多