前几天我转载了个c51模拟i2c的程序,在实际使用中发现偶尔通讯失败,经用逻辑分析仪分析,发现主要出现在ack和启动/停止i2c总线函数上,我将这部分改为asm的,实际效果很好。
sda bit p1.1
scl bit p1.2
public start_i2c,stop_i2c,_ack_i2c
i2c_dt segment data
i2c_pr segment code
rseg i2c_dt
stacksave:ds 1
rseg i2c_pr
start_i2c:
setb sda ;发送起始条件的数据信号
nop
setb scl
nop ;起始条件建立时间大于4.7us,延时
nop
nop
nop
nop
clr sda ;发送起始信号
nop ;起始条件锁定时间大于4μs
nop
nop
nop
nop
clr scl ;钳住i2c总线,准备发送或接收数据
nop
nop
ret
stop_i2c:
clr sda ;发送结束条件的数据信号
nop ;发送结束条件的时钟信号
setb scl ;结束条件建立时间大于4μs
nop
nop
nop
nop
nop
setb sda ;发送i2c总线结束信号
nop
nop
nop
nop
ret
_ack_i2c:
mov a,r7
jz ack
setb sda
sjmp no_ack
ack: clr sda
no_ack: nop
nop
nop
setb scl
nop
nop
nop
nop
nop
clr scl
nop
nop
ret
end
#include <reg51.h> /*头文件的包含*/
#include <intrins.h>
#define uchar unsigned char /*宏定义*/
#define uint unsigned int
#define _nop() _nop_() /*定义空指令*/
/* 常,变量定义区 */
extern void start_i2c(void);
extern void stop_i2c(void);
extern void ack_i2c(uchar idata);
sbit bell=p2^6; /*端口位定义*/
sbit sda=p1^1; /*模拟i2c数据传送位*/
sbit scl=p1^2; /*模拟i2c时钟控制位*/
/*状态标志*/
bit ack; /*应答标志位*/
/*******************************************************************
字节数据传送函数
函数原型: void sendbyte(uchar c);
功能: 将数据c发送出去,可以是地址,也可以是数据,发完后等待应答,并对
此状态位进行操作.(不应答或非应答都使ack=0 假)
发送数据正常,ack=1; ack=0表示被控器无应答或损坏。
********************************************************************/
void sendbyte(uchar c)
{
uchar bitcnt;
for(bitcnt=0;bitcnt<8;bitcnt++) /*要传送的数据长度为8位*/
{
if((c<<bitcnt)&0x80)sda=1; /*判断发送位*/
else sda=0;
_nop();
scl=1; /*置时钟线为高,通知被控器开始接收数据位*/
_nop();
_nop(); /*保证时钟高电平周期大于4μs*/
_nop();
_nop();
_nop();
scl=0;
}
_nop();
_nop();
sda=1; /*8位发送完后释放数据线,准备接收应答位*/
_nop();
_nop();
scl=1;
_nop();
_nop();
_nop();
if(sda==1)ack=0;
else ack=1; /*判断是否接收到应答信号*/
scl=0;
_nop();
_nop();
}
/*******************************************************************
字节数据传送函数
函数原型: uchar rcvbyte();
功能: 用来接收从器件传来的数据,并判断总线错误(不发应答信号),
发完后请用应答函数。
********************************************************************/
uchar rcvbyte()
{
uchar retc;
uchar bitcnt;
retc=0;
sda=1; /*置数据线为输入方式*/
for(bitcnt=0;bitcnt<8;bitcnt++)
{
_nop();
scl=0; /*置时钟线为低,准备接收数据位*/
_nop();
_nop(); /*时钟低电平周期大于4.7μs*/
_nop();
_nop();
_nop();
scl=1; /*置时钟线为高使数据线上数据有效*/
_nop();
_nop();
retc=retc<<1;
if(sda==1)retc=retc+1; /*读数据位,接收的数据位放入retc中 */
_nop();
_nop();
}
scl=0;
_nop();
_nop();
return(retc);
}
/*******************************************************************
向无子地址器件发送字节数据函数
函数原型: bit isendbyte(uchar sla,ucahr c);
功能: 从启动总线到发送地址,数据,结束总线的全过程,从器件地址sla.
如果返回1表示操作成功,否则操作有误。
注意: 使用前必须已结束总线。
********************************************************************/
bit isendbyte(uchar sla,uchar c)
{
start_i2c(); /*启动总线*/
sendbyte(sla); /*发送器件地址*/
if(ack==0)return(0);
sendbyte(c); /*发送数据*/
if(ack==0)return(0);
stop_i2c(); /*结束总线*/
return(1);
}
/*******************************************************************
向有子地址器件发送多字节数据函数
函数原型: bit isendstr(uchar sla,uchar suba,ucahr *s,uchar no);
功能: 从启动总线到发送地址,子地址,数据,结束总线的全过程,从器件
地址sla,子地址suba,发送内容是s指向的内容,发送no个字节。
如果返回1表示操作成功,否则操作有误。
注意: 使用前必须已结束总线。
********************************************************************/
bit isendstr(uchar sla,uchar suba,uchar *s,uchar no)
{
uchar i;
start_i2c(); /*启动总线*/
sendbyte(sla); /*发送器件地址*/
if(ack==0)return(0);
sendbyte(suba); /*发送器件子地址*/
if(ack==0)return(0);
for(i=0;i<no;i++)
{
sendbyte(*s); /*发送数据*/
if(ack==0)return(0);
s++;
}
stop_i2c(); /*结束总线*/
return(1);
}
/*******************************************************************
向无子地址器件读字节数据函数
函数原型: bit ircvbyte(uchar sla,ucahr *c);
功能: 从启动总线到发送地址,读数据,结束总线的全过程,从器件地
址sla,返回值在c.
如果返回1表示操作成功,否则操作有误。
注意: 使用前必须已结束总线。
********************************************************************/
bit ircvbyte(uchar sla,uchar *c)
{
start_i2c(); /*启动总线*/
sendbyte(sla+1); /*发送器件地址*/
if(ack==0)return(0);
*c=rcvbyte(); /*读取数据*/
ack_i2c(1); /*发送非就答位*/
stop_i2c(); /*结束总线*/
return(1);
}
/*******************************************************************
向有子地址器件读取多字节数据函数
函数原型: bit isendstr(uchar sla,uchar suba,ucahr *s,uchar no);
功能: 从启动总线到发送地址,子地址,读数据,结束总线的全过程,从器件
地址sla,子地址suba,读出的内容放入s指向的存储区,读no个字节。
如果返回1表示操作成功,否则操作有误。
注意: 使用前必须已结束总线。
********************************************************************/
bit ircvstr(uchar sla,uchar suba,uchar *s,uchar no)
{
uchar i;
start_i2c(); /*启动总线*/
sendbyte(sla); /*发送器件地址*/
if(ack==0)return(0);
sendbyte(suba); /*发送器件子地址*/
if(ack==0)return(0);
start_i2c();
sendbyte(sla+1);
if(ack==0)return(0);
for(i=0;i<no-1;i++)
{
*s=rcvbyte(); /*发送数据*/
ack_i2c(0); /*发送就答位*/
s++;
}
*s=rcvbyte();
ack_i2c(1); /*发送非应位*/
stop_i2c(); /*结束总线*/
return(1);
}
/* 完毕 */