OK,来到了重难点。
有关于I2C总线信号时序总结请参考https://blog.csdn.net/Xiaomo_haa/article/details/87902379
I2C寻址模式
I2C通信的起始信号之后,首先要发送一个从机地址,这个地址一共有7位,紧跟着第8位是数据方向位(#R/W),“0”表示接下来要发送数据(写),“1”表示接下来是请求数据(读)。
我们蓝桥杯单片机开发与设计的开发板所使用的E2PROM器件型号是AT24C02,我们查询datasheet可得
24C02的七位地址中,高四位是固定的0b1010,低三位的地址取决于具体的电路设计,由芯片上的A2、A1、A0决定。
如我们开发板的原理图:
在这里,24C02的7位地址实际上就是0b1010000,也就是0x50。
在比赛的时候提供的有I2C的代码,我现在直接给粘贴过来。
/*
程序说明: IIC总线驱动程序
软件环境: Keil uVision 4.10
硬件环境: CT107单片机综合实训平台 8051,12MHz
日 期: 2011-8-9
*/
#include "sys.h"
#include "intrins.h"
#define somenop {_nop_(); _nop_(); _nop_(); _nop_(); _nop_();}
#define SlaveAddrW 0xA0
#define SlaveAddrR 0xA1
//总线引脚定义
sbit SDA = P2^1; /* 数据线 */
sbit SCL = P2^0; /* 时钟线 */
//总线启动条件
void IIC_Start(void)
{
SDA = 1;
SCL = 1;
somenop;
SDA = 0;
somenop;
SCL = 0;
}
//总线停止条件
void IIC_Stop(void)
{
SDA = 0;
SCL = 1;
somenop;
SDA = 1;
}
//应答位控制
void IIC_Ack(bit ackbit)
{
if(ackbit)
{
SDA = 0;
}
else
{
SDA = 1;
}
somenop;
SCL = 1;
somenop;
SCL = 0;
SDA = 1;
somenop;
}
//等待应答
bit IIC_WaitAck(void)
{
SDA = 1;
somenop;
SCL = 1;
somenop;
if(SDA)
{
SCL = 0;
IIC_Stop();
return 0;
}
else
{
SCL = 0;
return 1;
}
}
//通过I2C总线发送数据
void IIC_SendByte(unsigned char byt)
{
unsigned char i;
for(i=0;i<8;i++)
{
if(byt&0x80)
{
SDA = 1;
}
else
{
SDA = 0;
}
somenop;
SCL = 1;
byt <<= 1;
somenop;
SCL = 0;
}
}
//从I2C总线上接收数据
unsigned char IIC_RecByte(void)
{
unsigned char da;
unsigned char i;
for(i=0;i<8;i++)
{
SCL = 1;
somenop;
da <<= 1;
if(SDA)
da |= 0x01;
SCL = 0;
somenop;
}
return da;
}
在这里我们还要注意一点,就是在程序中有个somenop宏定义函数,这里使用了5个nop操作。一个_nop_()操作的时间就是一个机器周期。
I2C通信分为低俗模式100kb/s、快速模式400kb/s和高速模式3.4Mb/s。因为所有的I2C器件都支持低速,但却未必支持另外两种速度,所以作为通用的I2C程序,我们选择100k这个速度来实现,也就是说实际程序产生的时序必须小于等于100k的时序参数,很明显要求SCL的高低电平持续时间都不段于5us。
在这里还有一个小技巧,就是在发送数据那里,如何将一个字节的数据发送出去。我们可以将IIC_SendByte稍微修改一下。
bit IIC_SendByte(unsigned char dat)
{
bit ack; //用于暂存应答位的值
unsigned char mask; //用于探测字节内某一位值的掩码变量
for(mask = 0x80; mask != 0; mask >>= 1) //从高位到低位依次执行
{
if((mask & dat) == 0) //该位的值输出到SDA
SDA = 0;
else
SDA = 1;
somenop;
SCL = 1; //拉高SCL
somenop;
SCL = 0; //再拉低SCL,完成一个位周期
}
SDA = 1; //8位数据发送完毕之后,主机释放SDA,以检测从机应答
somenop;
SCL = 1; //拉高SCL
ack = SDA; //读取此时的SDA值,即为从机的应答值
somenop;
SCL = 0; //再拉低SCL完成应答位,并保持住主线
return ack; //返回从机应答值
}