打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
51入门系列教程| 协议协议(IIC篇)

从这一贴开始,和大家一起聊聊有关通讯协议,通讯协议是指单片机和外部设备进行数据交互的基本准则.先说说物理连接吧!

地球上的通讯端口,从根本上来讲只有两种:一种是串行接口,即指数据按照高地位的一定顺序,一位一位分时进行传输;一种是并行接口,即指所有数据,一次性同时进行传输;由此可以看来,并行接口理论上的速度会比串行接口快.为了简化单片机(主)和外部设备(从)的物理连接复杂度,一般情况下会使用串行接口.而单片机内部的总线,一般就是并行的结构.俺们这里主要聊聊串行通信,再说协议,这个是非常重要的通信手段.

举个栗子,咱(主机)和女神(从机)聊天,首先得确认两个原则性的东西:第一,相同的语言.咱说国语,女神说那美克星语,直接后果就是双方完全不能沟通.类比下来,就是通讯中的信号协议,必须遵循一定的信号发送接收的格式.还记得韩梅梅和李雷吗?

韩:hi,lilei,how are you

李:fine,thank you,and you?

韩:I’m fine too,goodbye

李:goodbye

韩用英语先喊名字打招呼,李听到喊自己名字且听懂后,才会有下面的交流.这就是一次完整的通信过程,以hi开始,以goodbye结束.

第二,相同的语速.虽然咱和女神都说国语.但是咱每秒说10个字,女神每秒只能听见并理解5个字,这样下来,明显也无法正常沟通.不信你试试飞快地读“西安”一词,会不会有人理解成“先”一字呢?呵呵,速度快了,意思完全就不通了

抛开上面两点,接下来就是一些细节内容了,譬如,咱向女神阐述长达1分钟的故事.女神在这1分钟内,会隔一段时间回复一个“嗯”,来表示正在聆听,并且做好准备继续聆听.咱才能继续讲下去哈

反过来女神讲事情给咱也是一样,咱也得时不时回复一个“嗯”.好让女神继续讲下去,如果换个场景,咱一个人(主机)给多个人(多从机)吹水.也基本上就是上面的过程,个人认为一个串行通信比较重要的方面也就这么些了,下面来看看今天咱聊的具体对象IIC.

IIC,有些写成I2C,简单读成i方c,全称Inter-Integrated Circuit,是由飞利浦在上世纪80年代设计推广的一种通信总线协议,呃,现在应该叫恩智浦(NXP)了.它只需要两个IO口即可构成,一根是SCL时钟线,有些也标为SCK之类,说法不一;另外一根是SDA数据线,SCL是一个单向的IO,由主机向从机发送.SDA则是条双向IO,主机需要对其进行读写操作,每次8位数据.读的时候接收数据,写的时候发送数据.

IIC的速度分为三个等级:标准模式100kbit/s,快速模式400kbit/s,高速模式3.4Mbit/s.由SCL的频率来决定.一般情况下,需要参照外围器件的要求来决定SCL频率.在硬件上,SDA和SCL是需要有上拉电阻,从机设备需要是OC/OD状态,集电极开路或者漏极开路,且这个上拉电阻的取值,会对速度产生比较大的影响.10kohm以下比较常见,个人喜欢4.7k,IIC要求的细则很多,有兴趣可以下载英文原版的手册研读.这里聊聊一些基本要求:

1、基本原则

SCL为高时,SDA不能乱动.SCL为低时,SDA随便玩.SCL必须由主机控制.在寻址过程中,一次发送数据8位,其中高7位为从机地址,最后一位为读写标志位.So,每个从机一个地址,一条IIC总线理论上最多挂载127个从设备.主机呼叫,从机听到叫自己才作出回应

2、起始信号

类似于打招呼hi,告诉别人,咱要发话了

时序图

SCL在高电平器件,SDA出现一次下降沿,也就是SDA从高电平跳变到低电平,看代码:

void start()

{

SDA=1; //SDA拉高

delay();

SCL=1; //SCL拉高

delay();

SDA=0; //SDA拉低,出现一次下降沿,形成起始信号

delay();

SCL=0; //SCL拉低

delay();

}

3、停止信号

时序图

类似于再见goodbye,告诉别人交流结束,SCL高电平期间,SDA由低电平跳变到高电平 ,也就是出现一次上升沿 ,看代码:

void stop()

{

SDA=0; //SDA拉低

delay();

SCL=1; //SCL拉高

delay();

SDA=1; //SDA拉高,出现一次上升沿

delay();

}

4、应答信号

应答信号有两类:一类是主机发送给从机的,另一类是从机发送给主机的,出现在8位数据传输结束后,第九个时钟到来之时,应答信号一般称为ACK信号.至于NACK(非应答信号),其实就是ACK的另一种说法,时序图:

表示从机已经收到之前传输的8位数据,简单点理解,就是女神的“嗯”(邪恶了!!!!!!!) .这个需要由主机读取SDA的值,来判断是否有ACK信号.看代码:

bit respons()

{

bit temp=0;

SDA=1; //主机将SDA拉高,释放SDA控制权(SDA是上拉到电源的哟)

delay();

SCL=1; //SCL拉高

delay();

temp = SDA; //读取SDA的值,赋给temp

SCL=0; //SCL拉低

delay();

return temp; //返回temp值

}

So,temp=1的话,是个NACK信号,从机无响应 ,temp=0的话,则是个ACK信号咯,那就可以认为,从机已经接收到主机发送过来的数据了.

5、数据发送过程

每次传递8位的数据,这些数据啥时候发送呢 ?看时序图:

So easy ,SCL高电平期间,保持SDA数据稳定,就是1位数据传过去了 .SCL低电平期间,SDA可以进行数据变化...瞧代码:

void wr_byte(uchar date)

{

uchar i;

for(i=0;i<>

{

date=date<1;>

SDA=CY; //将左移进位信号1或0赋给SDA,详见REG51.h中SY寄存器

delay();

SCL=1; //SDA稳定后,SCL拉高

delay();

SCL=0; //SCL拉低

delay();

}

}

上面2-4肢解了IIC一次数据通信过程,就这么些.但是不同的IIC设备,读写时序略有不同,咱来看些具体的例子吧!正好手头上有块24C08

看看这货的地址情况

8位寻址数据,最后一位为读写标志位,0写1读,D7-D1为地址位,其中高四位固定为1010,B2、B1、B0可操作.但是注意一下,对于24C08而言,B2位是由外部管脚确定的,写无效.也就是说,一条IIC总线上可以挂2个24C08,B2为0或者1.而其他,特别是24C02,B0、B1、B2都是由外部管脚确定,可挂8片.不过24c08可以软件操作B1和B0.B1B0=00时,指向block0的256个字节空间.后面依次类推.总共有4x256=1024k字节=8kbit.所以叫做24c08,呵呵。在写寻址的时候,地址为0xa1.在读寻址的时候,地址为0xa0.看看这货的操作过程

其他细节,可以参考数据手册 ,动手撸代码:

#include

#include

#define uint unsigned int /*宏定义*/

#define uchar unsigned char /*宏定义*/

uchar WRDADDS= 0xa0 ; /*I2C器件地址,寻址写*/

uchar RDDADDS =0xa1 ; /*I2C器件地址,寻址读*/

sbit SDA = P1^0; /*数据线*/

sbit SCL = P1^1; /*时钟线*/

sbit LED = P1^3;

void Write(uchar address,uchar date); /*向24c02的地址address中,写入一字节数据date*/

uchar Read(uchar address); /*从24c02的地址address中,读取一个字节数据(返回值)*/

void wr_byte(uchar date); /*I2C总线写一个字节(有参数)*/

uchar rd_byte(); /*I2C总线读一个字节(返回值)*/

void start(); /*启动I2C总线*/

void stop(); /*停止I2C总线*/

bit respons(); /*I2C总线应答信号检查*/

void delay(); /*延时微秒*/

void delayms(uint xms); /*延时毫秒*/

/*向24c02的地址address中,写入一字节数据date*/

void Write(uchar address,uchar date)

{

start(); //启动信号

wr_byte(WRDADDS); //I2C器件地址,寻址写

while(respons()); //等待应答信号

wr_byte(address); //选择单元地址

while(respons()); //等待应答信号

wr_byte(date); //写数据

while(respons()); //等待应答信号

stop(); //停止信号

}

/*从24c02的地址address中读取一个字节数据*/

uchar Read(uchar address)

{

uchar temp;

start(); //开始信号

wr_byte(WRDADDS); //I2C器件地址,寻址写

while(respons()); //等待应答信号

wr_byte(address); //选择单元地址 信号

while(respons()); //等待应答信号

start(); //重发启动信号

wr_byte(RDDADDS); //I2C器件地址,寻址读

while(respons()); //等待应答信号

temp = rd_byte(); //读数据(函数返回值)

stop(); //停止信号

return temp;

} /*启动I2C总线*/

void start()

{

SDA=1;

delay();

SCL=1;

delay();

SDA=0;

delay();

SCL=0;

delay();

}

/*停止I2C总线*/

void stop()

{

SDA=0;

delay();

SCL=1;

delay();

SDA=1;

delay();

}

/*I2C总线应答信号检查*/

bit respons()

{

bit temp=0;

SDA=1;

delay();

SCL=1;

delay();

temp = SDA;

SCL=0;

delay();

return temp;

}

/*I2C总线写一个字节*/

void wr_byte(uchar date)

{

uchar i;

for(i=0;i<>

{

date=date<>

SDA=CY;

delay();

SCL=1;

delay();

SCL=0;

delay();

}

}

/*I2C总线读一个字节*/

uchar rd_byte()

{

uchar i,temp;

for(i=0;i<>

{

SCL=1;

delay();

temp=(temp<>

delay();

SCL=0;

delay();

}

return temp;

}

/*NOP延时函数*/

void delay()

{

_nop_();

_nop_();

_nop_();

_nop_();

_nop_();

}

/*延时函数*/

void delayms(uint xms)

{

uint i,j;

for(i=0;i<>

for(j=0;j<>

}

void main()

{

uchar num,x;

uint i,count1,count2;

LED = 1;

for(i=0;i<1024;i++)>

count2++;

if(i<256)>

Write(i,i);

delayms(20);

num = Read(i);

if(num==i)

{

count1++;

LED = ~LED;

}

}

if((256<><=512))>

x = (i-256);

WRDADDS = 0xa2;

RDDADDS = 0xa3;

Write(x,x);

delayms(20);

num = Read(x);

if(num==x)

{

count1++;

LED = ~LED;

}

}

if((512<><=768))>

x = (i-512);

WRDADDS = 0xa4;

RDDADDS = 0xa5;

Write(x,x);

delayms(20);

num = Read(x);

if(num==x)

{

count1++;

LED = ~LED;

}

}

if((768<><1024))>

WRDADDS = 0xa6;

RDDADDS = 0xa7;

x = (i-768);

Write(x,x);

delayms(20);

num = Read(x);

if(num==x)

{

count1++;

LED = ~LED; }

}

}

if(count1==count2)

{LED=0;}

else{LED = 1;}

while (1)

{

}

}

可以参照对比上面的时序,把代码简单读一下.写得比较随意,了解一下大概过程即可.尤其是Read和Write两个函数.大致功能就是把24C08所有的存储单元都进行一次读写同时blink.如果每个单元写入和读出的数据相同,最后LED点亮.上个GIF

有些时候咱操作的不一定是EEPROM,还有一些其他传感器器件 ,正好手头上有块TMP75B:

很早以前向TI申请的样片,温度传感器 ,看看这货的地址和读写时序吧

A0-A2均可由外部管脚确定,不过高4位固定为1001,tmp75b地址写为0x90,地址读为0x91

因为TMP75B需要16位来完成温度的读取,所以需要连续读2个字节来获得温度值.注意红圈内的时序要求.在多字节传输的时候,每个字节传输结束后,主机需要发送一个ACK.也就是SCL高电平期间,SDA保持低电平.表示接收到了一个8位数据.然后将SDA拉高.简单修改一下上面的代码,完成连续读的操作.

#define WRDADDS 0x90 ; /*宏定义I2C器件tmp75b地址,寻址写*/

#define RDDADDS 0x91 ; /*宏定义I2C器件tmp75b地址,寻址读*/

void ack_master()

{

SDA=0;

delay();

SCL=1;

delay();

SCL=0;

delay();

SDA=1;

delay();

}

uint Read_temprature(uchar address)//连续读2个字节的温度数据

{

uint temp;

start(); //开始信号

wr_byte(WRDADDS); //I2C器件地址,寻址写

while(respons()); //应答信号

wr_byte(address); //选择单元地址 信号

while(respons()); //应答信号

stop();

start(); //重发启动信号

wr_byte(RDDADDS); //I2C器件地址,寻址读

while(respons()); //应答信号

temp = rd_byte()<8;>

ack_master();

temp |= rd_byte(); //低8位数据

ack_master();

stop(); //停止信号

return temp;

}

void main()

{

float temprature;

LED = 1; //熄灭LED

while (1)

{

Write(0x00,0x00);

delayms(20);

nums = Read_temprature(0x00); //获取16位温度HEX

temprature = (nums>>4)*0.0625; //换算温度值

if(temprature>25.0){LED = 0;} //如果温度大于25,点LED

else {LED = 1;} //否则熄灭

}

}

看看

Read_temprature

函数,是不是和贴图的时序一致呢?代码基本功能就是,通过IIC与传感器TMP75B通信.获取温度信息

如果超过25度就点亮LED, 否则熄灭LED!上GIF:

好累!这次就先到这里.

了解更多51系列教程,请关注“云汉电子社区”官方微信公众号ickeybbs,或者登录云汉电子社区官方网站(bbs.ickey.cn)

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
基于51的IIC通讯原理及协议详解(I2C)
IIC总线通信协议
IIC 通讯协议
IIC总线操作AT24C02芯片详解
I2C总线三
I2c串行总线组成及其工作原理
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服