这东西它不简单,因为理论很多也要要记住,并且是使用程序来模拟IIC通信协议
但是请静下心学习,人能常清静,天地悉皆归,多念几遍
·
1.常用的串行总线协议
常用的微机与外设之间进行数据传输的串行总线主要有UART,1-wire,I2C,SPI
UART:异步通信,一条数据输入线,一条数据输出线
1-wire:单线总线
I2C:同步串行2线方式进行通信,一条时钟线和一条数据线
SPI:同步串行3线方式进行通信,一条时钟线和一条数据输入线和一条数据输出线
2.电路知识
场效应管(FET)是利用输入回路的电场效应来控制输出回路电流的一种半导体器件,又叫单极性晶体体。
结型场效应管(JEFT)给栅极一个足够的负电压场效应管截止,给0则导通(NPN型)
绝缘栅型场效应管(MOS管)
VCC : 接入电路中的电压 双极器件的正
下标可以理解为NPN晶体管的集电极C
来源于集电极电源电压,CollectorVoltage,一般用于双极型晶体管
PNP管时为负电源电压,有时也标成-Vcc,NPN管时为正电压
模拟电源
VDD : 器件内部的工作电压 单级器件的正
下标可以理解为场效应管的漏极D
来源于漏极电源电压,DrainVoltage,用于MOS晶体管电路,一般指正电源
VSS : 电路公共接地端电压 源极电源电压
在CMOS电路中指负电源,在单电源时指零伏或接地
VEE : 发射极电源电压
VBB : 基极电源电压
总:
一般来说VCC=模拟电源,VDD=数字电源,VSS=数字地,VEE=负电源。
有些IC既有VDD引脚又有VCC引脚,说明这种器件自身带有电压转换功能。
对于数字电路来说,VCC是电路的供电电压,VDD是芯片的工作电压(通常Vcc>Vdd),VSS是接地点。
在场效应管(或COMS器件)中,VDD为漏极,VSS为源极,VDD和VSS指的是元件引脚,而不表示供电电压
3. I2C串行总线的组成及工作原理
I2C 有两根线 数据线 - SDA(serial data I/O) serial:串行
时钟线 - SCL (serial clock)
I2C总线上可以挂很多器件,每个器件都有一个地址,主机负责主动联系从机,而从机则被动回应数据。
I2C总线通过上拉电阻接正电源,当总线空闲时,两根线均为高电平,当任意一个器件输出的低电平都会使总线的信号变低,各器件的SDA和SCL都是线与的关系
4. I2C总线传输协议
数据位的有效性规定
SCL为高电平期间,数据线上的数据必须保持稳定,只有SCL信号位低电平期间,SDA状态才允许变化
I2C的起始和终止信号
SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号,SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号
起始和终止都由主机发出,在起始信号产生后,总线就处于被占用的状态,在终止信号产生后,总线处于空闲状态
I2C字节的传送与应答
每一个字节必须保证是8位长度,数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟一位应答位(即一帧共有9位)
数据帧的格式
I2C总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号
在起始信号后必须穿送一个从机的地址(7位),第八位是数据传送的方向位(R/T),用0来表示主机发送数据(T),1表示主机接收数据(R)
总线的一次数据传送过程中,有以下几种组合方式
总线的寻址
I2C总线协议有明确的规定:采用七位的寻址字节(寻址字节是起始信号后的第一个字节)
从机地址由固定部分和可编程部分组成,在一个系统中,从机地址中的可编程部分决定了可接入总线该类器件的最大数目。
5.软件模拟I2C的通信时序
自己去找数据手册,如果看不懂,就慢慢的看,迟早要学会看,我也不怎么看得懂,多看看就熟悉了。但是我给你列几个翻译还是可以的,要注意我上面画的图和真实的数据图有差别是为了更好的理解,如果你能看懂数据手册,以数据手册为准,其实没什么难的,不需要麻烦的计算,没让你去自己设计,先看懂再说,一口吃不成胖子
Clock Frequency, SCL 时钟频率
Clock Pulse Width Low 时钟脉冲宽度低
Clock Pulse Width High 时钟脉冲宽度高
Noise Suppression Time 噪声抑制时间
Clock Low to Data Out Valid 时钟低电平至数据输出有效
Time the bus must be free before a new transmission can start
在开始新传输之前,总线必须空闲的时间
Start Hold Time 开始保持时间
Start Setup Time 开始设置时间
Data In Hold Time 数据处于保留时间
Data In Setup Time 设置时间中的数据
Inputs Rise Time 输入上升时间
Inputs Fall Time 输入下降时间
Stop Setup Time 停止设置时间
Data Out Hold Time 数据输出保留时间
Write Cycle Time 写入周期时间
volt 伏特
至于时序图自己查,去看数据手册,别偷懒,否则你不明白我代码里的延时的意义在哪,如果你自信的删掉了,恭喜你,你会收获到一个没有错误的代码但是就是做不到你要做的功能。
真的很绕的一定要去理解为什么,最好带上逻辑分析仪,不贵,否则很难找出问题。至于里面的判断你感觉没有任何意义,其实还真没啥用,但是它展示了过程,你要想让他应答和非应答有意义留给你自己去改正。
#include <reg52.h>
#include <intrins.h>
#define uint unsigned int
#define uchar unsigned char
#define At24c02ADDR 0xA0//AT24C02硬件地址
#define I2CRead 1 //I2C读方向位
#define I2CWrite 0 //I2C写方向位
sbit SCL = P2^1; //时钟
sbit SDA = P2^0; //数据
sbit DU = P2^6;//数码管段选
sbit WE = P2^7;//数码管段选
uchar num;//数码管显示的值
bit Ackflag;//应答标志位
//共阴数码管段选表0-9
uchar code duanxuan[]= {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,};
//数码管位选码
uchar code weixuan[] = {0xfe, 0xfd, 0xfb};
void delay(uint z)
{
uint x,y;
for(x = z; x > 0; x--)
for(y = 114; y > 0 ; y--);
}
void display(uchar i)
{ static uchar wei;
DU=1;
P0=0x00;
DU =0;
WE = 1;//打开位选锁存器
P0 = weixuan[wei];
WE = 0;//锁存位选数据
switch(wei)
{
case 0: DU = 1; P0 = duanxuan[i/100%10]; DU = 0; break;
case 1: DU = 1; P0 = duanxuan[i/10%10]; DU = 0; break;
case 2: DU = 1; P0 = duanxuan[i%10]; DU = 0; break;
}
wei++;
if(wei == 3)
wei = 0;
}
void timer0Init()
{
EA = 1; //打开总中断
ET0 = 1;//打开定时器0中断
TR0 = 1; //启动定时器0
TMOD |= 0X01; //定时器工作模式1,16位定时模式
TH0 = 0xED;
TL0 = 0xFF; //定时5ms
}
/*********************************
IIC通信代码
**********************************/
void delay5us()//延时函数,大概是5.43us
{
_nop_();//上面要调用库函数才行
}
void I2C_Start()//启动信号函数
{
SCL = 1;//将时钟置于高电平
SDA = 1;//将数据也置于高电平
delay5us();//根据时序要使SDA保持一定时间
SDA = 0;//从高电平到低电平产生启动信号
delay5us();//同上
}
void I2C_Stop()//函数停止函数
{
SCL = 0;//将时钟置于低电平
SDA = 0;//将数据也置于低电平
SCL = 1;//让SCL置于高电平,并延时
delay5us();
SDA = 1;//SDA置于高电平,电平由低到高,产生停止信号,
delay5us();
}
bit ReadACK()//I2C总线读从机应答信号
{
SCL = 0;//允许从机控制SDA
SCL = 1;//拉高SCL读SDA值
delay5us();
if(SDA)//判断SDA状态
{
SCL = 0;
return(1);//返回1
}
else
{
SCL = 0;
return(0);//返回0
}
}
void SendACK(bit i)//主机发送应答
{
SCL = 0;///拉低时钟总线,允许主机控制SDA
if(i) //发非应答
SDA = 1;
else //发应答
SDA = 0;
SCL = 1;//拉高总线,让从机读SDA
delay5us();
SCL = 0; //拉低时钟总线,允许SDA释放
SDA = 1; //释放数据总线
}
void I2C_SendByte(uchar DAT)//I2C发送一个字节数据
{
uchar i;
for(i=0;i<8;i++)//分别写8次,每次写1位
{
SCL = 0;//拉低时钟总线,允许SDA变化
if(DAT & 0x80)//先写数据最高位
SDA = 1;
else
SDA = 0;
SCL = 1;//拉高时钟,让从机读SDA
DAT<<=1;//为发送下一位左移1位
}
SCL = 0;//拉低时钟总线,允许SDA释放
SDA = 1;//释放数据总线
}
void At24c02_Write(uchar ADDR,DAT)
{
I2C_Start();//I2C起始信号
I2C_SendByte(At24c02ADDR + I2CWrite);//发送器件地址加读写方向位
if(ReadACK())//读从机应答
Ackflag = 1; //没应答
else
Ackflag = 0;//应答
I2C_SendByte(ADDR);//发送储存单元地址字节
if(ReadACK())//读从机应答
Ackflag = 1; //没应答
else
Ackflag = 0;//应答
I2C_SendByte(DAT);//发送一字节数据
if(ReadACK())//读从机应答
Ackflag = 1; //没应答
else
Ackflag = 0;//应答
I2C_Stop(); //I2C停止信号
}
uchar I2C_Readbyte()
{
uchar i ,DAT;
for(i=0;i<8;i++)
{
DAT<<=1;//数据左移1位,准备接收一位
SCL = 0;//拉低时钟总线,允许从机控制SDA变化
SCL = 1;//拉高时钟总线,读取SDA上的数据
if(SDA)
DAT |= 0x01;//为1则写1
}
return(DAT);//返回读出的数据
}
uchar At24c02_Read(uchar ADDR)
{
uchar DAT;
I2C_Start();//I2C起始信号
I2C_SendByte(At24c02ADDR + I2CWrite);//发送器件地址加读写方向位
if(ReadACK())//读从机应答
Ackflag = 1; //没应答
else
Ackflag = 0;//应答
I2C_SendByte(ADDR);//发送一个字节
ReadACK();
I2C_Start();//I2C起始信号
I2C_SendByte(At24c02ADDR + I2CRead);//发送器件地址加读写方向位
if(ReadACK())//读从机应答
Ackflag = 1; //没应答
else
Ackflag = 0;//应答
DAT = I2C_Readbyte();//读一字节
SendACK(1);//主机发送非应答
I2C_Stop(); //I2C停止信号
return(DAT);//返回读出数据
}
void main()//main函数自身会循环
{
EA = 0;//屏蔽中断
timer0Init();
At24c02_Write(3,188);
delay(2);
num = At24c02_Read(3);
if(Ackflag)
P1 = 0;
else
P1 = 0xff;
EA = 1;//开中断
while(1);
}
//定时器0中断函数
void timer0() interrupt 1
{
TH0 = 0xED;
TL0 = 0xFF; //定时5ms
display(num); //数码管显示函数
}
算了算了我改好
#include <reg52.h>
#include <intrins.h>
#define uint unsigned int
#define uchar unsigned char
#define At24c02ADDR 0xA0//AT24C02硬件地址
#define I2CRead 1 //I2C读方向位
#define I2CWrite 0 //I2C写方向位
sbit SCL = P2^1; //时钟
sbit SDA = P2^0; //数据
sbit DU = P2^6;//数码管段选
sbit WE = P2^7;//数码管段选
uchar num;//数码管显示的值
bit Ackflag;//应答标志位
//共阴数码管段选表0-9
uchar code duanxuan[]= {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,};
//数码管位选码
uchar code weixuan[] = {0xfe, 0xfd, 0xfb};
void delay(uint z)
{
uint x,y;
for(x = z; x > 0; x--)
for(y = 114; y > 0 ; y--);
}
void display(uchar i)
{ static uchar wei;
DU=1;
P0=0x00;
DU =0;
WE = 1;//打开位选锁存器
P0 = weixuan[wei];
WE = 0;//锁存位选数据
switch(wei)
{
case 0: DU = 1; P0 = duanxuan[i/100%10]; DU = 0; break;
case 1: DU = 1; P0 = duanxuan[i/10%10]; DU = 0; break;
case 2: DU = 1; P0 = duanxuan[i%10]; DU = 0; break;
}
wei++;
if(wei == 3)
wei = 0;
}
void timer0Init()
{
EA = 1; //打开总中断
ET0 = 1;//打开定时器0中断
TR0 = 1; //启动定时器0
TMOD |= 0X01; //定时器工作模式1,16位定时模式
TH0 = 0xED;
TL0 = 0xFF; //定时5ms
}
/*********************************
IIC通信代码
**********************************/
void delay5us()//延时函数,大概是5.43us
{
_nop_();//上面要调用库函数才行
}
void I2C_Start()//启动信号函数
{
SCL = 1;//将时钟置于高电平
SDA = 1;//将数据也置于高电平
delay5us();//根据时序要使SDA保持一定时间
SDA = 0;//从高电平到低电平产生启动信号
delay5us();//同上
}
void I2C_Stop()//函数停止函数
{
SCL = 0;//将时钟置于低电平
SDA = 0;//将数据也置于低电平
SCL = 1;//让SCL置于高电平,并延时
delay5us();
SDA = 1;//SDA置于高电平,电平由低到高,产生停止信号,
delay5us();
}
bit ReadACK()//I2C总线读从机应答信号
{
SCL = 0;//允许从机控制SDA
SCL = 1;//拉高SCL读SDA值
delay5us();
if(SDA)//判断SDA状态
{
SCL = 0;
return(1);//返回1
}
else
{
SCL = 0;
return(0);//返回0
}
}
void SendACK(bit i)//主机发送应答
{
SCL = 0;///拉低时钟总线,允许主机控制SDA
if(i) //发非应答
SDA = 1;
else //发应答
SDA = 0;
SCL = 1;//拉高总线,让从机读SDA
delay5us();
SCL = 0; //拉低时钟总线,允许SDA释放
SDA = 1; //释放数据总线
}
void I2C_SendByte(uchar DAT)//I2C发送一个字节数据
{
uchar i;
for(i=0;i<8;i++)//分别写8次,每次写1位
{
SCL = 0;//拉低时钟总线,允许SDA变化
if(DAT & 0x80)//先写数据最高位
SDA = 1;
else
SDA = 0;
SCL = 1;//拉高时钟,让从机读SDA
DAT<<=1;//为发送下一位左移1位
}
SCL = 0;//拉低时钟总线,允许SDA释放
SDA = 1;//释放数据总线
}
void At24c02_Write(uchar ADDR,DAT)
{
I2C_Start();//I2C起始信号
I2C_SendByte(At24c02ADDR + I2CWrite);//发送器件地址加读写方向位
if(ReadACK())//读从机应答
Ackflag = 1; //没应答
else
Ackflag = 0;//应答
if(Ackflag == 0)
{
I2C_SendByte(ADDR);//发送储存单元地址字节
if(ReadACK())//读从机应答
Ackflag = 1; //没应答
else
Ackflag = 0;//应答
if(Ackflag == 0)
{
I2C_SendByte(DAT);//发送一字节数据
ReadACK();
I2C_Stop(); //I2C停止信号
}
}
}
uchar I2C_Readbyte()
{
uchar i ,DAT;
for(i=0;i<8;i++)
{
DAT<<=1;//数据左移1位,准备接收一位
SCL = 0;//拉低时钟总线,允许从机控制SDA变化
SCL = 1;//拉高时钟总线,读取SDA上的数据
if(SDA)
DAT |= 0x01;//为1则写1
}
return(DAT);//返回读出的数据
}
uchar At24c02_Read(uchar ADDR)
{
uchar DAT;
I2C_Start();//I2C起始信号
I2C_SendByte(At24c02ADDR + I2CWrite);//发送器件地址加读写方向位
if(ReadACK())//读从机应答
Ackflag = 1; //没应答
else
Ackflag = 0;//应答
if(Ackflag == 0)
{
I2C_SendByte(ADDR);//发送一个字节
ReadACK();
I2C_Start();//I2C起始信号
I2C_SendByte(At24c02ADDR + I2CRead);//发送器件地址加读写方向位
if(ReadACK())//读从机应答
Ackflag = 1; //没应答
else
Ackflag = 0;//应答
if(Ackflag == 0)
{
DAT = I2C_Readbyte();//读一字节
SendACK(1);//主机发送非应答
I2C_Stop(); //I2C停止信号
}
}
return(DAT);//返回读出数据
}
void main()//main函数自身会循环
{
EA = 0;//屏蔽中断
timer0Init();
At24c02_Write(5,18);
delay(2);
num = At24c02_Read(5);
if(Ackflag)
P1 = 0;
else
P1 = 0xff;
EA = 1;//开中断
while(1);
}
//定时器0中断函数
void timer0() interrupt 1
{
TH0 = 0xED;
TL0 = 0xFF; //定时5ms
display(num); //数码管显示函数
}