开发板上的蜂鸣器下面是温度传感器DS18B20
DA转换器的下面是SPI总线(RFR、IOUT、DI0和GND)
I2C总线和SPI总线用的多。
I2C总线仲裁:具有 C总线接口的设备都接在总线上,主机和哪个设备进行通信时先在总线上发个地址码过去,总线上全部响应地址码,所有 C总线上的设备都检测这个地址码,哪个设备的地址码相符就和主机通信
见LESSON8_IIC总线协议的PPT的P3的图,各器件的SDA及SCL都是线“与”关系(各设备的SDA做线“与”运算,结果送到SDA总线上,SCL也同理), I2C总线必须接上拉电阻, 一般为10K
MSB为最高位,LSB为最低位
漏极开路相当于场效应管来说的,和三极管一样都具有开关的作用,场效应管还有放大管的作用,场效应管是压控压型
集电极开路对应三极管来说的,三极管的集电极不能输出,只有接上拉电阻时才能输出,开关和放大管的作用,压控流型,即PN结利用导通的压降来控制导通电流的大小
I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
见LESSON8_IIC总线协议的PPT的P3的图,SDA上的交叉部分表示允许数据变化
SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号。
每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位);应答位必须是由从机发给主机的;第九位应答位如果很长时间主机没有收到就默认收到了
开发板上AD和DA是并口一次都传走八位数据,而I2C总线是串口,八位数据一个一个传
在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/T),用“0”表示主机发送数据(T),“1”表示主机接收数据(R),是主机传送完一个数据后用0或1表示是下一次是主机发送数据还是接收数据。
a、主机向从机发送数据,数据传送方向在整个传送过程中不变:
S表示起始信号,后面是从机地址,0表示由主机要向从机发送数据,A表示从机的应答(低电平表示),A非表示非应答(高电平表示),P表示终止信号
有阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送
A/ 表示应不应答不管了
b、主机在第一个字节后,立即向从机读数据
从机接到主机的信号后,应答然后从机给主机传数据,主机再应答,从机再给主机传数据,主机不应答,主机再发送停止信号
c、在传送过程中,当需要改变传送方向时,起始信号和从机地址都被重复产生一次,但两次读/写方向位正好反相
主机起始信号,从机地址,0表示主机要向从机发送数据,从机应答,主机向从机发送数据,A/ 表示应不应答不管了,读写方向要改变,主机要向从机读取数据了,就重新发出起始信号,从机地址,1表示主机要接收从机发送的数据,从机给了应答,从机向主机发送数据,主机不给应答,发出停止信号
参考LESSON8_IIC总线协议的PPT的P14和P15
主机可以采用不带I2C总线接口的单片机,如80C51、AT89C2051等单片机,利用软件实现I2C总线的数据传送,即软件与硬件结合的信号模拟,很多高级单片机都自带有 C总线,把数据放到寄存器入SBUF,单片机自动传送数据,不用软件实现
参考LESSON8_IIC总线协议的PPT的P17,P18,和P19
SomeNop( )是延时函数
见LESSON8_IIC总线协议的PPT的P20,总线连接的器件下面的A2、A1、A0等是地址线,
E2PROM(Electric erase program read only memory)是电可擦除编程只读存储器,掉电后程序仍然保持在存储器当中,这里用的是AT24C02
写的时候要先写地址再写数据,读的时候要先写地址再读数据
MSB是最高位,MLB是最高位,ACK是应答
参考LESSON8_IIC总线协议的PPT的P22,P23,P24和P25
读出过程:单片机先发送该器件的7位地址码和写方向位“0”(“伪写”),发送完后释放SDA线并在SCL线上产生第9个时钟信号,被选中的存储器器件在确认是自己的地址后,在SDA线上产生一个应答信号作为回应,然后,再发一个字节的要读出器件存储区的首地址(针对E2PROM的存储器有地址),收到应答后,单片机要重复一次起始信号并发出器件地址和读方向位(“1”),收到器件应答后就可以读出数据字节,每读出一个字节,单片机都要回复应答信号。当最后一个字节数据读完后,单片机应返回以“非应答”(高电平),并发出终止信号以结束读出操作。
TX-1C型单片机实验板原理图,上单片机下面的24C02(图中打字打错了,打成24C00)就是E2PROM,管脚1,2,3是地址线,这里管脚1,2,3都接地,管脚5是数据线,管脚6是时钟线,管脚5和6都通过上拉电阻接电源(这里用2.7V to 2.5V),管脚4接地,管脚7WP(Write Protect)是写保护,写保护为高就是写不进去,写保护为低就是可以进行读写,这里接地,始终可以进行读写,管脚8接电源;SDA连接单片机的P2.0口,SCL连接单片机的P2.1口
E2PROM的MAP封装就是表面看不到E2PROM的引脚图,引脚在E2PROM芯片的下方
开发板上AD转换器的下面的芯片就是24C02
见AT24C02A的PPT的P11的Byte Write图,DEVICE ADDRESS是设备地址(后面加一个传送方向位(R/T),高电平为由单片机向从机读数据,低电平为由单片机向从机写数据,以后只要不改变读写方向,确定一次设备地址和传送方向位(R/T)后就不用再添加传送方向位(R/T),当要改变读写方向即改变传送方向位(R/T)时,再重新写入设备地址和改变后的传送方向位(R/T)),ACK表示应答,WORD ADDRESS写的地址(八位地址位,这里是EEPROM内部存储区的地址, I2C总线上面有多个EEPROM,DEVICE ADDRESS设备地址就是选中哪个EEPROM,WORD ADDRESS写地址就是选中EEPROM后要确定在这个EEPROM中哪个地址上写数据,这里就不用“第8位数据的传送方向位(R/T)”,等要改变读写方向时重新给初始信号从机地址传送方向位(R/T)),DATA是数据(八位数据,这里就不用“第8位数据的传送方向位(R/T)”,等要改变读写方向时重新给初始信号从机地址传送方向位(R/T))
见AT24C02A的PPT的P11的Current Address Read(当前地址的读)图,NO ACK是不给应答,主机不再读数据就不给从机应答
谁给应答:当主机给从机数据,从机接受数据后就由从机给应答,当从机给主机数据,主机接受数据后就由主机给应答
见AT24C02A的PPT的P11的Random Read(随机地址的读),只在同一个地址上先写时给了地址,再从该设备读时就不用再给地址了
思路就是把上面的几个时序图对应的几种方式的读写都编成函数,使用时,例如读一组数据可以直接调用读函数
写程序时,先添加头文件,再定义一些类型,然后写主函数,写的时候缺什么(例如缺子函数)就补充什么
把一个数据送到EEPROM里,然后读出来,再送到发光二极管,让发光二极管闪亮:
关键是把重要时间(LESSON8_IIC总线协议的PPT的P17图中的阴影区)的变化写在程序里,我尝试把起始信号的所以过程写全,显示的结果和只把关键地方写全相同:
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总线协议的PPT的P17的起始信号的图,SCL在SDA变成
//低电平之后变不变成低电平都可以,不影响起始信号的写入,所以这句的下面不用加上//SCL=0
}
void stop() //停止信号
{
sda=0;//区分起始信号、停止信号、应答信号和非应答信号,与数据信号(在时钟scl
//拉高时,送数据,数据也是有0和1组成的)的区别就是:数据信号的范围包含了在时钟//scl高电平范围,见LESSON8_IIC总线协议的PPT的P16,起始信号、停止信号是在时钟//scl由高到低变化之前就拉低,应答信号和非应答信号是在传送完数据再给的(每一个被传//送的字节后面都必须跟随一位应答位(即一帧共有9位))
delay();
scl=1;
delay();
sda=1;
delay();//见LESSON8_IIC总线协议的PPT的P17的终止信号的图,SDA由低电平变成//高电平就完成了终止信号,SDA再变不变成低电平都不影响终止信号的写入,所以这句的//下面不用加上SDA=0,关键是把重要时间(图中的阴影区)的变化写在程序里,
}
void respons() //应答信号,是在传送完数据再给的,所以不会和传送数据混淆
{
uchar i;
scl=1;//在时钟为高电平时读取信号
delay();
while((sda==1)&&(i<250)) i++;//第一种情况是在前八位数据传送完,当sda变成零说明
//有应答,第二种情况是第九个时钟等待SDA变成低电平,如果超过这段时间还没给应答,//就自动认为给了应答,i从0计数到250就是这个计时作用;sda==1和i<250有一个不满
//足就退出while循环,继续往下执行
scl=0;//把时钟拉低
delay();
}
void init()//总线初始化
{
sda=1;//见LESSON8_IIC总线协议的PPT的P7的图,起始时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总线协议的PPT的P7的图
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总线协议的PPT的P22,AT24C系列E2PROM芯
//片地址的固定部分为1010,在TX-1C型单片机实验板原理图中A2、A1和A0都接地,第//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读出数据,在指定地址处读数据,见AT24C02A的PDF
//的P11的Random 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存储区内部指定地址处写数据,见//AT24C02A的PDF的P11的Byte Write
{
start();
write_byte(0xa0); //见LESSON8_IIC总线协议的PPT的P22,AT24C系列E2PROM芯
//片地址的固定部分为1010,在TX-1C型单片机实验板原理图中A2、A1和A0都接地,第//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)//在指定地址处读数据,见AT24C02A的PDF的P11的Random //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开始继续走,即关闭电源时EEPROM把8存储起来了;电源检测芯片实时保存数据
以后用到I2C总线上面这个程序都可以直接拿来用,在主函数中修改就可以,这就是C语言的方便的可移植性,我们编过的很多程序都可以移植,加以修改就可以使用
见AT24C02A的PPT的P11的Current 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
暂无评论