原创 基于51单片机的I2C总线

2010-5-24 10:16 5342 9 9 分类: MCU/ 嵌入式

基于51单片机的I2C总线<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />


    I2C(Inter Intergrated Circuit)总线是Philips公司推出的一种用于IC器件之间连接的二线制串行扩展总线,它通过两根信号线(SDA-串行数据线;SCL-串行时钟线)在连接到总线上的器件之间传送数据,并根据地址来识别每个器件。


51单片机一般并没有在硬件中集成这种新的接口,所以要用软件来进行模拟。


 


1  硬件设计


24CXX系列串行E2PROM是常用的I2C串行E2PROM,正被广泛地用在各种智能仪器仪表当中。本例就是将一组数据写入24C02C中,然后读出并在LCD上显示,其电路如下图所示。


<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />


点击看大图 


在桌面上双击图标afa4ed45-2c86-4804-9583-273496394c70.jpg,打开ISIS 7 Professional窗口(本人使用的是v7.4 SP3中文版)。单击菜单命令“文件”→“新建设计”,选择DEFAULT模板,保存文件名为“IIC.DSN”。在器件选择按钮06f38b97-a27d-4802-8930-97e6bc4b4eb1.jpg中单击“P”按钮,或执行菜单命令“库”→“拾取元件/符号”,添加如下表所示的元件。



51单片机AT89C51        一片


晶体CRYSTAL 12MHz        一只


瓷片电容CAP 22pF        二只


电解电容CAP-ELEC 10uF     一只


电阻RES 10K             一只


排阻 RESPAC-8 10K         一只


1602液晶显示器 LM016L  一只


I2C存储器芯片 24C02C      一片


若用Proteus软件进行仿真,则上图中的晶体U1复位电路和U131脚可以不画,它们大都是默认的。


ISIS原理图编辑窗口中放置元件,再单击工具箱中元件终端图标f4478d17-319d-46b6-9b60-f4a239264b1f.jpg,在对象选择器中单击POWERGROUND放置电源或地。放置好元件后,布好线。左键双击各元件,设置相应元件参数,完成电路图的设计。


 


2  软件设计


将若干个数据写入24C02C,然后读出并在LCD上显示,其流程图如下所示。



c7f4125a-7a09-48f1-a0ad-ff6488b68370.jpg 


32个数据写入24C02C中,然后再读出并用1602LCD显示。本例主要目的是如何用软件模拟I2C总线对24C02C进行读、写,其详细的C51程序如下所示。


 


#include<reg51.h>       //包含单片机寄存器的头文件


#include<intrins.h>     //包含_nop_()函数定义的头文件


sbit RS="P2"^0;           //(LCD)寄存器选择位,将RS位定义为P2.0引脚


sbit RW="P2"^1;           //(LCD)读写选择位,将RW位定义为P2.1引脚


sbit E="P2"^2;            //(LCD)使能信号位,将E位定义为P2.2引脚


sbit BF="P0"^7;           //(LCD)忙碌标志位,将BF位定义为P0.7引脚


#define OP_READ 0xa1 // (IIC)器件地址以及读取操作,0xa1即为1010 0001B


#define OP_WRITE 0xa0// (IIC)器件地址以及写入操作,0xa0即为1010 0000B


sbit SDA="P1"^1;          //(IIC)将串行数据总线SDA位定义在为P1.1引脚


sbit SCL="P1"^0;          //(IIC)将串行时钟总线SDA位定义在为P1.0引脚


unsigned char code string[ ]={"0123456789ABCDEFGHIJKLMNOPQRSTUV"};


//定义字符数组显示数字和字母


 


/*****************************************************


函数功能:延时1ms


(3j+2)*i=(3×33+2)×10=1010(微秒),可以认为是1毫秒


***************************************************/


void delay1ms()


{


    unsigned char i,j;  


    for(i=0;i<10;i++)


    for(j=0;j<33;j++)


       ;      


}


 


/*****************************************************


函数功能:延时若干毫秒


入口参数:n


***************************************************/


 void delaynms(unsigned char n)


{


    unsigned char i;


    for(i=0;i<n;i++)


    delay1ms();


}


 


/******************************


以下是对液晶模块的操作程序


*******************************/


/************************************************


函数功能:判断液晶模块的忙碌状态


返回值:resultresult=1,忙碌;result=0,不忙


*************************************************/


bit BusyTest(void)


{


    bit result;


    RS=0;       //根据规定,RS为低电平,RW为高电平时,可以读状态


    RW=1;


    E=1;        //E=1,才允许读写


    _nop_();   //空操作


    _nop_();


    _nop_();


    _nop_();   //空操作四个机器周期,给硬件反应时间 


    result=BF;  //将忙碌标志电平赋给result


   E=0;         //E恢复低电平


   return result;


}


 


/*****************************************************


函数功能:将模式设置指令或显示地址写入液晶模块


入口参数:dictate


***************************************************/


void WriteInstruction (unsigned char dictate)


{  


    while(BusyTest()==1);   //如果忙就等待


    RS=0;            //根据规定,RSR/W同时为低电平时,可以写入指令


    RW=0;  


    E=0;                   //E置低电平(根据表8-6,写指令时,E为高脉冲,


                         // 就是让E01发生正跳变,所以应先置"0"


    _nop_();


    _nop_();               //空操作两个机器周期,给硬件反应时间


    P0=dictate;            //将数据送入P0口,即写入指令或地址


    _nop_();


    _nop_();


    _nop_();


    _nop_();               //空操作四个机器周期,给硬件反应时间


    E=1;                   //E置高电平


    _nop_();


    _nop_();


    _nop_();


    _nop_();               //空操作四个机器周期,给硬件反应时间


    E=0;          //E由高电平跳变成低电平时,液晶模块开始执行命令


}


 


/*****************************************************


函数功能:将数据(字符的标准ASCII)写入液晶模块


入口参数:y(为字符常量)


***************************************************/


void WriteData(unsigned char y)


{


    while(BusyTest()==1); 


    RS=1;           //RS为高电平,RW为低电平时,可以写入数据


    RW=0;


    E=0;            //E置低电平(根据表8-6,写指令时,E为高脉冲,


                       // 就是让E01发生正跳变,所以应先置"0"


    P0=y;           //将数据送入P0口,即将数据写入液晶模块


    _nop_();


    _nop_();


    _nop_();


    _nop_();       //空操作四个机器周期,给硬件反应时间


    E=1;          //E置高电平


    _nop_();


    _nop_();


    _nop_();


    _nop_();        //空操作四个机器周期,给硬件反应时间


    E=0;            //E由高电平跳变成低电平时,液晶模块开始执行命令


}


 


/*****************************************************


函数功能:对LCD的显示模式进行初始化设置


***************************************************/


void LcdInitiate(void)


{


    delaynms(15);            //首次写指令时应给LCD一段较长的反应时间


WriteInstruction(0x38);    


//显示模式设置:16×2显示,5×7点阵,8位数据接口


    delaynms(5);                //给硬件一点反应时间


    WriteInstruction(0x38);


    delaynms(5);


    WriteInstruction(0x38);     //连续三次,确保初始化成功


    delaynms(5);


    WriteInstruction(0x0c);    


//显示模式设置:显示开,无光标,光标不闪烁


    delaynms(5);


    WriteInstruction(0x06);     //显示模式设置:光标右移,字符不移


    delaynms(5);


    WriteInstruction(0x01);     //清屏幕指令,将以前的显示内容清除


    delaynms(5);


}


 


/********************************


以下是对24C02的读写操作程序


*********************************/


/*******************************


函数功能:开始数据传送


********************************/


void start()


// 开始位


{


    SDA = 1;    //SDA初始化为高电平“1


    SCL = 1;    //开始数据传送时,要求SCL为高电平“1


    _nop_();   


    _nop_();    //等待二个机器周期


    SDA = 0;    //SDA的下降沿被认为是开始信号


    _nop_();   


    _nop_();   


    _nop_();   


    _nop_();    //等待四个机器周期


    SCL = 0; 


//SCL为低电平时,SDA上数据才允许变化(即允许以后的数据传递)


}


 


/************************


函数功能:结束数据传送


*************************/


void stop()


// 停止位


{


    SDA = 0;     //SDA初始化为低电平“0


    _nop_();    


    _nop_();     //等待二个机器周期


    SCL = 1;     //结束数据传送时,要求SCL为高电平“1


    _nop_();    


    _nop_();    


    _nop_();    


    _nop_();     //等待四个机器周期


    SDA = 1;    //SDA的上升沿被认为是结束信号


}


 


/***************************


函数功能:从24Cxx读取数据


出口参数:x


****************************/


unsigned char ReadData()


// 24Cxx移入数据到MCU


{


    unsigned char i;


    unsigned char x;   //储存从24Cxx中读出的数据


    for(i = 0; i < 8; i++)


    {


       SCL = 1;                //SCL置为高电平


       x<<=1;                  //x中的各二进位向左移一位


       x|=(unsigned char)SDA;


//SDA上的数据通过按位“或“运算存入x


       SCL = 0;                        //SCL的下降沿读出数据


    }


    return(x);                //将读取的数据返回


}


 


/************************************


函数功能:向24Cxx的当前地址写入数据


入口参数:y (储存待写入的数据)


************************************/


//在调用此数据写入函数前需首先调用开始函数start(),所以SCL=0


bit WriteCurrent(unsigned char y)


{


    unsigned char i;


    bit ack_bit;               //储存应答位


    for(i = 0; i < 8; i++)      // 循环移入8个位


    {


       SDA = (bit)(y&0x80);   //通过按位“与”运算将最高位数据送到S


                                  //因为传送时高位在前,低位在后


       _nop_();            //等待一个机器周期  


        SCL = 1;            //SCL的上升沿将数据写入AT24Cxx     


       _nop_();            


        _nop_();             //等待二个机器周期      


       SCL = 0;           


//SCL重新置为低电平,以在SCL线形成传送数据所需的8个脉冲


       y <<= 1;           //y中的各二进位向左移一位


    }


    SDA = 1;           


// 发送设备(主机)应在时钟脉冲的高电平期间(SCL=1)释放SDA线,


                    //以让SDA线转由接收设备(AT24Cxx)控制


    _nop_();        


    _nop_();        //等待二个机器周期


    SCL = 1;       //根据上述规定,SCL应为高电平


    _nop_();       


    _nop_();       


    _nop_();       


    _nop_();       //等待四个机器周期


  ack_bit = SDA;    //接受设备(24Cxx)SDA送低电平,表示已经接收到一


//个字节;若送高电平,表示没有接收到,传送异常


    SCL = 0;      


//SCL为低电平时,SDA上数据才允许变化(即允许以后的数据传递)


    return  ack_bit;         // 返回AT24Cxx应答位


}


 


/****************************************


函数功能:从24Cxx中的当前地址读取数据


出口参数:x (储存读出的数据)


*****************************************/


unsigned char ReadCurrent()


{


    unsigned char x;


    start();               //开始数据传递


    WriteCurrent(OP_READ); //选择要操作的24Cxx芯片,并告知要读其数据


    x=ReadData();         //将读取的数据存入x


    stop();                //停止数据传递


    return x;              //返回读取的数据


}


 


/***********************


函数功能:主函数


************************/


 void main(void)


{


    unsigned char i;


    unsigned char x;      //储存从24C02读出的值


    LcdInitiate();        //调用LCD初始化函数 


   for(i=0;i<32;i++)


    {   start();               //开始数据传递


       WriteCurrent(OP_WRITE); 


//选择要操作的24Cxx芯片,并告知要对其写入数据


       WriteCurrent(i);       //写入指定地址


       WriteCurrent(string);       //向当前地址写入数据


       stop();                //停止数据传递


       delaynms(4);


    }      


    start();               //开始数据传递


    WriteInstruction(0x80); //第一行显示地址


    WriteCurrent(OP_WRITE); 


//选择要操作的24Cxx芯片,并告知要对其写入数据


    WriteCurrent(0x00);       //写入指定地址


    for(i=0;i<16;i++)


    {


        x=ReadCurrent();    //24C02中读出


        WriteData(x);       //将值用1602LCD显示


    }


    WriteInstruction(0xc0); //第二行显示地址


    for(i=0;i<16;i++)


    {


        x=ReadCurrent();    //24C02中读出


        WriteData(x);       //将值用1602LCD显示


    }


    stop();                //停止数据传递


    delaynms(4);


    while(1)             //无限循环


    {


    }


}


打开Keil程序(本人使用的是Keil8.05中文版),执行菜单命令“工程”→“新建工程”创建“IIC”项目,并选择单片机型号为AT89C51。执行菜单命令“文件”→“新建”创建文件,输入C语言源程序,保存为“IIC.C”。在Project Workspace窗口中右击源代码组1,选择“添加文件到组‘源代码组 l’”将源程序“IIC.C”添加到项目中。


Keil中执行执行菜单命令“工程”→“创建目标”(或点击“创建目标”快捷按钮),编译源程序。如果编译成功,则在“Output Window”的“创建”窗口中显示没有错误,并创建了“IIC.HEX”文件。


 


3  仿真与调试


关于ProteusKeil的联合仿真调试,可参见我以前所写的博文或其它参考资料。


启动ProteusISIS,并将其放在屏幕的右上角(可将原理图放大到合适大小);再启动KeilμVision3,并将其放在屏幕的左下角。


    Keil中执行菜单命令“调试”→“启动/停止调试”,或直接单击图标73ec0c64-cdc9-405e-8596-2bd9d323bfe1.jpg进入Keil调试环境。同时,在Proteus ISIS的窗口中可看出Proteus也进入了程序调试状态。


    Keil代码编辑窗口中设置相应断点,断点的设置方法:在需要设置断点语句前双击鼠标左键,可设置断点;再次双击,可取消该断点。


Keil中按F5键(或点击“运行”快捷按钮)运行程序。1602LCD将显示012……9AB……V32个字符,如下图所示。


309098e0-cee6-40fc-bd8e-00c86eb4f36e.jpg


或可以点击单步、运行到光标处、全速运行等快捷按钮,以及同时观察工程窗口寄存器页面、存储器窗口等,来进行仿真调试。


 


    本人邮箱:txxyc104@163.com,欢迎来信讨论.

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
EE直播间
更多
我要评论
0
9
关闭 站长推荐上一条 /3 下一条