历史上的今天
返回首页

历史上的今天

今天是:2025年07月14日(星期一)

正在发生

2018年07月14日 | 单片机 IIC 总线协议 和 详细例程

2018-07-14 来源:eefocus

先简单的说明以下I2C总线, I2C 总线是一种串行数据总线,只有二根信号线,一根是双向的数据线SDA,另一根是时钟线SCL。

处理器和芯片间的通信可以形象的比喻成两个人讲话:1、你说的别人得能听懂:双方约定信号的协议。2、你的语速别人得能接受:双方满足时序要求。

看IIC协议先:两条线可以挂多个设备。IIC设备(稍微有点智能的)里有个固化的地址。只有在两条线上传输的值等于我(IIC设备)的地址时,我才作出响应。


开始信号:处理器让SCL时钟保持高电平,然后让SDA数据信号由高变低就表示一个开始信号。同时IIC总线上的设备检测到这个开始信号它就知道处理器要发送数据了。

停止信号:处理器让SCL时钟保持高电平,然后让SDA数据信号由低变高就表示一个停止信号。同时IIC总线上的设备检测到这个停止信号它就知道处理器已经结束了数据传输,我们就可以各忙各个的了,如休眠等。

再看数据怎么传:SDA上传输的数据必须在SCL为高电平期间保持稳定:因为外接IIC设备在SCL为高电平的期间采集数据方知SDA是高或低电平。SDA上的数据只能在SCL为低电平期间翻转变化。

响应信号(ACK):处理器把数据发给外接IIC设备,如何知道IIC设备数据已经收到呢?就需要外接IIC设备回应一个信号给处理器。处理器发完8bit数据后就不再驱动总线了(SDA引脚变输入),而SDA和SDL硬件设计时都有上拉电阻,所以这时候SDA变成高电平。那么在第8个数据位,如果外接IIC设备能收到信号的话接着在第9个周期把SDA拉低,那么处理器检测到SDA拉低就能知道外接IIC设备数据已经收到。

IIC数据从最高位开始传输。


再进一步说:IIC总线是允许挂载多个设备的,如何访问其中一个设备而不影响其他设备呢?

用7bit表示从地址,那么可以挂载的从设备数是2的7次方128个。处理器想写的话:先发送起始位,再发一个8bit数据:前7bit表示从地址,第8bit表示读或者写。0write是处理器往IIC从设备发,1read是IIC从设备往处理器发。第9个时钟周期回复响应信号。

下面就以AT24Cxx为例详细说明一下:

首先发出一个start信号,从设备地址,R/W(0,写),回应ACK表示有这个从设备存在。这时候是处理器从指定的从设备读数据的从设备里8bit存储地址的指定。所以这里R/W是0为写。ACK回应有这个设备的话,处理器把要访问的从设备里的8bit存储地址写好。ACK对方回应。继续一个start信号+从设备地址,最低位是高电平表示读数据,回应ACK表示有这个从设备存在。在读数据的时候,每发出一个时钟,处理器会SDA上的数据存起来。那么发出8个时钟后处理器就能得到8位的数据。这时候若想连续读就不断回应ACK信号否则就发出停止信号。

读的过程:start信号,从设备地址,写,待读取存储地址,再一个start信号,从设备地址,读,8个时钟,从设备就把对应的数据反馈给处理器。

start信号,哪一个设备地址,写,紧跟连续两个字节的数据:要写的地址,对方收到8bit地址后回应ACK,再8bit数据发给从设备,对方收到8bit数据后回应ACK,处理器写完后发送停止信号。


例程:

实现功能:0-99秒的自动计时器,随机关断电源,在通电以后计时器接着断电时的状态继续计时

实现工具:51单片机

电路图如图所示:

所用芯片是:AT24C02芯片,51单片机基本搭载这块芯片。AT24C系列E2PROM的型号地址皆为1010,器件地址中的低3位为引脚地址A2A1A0,对应器件寻址字节中的D3、D2、D1 位,在硬件设计时由连接的引脚电平给定。对 AT24C 系列 E2PROM的读写操作完全遵守 I2C总线的主收从发和主发从收的规则。


#include

#include

#define uint unsigned int

#define uchar unsigned char

unsigned char sec; // 定义计数值,每过1s,sec加1

unsigned int tcnt; // 定时中断次数

bit write = 0; // 写2408的标志

sbit sda = P2^0; // I2C接口SDA定义

sbit scl = P2^1; // I2C接口SCL定义

sbit dula = P2^6;

sbit wela = P2^7;

unsigned char j,k;

 

void delay(unsigned char i) // 延时程序

{

for( j=i ; j>0 ; j-- )

for( k=125; k>0; k-- );

}

 

uchar code table[] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71}; // 数码管编码

 

void display(uchar bai_c,uchar sh_c) // 显示程序

{

dula = 0;

P0 = table[bai_c];// 显示百位

dula = 1;

dula = 0;

wela = 0;

P0 = 0x7e;

wela = 1;

wela = 0;

delay(5);

dula = 0;

P0 = table[sh_c];

dula = 1;

dula = 0;

wela = 0;

P0 = 0x7d;

wela = 1;

wela = 0;

delay(5);

}

// 24c02读写驱动程序

void delay1(unsigned char x)

{

unsigned int i ;

for( i=0; i

}

void flash() // 延时子函数

{

; ; ;

}

void x24c08_init()// 2402初始化子程序

{

scl = 1;

flash();

sda = 1;

flash();

}

void start()// 启动I2C总线

{

sda = 1;

flash();

scl = 1;

flash();

sda = 0;

flash();

scl = 0;

flash();

}

void stop()// 停止I2C总线

sda = 0;

flash();

scl = 1;

flash();

sda = 1;

flash();

}

 

void writex(unsigned char j)// 写一个字节

{

unsigned char i,temp;

temp = j;

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

  {

  temp = temp<<1;

  scl = 0;

  flash();

  sda = CY;

  flash();

  scl = 1;

  flash();

  }

    scl = 0;

  flash();

  sda = 1;

  flash();

}

 

unsigned char readx()// 读一个字节

{

unsigned char i,j,k=0;

scl = 0;

  flash();

  sda = 1;

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

  {

  flash();

  scl = 1;

  flash();

  if(sda==1) j = 1;

  else j = 0;

  k = (k<<1)|j;

  scl = 0;

  }

  flash();

  return(k);

}

void clock()// I2C总线应答子函数

{

unsigned char i = 0;

scl = 1;

flash();

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

scl = 0;

flash();

}

 

unsigned char x24c08_read(unsigned char address)  // 从24c02的地址address中读取一个字节数据

{

unsigned char i;

start();

writex(0xa0);

clock();

writex(address);

clock();

start();

writex(0xa1);

clock();

i = readx();

stop();

delay(10);

return(i);

}

 

void x24c08_write(unsigned char address,unsigned char info)// 向2402的address地址中写入一个字节数据

{

EA = 0;

start();

writex(0xa0);

clock();

writex(address);

clock();

writex(info);

clock();

stop();

EA = 1;

delay1(50); 

}

 

void t0(void) interrupt 1 using 0 // 定时中断服务函数

{

TH0 = (65536-50000)/256; // 对TH0,TL0赋值

TL0 = (65536-50000)%256;

tcnt++; // 每过250us tcnt加1

if(tcnt == 20)

{

tcnt = 0; // 重新再计

sec++;

write = 1; // 1s写一次24c02

if(sec == 100)

sec = 0; // 定时100s,从0开始计时

}

}

}

 

void main() // 主函数

{

unsigned char i;

TMOD = 0x01; // 定时器工作在方式1

ET0 = 1;

EA = 1; // 开中断

x24c08_init();// 初始化24c08

sec = x24c08_read(2); // 读出保存的数据赋予sec

TH0 = (65536-50000)/256;

TL0 = (65534-50000)%256 ; // 使定时器0.05s中断一次

TR0 = 1;// 启动定时器

while(1)

{

i = 10;

while(i--)

display(sec/10,sec%10);

}

if(write = 1) // 判断计时器是否计时1s

{

write = 0; // 清0

x24c08_write(2,sec); // 在24c02的地址中写入数据sec

}

}

}

经试验,得到实验结果。

推荐阅读

史海拾趣

问答坊 | AI 解惑

IC设计公司领先者、巨头、排头兵

中国IC产业在过去十几年取得了巨大的成就,IC设计企业已接近500家,2004年销售收入过亿元人民币的企业达到了16家之多。但是IC企业仍然有很长的路要走,一方面产品市场范围过窄,主要集中于电源管理、信号处理、视频编解码、玩具控制等几个方面,在 ...…

查看全部问答>

急,求LM2576-ADJ的中文资料

求LM2576-ADJ的中文资料,有急用…

查看全部问答>

电流的磁场

关于电流和磁场…

查看全部问答>

拒绝采用无线报警的理由(转贴精华)

拒绝采用无线报警的理由 在今年2月19日,我把本网站《报警器现状》里的文字以《向防盗报警行业打一棒子》为题分别发 表在@2@1IC电子工程师论坛、慧聪安防商务网的安防论坛、中华安防论坛以及阿里巴巴论坛。 其中在2@1IC电子工程师论坛有个网名 ...…

查看全部问答>

求助!如何编写单色液晶屏的framebuffer驱动?

小弟我刚开始学习Linux,现在想用一块S3C2440的开发板,在Linux下驱动一块单色stn液晶屏,疑惑主要有: 1.我更改了Linux/arch/arm/mach-s3c2440.c在其中添加了 #elif defined(CONFIG_FB_S3C2410_NS320240B)//NS320240B我用的屏 #define LCD_WIDT ...…

查看全部问答>

PC机连接GPRS

我用COM1口连接一个插SIM卡的无线网关 然后用COM1作为调制解调器 但是当我创建GPRS连接的时候 连接显示无效 原因是:调制解调器 已删除- 不可使用的设备(COM1) 请问这是为什么啊???…

查看全部问答>

ee辛苦了![噩梦]百合花物语收藏版。另:BS下某些人,顺便提醒下及时支付定金和尾款。

一 其时酒席已散,男人们互相搀着,喷着酒气继续集体意淫着新娘。女人们则大多沉浸在刚刚的幸福感之中,站在酒店门口等着自己的男人出来。 酒店之中早已一片狼藉,凌乱之中有一只苍白的手,伸向了一朵白百合。 咔嚓一声脆响,这手迟疑了一下,手 ...…

查看全部问答>

急需GB18030标准的16*16宋体字库

急需GB18030标准的16*16宋体字库,我得邮箱amy84104@163.com 谢谢谢谢!!…

查看全部问答>

datasheet中没有详细介绍USART

                                 在STM32F101 data sheet中,介绍了SPI ,I2C,ADC,TIMER等,唯独没介绍UART.是疏 ...…

查看全部问答>