S2.7 iic(下)

S2.7 iic

本章由我编写,iic从实战开始,希望你已经把上一章和上一章的视频看完了,如果没有。我建议你看完 这个视频 补完理论,然后跟我从实操开始。

并行通信与串行通信

iic本身指的是一种通信协议,由于数字电子线路只存在0与1,所以机器的沟通也只是0与1的传递,对于简单的沟通来说(例如按钮控制灯泡亮灭),他就是io口接收0与1来实现开与关的简单效果。但对于较为复杂的情况,例如你想要按照asiic字符发送一个大写字母A(2进制0100 0001 16进制0x41)到另外一个 机器/元件 当中这个情况就比较麻烦了,对于这种问题来说业界有两种解决方案,就是串行通信与并行通信。下面是最简单的并行通信示意图。

并行通信

而串行通信我们以iic来举例,他的传递方式靠两根线只需要靠两根线来实现。相比并行通信有效的减少了并行线路的干扰以及节约了io口。

串行通信(以iic为例)

iic通信方式

在iic的通信当中,默认SDA(数据线)与SCL(时钟线)处于电平拉高的状态(也就是高电平),之后的数据传输按照以下方式传输。

SDA首次被拉低,代表着进入传输模式(图中S区)

SCL(时钟线)拉低后,将SCL重新拉高的那一刻 将此刻SDA(数据线)的高低电平记录 (图中绿色区域)

在发送完1个字节(8帧数据 / 8个高低电平)会发送一帧校验位(每一次通信视作一帧)。如果对应元件收到信号,会进行 应答。在 应答 结束后,才会发送下一个字节的数据。

发送完后将SDA、SCL重新拉高,作为终止信号广播给所有节点

应答(ACK/NACK) 是一个非常重要的机制,用于确保数据在发送方和接收方之间正确传输。

ACK(Acknowledge,应答):表示接收方成功接收到了数据。
NACK(Not Acknowledge,非应答):表示接收方未能成功接收数据,或者通信需要终止。

ACK用SDA低电平表示:

  1. 表示接收方成功接收到了数据,并准备好接收下一个字节。

  2. 通常由接收方(Slave)在接收到数据后主动拉低 SDA 线。

NACK用SDA高电平表示:

表示接收方未能成功接收数据,或者通信需要终止。

可能的原因包括:

  1. 接收方未正确接收到数据。

  2. 接收方不希望继续接收数据(例如,通信结束)。

  3. 总线上没有设备响应发送方的地址。

他的发送标志代码和终止代码如下:

// 微秒级延时函数
void Delay_us(unsigned int n) {
    while(n--) {
        _nop_(); 	// nop函数就是让机器空转一个周期
        _nop_();
        _nop_();
        _nop_();
    }
}

// I2C起始信号
void I2C_Start() {
    SDA = 1;
    SCL = 1;
    Delay_us(5);
    SDA = 0;
    Delay_us(5);
    SCL = 0;
}

// I2C停止信号
void I2C_Stop() {
    SDA = 0;
    SCL = 1;
    Delay_us(5);
    SDA = 1;
    Delay_us(5);
}

// 检测应答信号
bit I2C_CheckAck() {
    bit ack;
    SDA = 1;
    SCL = 1;
    Delay_us(2);
    ack = SDA;
    SCL = 0;
    return ack;
}

I2C核心代码 发送一个字节(8bit)数据:

void I2C_SendByte(unsigned char dat) {
    unsigned char i;
    for(i=0; i<8; i++) {  // 循环8次,逐位发送一个字节
        /* 
            SDA = (dat & 0x80) ? 1 : 0; 会将dat的最高位赋值给SDA
            将dat的数据与0x80 (0b1000 0000)相与而后进行判断为真则SDA = 1,为假SDA = 0
            0b1xxx xxxx(x为任意数据) 就会得到1,0b0xxx xxxx会得到0 
         */
        SDA = (dat & 0x80) ? 1 : 0;  

        dat <<= 1;      // 左移dat,准备发送下一位 
                        //例如 0bx1xx xx1x 左移后会变成 0b1xxx x1x0 (八位数据向左移动一位,空出来的数据用0填充) 例如 0b1010 1111 左移后会变成 0b0101 1110 

        SCL = 1;      // 将SCL线拉高,表示数据位有效
        Delay_us(5);  // 延时5微秒,确保数据稳定
        SCL = 0;      // 将SCL线拉低,为下一位数据做准备
        Delay_us(5);  // 延时5微秒,确保时序正确
    }
    I2C_CheckAck();   // 发送完一个字节后,检查从设备的应答(ACK/NACK)
}

iic的数据包

iic可以进行双向传输,但主机发送与从机发送的数据包并不完全相同。可参考下方图片

主机发送

从机发送

iic总线可以承载多个设备,我们以主机发送为例,按照图片从左到右进行介绍。

  1. S:IIC开始信号
  2. 地址:每个iic总线上的设备都有一个8位地址作为自己的"门牌号",每次进行通信会先确认“与谁通信”来确保数据能够正确传输。
  3. A: 第一次应答成功(证明你发送的这个地址有设备进行回应)
  4. 发送一字节数据
  5. A: 第二次应答成功(第一字节数据被接收)
  6. 发送亿字节数据
  7. A:第三次应答成功(第一字节数据被接收) / N(~A): 从机想结束通讯
  8. P: Pause 停止通讯

这一整个流程发送的数据,我们称之为一个 数据包 ,所以如果我们要通过iic发送数据,最好将其进行封包操作,这是一个封包函数(要注意的是,应答函数在I2C_SendByte()里,请查看上文的函数内容)。

// 封包函数
void Write_I2C_Command(unsigned char addr,unsigned char cmd, unsigned char dat) {
    I2C_Start();                // 启动I2C
    I2C_SendByte(addr);         // 发送器件地址
    I2C_SendByte(cmd);          // 发送指令
    I2C_SendByte(dat);          // 发送数据
    I2C_Stop();                 // 停止I2C
}

电路图如下 演示电路图

我们运行函数

Write_I2C_Command(0x7C,0xAA,0xBB);

通过debuger看到数据包,如果没有点击工具栏 Debug -> 最下方 I2C Debugger 就会有窗口

debugger

S(开始) -> 0x7C地址 -> 无应答 -> 数据1 0xAA -> 无应答 -> 数据2 0xBB -> 无应答 -> 结束

在我们接入了一个地址为0x7C的iic显示器后,电路图如(可以点击图片放大)

电路

S(开始) -> 0x7C地址 -> 应答 -> 数据1 0xAA -> 应答 -> 数据2 0xBB -> 应答 -> 结束

作业

在2月18号之前,最好不要复制本文的iic代码,自己编写iic驱动然后自行查找此显示器的资料,在显示器上写下 700LAB 和自己名字的拼音 发送到实验室邮箱.(示例如下) 作业示例

作业指导
Licensed under CC BY-NC-SA 4.0