【硬件通讯协议】IIC总线协议以及模拟(软件)IIC

  • Post author:
  • Post category:其他


参考资料


NXP 官方网站提供的IIC总线规范




I2C-bus specification and user manua



https://www.nxp.com/docs/en/user-guide/UM10204.pdf



别再去下那些狗东西不知道从哪里弄来还要下载积分的烂玩意了!

本篇所有图片均摘自 IIC总线规范 。


IICBus

IIC一共只有两条线,双向串行数据线SDA、串行时钟线SCL。

IIC是半双工的!



  • SDA:Serial Data(数据信号线)



  • SCL: Serial Colck Line(时钟信号线)



IIC应用电路


IIC 总线要求每个设备的SCL/SDA线都是

漏极开路(OD)模式

,因此总线必须带有上拉电阻才能正常工作。

上拉电阻的大小也会影响电平转换的时间,4.7K 就行。(是我在5V通讯下验证的值,应用需注意)



什么是漏极开路模式?


想想 51 单片机的 P0 口,就是很典型的漏极开路输出,因此要作为准双向口时,需要上拉电阻。

对于晶体管和场效应管来说:就是将 OUTPUT 端口接在漏极:

图中电阻即为上拉电阻,因 MOSFET 导通时,只有将 OUTPUT 拉低的能力而没有将 OUTPUT 抬升的能力,所以需要上拉电阻拉高 OUTPUT 的电平。


这种办法应用在驱动电路上是有很大好处的,驱动电流也不用很大就能控制大负载。



IIC 可在元件不同电压下进行通讯


各种电源电压共享同一IIC总线的例子



数据有效性



SDA 线上的数据要在时钟信号为高电平时保持稳定,在时钟信号为低电平时进行切换!



起始和停止




当 SCL(时钟信号线) 为高电平时,SDA 下降沿表示为起始信号,SDA 上升沿表示停止信号。

在这个起始和停止检测时,对硬件IIC很简单,软件IIC作为从机时,需要注意一下。



数据位


手册规定。SDA 上的数据必须为8位每字节。但是每次传输的字节数不受限制,每字节后要跟一个应答位(ACK)。

数据先由最高位(MSB)传输。

从机或主机暂时无法传输或接收完整数据时,可将SCL保持为低,进入等待状态。待时钟恢复时继续传输。


关于应答位:

每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据。

发送完一个字节后,主机释放 SDA 的控制权,以便从机发送应答位。



  • ACK




    :SCL高电平时,SDA为低电平,表示数据被接收了。



  • NACK




    :(无应答)SCL高电平时,SDA为高电平



从机地址


开始后,发送7位地址,第8位为读/写标志位(0:W、1:R)。

开始后的第一个字节:


这样有几种情况,从机向主机发送信息,主机向从机发送信息,发送接收交替进行。



主机向从机写入的情况




主机读取从机情况




改变数据流方向




十位地址寻址方式


第一字节的前七位为

1111 0XX

,最后两位是十位地址的最高有效位(MSB)。第8位为 R/W 位。


有这种使用方法:

10 位寻址未广泛使用,不做过多介绍。


软件IIC时还应注意电平跳变不是瞬间完成的,需要一定的延时!!!



IIC总线接口的软件实现-89C51

为了方便理解,将软件实现分为几个部分:

sbit I2C_SDA = P1^0;
sbit I2C_SCL = P1^1;



延时函数



nop 代表运行一个时钟周期,此函数常常用在某些协议中的一个短延时!

void I2C_Delay(void)
{
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
}



IICBus发出起始信号


起始信号是在SCL高电平时SDA实现下降沿。

void I2C_Start(void)
{
    I2C_SDA = 1;
    I2C_SCL = 1;
    I2C_Delay();
    I2C_SDA = 0;
    I2C_Delay();
    I2C_SCL = 0;
}



IICBus发出结束信号


结束信号是在SCL高电平时SDA实现上升沿。

void I2C_Stop(void)
{
    I2C_SDA = 0;
    I2C_SCL = 1;
    I2C_Delay();
    I2C_SDA = 1;
    I2C_Delay();
    I2C_SCL = 0;
}



IICBus发出应答信号


SCL高电平时,SDA为低电平,表示数据被接收了。

void I2C_ASK(void)
{
    I2C_SDA = 0;
    I2C_SCL = 1;
    I2C_Delay();
    I2C_SDA = 1;
    I2C_SCL = 0;
}



IICBus发出非应答信号


void I2C_NOASK(void)
{
    I2C_SDA = 1;
    I2C_SCL = 1;
    I2C_Delay();
    I2C_SDA = 0;
    I2C_SCL = 0;
}



IICBus上发送一字节的数据


void I2C_SendByte(unsigned char c)
{
    unsigned char i;
    for(i=8; i>0; i--)
    {
        if(c & 0x80)
            I2C_SDA = 1;
        else
            I2C_SDA = 0;
        I2C_SCL = 1;
        I2C_Delay();
        I2C_SCL = 0;
        C = C<<1;
    }
    I2C_SDA = 1;    //释放 I2C_SDA 数据线,准备接收应答信号
    I2C_SCL = 1;
    I2C_Delay();
    while(!(0 == I2C_SDA && 1 == I2C_SCL));
    // 这里可加入超时执行其他函数的功能
}


把上述代码后四行改成检测主机应答位的情况

void I2C_ChickACK(void)
{
    I2C_SDA = 1;
    I2C_SCL = 1;
    F0 = 0;
    if(I2C_SDA == 1)
        F0 = 1;    //无应答
    I2C_SCL = 0;
}



IICBus上接收一字节的数据


unsigned char I2C_ReceiveByte(void)
{
    unsigned char i;
    unsigned char r;
    I2C_SDA = 1;
    for(i=8; i>0; i--)
    {
        r = r<<1;
        I2C_SCL = 1;
        I2C_Delay();
        if(I2C_SDA)
            r = r|0x01;
        I2C_SCL = 0;
    }
    return r;
}


实际例子

读 ADS1100 的 A/D 转换值。

#define READ_ADDR 0X90      // 1001 0000 // 读寄存器地址
#define WRITE_ADDR 0X91     // 1001 0001 // 写寄存器地址
#define CFG_WORD 0X8F       // 1000 1111 // 配置寄存器预设
unsigned char AD_H;         // 存储AD转换高八位
unsigned char AD_L;         // 存储AD转换低八位
bit SYS_ERR;                // 从机错误标志位

/* 配置寄存器进行设置
 * @ setting_data: 要配置的参数
 */
void ADS1100_INIT(unsigned char setting_data)
{
    I2C_Start();
    I2C_SendByte(WRITE_ADDR);
    I2C_CheckACK();
    if(FO == 1)
    {
        SYS_ERR = 1;
        return;
    }
    I2C_SendByte(setting_data);
    I2C_CheckACK();
    if(FO == 1)
    {
        SYS_ERR = 1;
        return;
    }
    I2C_Stop();
}

/* 用于读取A/D转换的结果
 */
void READ_ADS1100(void)
{
    I2C_Start();
    I2C_SendByte(READ_ADDR);
    I2C_CheckACK();
    {
        SYS_ERR = 1;
        return;
    }
    AD_H = I2C_ReceiveByte();
    I2C_ASK();
    AD_L = I2C_ReceiveByte();
    I2C_NOACK();
    I2C_Stop();
}


void main()
{
    ADS1100_INIT(CFG_WORD);
    READ_ADS1100();
}


现在基本上芯片都有硬件I2C接口了,此只是作为一个了解,另此部分代码为自己推理,位编译进行验证,仅供参考用。

🎉 今日学习达成!



版权声明:本文为qq_41650023原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。