感觉心有不甘,呵呵
,小小整理一下。
我们知道51单片机中具有IIC总线接口的毕竟是少数(其实我就不知道那款~~)如果是是不带IIC总线的单片机,并不必扩展IIC总线接口,只是需要通过软件模拟,这无疑会给IIC总线的应用提供更广泛的空间。通常大多数的单片机应用系统中只有一个CPU,这种单主系统如果采用IIC总线技术,则总线上只有单片机对IIC总线从器件的访问,没有总线的竞争问题。这是后只需要模拟主发送和主接收时序。基于上述考虑,(这才是重点,呵呵
),提供在这种使用情况下的时序模拟软件,使IIC总线的使用不受单片机必须带有IIC总线接口的限制。
下面的9个例子可以作为一个软件包,这个软件放入单片机系统的程序程序存储器中,以便用来调用。
1、IIC总线典型信号时序及模拟子程序(其实上一篇已经介绍过了,不过我这里有点补充,顺便小小介绍一下吧)
IIC总线上数据传送时,有起始位,停止位,应答位,非应答位等信号。按照典型IIC总线传送速率的要求,如图:
对于IIC总线的典型信号,可以用指令操作来模拟其时序过程。
若89C51单片机的系统时钟为12MHz,相信的单周期指令为1us,则起始位(START)、停止位(STOP)、发送应答位(MACK)、发送非应答位(MNACK)的5个模拟子程序如下:
1)启动IIC总线子程序START(先是汇编,然后是c语言的)
;--------------------------------------------------------
;发开始信号子程序
START: SETB SDA
SETB SCL ;起始条件建立大于4.7us
NOP
NOP
NOP
NOP
NOP
CLR SDA ;起始条件锁定大于4us
NOP
NOP
NOP
NOP
NOP
CLR SCL ;钳住总线,准备好发送数据
NOP
RET
//----------------------------------------------------------
//开始信号函数
void IIC_start()
{
sda=1;
_nop_();
scl=1; //开始信号建立时间大于4.7us _nop_();
_nop_();
_nop_();
_nop_();
_nop_();
sda=0;
//开始信号锁定大于4us
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
scl=0; //钳住IIC总线,准备发送或者接收数据
_nop_();
_nop_();
}
2)停止IIC总线程序STOP(先是汇编,后是c语言)
;--------------------------------------------------------
;发结束信号子程序
STOP: CLR SDA
NOP
SETB SCL ;结束总时间大于4us
NOP
NOP
NOP
NOP
NOP
SETB SDA
NOP ;保证一个终止信号和其实信号的空闲时间大于4.7us
NOP
NOP
NOP
NOP
RET
//---------------------------------------------------------------
//结束信号函数
void IIC_stop()
{
sda=0;
_nop_();
scl=1; //结束时间大于4us
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
sda=1; //保证一个终止信号和其实信号的空闲时间大于4.7us
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
}
3)主控器向被控器发送应答位信号子程序MACK(由于这个信号和发送非应答信号在c语言里写在了一起,所以最后写c语言的程序)
;--------------------------------------------------------------
;发送应答信号子程序
MACK: CLR SDA
NOP
NOP
SETB SCL NOP ;保持数据时间,既SCL为高,时间大于4.7us
NOP
NOP
NOP
NOP
CLR SCL
NOP
NOP
RET
4)主控器向被控器发送非应答新号子程序MNACK
;------------------------------------------------------------
;发送非应答信号子程序
MNACK: SETB SDA
NOP
NOP
SETB SCL
NOP ;保持数据时间,既SCL为高,时间大于4.7us
NOP
NOP
NOP
NOP
CLR SCL
NOP
NOP
RET
然后就是上两个的c的程序:
//---------------------------------------------------------------------
//主控器件向被控器件发送应答函数
void IIC_ack_send(bit a)
{ if(a==0) //发应答信号
sda=0;
else
sda=1;
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
scl=1;
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
scl=0;
_nop_();
_nop_(); }
5)主控器件检查被控器件发送来的应答位
;---------------------------------------------------------------
;检测应答位子程序
;返回值ACK=1时表示应达
CACK: SETB SDA
NOP
NOP
SETB SCL
CLR ACK
NOP
NOP
MOV C,SDA
JC CEND
SETB ACK ;应答位置1,既返回应答位为1
CEND: NOP
CLR SCL
NOP
//-----------------------------------------------------------------
//主控器件接收应答函数
void IIC_ack_acc()
{
_nop_();
_nop_();
sda=1; //8位数据发送完后,准备接收数据
_nop_();
_nop_();
scl=1;
_nop_();
_nop_();
_nop_();
if(sda==1)
ack=0;
else
ack=1; //接收到应答位,ack为1,否则置0
scl=0;
_nop_();
_nop_(); }
在使用上述的子程序时,如果单片机不是12MHz,自己调整啊~~哈哈
2、IIC总线数据传送的模拟子程序
从IIC总线的数据操作中可以看出,出了起始位、停止位、发送应答函数、发送非应答函数、应答检测函数(这里书上把它看作了数据传送的那部分,管他呢~~~呵呵
),还有发送一字节数据,接受以字节数据,发送n字节数据,接收n字节数据这几个子程序。
1)发送一字节数据WR_BYTE
该子程序是向虚拟的IIC总显得数据线SDA上发送一字节数据的操作。调用该子程序前,将要发送的数据送入A中。
还是先汇编,然后c代码:
;-----------------------------------------------------------------
;发送字节子程序
;字节数据放入ACC
;每发送一个字节筪掉用一次CACK(检测应答子程序),取应答位
WR_BYTE: MOV R0,#08H ;8位数据长度送R0
W_LP:
RLC A ;发送数据左移,使发送位入c
JC W_R1 ;判读发送1,还是发送0,发送1,转入W_R1
SJMP W_R0 ;发送0转入W_R0
W_LP1: DJNZ R0,W_LP
NOP
RET
W_R1: ;发送1
SETB SDA
NOP
SETB SCL
NOP
NOP
NOP
NOP
NOP
CLR SCL
SJMP W_LP1
W_R0: ;发送0
CLR SDA
NOP
SETB SCL
NOP
NOP
NOP
NOP
NOP
CLR SCL
SJMP W_LP1
//----------------------------------------------------------------------
//写一个字节函数
void send_byte(uchar c)
{
uchar bit_cnt;
for(bit_cnt=0;bit_cnt<8;bit_cnt++) //循环传送8位
{
if((c<
sda=1;
else
sda=0;
_nop_(); scl=1; //发送到数据线上
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
scl=0;
} }
2)接收一字节数据RD_BYTE(这个的汇编感觉并不肯定,有可能有错~~
,当然c语言还是没问题的,呵呵)
;----------------------------------------------------------------------
;读取字节子程序
;读出的值再ACC中
;每取一个字节要发送一个应达或者非应答信号(要知道你读取就成了从机,就要发送一个信号告诉主机)
RD_BYTE: MOV R0,#08H ;8位数据长度
R_LP: SETB SDA ;置SDA为输入方式
NOP
CLR SCL ;置时钟线为低电平,准备接收数据
NOP
NOP
NOP
NOP
NOP
SETB SCL
NOP
MOV C,SDA ;读取数据位
RLC A ;进行数据位的处理
NOP
DJNZ R0,R_LP ;没有到8位,再来一次
RET
//------------------------------------------------------------------------
//接收一个字节函数
//返回接收的8位数据
uchar rec_byte()
{
uchar temp;
uchar bit_cnt;
temp=0;
sda=1; //置数据线为输入方式
for(bit_cnt=0;bit_cnt<8;bit_cnt++)
{
_nop_();
scl=0; //置时钟线为低电平,准备接收数据
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
scl=1;
_nop_();
_nop_();
temp<<=1;
if(sda==1) //接收当前数据位,接收内容放入temp中
temp+=1;
_nop_();
_nop_(); }
scl=0;
_nop_();
_nop_();
return(temp);
}
3)向被控器件发送n个字节数据子程序MCU_WRN_BYTE
;-----------------------------------------------------------------------
;向器件指定地址写入N个数据
;入口参数:器件从地质SLA,器件子地址SUBA、发送数据缓冲区MTD、发送字节数NUMBYTE
;占用:A,R0,R1,R3,CY
MCU_WRN_BYTE:
MOV A,NUM_BYTE
MOV R3,A
LCALL START ;启动总线
MOV A,SLA
LCALL WR_BYTE ;发送器件地址
LCALL CACK
JNB ACK,RET_WRN
MOV A,SUBA ;指定子地址,并发送
LCALL WR_BYTE
LCALL CACK
MOV R1,#MTD
WRN_DA: MOV A,@R1
LCALL WR_BYTE ;开始写入数据
LCALL CACK
JNB ACK,MCU_WRN_BYTE
INC R1
DJNZ R3,WRN_DA ;判断写完没有
RET_WRN: LCALL STOP
RET
//---------------------------------------------------------------
//向器件指定地址按页写函数(这里虽然是说按页,其实原理是一样的~~,要是不明白可以看上一篇我整理的)
//入口参数有4个:器件地址码、器件单元地址、写入的数据串的指针、写入的字节个数
//写入成功,返回1;不成功,返回0,使用后必须结束总线
bit mcu_send_string(uchar add,uchar rom_add,uchar *s,uchar
num)
{
uchar i;
IIC_start(); send_byte(add); //发送器件地址码
IIC_ack_acc();
if(ack==0)
return(0); //有应答,发送ROM的单元地址
send_byte(rom_add);
IIC_ack_acc();
if(ack==0)
return(0);
for(i=0;i
{
send_byte(*s);
IIC_ack_acc();
if(ack==0)
return(0);
s++;
}
IIC_stop();
return(1);
}
4)从被控器件读取n个字节数据子程序MCU_RDN_BYTE
;--------------------------------------------------------------------------------
;从器件地址读取N个数据
;入口参数:器件从地址,器件子地址SUBA,接收字节数NUM_BYTE
;出口参数:接收数据缓冲区
;占用:A,R0,R1,R2,R3,CY
MCU_RDN_BYTE:
MOV R3,NUM_BYTE
LCALL START
MOV A,SLA
LCALL WR_BYTE ;发送器件地址
LCALL CACK JNB ACK,RET_RDN
MOV A,SUBA ;指定子地址
LCALL WR_BYTE
LCALL CACK
LCALL START
MOV A,SLA
INC A ;准备进行读操作
LCALL WR_BYTE
LCALL CACK
JNB ACK,MCU_RDN_BYTE
MOV R1,#MRD
RDN1: LCALL RD_BYTE ;读操作开始
MOV @R1,A
DJNZ R3,SACK
LCALL MNACK ;最后一个字节发非应答位
RET_RDN: LCALL STOP
RET
SACK: LCALL MACK ;发送应答位
INC R1
SJMP RDN1
//-------------------------------------------------------------------------------
//从器件指定地址读多个字节
//入口参数有4个:器件地址码、器件单元地址、读出的数据串、读出的字节个数,写入成功返回1,不成功返回0
bit mcu_rec_string(uchar add,uchar rom_add,uchar *s,uchar
num)
{
uchar i;
IIC_start();
send_byte(add); //发送器件的地址码,这里最低位为0,写
IIC_ack_acc();
if(ack==0)
return(0);
send_byte(rom_add); //发送器件的单元地址
IIC_ack_acc();
if(ack==0)
return(0);
IIC_start(); //重新发送开始信号,开始读取数据
send_byte(add+1); //发送器件的地址码,这里最后一位为1,读
IIC_ack_acc();
if(ack==0)
return(0);
for(i=0;i
{
*s=rec_byte();
IIC_ack_send(0); //送应答信号
s++;
}
*s=rec_byte();
IIC_ack_send(1); //送非应答信号
IIC_stop();
return(1);
}
总算是好了~~呵呵,哦了,整理好了~~要是哪里不对~~我就shit了