[STM32] 硬件I2C主模式时序

1.I2C主发送器

1.1 I2C主发送器时序

主发送时序主要包括5步:

  • 主机产生起始位(Start)
  • 主机发送从机地址+写方向(7位地址+0)
  • 从机应答(ACK)
  • 主机连续发送数据
  • 主机产生停止位(Stop),结束通信

START → 设备地址(写) → ACK → 存储地址 → ACK → 数据 → ACK → STOP

1.2 I2C主模式写入代码(以EEPROM为例)

cpp 复制代码
uint8_t EEPROM_Byte_Write(uint8_t* pData, uint8_t addr){
	//产生起始位
	I2C_GenerateSTART(EEPROM_I2C, ENABLE);
	
	I2CTimeOutCount = I2CT_FLAG_TIMEOUT;
	//检测事件EV5
	while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS){
		if((I2CTimeOutCount--)==0) return I2C_TIMEOUT_UserCallback(1);
	};
	
	//发送地址
	I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Transmitter);
	I2CTimeOutCount = I2CT_FLAG_TIMEOUT;
	//检测事件EV6
	while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)!=SUCCESS){
		if((I2CTimeOutCount--)==0) return I2C_TIMEOUT_UserCallback(2);
	};
	
	//发数据地址
	I2C_SendData(EEPROM_I2C,addr);
	I2CTimeOutCount = I2CT_FLAG_TIMEOUT;
	//检测事件EV8
	while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTING)!=SUCCESS){
		if((I2CTimeOutCount--)==0) return I2C_TIMEOUT_UserCallback(3);
	};
	//发数据
	I2C_SendData(EEPROM_I2C,*pData);
	I2CTimeOutCount = I2CT_FLAG_TIMEOUT;
	//检测事件EV8_2
	while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED)!=SUCCESS){
		if((I2CTimeOutCount--)==0) return I2C_TIMEOUT_UserCallback(4);
	};
	//产生结束位
	I2C_GenerateSTOP(EEPROM_I2C, ENABLE);
	
	return 0;
}

1.3 逐步对照

1.3.1 主机产生I2C起始位

cpp 复制代码
	I2C_GenerateSTART(EEPROM_I2C, ENABLE);

函数作用:置位CR1寄存器的START位

硬件行为:STM32硬件自动产生I2C起始信号(SDA拉低),并且起始位发送完成后,START位清零,并且SR1_SB位置1(EV5事件)。注意:此时SB位已置1,但还没清除,必须执行"读SR1+写DR"才能清除,否则SB位一直为1,硬件无法进行下一步。

1.3.2 检测EV5事件

cpp 复制代码
	I2CTimeOutCount = I2CT_FLAG_TIMEOUT;
	//检测事件EV5
	while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS){
		if((I2CTimeOutCount--)==0) return I2C_TIMEOUT_UserCallback(1);
	};

I2C_CheckEvent读取SR1与SR2寄存器,判断当前硬件事件是否匹配目标事件。

作用:读取SR1与SR2寄存器,为SB标志位清除做准备。

1.3.3 发送从机地址

cpp 复制代码
	I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Transmitter);

作用:将7位设备地址+写方向位(0)写入DR寄存器,硬件自动发送到DR寄存器。由于检测EV5事件调用了I2C_CheckEvent函数,已经读过了SR寄存器,那么写DR寄存器就可以清除SB位,将时序往下推进。发送完成ADDR位置1(EV6事件)。

1.3.4 检测EV6事件

cpp 复制代码
	//检测事件EV6
	while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)!=SUCCESS){
		if((I2CTimeOutCount--)==0) return I2C_TIMEOUT_UserCallback(2);
	};
	

检测EV6事件,读取SR1和SR2,清除ADDR标志位。

1.3.5 发送存储地址

cpp 复制代码
	I2C_SendData(EEPROM_I2C,addr);

将地址写入DR寄存器。完全送入移位寄存器后,且第8个SCL时钟结束,SR1中的TXE置1(EV8事件)

1.3.6 检测EV8事件

cpp 复制代码
	//检测事件EV8
	while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTING)!=SUCCESS){
		if((I2CTimeOutCount--)==0) return I2C_TIMEOUT_UserCallback(3);
	};

确认TXE置1,上一字节完全送入移位寄存器,数据寄存器空了。

1.3.7 发送数据

cpp 复制代码
I2C_SendData(EEPROM_I2C,*pData);

将数据写入DR寄存器。完全送入移位寄存器后SR1中的TXE置1(EV8事件,可以继续发送),若停止发送,等待数据+应答全部结束,TXE置1,BTF置1(EV8_2事件)

1.3.8 检测EV8_2事件

cpp 复制代码
	while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED)!=SUCCESS){
		if((I2CTimeOutCount--)==0) return I2C_TIMEOUT_UserCallback(4);
	};

确认TXE为1,BTF为1,完全发送完成,可以停止。

1.3.9 主机产生I2C停止位

cpp 复制代码
	I2C_GenerateSTOP(EEPROM_I2C, ENABLE);

函数作用:置位CR1寄存器的STOP位

硬件行为:STM32硬件自动产生I2C停止信号(SDA拉高),释放总线。

2.I2C主接收器

2.1 I2C主接收器时序

主接收时序主要包括5步:

  • 主机产生起始位(Start)
  • 主机发送从机地址+读方向(7位地址+1)
  • 从机应答(ACK)
  • 从机连续发送数据给主机
  • 主机产生停止位(Stop),结束通信

START → 设备地址 (读) → ACK ← 数据 ← ACK ← STOP

这个是顺序读取的时序。

随机读取按以下顺序来,START→设备地址 (写)→ACK→存储地址→START (重复)→设备地址 (读)→ACK←数据←NACK→STOP(例程为随机读取)

2.2 I2C 主模式读取代码(EEPROM 示例)

cpp 复制代码
uint8_t EEPROM_Byte_Read(uint8_t addr){

	uint8_t readTemp;
	Wair_For_EEPROM();
	
	//产生起始位
	I2C_GenerateSTART(EEPROM_I2C, ENABLE);
	//检测事件EV5
	while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS){

	};
	//发送地址
	I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Transmitter);
		//检测事件EV6
	while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)!=SUCCESS){

	};
	//发数据地址
	I2C_SendData(EEPROM_I2C,addr);
	//检测事件EV8
	while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTING)!=SUCCESS){

	};
	
	//产生起始位
	I2C_GenerateSTART(EEPROM_I2C, ENABLE);
	//检测事件EV5
	while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS){

	};
	//发送地址
	I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Receiver);
	//检测事件EV6
	while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)!=SUCCESS){

	};
	I2C_AcknowledgeConfig(EEPROM_I2C, DISABLE); //只接受一个数据,NO ACK
	
	//检测事件EV7
	while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_RECEIVED)!=SUCCESS){

	};
	readTemp = I2C_ReceiveData(EEPROM_I2C);
	//产生结束位
	I2C_GenerateSTOP(EEPROM_I2C, ENABLE);
	//恢复应答
    I2C_AcknowledgeConfig(EEPROM_I2C, ENABLE);
	return readTemp;
}

2.3 逐步对照

2.3.1 主机产生起始位(同1.3.1)

cpp 复制代码
	I2C_GenerateSTART(EEPROM_I2C, ENABLE);

2.3.2 检测 EV5事件(同1.3.2)

cpp 复制代码
	while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS){

	};

2.3.3 发送从机写地址(同1.3.3)

cpp 复制代码
	I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Transmitter);

2.3.4 检测 EV6 事件(同1.3.4)

cpp 复制代码
	while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)!=SUCCESS){

	};

2.3.5 发送目标读取存储地址(同1.3.5)

cpp 复制代码
I2C_SendData(EEPROM_I2C,addr);

2.3.6 检测 EV8 事件(同1.3.6)

cpp 复制代码
	while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTING)!=SUCCESS){

	};

2.3.7 发送重复起始位(同1.3.1)

cpp 复制代码
I2C_GenerateSTART(EEPROM_I2C, ENABLE);

2.3.8 再次检测 EV5 事件(同1.3.2)

cpp 复制代码
	while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT)!=SUCCESS){

	};

2.3.9 发送从机读地址(同1.3.3,但改为读写为改为1)

cpp 复制代码
	I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Receiver);

2.3.10 检测 接收EV6 事件

cpp 复制代码
	while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)!=SUCCESS){

	};

2.3.11 关闭应答配置(只接收一个字节)

cpp 复制代码
I2C_AcknowledgeConfig(EEPROM_I2C, DISABLE);

将CR_ACK复位为0。接受完一字节后,主机发送NACK。告诉从机不要发了,已经完成了。

2.3.12 检测 EV7 事件

cpp 复制代码
	while(I2C_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_RECEIVED)!=SUCCESS){

	};

从机端接收到地址后,开始向主机端发送数据。当主机接收到这些数据后,会产生"EV7"事
件,SR1 寄存器的 RXNE 被置 1,表示接收数据寄存器非空,我们读取该寄存器后,可对数据寄存器清空,以便接收下一次数据。此时可以控制I2C发送应答信号(ACK)或非应答信号(NACK),若应答继续接收数据,若非应答, 停止传输。

2.3.13 读取数据

cpp 复制代码
readTemp = I2C_ReceiveData(EEPROM_I2C);

读取DR位,RXNE 位硬件自动清零。

2.3.14 主机产生 I2C 停止位(同1.3.9)

cpp 复制代码
I2C_GenerateSTOP(EEPROM_I2C, ENABLE);

2.3.15 恢复应答使能

将CR_ACK为1。

cpp 复制代码
I2C_AcknowledgeConfig(EEPROM_I2C, ENABLE);
相关推荐
雾削木4 小时前
STM32 HAL + FreeRTOS(CMSIS-RTOS V1)完整开发攻略
stm32·单片机·嵌入式硬件
wuyk55513 小时前
第2章:六步换相原理全解+STM32工程实战
c语言·开发语言·stm32·单片机·嵌入式硬件
小僧景贤17 小时前
单片机全方位调试实战指南:串口/RTT/逻辑分析仪/示波器/内核排错分层避坑教程
单片机·嵌入式硬件·物联网
2401_8275012821 小时前
【无标题】
arm开发·嵌入式硬件
LeoZY_21 小时前
LinkScope 使用笔记:基于 OpenOCD 的通用硬件芯片调试助手
笔记·单片机·嵌入式硬件·开源软件
星空真迷人1 天前
嵌入式鸿蒙并非精简版,核心究竟是什么?
stm32·单片机·嵌入式硬件·物联网·华为·harmonyos·iot
iCxhust1 天前
8088单板机VScode集成开发环境使用方法二(汇编版)
汇编·单片机·微机原理·8088单板机
hongmai6668881 天前
GK7205V300芯片深度解析:H.265低功耗视觉主控,助力安防监控高效选型
arm开发·嵌入式硬件·物联网·智能家居·h.265·risc-v
m0_466607701 天前
只用三步使用MCSDK6生成电机控制工程
stm32·电机控制
2301_801434981 天前
STM32F407移植TinyUSB
stm32·单片机·嵌入式硬件