[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 小时前
基于51单片机汽车智能灯光控制系统
stm32·单片机·嵌入式硬件·汽车·51单片机·keil
fie88894 小时前
无刷直流电机(BLDC)控制程序 - STM32实现方案
stm32·单片机·嵌入式硬件
LCG元4 小时前
STM32实战:基于STM32F103的智能鹌鹑孵化箱(温湿度+翻蛋控制)
stm32·单片机·嵌入式硬件
黑猫学长呀18 小时前
存储宝典第2篇:盲封TT wafer是什么意思?
linux·嵌入式硬件·项目·芯片·ufs·晶圆·产测
都在酒里18 小时前
STM32标准库驱动HC-SR04超声波测距模块(定时器输入捕获,附完整工程代码)
stm32·嵌入式硬件·mongodb
qq_370773091 天前
梁山派GD32F470ZGT6 FreeRTOS CMake 模板适配指南
单片机·嵌入式硬件·gd32·梁山派
嵌入式小站1 天前
STM32 零基础可移植教程 03:蜂鸣器响一声,LED 跟着翻转一次
stm32·单片机·嵌入式硬件
星夜夏空991 天前
STM32单片机学习(15) —— PC串口通信实验
stm32·单片机·学习
星夜夏空991 天前
STM32单片机学习(14) —— STM32的串口外设
stm32·单片机·学习