[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);
相关推荐
榴莲llll1 天前
应用于计时器/微波炉等产品的高亮LED数显驱动VK16K33C 数码管屏显驱动芯片
单片机
华一精品Adreamer1 天前
T606 vs 骁龙662/RK3566:主流安卓+4G定制平板芯片横向测评指南
单片机
Zyed1 天前
[STM32]Day9-Part1USART+串口接收+串口收发
stm32·单片机·嵌入式硬件
小慧10241 天前
手动建立工程模板
stm32·单片机
嵌入式ZYXC1 天前
STM32烧录一次后无法再次烧录的两种原因
stm32·单片机·嵌入式硬件
踏着七彩祥云的小丑1 天前
嵌入式测试学习第33 天:压力测试、反复开关机、反复插拔接口测试
单片机·嵌入式硬件·学习
布子麟1 天前
NodeMcu(ESP8266)之更新固件
单片机·物联网·iot
开发笔记-阿牛1 天前
CK6159A 可靠性测评:芯片工艺、环境测试与量产稳定性分析
stm32·单片机·嵌入式硬件
国科安芯1 天前
商业航天通信载荷数字处理单元供电架构研究——基于ASP7A84AS的高精度低压差线性稳压器技术分析
前端·单片机·嵌入式硬件·fpga开发·架构·安全性测试
嵌入式ZYXC1 天前
第8篇:《面试题:模拟地和数字地为什么要分开?怎么接?》
stm32·单片机·嵌入式硬件·面试·职场和发展