[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);
相关推荐
AOI小白新手上路1 天前
江科协51单片机 17-2 红外遥控电机调速实验调试总结
单片机·嵌入式硬件·51单片机
oshan20121 天前
零基础STM32入门教程--10-PWM 呼吸灯
stm32·单片机·嵌入式硬件
记帖1 天前
STM32C5A3R开发(7)----ADC电压采集
嵌入式硬件·adc·dac·stm32cubeide·stm32c5·stm32cubemx2·stm32c5a3rgt6
自小吃多1 天前
器件移动、旋转、镜像、对齐、等间距操作笔记
笔记·嵌入式硬件
jianqiang.xue1 天前
智能体整体架构设计:四大模块与目录组织
单片机·嵌入式硬件·物联网·架构·esp32
Messy create1 天前
【BMS-AFE-温度检测】
stm32·单片机·能源
东莞市永盛电气1 天前
三相208V变380V变压器,出海工业设备供电适配方案
python·单片机·嵌入式硬件
桃蹊、1 天前
串口/网络透传实战:FreeRTOS 多任务架构与连接池设计
stm32·物联网·wifi·freertos
Learn-Share_HY1 天前
[IT Network]如何配置反向路徑過濾器(rp_filter),以解決路由不對稱問題?
linux·嵌入式硬件·物联网·网络协议·tcp/ip·http·iot
深圳市宝华视联1 天前
医院内网部署手术示教系统注意事项
嵌入式硬件·实时互动·音视频·实时音视频·视频编解码·嵌入式实时数据库