找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 3388|回复: 12
收起左侧

学习I2C总线感觉有几个不明白的地方,希望大家给予帮助

  [复制链接]
ID:485870 发表于 2019-3-8 21:02 | 显示全部楼层 |阅读模式

*****以下代码来源于清翔电子教程******


    问题1:I2C为什么要初始化呢?感觉不初始化得到的也是一样的结果啊
描述:I2C总线初始化
====================================*/
void I2C_init()
{
        SDA = 1;   //数据总线高
        _nop_();
        SCL = 1;   //时钟总线高
        _nop_();
}


       问题2: I2C的停止信号函数为什么要先把数据总线SDA拉低呢
描述:I2C停止信号
====================================*/
void I2C_Stop()
{
        SDA = 0;
        _nop_();
        SCL = 1;
        Delay5us();
        SDA = 1;
        Delay5us();
}

       问题3:I2C主机发送应答函数为什么最后要拉高数据总线呢?那个释放SDA又是什么意思
描述:I2C主机发送应答
====================================*/
void Master_ACK(bit i)       
{
        SCL = 0; // 拉低时钟总线允许SDA数据总线上的数据变化
        _nop_(); // 让总线稳定
        if (i)         //如果i = 1 那么拉低数据总线 表示主机应答
        {
                SDA = 0;
        }
        else         
        {
                SDA = 1;         //发送非应答
        }
        _nop_();//让总线稳定
        SCL = 1;//拉高时钟总线 让从机从SDA线上读走 主机的应答信号
        Delay5us();
        SCL = 0;//拉低时钟总线, 占用总线继续通信
        _nop_();
        SDA = 1;//释放SDA数据总线。
        _nop_();
}


        问题4: 检测到从机非应答或者非应答后为什么要把SCL拉低呢(代码中标有问题4处)
描述:I2C检测从机应答
====================================*/
bit Test_ACK()         // 检测从机应答
{
        SCL = 1;//时钟总线为高电平期间可以读取从机应答信号
        Delay5us();
        if (SDA)
        {
                SCL = 0;        //问题4
                I2C_Stop();
                return(0);
        }
        else
        {
                SCL = 0;
                return(1);
        }
}


还有为什么在这个AD—DA转换代码中,数码管显示转换过来的数字量不会随光照的变化而变化呢,用LED检测I2C读写结果都没有问题。

#include <reg52.h>
#include <intrins.h>
#define MAIN_Fosc                11059200UL        //宏定义主时钟HZ
#define PCF8591_ADDR        0x90                //PCF8591地址
#define DACOUT_EN                0x40                //DAC输出使能

/*====================================
自定义类型名
====================================*/
typedef unsigned char INT8U;
typedef unsigned char uchar;

typedef unsigned int INT16U;
typedef unsigned int uint;

/*====================================
硬件接口位声明
====================================*/
sbit SDA = P2^0;   //I2C串行数据
sbit SCL = P2^1;   //I2C串行时钟
sbit DU  = P2^6;   //数码管段选
sbit WE  = P2^7;   //数码管位选
sbit LED1= P1^0;   //读取AD的值是否成功(亮成功,灭失败)
sbit LED2= P1^1;   //DAC成功输出(亮成功,灭失败)
sbit BEEP= P2^3;   //蜂鸣器引脚定义

uchar AD_Value;        //存储AD转换回的数字量
/*====================================
共阴极数码管段选码
====================================*/
uchar code table[]={
//0                1         2     3     4     5     6     7     8
0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F,
//9     A     B           C         D           E         F                -         .          关显示
0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x40, 0x80, 0x00
                   };

/*====================================
数码管位选码
====================================*/
                                  //第1位        2位          3位         4位   5位        6位          7位        8位
uchar code T_COM[] = {0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f};//数码管位码

/*====================================
函数:void Delay_Ms(INT16U ms)
参数:ms,毫秒延时形参
描述:12T 51单片机自适应主时钟毫秒级延时函数
====================================*/
void Delay_Ms(INT16U ms)
{
     INT16U i;
         do{
              i = MAIN_Fosc / 96000;
                  while(--i);   //96T per loop
     }while(--ms);
}

/*====================================
函数:void Delay5us()
描述:12T 51单片机5微秒延时函数自适应时钟(11.0592M,12M,22.1184M)
====================================*/
void Delay5us()
{
        #if MAIN_Fosc == 11059200
                _nop_();
        #elif MAIN_Fosc == 12000000
                _nop_()
        #elif MAIN_Fosc == 22118400
                _nop_(); _nop_(); _nop_();
        #endif
}

/*====================================
函数:void Display(INT8U Value)
参数:Value,显示值 取值0-255
描述:共阴极数码管显示函数可显示一个字节的数
====================================*/
void Display(INT8U Value)
{
//------------------------------
        DU = 1;
        P0 = table[Value/100];        //管显示百位
        DU = 0;

        P0 = 0xff;                                  //清除断码

        WE = 1;
        P0 = T_COM[0];                          //第一位数码管
        WE = 0;
        Delay_Ms(5);
//-------------------------------
        DU = 1;
        P0 = table[Value%100/10]; //显示十位
        DU = 0;

        P0 = 0xff;                                  //清除断码

        WE = 1;
        P0 = T_COM[1];                          //第二位数码管
        WE = 0;
        Delay_Ms(5);
//-------------------------------
        DU = 1;
        P0 = table[Value%10];                //显示个位
        DU = 0;
                                                               
        P0 = 0xff;                                        //清除断码

        WE = 1;
        P0 = T_COM[2];                                //第三位数码管
        WE = 0;
        Delay_Ms(5);
}

/*====================================
函数:I2C_init()
描述:I2C总线初始化
====================================*/
void I2C_init()
{
        SDA = 1;   //数据总线高
        _nop_();
        SCL = 1;   //时钟总线高
        _nop_();
}

/*====================================
函数:I2C_Start()
描述:I2C起始信号
====================================*/
void I2C_Start()  
{
        SCL = 1;
        _nop_();
        SDA = 1;
        Delay5us();
        SDA = 0;
        Delay5us();
}

/*====================================
函数:I2C_Stop()
描述:I2C停止信号
====================================*/
void I2C_Stop()
{
        SDA = 0;
        _nop_();
        SCL = 1;
        Delay5us();
        SDA = 1;
        Delay5us();
}

/*====================================
函数:Master_ACK(bit i)
参数:i 为0时发送非应答 为1时发送应答
描述:I2C主机发送应答
====================================*/
void Master_ACK(bit i)       
{
        SCL = 0; // 拉低时钟总线允许SDA数据总线上的数据变化
        _nop_(); // 让总线稳定
        if (i)         //如果i = 1 那么拉低数据总线 表示主机应答
        {
                SDA = 0;
        }
        else         
        {
                SDA = 1;         //发送非应答
        }
        _nop_();//让总线稳定
        SCL = 1;//拉高时钟总线 让从机从SDA线上读走 主机的应答信号
        Delay5us();
        SCL = 0;//拉低时钟总线, 占用总线继续通信
        _nop_();
        SDA = 1;//释放SDA数据总线。
        _nop_();
}

/*====================================
函数:Test_ACK()
返回:0为非应答 1为应答
描述:I2C检测从机应答
====================================*/
bit Test_ACK()         // 检测从机应答
{
        SCL = 1;//时钟总线为高电平期间可以读取从机应答信号
        Delay5us();
        if (SDA)
        {
                SCL = 0;
                I2C_Stop();
                return(0);
        }
        else
        {
                SCL = 0;
                return(1);
        }
}

/*====================================
函数:I2C_send_byte(uchar byte)
参数:byte 要发送的字节
描述:I2C发送一个字节
====================================*/
void I2C_send_byte(uchar byte)
{
        uchar i;
        for(i = 0 ; i < 8 ; i++)
        {
                SCL = 0;
                _nop_();
                if (byte & 0x80)       
                {
                        SDA = 1;
                        _nop_();
                }
                else
                {
                        SDA = 0;
                        _nop_();
                }
                SCL = 1;
                _nop_();
                byte <<= 1;
        }
        SCL = 0;
        _nop_();
        SDA = 1;
        _nop_();       
}

/*====================================
函数:I2C_read_byte()
返回:读取的字节
描述:I2C读一个字节
====================================*/
uchar I2C_read_byte()
{
        uchar i, dat;
        SCL = 0 ;
        _nop_();
        SDA = 1;
        _nop_();
        for(i = 0 ; i < 8 ; i++)
        {
                SCL = 1;
                _nop_();
                dat <<= 1;          
                if (SDA)
                {
                        dat |= 0x01;  
                }
                _nop_();
                SCL = 0;
                _nop_();
        }
        return(dat);
}

/*DAC输出*/
bit DAC_OUT(uchar DAT)
{
        I2C_Start();
        I2C_send_byte(PCF8591_ADDR+0);
        if (!Test_ACK())
        {
                return(0);
        }
        I2C_send_byte(DACOUT_EN);        //DA输出使能
        if (!Test_ACK())
        {
                return(0);
        }
        I2C_send_byte(DAT);
        if (!Test_ACK())
        {
                return(0);
        }
        I2C_Stop();
        return(1);       
}

/*读AD数据*/
bit ADC_Read(uchar CON)
{
        I2C_Start();
        I2C_send_byte(PCF8591_ADDR+0);
        if (!Test_ACK())
        {
                return(0);
        }
        I2C_send_byte(CON);
        Master_ACK(0);
        I2C_Start();
        I2C_send_byte(PCF8591_ADDR+1);
        if (!Test_ACK())
        {
                return(0);
        }
        AD_Value = I2C_read_byte();
        Master_ACK(0);
        I2C_Stop();
        return(1);       
}

void main()
{
        I2C_init();
        while(1)
        {               
                //单端输入,读出通道2的值
                if (ADC_Read(0x02))        LED1 = 0;        else        LED1 = 1;       
                if (DAC_OUT(AD_Value))        LED2 = 0;        else        LED2 = 1;
                Display(AD_Value);
                if (AD_Value > 150)        BEEP = 0;        else        BEEP = 1;
                Delay_Ms(5);
        }       
}







回复

使用道具 举报

ID:469417 发表于 2019-3-9 02:40 | 显示全部楼层
所谓初始化并不是一定要达到什么目的,有时我们是在做一些声明,有些是对一些数据进行使用前的预处理或者是清零,方便后面的使用不会出错,比如我们声明一个变量的时候,我们明明知道他开始的值是0,但有时我们就是要声明让它等于0,道理都是一个样。不过有的初始化就必须要做。

评分

参与人数 1黑币 +10 收起 理由
zhengchong60 + 10 回帖助人的奖励!

查看全部评分

回复

使用道具 举报

ID:401564 发表于 2019-3-9 09:12 | 显示全部楼层
这都是IIC的基础知识,说了也是和网上复制过来的一样。
你应该是没有去看过IIC协议的时序图,IIC是一个有严格的时序要求的通讯协议,每一步都要严格按照IIC的协议来走的。
推荐你去PIC的官方网站找一下24VL04的中文数据手册,它就是一个IIC器件,里面有对于IIC的时序图,比较完整的。
需要说明的是“释放SDA线”,8051的端口是默认的双向端口,既可以读也可以写。如果你把这个SDA端口变成低电平,那么你读取这个SDA端口的状态就只能读取到一个低电平。但如果你把SDA拉高了,它可以读取到高电平,如果外部是低电平的话,那么这个低电平会把SDA线拉低,读取到的内容就是低电平,这样一来,CPU就可以正确的读取到SDA的电平状态了。
这个操作只在8051这类默认端口为双向的单片机上用到,如果是别的单片机(像PIC这种)就没有这个操作了,PIC的端口是要去设定这个端口是读取的还是输出的,设定为读取了就不能输出了,设定为输出了就读取不到外部的信号了。

评分

参与人数 1黑币 +10 收起 理由
zhengchong60 + 10 回帖助人的奖励!

查看全部评分

回复

使用道具 举报

ID:485870 发表于 2019-3-11 19:15 | 显示全部楼层
刀刀见红 发表于 2019-3-9 02:40
所谓初始化并不是一定要达到什么目的,有时我们是在做一些声明,有些是对一些数据进行使用前的预处理或者是 ...

谢谢您!
回复

使用道具 举报

ID:485870 发表于 2019-3-11 19:16 | 显示全部楼层
Y_G_G 发表于 2019-3-9 09:12
这都是IIC的基础知识,说了也是和网上复制过来的一样。
你应该是没有去看过IIC协议的时序图,IIC是一个有 ...

十分感谢
回复

使用道具 举报

ID:142059 发表于 2019-3-11 21:13 来自手机 | 显示全部楼层
不管哪种通讯协议,总线在空闲的时候,都是高电平,为什么?因为io口内部的设计原因,只有高电平能被拉低成低电平,而低电平是不能被拉高成高电平的,因为拉低是内部mos管直接导通,等效io接在GND,所以怎么拉高都不会变成高电平。所以我们要用高电平表示空闲,这样我们要传输数据的时候,把总线拉低一下就可以表示要准备传输数据了。所以要初始化为高电平。而有的人还有疑问,51单片机io口开机状态就是高电平啊,初始化再设置高电平不是多此一举么?是的,在开机为高电平的单片机里是多此一举,但是有的单片机开机后,io口是浮空状态的,也就是电平不确定的状态,这时候的初始化就很重要了。说了这么多,不知道你有没有理解。

评分

参与人数 2黑币 +55 收起 理由
_nop_() + 5 很给力!
admin + 50 回帖助人的奖励!

查看全部评分

回复

使用道具 举报

ID:162622 发表于 2019-3-11 23:47 | 显示全部楼层
IIC的应答是谁接收数据(或命令)谁应答,因而就有主机应答与从机应答之分,最后一次接收完可以不应答给停止信号
回复

使用道具 举报

ID:473159 发表于 2019-3-12 06:29 来自手机 | 显示全部楼层
初始化又不妨碍你什么你干嘛要那样斤斤计较

评分

参与人数 1黑币 +15 收起 理由
正规007 + 15 很给力!

查看全部评分

回复

使用道具 举报

ID:449525 发表于 2019-3-12 08:24 | 显示全部楼层
大家都在2跟线上。如何通信?如何判断是你在发送还是在接受?所以要规定下。
回复

使用道具 举报

ID:370231 发表于 2019-3-12 10:19
51黑论坛_iic协议.pdf (7.15 MB, 下载次数: 0)

ID:471782 发表于 2019-3-12 12:00 来自手机 | 显示全部楼层
建议楼主在网上找个iic的协议资料好好看下
回复

使用道具 举报

ID:485870 发表于 2019-3-14 20:55 | 显示全部楼层
Angle145 发表于 2019-3-11 21:13
不管哪种通讯协议,总线在空闲的时候,都是高电平,为什么?因为io口内部的设计原因,只有高电平能被拉低成 ...

非常感谢您的帮助,这点理解到了
回复

使用道具 举报

ID:485870 发表于 2019-3-14 21:03 | 显示全部楼层
Y_G_G 发表于 2019-3-9 09:12
这都是IIC的基础知识,说了也是和网上复制过来的一样。
你应该是没有去看过IIC协议的时序图,IIC是一个有 ...

我看了时序图手册的,感觉代码并没有完全按照时序图的要求来
回复

使用道具 举报

ID:485870 发表于 2019-3-14 21:15 | 显示全部楼层
Angle145 发表于 2019-3-11 21:13
不管哪种通讯协议,总线在空闲的时候,都是高电平,为什么?因为io口内部的设计原因,只有高电平能被拉低成 ...

能加一下qq吗?以后遇到的问题还希望有人能帮帮忙,谢谢
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表