EBU5476 Microprocessor System Design 知识点总结_8 I2C

I2C

连接多个模块的传输方案:I2C,使用两根总线。

image-20230622000302459

两根总线分别是时钟总线 SCL 和数据总线 SDA。

通信过程

现在我们串一遍I2C上一个模块(master)要给另一个模块(slave)发消息的过程。

1687363434463

  1. MCU 使用一定的方法标识自己开始传输了。
  2. MCU 发送 LCD slave 的地址+一位读写位,其他模块接收到发现地址不是自己的,就不做处理。
  3. LCD 接收到后知道目标是自己,于是返回 ack。
  4. MCU 收到 ACK 后发送一帧数据。
  5. 发送完 MCU 等着 ACK,收到 ACK 后继续发送下一帧数据。
  6. 一直发送到发送停止位 stop 结束。

image-20230622000920336

数据长度可以设置,比如789.

总线上的器件是开漏输出的半双工通信。

image-20230713135505107

默认总线是上拉电阻拉成高电平。

当器件输出 out 为低电平时,总线导通到接地,总线被拉低(整条总线都被拉低)。江协科技老师举的例子很好,就像公交车上的一根横杆,有人拉住横杆拽下来,整条横杆都被拉低了,其他人都知道“横杆被一个人拉低了,说明有人正在使用总线”。

然后是总线传输数据的方式,SCL SDA 两根总线在何种情况下表示 start stop 0 1 bit?

image-20230713140225426

首先都是 SCL 为高电平时 SDA 的数值才有意义。

SDA 从高到低,表示 start 位。从低到高,表示 stop 位。

start 位后,SDA 高电平表示1,低电平表示0.

发送完 1byte 数据后,总线保持拉高状态。如果接收方把总线拉低了,发送方发现总线1→0了(不是发送方自己拉的,是接收方给他拉下来的,但是发送方能察觉到),说明接收方成功接收了并且拉了拉总线以示“收到”。如果 SDA 还是保持在高电平,说明接收方没有成功收到或者成功发送 ACK。

image-20230713140822834

问题处理

I2C 是一种很简单的主从通信协议了,但是局限性也很多,比如7 bit 的地址线只允许 2^7 个设备;一次顶多两个设备主从通信;一个设备的快慢会影响到整条总线的通信等。

问题1:从设备处理速度太慢了,赶不及在下一个时钟周期接收新数据帧怎么办?

方法:clock stretching, 拉低一段时间 SCL 假装下一个时钟周期还没到。

image-20230713141906054

问题2:多个设备同时发数据冲突了怎么办?

方法:Bus Aribitation,前面我们知道总线被一个设备拉低了,所有设备都能接收到总线拉低的信号。因此如果两个设备同时开始发信息,前面数据一致都无所谓,等到第一次数据不一致的时候,一个设备发送数据0,一个发送数据1,这时 SDA 总线被 DATA2 的0拉低了。

image-20230713142029471

发送 DATA1 数据的设备就明白了:有人同时在和我一起发数据,因此总线不是我预期的1而是被他拉低为0了。那我 quit,你发吧。然后就只有 DATA2 发送的数据了。

问题3:以上发送的数据每次都是 1byte 8bits 很正好。那如果要发送的地址不是 8bits 呢?

方法:少于 8bits 用一些固定的额外的 start 位填充,多于 8bits 的地址用两个 bytes,不够的也是用额外的 start 位填充。

image-20230713143052018

问题3:如果我 master 发完数据,想紧接着再收数据,变成 slave,可行吗?

方法:通过一个 sr 信号,也就是 repeat start 重发 start 位,来标识自己是 read 而不再是 write 了重新开始通信。

image-20230713143601570

编址格式

slave 地址编址有一些固定格式。

image-20230713143744619

0000 000 0:广播,对所有 slave 结点讲话。如果 slave 无视(NACK),就不会参与广播。如果返回 ACK 就参与进来了。不过多个 slave 都返回 ACK 的话 master 是不知道都有谁回应了的。

第二个 byte 发送一些行为相关,比如:start,clear,reset software

编程应用

slave mode:

  • I2C 设备默认工作在 slave mode。
  • 外设时钟在 I2C_CR2 寄存器中编程。频率介于 2kHz~100kHz。
  • 硬件自动等待发过来的 start 和 addr 信息。
  • 如果 addr 信息和 OAR1 中存储的地址相同,说明目标是自己。如果 ACK 位为1,则发送 ack pulse。
  • 设置 ADDR 位,1表示匹配。
  • 如果 ITEVFEN 就是中断事件 flag 为1,则生成中断。
  • TRA 位标明 slave 是 R 还是 T 模式(收 or 发)。
  • BTF 位标识收没收完。

1689255491311

image-20230713214410998

这么说起来还是有点混乱 I2C 到底经历了哪些才顺利发送了数据?

首先,从主模式的概念。master 主模式驱动时钟信号,发起传输;slave 从模式响应传输。

主模式

用于主发送数据的 I2C 传输序列图

发送:

所有 EV 事件都会拉低 SCL,直到相应软件序列执行完成。

S:start 事件。比如CR2 寄存器中设置外设时钟,配置时钟寄存器,上升时钟寄存器,使能 CR1 来启用时钟,CR1 中设置 start 位,等待总线被拉低表示就绪,发送启动信号,并切换为主模式。

EV5:启动事件成功进行,设置 SB 寄存器=1. SB 寄存器=1后才可以进行地址阶段,执行完地址阶段会自动清除 SB 和 EV5 事件。

Address:地址阶段。传输7位地址+1位读写位,然后等待从机的 ack。收到 ack 进入 EV6.

EV6:设置 addr 位=1代表地址阶段顺利执行, master 收到 ack了。清除 EV6 后自动进入 EV8.

EV8:设置 TxE ,准备写入主机要传入的数据。TxE 表示数据寄存器为空可以写入。每次数据写入 DR 都会清空 TxE 和 EV8 事件。写完数据数据传过去了,主机收到 ack 后继续传输。以 BTF=1 表示数据传输的结尾。

void i2c_write(uint8_t address, uint8_t *buffer, int buff_len) {
    
    
	int i = 0;
    // Send in sequence: Start bit, Contents of buffer 0..buff_len, Stop
    while (((I2C1->SR2>>1)&1)); // wait until I2C1 is not busy anymore
    I2C_GenerateSTART(I2C1, ENABLE); // Send I2C1 START condition
    // wait for I2C1 EV5 --> Slave has acknowledged start condition
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
    // Send slave Address for write then wait for EV6
    I2C_Send7bitAddress(I2C1, address, I2C_Direction_Transmitter);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
    while (i < buff_len){
    
    
        I2C_SendData(I2C1, buffer[i]); // send data then wait for EV8_2
        while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
        i++;
    }
    I2C_GenerateSTOP(I2C1, ENABLE); // send stop bit
}

image-20230714110003657

接收:

前面和 master transmit 都一样。

TxE 改为 RxE 了,=1标识接收到了数据。

master 自己设置 stop 事件后(发送 NACK)停止接收。

void i2c_read(uint8_t address, uint8_t *buffer, int buff_len) {
    
    
    int i = 0;
    // Start bit, Contents of buffer from 0..buff_len, sending a NACK
    // for the last item and an ACK otherwise, Stop bit
    I2C_GenerateSTART(I2C1, ENABLE);
    while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); //EV5
    // Send slave Address for write then wait for EV6
    I2C_Send7bitAddress(I2C1, address, I2C_Direction_Receiver);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
    I2C_AcknowledgeConfig(I2C1, ENABLE); // going to send ACK
    while (i < buff_len - 1){
    
    
        while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); //EV7
        buffer[i] = I2C_ReceiveData(I2C1); // get data byte
        i++;
    }
    I2C_AcknowledgeConfig(I2C1, DISABLE); // going to send NACK
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); //EV7
    buffer[i] = I2C_ReceiveData(I2C1); // get the last byte
    I2C_GenerateSTOP(I2C1, ENABLE); // send stop
}

从模式

1689259086166

发送:

start 启动事件由 master 发起。从机校验地址并决定是否发送 ack 位。

EV1:设置 addr 位表示地址匹配。

EV3-1:设置 TxE 位,开始传入数据。一直到主机返回 NACK 表示不想再要数据了,或者 AF=1 说明 ack 失败了为止。

1689259113895

接收:

前面到 EV1 和 slave transmit 都一样。

  1. 数据从 DR 寄存器中读。
  2. 读入一个 byte 后,如果 ack 位已经设置,则返回 ack 信息。
  3. RxE 位是接收数据的状态寄存器。
  4. 主机生成停止条件时停止。

异常情况:

总线错误,NACK,仲裁失败,时钟异常超时。

image-20230714110916968

猜你喜欢

转载自blog.csdn.net/jtwqwq/article/details/131719394