郭天祥的10天学会51单片机_第九节

开发板上的蜂鸣器下面是温度传感器DS18B20

DA转换器的下面是SPI总线(RFRIOUTDI0GND

I2C总线和SPI总线用的多。

I2C总线仲裁:具有 C总线接口的设备都接在总线上,主机和哪个设备进行通信时先在总线上发个地址码过去,总线上全部响应地址码,所有 C总线上的设备都检测这个地址码,哪个设备的地址码相符就和主机通信

LESSON8_IIC总线协议的PPTP3的图,各器件的SDASCL都是线“与”关系(各设备的SDA做线“与”运算,结果送到SDA总线上,SCL也同理), I2C总线必须接上拉电阻, 一般为10K

MSB为最高位,LSB为最低位

漏极开路相当于场效应管来说的,和三极管一样都具有开关的作用,场效应管还有放大管的作用,场效应管是压控压型

集电极开路对应三极管来说的,三极管的集电极不能输出,只有接上拉电阻时才能输出,开关和放大管的作用,压控流型,即PN结利用导通的压降来控制导通电流的大小

I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。

LESSON8_IIC总线协议的PPTP3的图,SDA上的交叉部分表示允许数据变化

SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号。 

每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位);应答位必须是由从机发给主机的;第九位应答位如果很长时间主机没有收到就默认收到了

开发板上ADDA是并口一次都传走八位数据,而I2C总线是串口,八位数据一个一个传

在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/T),用“0”表示主机发送数据(T),“1”表示主机接收数据(R),是主机传送完一个数据后用01表示是下一次是主机发送数据还是接收数据。

a、主机向从机发送数据,数据传送方向在整个传送过程中不变:

S表示起始信号,后面是从机地址,0表示由主机要向从机发送数据,A表示从机的应答(低电平表示),A非表示非应答(高电平表示),P表示终止信号

有阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送

A/ 表示应不应答不管了

b、主机在第一个字节后,立即向从机读数据

从机接到主机的信号后,应答然后从机给主机传数据,主机再应答,从机再给主机传数据,主机不应答,主机再发送停止信号

c、在传送过程中,当需要改变传送方向时,起始信号和从机地址都被重复产生一次,但两次读/写方向位正好反相

主机起始信号,从机地址,0表示主机要向从机发送数据,从机应答,主机向从机发送数据,A/ 表示应不应答不管了,读写方向要改变,主机要向从机读取数据了,就重新发出起始信号,从机地址,1表示主机要接收从机发送的数据,从机给了应答,从机向主机发送数据,主机不给应答,发出停止信号

参考LESSON8_IIC总线协议的PPTP14P15

主机可以采用不带I2C总线接口的单片机,如80C51AT89C2051等单片机,利用软件实现I2C总线的数据传送,即软件与硬件结合的信号模拟,很多高级单片机都自带有 C总线,把数据放到寄存器入SBUF,单片机自动传送数据,不用软件实现

参考LESSON8_IIC总线协议的PPTP17P18,P19

SomeNop( )是延时函数

LESSON8_IIC总线协议的PPTP20,总线连接的器件下面的A2A1A0等是地址线,

E2PROM(Electric erase program read only memory)是电可擦除编程只读存储器,掉电后程序仍然保持在存储器当中,这里用的是AT24C02

写的时候要先写地址再写数据,读的时候要先写地址再读数据

MSB是最高位,MLB是最高位,ACK是应答

参考LESSON8_IIC总线协议的PPTP22P23P24P25

读出过程:单片机先发送该器件的7位地址码和写方向位“0”(“伪写”),发送完后释放SDA线并在SCL线上产生第9个时钟信号,被选中的存储器器件在确认是自己的地址后,在SDA线上产生一个应答信号作为回应,然后,再发一个字节的要读出器件存储区的首地址(针对E2PROM的存储器有地址),收到应答后,单片机要重复一次起始信号并发出器件地址和读方向位(“1”),收到器件应答后就可以读出数据字节,每读出一个字节,单片机都要回复应答信号。当最后一个字节数据读完后,单片机应返回以“非应答”(高电平),并发出终止信号以结束读出操作。

TX-1C型单片机实验板原理图,上单片机下面的24C02(图中打字打错了,打成24C00)就是E2PROM,管脚1,2,3是地址线,这里管脚1,2,3都接地,管脚5是数据线,管脚6是时钟线,管脚56都通过上拉电阻接电源(这里用2.7V to 2.5V),管脚4接地,管脚7WPWrite Protect)是写保护,写保护为高就是写不进去,写保护为低就是可以进行读写,这里接地,始终可以进行读写,管脚8接电源;SDA连接单片机的P2.0口,SCL连接单片机的P2.1

E2PROMMAP封装就是表面看不到E2PROM的引脚图,引脚在E2PROM芯片的下方

开发板上AD转换器的下面的芯片就是24C02

AT24C02APPTP11Byte Write图,DEVICE ADDRESS是设备地址(后面加一个传送方向位(R/T,高电平为由单片机向从机读数据,低电平为由单片机向从机写数据,以后只要不改变读写方向,确定一次设备地址和传送方向位(R/T后就不用再添加传送方向位(R/T),当要改变读写方向即改变传送方向位(R/T)时,再重新写入设备地址和改变后的传送方向位(R/T),ACK表示应答,WORD ADDRESS写的地址(八位地址位,这里是EEPROM内部存储区的地址, I2C总线上面有多个EEPROMDEVICE ADDRESS设备地址就是选中哪个EEPROMWORD ADDRESS写地址就是选中EEPROM后要确定在这个EEPROM中哪个地址上写数据,这里就不用“第8位数据的传送方向位(R/T)”,等要改变读写方向时重新给初始信号从机地址传送方向位(R/T)),DATA是数据(八位数据,这里就不用“第8位数据的传送方向位(R/T)”,等要改变读写方向时重新给初始信号从机地址传送方向位(R/T))

AT24C02APPTP11Current Address Read(当前地址的读)图,NO ACK是不给应答,主机不再读数据就不给从机应答

谁给应答:当主机给从机数据,从机接受数据后就由从机给应答,当从机给主机数据,主机接受数据后就由主机给应答

AT24C02APPTP11Random Read(随机地址的读),只在同一个地址上先写时给了地址,再从该设备读时就不用再给地址了

思路就是把上面的几个时序图对应的几种方式的读写都编成函数,使用时,例如读一组数据可以直接调用读函数

写程序时,先添加头文件,再定义一些类型,然后写主函数,写的时候缺什么(例如缺子函数)就补充什么

把一个数据送到EEPROM里,然后读出来,再送到发光二极管,让发光二极管闪亮:

关键是把重要时间(LESSON8_IIC总线协议的PPTP17图中的阴影区)的变化写在程序里,我尝试把起始信号的所以过程写全,显示的结果和只把关键地方写全相同:

void start() 

{    

       sda=0;

       delay();

       sda=1;

       delay();

       scl=1;

       delay();

       sda=0;

       delay();

       scl=0;

}

先编程序,能不能读入数据

#include<reg52.h>

#define uchar unsigned char

sbit sda=P2^0;

sbit scl=P2^1;

uchar a;

void delay()

{ ;; }//空语句,延时时间大约5us

void start()  //起始信号

{    

       sda=1;

       delay();

       scl=1;

       delay();

       sda=0;

       delay();//LESSON8_IIC总线协议的PPTP17的起始信号的图,SCLSDA变成

//低电平之后变不变成低电平都可以,不影响起始信号的写入,所以这句的下面不用加上//SCL=0

}

void stop()   //停止信号

{

       sda=0;//区分起始信号、停止信号、应答信号和非应答信号,与数据信号(在时钟scl

//拉高时,送数据,数据也是有01组成的)的区别就是:数据信号的范围包含了在时钟//scl高电平范围,见LESSON8_IIC总线协议的PPTP16,起始信号、停止信号是在时钟//scl由高到低变化之前就拉低,应答信号和非应答信号是在传送完数据再给的(每一个被传//送的字节后面都必须跟随一位应答位(即一帧共有9位))

       delay();

       scl=1;

       delay();

       sda=1;

       delay();//LESSON8_IIC总线协议的PPTP17的终止信号的图,SDA由低电平变成//高电平就完成了终止信号,SDA再变不变成低电平都不影响终止信号的写入,所以这句的//下面不用加上SDA=0关键是把重要时间(图中的阴影区)的变化写在程序里,

}

void respons()  //应答信号,是在传送完数据再给的,所以不会和传送数据混淆

{

       uchar i;

       scl=1;//在时钟为高电平时读取信号

       delay();

       while((sda==1)&&(i<250)) i++;//第一种情况是在前八位数据传送完,当sda变成零说明

//有应答,第二种情况是第九个时钟等待SDA变成低电平,如果超过这段时间还没给应答,//就自动认为给了应答,i0计数到250就是这个计时作用;sda==1i<250有一个不满

//足就退出while循环,继续往下执行

       scl=0;//把时钟拉低

       delay();

}

void init()//总线初始化

{

       sda=1;//LESSON8_IIC总线协议的PPTP7的图,起始时SCL为高电平,SDA

//为高电平

       delay();

       scl=1;

       delay();

}

void write_byte(uchar date)//写一个字节(单片机向EEPROM写)

{

       uchar i,temp;

       temp=date;

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

       {

              temp=temp<<1;//temp左移一位就将最高位送入CY当中

              scl=0; //scl拉高时才会送数据(读数据或送数据),所以开始先将scl拉低

           delay();

              sda=CY;  //单片机控制sda口(单片机的P2.0口)将temp的八位数据一位一位的

//都送到EEPROM中,这里先准备数据,准备好后把scl拉高,将八位数据送走

              delay();

              scl=1;//sda=CY就是准备数据,延时一会,数据稳定之后再开始让时钟为高电平,//开始送数据

              delay();

       }

       scl=0;//scl为高电平期间将数据送走,scl为低电平时数据全送走

       delay();

       sda=1;//将数据总线释放,为了读完数据再读取应答信号,应答信号对应的sda为低电

//平,所以这里让sda为高电平为应答的低电平做准备;时钟来控制送数据,即使这里sda

//为高电平,时钟不给高电平,送不走sda,当想要送数据时将scl拉高,sda的高电平或低//电平(对应的一位数据)被送走

       delay();

}

uchar read_byte()//读一个字节(单片机从EEPROM读),一位一位的读回来,都放到一个字//节当中

{

       uchar i,k;

       scl=0;//scl拉高时才会送数据(读数据或送数据),所以开始先将scl拉低

       delay();

       sda=1;//这句可有可无

       delay();

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

       {

              scl=1;//scl为高电平时数据稳定,才开始读数据,这句可以放在k=(k<<1)|sda之下

              delay();  

              k=(k<<1)|sda;//要读入的数据会一位一位的送到sda里,再将八位数据都放到一个

//字节里

              scl=0;//LESSON8_IIC总线协议的PPTP7的图

              delay();  

       }

       return k;

}

void delay1(uchar x)

{

       uchar a,b;

       for(a=x;a>0;a--)

        for(b=100;b>0;b--);

}

void main()

{

       init();

       start();//先从单片机向EEPROM写入数据,再由单片机向EEPROM读入数据

       write_byte(0xa0); //LESSON8_IIC总线协议的PPTP22AT24C系列E2PROM

//片地址的固定部分为1010,在TX-1C型单片机实验板原理图中A2A1A0都接地,第//8位是数据的传送方向位(R/T),这里是由单片机向EEPROM写入数据所以第8位是0//所以写入从机地址是0xa0,即选中哪个EEPROM

       respons();

       write_byte(3);// EEPROM内部储存区的地址,即选中所用EEPROM中的哪个地址

       respons();

       write_byte(0xfe);//EEPROM的地址3写入数据0xfe

       respons();

       stop();

      

       delay1(100);//读和写之间时间间隔要大一些,否则会出错

       start();//由单片机向EEPROM读出数据,在指定地址处读数据,见AT24C02APDF

//P11Random Read;要先写选中的总线上哪个EERPOM对应的地址和该EEPROM//储存地址,再开始读取该EEPROM的数据

       write_byte(0xa0);

       respons();

       write_byte(3);

       respons();

       start();

       write_byte(0xa1); //单片机向EEPROM读数据,所以第8位是1,设备地址+8位,写//EEPROM的地址是0xa1

       respons();

       P1=read_byte();

       stop();

       while(1);

}

编译时将晶振改为11.0592MHZ

用仿真芯片模拟,比如当调试中需要按下键盘,仿真芯片调试中可以,而在软件调试中则按不了键盘

调试时想跳出一个函数,可以在这个函数里设置断点

起始信号,终止信号,应答信号,写信号,读信号都是在SCL高电平期间内做的

调试按钮step into作用是进入一个函数中

完整函数:

#include<reg52.h>

#define uchar unsigned char

sbit sda=P2^0;

sbit scl=P2^1;

uchar a;

void delay()

{ ;; }

void start()  //开始信号

{    

       sda=1;

       delay();

       scl=1;

       delay();

       sda=0;

       delay();

}

void stop()   //停止

{

       sda=0;

       delay();

       scl=1;

       delay();

       sda=1;

       delay();

}

void respons()  //应答

{

       uchar i;

       scl=1;

       delay();

       while((sda==1)&&(i<250))i++;

       scl=0;

       delay();

}

void init()

{

       sda=1;

       delay();

       scl=1;

       delay();

}

void write_byte(uchar date)

{

       uchar i,temp;

       temp=date;

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

       {

              temp=temp<<1;

              scl=0;//读和写的精髓:数据先准备好,在时钟scl的高电平期间送走,即scl=1

//scl=0之间送走数据

           delay();

              sda=CY;

              delay();

              scl=1;

              delay();

       }

       scl=0;

       delay();

       sda=1;

       delay();

}

uchar read_byte()

{

       uchar i,k;

       scl=0;

       delay();

       sda=1;

       delay();

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

       {

              scl=1;

              delay();  

              k=(k<<1)|sda;

              scl=0;

              delay();  

       }

       return k;

}

void delay1(uchar x)

{

       uchar a,b;

       for(a=x;a>0;a--)

        for(b=100;b>0;b--);

}

void write_add(uchar address,uchar date)//EEPROM存储区内部指定地址处写数据,见//AT24C02APDFP11Byte Write

{

       start();

       write_byte(0xa0); //LESSON8_IIC总线协议的PPTP22AT24C系列E2PROM

//片地址的固定部分为1010,在TX-1C型单片机实验板原理图中A2A1A0都接地,第//8位是数据的传送方向位(R/T),这里是由单片机向EEPROM写入数据所以第8位是0//所以写入从机地址是0xa0,即选中哪个EEPROM

       respons();

       write_byte(address); // EEPROM内部储存区的地址,即选中所用EEPROM中的哪个地

//

       respons();

       write_byte(date); //EEPROM的地址address写入数据date

       respons();

       stop();

}

uchar read_add(uchar address)//在指定地址处读数据,见AT24C02APDFP11Random //Read

{

       uchar date;

       start();

       write_byte(0xa0);

       respons();

       write_byte(address);

       respons();

       start();

       write_byte(0xa1);//单片机向EEPROM读数据,给设备地址,所以第8位是1,写入//EEPROM的地址是0xa1

       respons();//上面write_byte(address)已经确定在EEPROM中的哪个地址读或写数据,所//以就不用再写write_byte(新EEPROM内部存储器的地址)

       date=read_byte();//读一个字节的数据

       stop();

       return date;

}

void main()

{

       init();

       write_add(23,0x55);

       delay1(100);

       P1=read_add(23);

       while(1);

}

EEPROM有掉电存储功能,开发板上数码管在做加1计数,比如加到8,关闭电源,再打开电源数码管从8开始继续走,即关闭电源时EEPROM8存储起来了;电源检测芯片实时保存数据

以后用到I2C总线上面这个程序都可以直接拿来用,在主函数中修改就可以,这就是C语言的方便的可移植性,我们编过的很多程序都可以移植,加以修改就可以使用

AT24C02APPTP11Current Address Read(当前地址的读)图,比如从第五个地址开始写,写完之后地址加1变成6,那么再读数据的时候,就会读出地址6的数据,而上面的程序uchar read_add(uchar address)是要在指定地址处读数据,所以Current Address Read不适用上面的程序

课件练习:数码管做加1计数,利用EEPROM的掉电存储功能,控制两个数码管显示,掉电保存数据,开电源继续显示:

思路:每次上电就从EEPROM读取数据回来,每加一秒就将数据放回存储的位置,同时在数码管显示,每过一秒再放回去

我的思路:先把各种信号,读写,读写过程都写好,再试试读写一个字节让发光二极管点亮,再用定时器

编程中,我先把各种信号,读写,读写过程都写好,读写一个字节让发光二极管点亮也实现了,然后我编写数码管显示函数(主函数中只让数码管显示,隔开总线函数,这样可以不让总线干扰,看显示函数是否正确),也实现了,再使用中断,在主函数中实现写入数据和读出数据

我的程序:

#include<reg52.h>

#define uint unsigned int

#define uchar unsigned char

sbit sda=P2^0;

sbit scl=P2^1;

sbit dula=P2^6;

sbit wela=P2^7;

uchar code table[]={

0x3f,0x06,0x5b,0x4f,

0x66,0x6d,0x7d,0x07,

0x7f,0x6f,0x77,0x7c,

0x39,0x5e,0x79,0x71

};

uchar num,shi,ge,flag;

void delay()

{;;}

void delay1(uint z)

{

       uchar x,y;

       for(x=z;x>0;x--)

              for(y=110;y>0;y--);

}

void start()

{

       scl=1;

       delay();

       sda=1;

       delay();

       sda=0;

       delay();

}

void stop()

{

       scl=1;

       delay();

       sda=0;

       delay();

       sda=1;

       delay();

}

void respons()

{

       uchar i;

       scl=1;

       delay();

       while((sda==1) | (i<250)) i++;

       delay();

       scl=0;

       delay();

}

void write(uchar date)

{

       uchar i,temp;

       temp=date;

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

       {

              scl=0;

              delay();

              temp=temp<<1;

              sda=CY;

              delay();

              scl=1;

              delay();

       }

       scl=0;

       delay();

}

uchar read()

{

       uchar i,kk;

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

       {

              scl=0;

              delay();

              kk=(kk<<1)|sda;

              delay();

              scl=1;

              delay();

       }

       scl=0;

       delay();

       return kk;

}

void write_byte(uchar address,uchar date)

{

       start();

       write(0xa0);

       respons();

       write(address);

       respons();

       write(date);

       respons();

       stop();

}

uchar read_byte(uchar address)

{

       uchar aa;

       start();

       write(0xa0);

       respons();

       write(address);

       respons();

       start();

       write(0xa1);

       respons();

       aa=read();

       stop();

       return aa;

}

void init()

{

       scl=1;

       delay();

       sda=1;

       delay();

       TMOD=0x01;

       TH0=(65536-50000)/256;

       TL0=(65536-50000)%256;

       EA=1;

       ET0=1;

       TR0=1;

}

void display()

{

       dula=1;

       P0=table[shi];

       dula=0;

       P0=0xff;

       wela=1;

       P0=0xfe;

       wela=0;

       delay1(5);//作用是让显示的数据保持住

       dula=1;

       P0=table[ge];

       dula=0;

       P0=0xff;

       wela=1;

       P0=0xfd;

       wela=0;

       delay1(5);

}

void main()

{

       init();

       shi=read_byte(3)/10;

       delay1(100);

       ge=read_byte(3)%10;

       delay1(100);

       display();

       while(1)

       {

              if(flag==1)

              {

                     flag=0;

                     write_byte(3,shi*10+ge);

                     delay1(100);

              }

              display();

       }

}

void timer0() interrupt 1

{

       TH0=(65536-50000)/256;

       TL0=(65536-50000)%256;

       num++;

       if(num==20)

       {

              num=0;

              ge++;

              if(ge==10)

              {

                     ge=0;

                     shi++;

                     if(shi==10)

                            shi=0;

              }

              flag=1;//为了不在定时器写数据,这里用一个标志位flag来控制些数据,因为写数据的延时很长会影响定时器的下次进入中断

       }

}

版权声明:本文为CSDN博主「小乖乖_学技术」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sunxingzhesun/article/details/121321509

生成海报
点赞 0

小乖乖_学技术

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

用51单片机做宿舍门禁系统(1)--1602显示

由于时间紧迫,还有其他考试,所以目前只能抽时间慢慢做慢慢更新了,本身就是一个练手项目。 1602显示 所谓的智能系统,没有显示是万万不能的,所以有一个显示的东西是很必要的

51单片机、直流电机(电机工作5s后停止工作)

对于大功率外设,直接用IO口进行驱动很容易把芯片烧毁,或者无法驱动。那么要想驱动大功率外设,就必须搭建驱动电路。而我们的开发板上搭载了ULN2003驱动芯片,它是一个单片高电压、高电流的达

TM1650芯片驱动四位数码管

自言自语 今天上班被丢了块4位数码管过来,还有一份驱动数码管的芯片资料。还好只有十几页,哈哈哈。 大致浏览下手册,了解到这个芯片叫TM1650,然后是使用模拟IIC协议的。那也就是说&#

ADC采样调试的理解

1)用万用表测得一个电压,例如:3.6V 2) 查看原理图,电阻分压。例如:3.0V 3) 到MCU内部,考虑约0.2V左右压降。例如:2.8