目前常用的微机与外设之间进行数据传输的串行总线主要有UART、1-wire、I2C和SPI总线。UART是以异步方式进行通信(一条数据输入线,一条数据输出线)。1-wire即单线总线,又叫单总线(只有一条线)。I2C同步串行2线方式进行通信(一条时钟线,一条数据线)。SPI同步串行3线方式进行通信(一条时钟线,一条数据输入线,一条数据输出线)。
I2C串行总线的组成及工作原理 I2C总线是PHLIPS公司推出的一种串行总线,它只有两根双向信号线。一根是数据线SDA(serial data I/O),另一根是时钟线SCL(serial clock)。如下图所示,IIC总线上可以挂多个器件,而每个器件都有唯一的地址,这样可以标识通信目标。数据的通信的方式采用主从方式,主机负责主动联系从机,而从机则被动回应数据。
I 2C总线通过上拉电阻接正电源。当总线空闲时,两根线均为高电平。连到总线上的任一器件输出的低电平,都将使总线的信号变低,即各器件的SDA及SCL都是“线与”关系。
I 2C总线传输协议SCL为高电平期间,数据线上的数据必须保持稳定,只有SCL信号为低电平期间,SDA状态才允许变化。
SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号。
起始和终止信号都是由主机发出的,在起始信号产生后,总线就处于被占用的状态;在终止信号产生后,总线就处于空闲状态。
I 2C字节的传送与应答每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。
主机在发送数据时,每次发送一字节数据,都需要读取从机应答位,当从机空闲可以接收该字节数据时,从机会发出应答(一帧数据的第9位为“0”),当从机正忙于其他工作的处理来不及接收主机发送的数据时,从机会发出非应答(一帧数据的第9位为“1”)主机则应发出终止信号以结束数据的继续传送,主机通过从机发出的应答位来判断从机是否成功接收数据。
当主机接收数据时,它收到最后一个数据字节后,必须向从机发出一个结束传送的信号。这个信号是由对从机的“非应答”来实现的。然后,从机释放SDA线,以允许主机产生终止信号。
数据帧格式 I 2C总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/T),用0表示主机发送数据(T),1表示主机接收数据(R)。每次数据传送总是由主机产生的终止信号结束。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。
当要写入的数据传送完后,单片机应发出终止信号以结束写入操作。写入n个字节的数据格式:
读出过程 :单片机先发送该器件的7位地址码和写方向位0(“伪写”),发送完后释放SDA线并在SCL线上产生第9个时钟信号。被选中的存储器器件在确认是自己的地址后,在SDA线上产生一个应答信号作为回应。 然后,再发一个字节的要读出器件的存储区的首地址,收到应答后,单片机要重复一次起始信号并发出器件地址和读方向位1,收到器件应答后就可以读出数据字节,每读出一个字节,单片机都要回复应答信号。当最后一个字节数据读完后,单片机应返回以“非应答”(高电平),并发出终止信号以结束读出操作。
C语言编程 点亮数码管 将通讯数据在数码管中显示出来,首先要驱动数码管点亮,用3位数码管即可程序如下:
#include <reg52.h> #include <intrins.h> #define uint unsigned int #define uchar unsigned char sbit WE=P2^7; //位选 sbit DU=P2^6; //段选 uchar x,y,c,h,m,s; uchar code table[]={ //0 1 2 3 4 5 6 7 8 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, //9 A B C D E F - . 关显示 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x40, 0x80, 0x00 }; //数码管显示数字 uchar code table1[]={0xfe,0xfd,0xfb}; //数码管的位 void delay(uint z) //延时 { for( c=z;c>0;c--) for( y=110;y>0;y--); } void display(uchar i) //数码管显示 { P0=0xff; WE=1; P0= table1[x]; WE=0; switch(x) { case 0: DU=1; P0=table[i/100];DU=0; break; case 1: DU=1; P0=table[i%100/10];DU=0; break; case 2: DU=1; P0=table[i%10];DU=0; break; } x++; if(x>2) x=0; delay(10); } void main() { while(1) { display(121); //数码管显示121 } }
在数数码管上显示121:
通讯信号 通讯信号包括起始信号、终止信号、应答信号、非应答信号,程序编写是根据器件手册中的时序图编写的:
void START() // 开始信号 { SCL=1; //拉高时钟总线 SDA=0; //拉低数据总线 SDA=1; //拉高数据总线 delay5us(); //延时 SDA=0; //拉低数据总线 delay5us(); //延时 SCL=0; //拉低时钟总线 } void STOP() //终止信号 { SCL=0; SDA=0; SCL=1; delay5us(); SDA=1; delay5us(); SDA=0; } void ACK() //应答信号 { SCL=0; SDA=1; SDA=0; SCL=1; delay5us(); SCL=0; SDA=1; } void NACK() //非应答信号 { SCL=0; SDA=0; SDA=1; SCL=1; delay5us(); SCL=0; SDA=0; }
读、写数据 接下来还要编写读、写的程序,程序一定要严格按照器件手册中的时序图编写,否则不会通讯成功,详见如下的总程序:
#include <reg52.h> #include <intrins.h> #define uint unsigned int #define uchar unsigned char sbit WE=P2^7; //位选 sbit DU=P2^6; //段选 sbit SCL=P2^1; //时钟总线 sbit SDA=P2^0; //数据总线 uchar x,y,c,h,m,s; uchar code table[]={ //0 1 2 3 4 5 6 7 8 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, //9 A B C D E F - . 关显示 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x40, 0x80, 0x00 }; //数码管显示数字 uchar code table1[]={0xfe,0xfd,0xfb}; //数码管的位 void delay(uint z) //延时 { for( c=z;c>0;c--) for( y=110;y>0;y--); } void delay5us() //5us延时 { _nop_(); } void display(uchar i) //数码管显示 { P0=0xff; WE=1; P0= table1[x]; WE=0; switch(x) { case 0: DU=1; P0=table[i/100];DU=0; break; case 1: DU=1; P0=table[i%100/10];DU=0; break; case 2: DU=1; P0=table[i%10];DU=0; break; } x++; if(x>2) x=0; delay(10); } void START() // 开始信号 { SCL=1; //拉高时钟总线 SDA=0; //拉低数据总线 SDA=1; //拉高数据总线 delay5us(); //延时 SDA=0; //拉低数据总线 delay5us(); //延时 SCL=0; //拉低时钟总线 } void STOP() //终止信号 { SCL=0; SDA=0; SCL=1; delay5us(); SDA=1; delay5us(); SDA=0; } void ACK() //应答信号 { SCL=0; SDA=1; SDA=0; SCL=1; delay5us(); SCL=0; SDA=1; } void NACK() //非应答信号 { SCL=0; SDA=0; SDA=1; SCL=1; delay5us(); SCL=0; SDA=0; } void Send(uint dat) //发送地址 { uint i; for(i=0;i<8;i++) //8位 { SCL=0; SDA=dat & 0x80; //先发送最高位地址 dat <<=1; //左移一位 SCL=1; } SCL=0; SDA=1; } void Write(uint dat,uint cmd) //写入数据 { START(); //开始信号 Send(0xA0); //发送地址 ACK(); //应答信号 Send(dat); //发送字节 ACK(); Send(cmd); //发送数据 ACK(); STOP(); //停止信号 } uchar Read(uint dat) //读数据 { uint i,cmd; START(); //开始信号 Send(0xA0); //发送地址 ACK(); //应答信号 Send(dat); //发送字节 ACK(); START(); Send(0xA1); ACK(); for(i=0;i<8;i++) //读数据 { cmd <<=1; //左移 SCL=0; if(SDA) cmd |=0x01; //将数据写入cmd中 SCL=1; } SCL=0; SDA=1; NACK(); STOP(); return cmd; //返回cmd的值 } void main() { Write(2,131); //在2通道中写下131 delay(5); while(1) { display(Read(2)); //读出2号通道中的数据在数码管上显示131 } }
I2C通讯的调试需要借助工具,可以使用示波器来抓取波形,从而检查出程序哪里有问题,下面是程序烧录单片机后的果,将131写入2通道中,然后再读出来,显示在数码管上: