文章目录
1、IIC总线结构
这个可以参考我之前写的一遍文章
https://blog.csdn.net/lzxiaotu/article/details/119354515
2、IIC总线传输协议
- 数据位的有效性规定:SCL为高电平期间,数据线上的数据必须保持稳定,只有SCLSCL信号为低电平期间,SDA状态才允许变化。
- IIC的起始和终止信号
-SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;SCL线为高电平期间,SDA线由低电平向高电平变化表示终止信号。
起始和停止条件一般由主机产生,总线在起始条件后被认为处于忙的状态,在停止条件的某段时间后,总线被认为再次处于空闲状态。
模拟IIC参考代码
//I2C总线起始信号
void I2cStart(void)
{
SCL = 1;
SDA = 1;
I2cDelay_5us();//状态保持5us
SDA = 0;
I2cDelay_5us();//状态保持5us
}
//I2C总线停止信号
void I2cStop(void)
{
SCL = 0;
SDA = 0;
SCL = 1;
I2cDelay_5us();//状态保持5us
SDA = 1;
I2cDelay_5us();//状态保持5us
}
- 传输数据
发送到SDA线上的每个字节必须为8位,每次传输可以发送的字节数量不受限制,每个字节后必须更上一个响应位。
主机在发送数据时,都需要读取从机应答位,当从机空闲可以接受该字节数据时,从机发出应答(低电平),当从机正忙于其他工作处理来不及接受时,从机发出非应答(高电平),主机可以通过从机发出的应答位来判断从机是否成功接收数据。
当主机接收数据接收到最后一个数据字节后,必须向从机发出一个结束传送的信号,这个信号是由从机的“非应答”来实现,然后释放SDA线,以允许主机产生终止信号。 - 数据帧格式
在IIC总线上传送数据信号是广义的,既包括地址信号,有包括真实数据。
在起始信号后必须传送一个从机地址(7位),第八位数数据时传送方向位(R/T),用‘0’表示主机发送数据,‘1’表示主机接收数据。每次数据传送总是由主机产生终止信号。
若主机希望继续占用总线进行新的数据传送,则可以不产生终止,马上再次发送起始信号对另一从机进行寻址。
模拟IIC参考代码
/********************************
*函数名称:ReadACK(void)
*函数输入:无
*函数返回:1非应答,0应答
*函数说明:I2C总线读从机应答信号
********************************/
bit ReadACK(void)
{
SCL = 0;//拉低时钟总线,允许从机控制SDA
SCL = 1;//拉高,读SDA
I2cDelay_5us();
if(SDA)//非应答
{
SCL = 0;
return(1);//返回1
}
else
{
SCL = 0;
return(0);//返回0
}
}
/***************************************
*函数名称:SendACK(bit i)
*函数输入:1主机发送非应答,0发送应答
*函数返回:无
*函数说明:主机发送应答信号
***************************************/
void SendACK(bit i)
{
SCL = 0; //拉低时钟总线,允许主机控制SDA
if(i) //发送非应答
SDA = 1;
else
SDA = 0;
SCL = 1; //拉高总线,让从机读SDA
I2cDelay_5us();
SCL = 0; //拉低时钟总线,允许SDA释放
SDA = 1; //释放数据总线
}
/***************************************
*函数名称:I2cSendByte(uchar DAT)
*函数输入:DAT需要发送的数据
*函数返回:无
*函数说明:I2C发送一个字节数据
***************************************/
void I2cSendByte(uchar DAT)
{
uchar i;
for(i=0; i<8; i++) //分别写8次,每次写1位
{
SCL = 0; //拉低时钟总线,允许SDA变化
if(DAT & 0x80) //先写数据最高位
SDA = 1; //写1
else
SDA = 0; //写0
SCL = 1; //拉高时钟,让从机读SDA
DAT <<= 1; //为发送下一位左移1位
}
SCL = 0; //拉低时钟总线,允许SDA释放
SDA = 1; //释放数据总线
}
/*====================================
函数 :I2cReadByte()
参数 :无
返回值 :返回读出的一字节数据
描述 :I2C总线读一字节数据
====================================*/
uchar I2cReadByte(void)
{
uchar i, DAT;
for(i=0; i<8; i++)//分别读8次,每次读一位
{
DAT <<= 1; //数据左移1位,准备接收一位
SCL = 0; //拉低时钟总线,允许从机控制SDA变化
SCL = 1; //拉高时钟总线,读取SDA上的数据
if(SDA)
DAT |= 0X01;//为1则写1,否则不行执行写1,通过左移补0
}
return(DAT); //返回读出的数据
}
基于EEPROM向4单元存数据(主机向从机写数据)
/*====================================
函数 :At24c02Read(uchar ADDR)
参数 :ADDR 单元地址 0-255
返回值 :返回指定单元的数据
描述 :读AT24C02指定单元内数据
====================================*/
uchar At24c02Read(uchar ADDR)
{
uchar DAT;
I2cStart();//I2C起始信号
I2cSendByte(At24c02ADDR + I2cWrite);//发送器件地址加读写方向位
if(ReadACK()) //读从机应答
AckFlag = 1; //NOACK
else
AckFlag = 0; //ACK
I2cSendByte(ADDR);//I2C发送一个字节
ReadACK();//读从机应答
I2cStart();//再次产生I2C起始信号
I2cSendByte(At24c02ADDR + I2cRead);//发送器件地址加读写方向位 读
if(ReadACK())//读从机应答
AckFlag = 1; //NOACK
else
AckFlag = 0; //ACK
DAT = I2cReadByte();//读一字节
SendACK(1);//主机发送非应答
I2cStop(); //I2C停止信号
return(DAT);//返回读出数据
}
主函数调用情况
//写数据
At24c02Write(4,'1');//给第1单元写入数据'1'
Delay_ms(1);//延时一段时间等待AT24C02处理
运行示波器解码图
A0表示AT24C02的硬件地址
EOT表示ASCLL表结束字符,十进制“04”
1表示存储‘1’这个字符
由此可以主机向从机发送数据,格式如下:
————————
读出EEPROM代码部分
/*====================================
函数 :At24c02Read(uchar ADDR)
参数 :ADDR 单元地址 0-255
返回值 :返回指定单元的数据
描述 :读AT24C02指定单元内数据
====================================*/
uchar At24c02Read(uchar ADDR)
{
uchar DAT;
I2cStart();//I2C起始信号
I2cSendByte(At24c02ADDR + I2cWrite);//发送器件地址加读写方向位
if(ReadACK()) //读从机应答
AckFlag = 1; //NOACK
else
AckFlag = 0; //ACK
I2cSendByte(ADDR);//I2C发送一个字节
ReadACK();//读从机应答
I2cStart();//再次产生I2C起始信号
I2cSendByte(At24c02ADDR + I2cRead);//发送器件地址加读写方向位 读
if(ReadACK())//读从机应答
AckFlag = 1; //NOACK
else
AckFlag = 0; //ACK
DAT = I2cReadByte();//读一字节
SendACK(1);//主机发送非应答
I2cStop(); //I2C停止信号
return(DAT);//返回读出数据
}
主函数调用情况
//读数据
ch = At24c02Read(4);//读出第1单元内数据送给显示变量
数据格式如下
3、完成工程代码
STC89C52RC读取EEPROM向例程串口打印结果
main.c
#include <reg52.h>
#include <intrins.h>
#include "STC89C52RC_I2C.h"
/*数据类型宏定义*/
#define uchar unsigned char
#define uint unsigned int
/*常用变量宏定义*/
#define At24c02ADDR 0xa0 //AT24C02硬件地址
/*全局变量定义*/
bit AckFlag; //应答标志位
void Delay1ms(void) //@11.0592MHz
{
unsigned char i, j;
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
void Delay_ms(uint timer)
{
uint i;
for(i=0; i<timer; i++)
Delay1ms();
}
//使用定时T1工作方式2,波特率9600,晶振11.0592MHZ
//禁止接收,不启动串口中断,波特率不加倍
void InitUART(void)
{
TMOD = 0x20;
SCON = 0x40;
TH1 = 0xFD;
TL1 = TH1;
PCON = 0x00;
TR1 = 1;
}
//向串口发送一个字符
void putchar(char ch)
{
SBUF = ch;
while(!TI);TI = 0;
}
//向串口发送一段字符串
void prints(char *s)
{
while(*s != '\0')//发送字符串,直到遇到0才结束
{
SBUF = *s++;
while(!TI);
TI = 0;
}
}
/*====================================
函数 :At24c02Write(uchar ADDR, DAT)
参数 :ADDR 单元地址0-255,DAT 需要输入的数据0-255
返回值 :无
描述 :At24c02指定单元写入一个字节数据
====================================*/
void At24c02Write(uchar ADDR, DAT)
{
I2cStart(); //I2C起始信号
I2cSendByte(At24c02ADDR + I2cWrite); //发送器件地址加读写方向位
if(ReadACK()) //读从机应答
AckFlag = 1; //NOACK
else
AckFlag = 0; //ACK
I2cSendByte(ADDR); //发送储存单元地址字节
if(ReadACK()) //读从机应答
AckFlag = 1; //NOACK
else
AckFlag = 0; //ACK
I2cSendByte(DAT); //发送一字节数据
if(ReadACK()) //读从机应答
AckFlag = 1; //NOACK
else
AckFlag = 0; //ACK
I2cStop(); //I2C停止信号
}
/*====================================
函数 :At24c02Read(uchar ADDR)
参数 :ADDR 单元地址 0-255
返回值 :返回指定单元的数据
描述 :读AT24C02指定单元内数据
====================================*/
uchar At24c02Read(uchar ADDR)
{
uchar DAT;
I2cStart();//I2C起始信号
I2cSendByte(At24c02ADDR + I2cWrite);//发送器件地址加读写方向位
if(ReadACK()) //读从机应答
AckFlag = 1; //NOACK
else
AckFlag = 0; //ACK
I2cSendByte(ADDR);//I2C发送一个字节
ReadACK();//读从机应答
I2cStart();//再次产生I2C起始信号
I2cSendByte(At24c02ADDR + I2cRead);//发送器件地址加读写方向位 读
if(ReadACK())//读从机应答
AckFlag = 1; //NOACK
else
AckFlag = 0; //ACK
DAT = I2cReadByte();//读一字节
SendACK(1);//主机发送非应答
I2cStop(); //I2C停止信号
return(DAT);//返回读出数据
}
void main(void)
{
char ch;
InitUART();//串口初始化
prints("OK!\n");//串口初始化完成,向串口发送“OK”字符串
//写数据
// At24c02Write(4,'1');//给第1单元写入数据'1'
// Delay_ms(1);//延时一段时间等待AT24C02处理
//读数据
ch = At24c02Read(4);//读出第1单元内数据送给显示变量
if(AckFlag)//当从机非应答
P1 = 0;//亮P1所有灯
else
P1 = 0XFF;//灭P1所有灯
prints("AT24C02 data:");
putchar(ch);
while(1);
}
STC89C52RC_I2C.c
#include "STC89C52RC_I2C.h"
/*I2C硬件接口定义*/
sbit SCL = P2^1; //I2C时钟总线
sbit SDA = P2^0; //I2C数据总线
/**********************************
89C52RC单片机模拟IIC通信代码
***********************************/
//5us延时函数
void I2cDelay_5us(void)
{
_nop_();
}
//I2C总线起始信号
void I2cStart(void)
{
SCL = 1;
SDA = 1;
I2cDelay_5us();//状态保持5us
SDA = 0;
I2cDelay_5us();//状态保持5us
}
//I2C总线停止信号
void I2cStop(void)
{
SCL = 0;
SDA = 0;
SCL = 1;
I2cDelay_5us();//状态保持5us
SDA = 1;
I2cDelay_5us();//状态保持5us
}
/********************************
*函数名称:ReadACK(void)
*函数输入:无
*函数返回:1非应答,0应答
*函数说明:I2C总线读从机应答信号
********************************/
bit ReadACK(void)
{
SCL = 0;//拉低时钟总线,允许从机控制SDA
SCL = 1;//拉高,读SDA
I2cDelay_5us();
if(SDA)//非应答
{
SCL = 0;
return(1);//返回1
}
else
{
SCL = 0;
return(0);//返回0
}
}
/***************************************
*函数名称:SendACK(bit i)
*函数输入:1主机发送非应答,0发送应答
*函数返回:无
*函数说明:主机发送应答信号
***************************************/
void SendACK(bit i)
{
SCL = 0; //拉低时钟总线,允许主机控制SDA
if(i) //发送非应答
SDA = 1;
else
SDA = 0;
SCL = 1; //拉高总线,让从机读SDA
I2cDelay_5us();
SCL = 0; //拉低时钟总线,允许SDA释放
SDA = 1; //释放数据总线
}
/***************************************
*函数名称:I2cSendByte(uchar DAT)
*函数输入:DAT需要发送的数据
*函数返回:无
*函数说明:I2C发送一个字节数据
***************************************/
void I2cSendByte(uchar DAT)
{
uchar i;
for(i=0; i<8; i++) //分别写8次,每次写1位
{
SCL = 0; //拉低时钟总线,允许SDA变化
if(DAT & 0x80) //先写数据最高位
SDA = 1; //写1
else
SDA = 0; //写0
SCL = 1; //拉高时钟,让从机读SDA
DAT <<= 1; //为发送下一位左移1位
}
SCL = 0; //拉低时钟总线,允许SDA释放
SDA = 1; //释放数据总线
}
/*====================================
函数 :I2cReadByte()
参数 :无
返回值 :返回读出的一字节数据
描述 :I2C总线读一字节数据
====================================*/
uchar I2cReadByte(void)
{
uchar i, DAT;
for(i=0; i<8; i++)//分别读8次,每次读一位
{
DAT <<= 1; //数据左移1位,准备接收一位
SCL = 0; //拉低时钟总线,允许从机控制SDA变化
SCL = 1; //拉高时钟总线,读取SDA上的数据
if(SDA)
DAT |= 0X01;//为1则写1,否则不行执行写1,通过左移补0
}
return(DAT); //返回读出的数据
}
/*****************************************************************/
STC89C52RC_I2C.h
#ifndef __STC89C52RC_H__
#define __STC89C52RC_H__
#include <reg52.h>
#include <intrins.h>
/*数据类型宏定义*/
#define uchar unsigned char
#define uint unsigned int
/*I2C常用变量宏定义*/
#define I2cRead 1 //I2C读方向位
#define I2cWrite 0 //I2C写方向
//5us延时函数
extern void I2cI2cDelay_5us(void);
//I2C总线起始信号
extern void I2cStart(void);
//I2C总线停止信号
extern void I2cStop(void);
//I2C总线读从机应答信号
extern bit ReadACK(void);
//主机发送应答信号
extern void SendACK(bit i);
//I2C发送一个字节数据
extern void I2cSendByte(uchar DAT);
//I2C总线读一字节数据
extern uchar I2cReadByte(void);
#endif
本文参考资料清翔零基础教你学51单片机视频教程,
我通过实操把抽象的IIC理论全部给消化掉!