【STM32】IIC的初步使用

news2024/12/24 3:00:22

IIC简介

物理层

在这里插入图片描述

连接多个devices

它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。

两根线

一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA)一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。

设备地址

每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。

上拉电阻和高阻态

总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。

仲裁

多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。

三种速度模式

具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。

最大devices限制

连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制 。

协议层

I2C 的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。

I2C 基本读写过程

在这里插入图片描述
主机写数据到从机
在这里插入图片描述
主机由从机中读数据

在这里插入图片描述
I2C 通讯复合格式

在这里插入图片描述 数据由主机传输至从机
在这里插入图片描述数据由从机传输至主机

S : 传输开始信号
R/W: 传输方向选择位,1 为读,0 为写
A/ A: 应答(ACK)或非应答(NACK)信号
在这里插入图片描述停止传输信号

1.S是起始信号,由主机产生,挂载在总线上的设备都会收到,准备接收主机下一个数据
2.设备聆听地址信息,选择从机: 起始信号之后,主机会广播从机地址,在 I2C 总线上,每个设备的地址都是唯一的,当主机广播的地址与某个设备地址相同时,这个设备就被选中了,没被选中的设备将会忽略之后的数据信号。根据 I2C 协议,这个从机地址可以是 7 位或 10 位。
3.传输方向:主机广播传输方向,0是主机到从机(主机往从机写),1为从机到主机(从机往主机写)
4.从机应答:回复ack与nack,只有收到ack之后,主机继续发送或者接收数据

写数据

在这里插入图片描述
主机首先在 IIC 总线上发送起始信号,那么这时总线上的从机都会等待接收由主机发出的数据。主机接着发送从机地+0(写操作)组成的 8bit 数据,所有从机接收到该 8bit 数据后,自行检验是否是自己的设备的地址,假如是自己的设备地址,那么从机就会发出应答信号。主机在总线上接收到有应答信号后,才能继续向从机发送数据。注意:IIC 总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。

读数据

在这里插入图片描述
若配置的方向传输位为“读数据”方向,即第二幅图的情况,广播完地址,接收到应答信号后,从机开始向主机返回数据(DATA),数据包大小也为 8 位,从机每发送完一个数据,都会等待主机的应答信号(ACK),重复这个过程,可以返回 N 个数据,这个 N 也没有大小限制。当主机希望停止接收数据时,就向从机返回一个非应答信号(NACK),则从机自动停止数据传输。

信号电平

起始 停止信号

在这里插入图片描述
当 SCL 线是高电平时 SDA 线从高电平向低电平切换,这个情况表示通讯的起始。当 SCL 是高电平时 SDA 线由低电平向高电平切换,表示通讯的停止。起始和停止信号一般由主机产生。

数据有效性 SCL的高电平

在这里插入图片描述

SCL为高电平的时候 SDA表示的数据有效,SCL为低电平时数据变换

地址及数据方向

I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过 SDA 信号线发送设备地址(SLAVE_ADDRESS)来查找从机。I2C 协议规定设备地址可以是 7 位或 10 位,实际中 7 位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向。
读数据方向时,主机会释放对 SDA 信号线的控制,由从机控制 SDA 信号线,主机接收信号,写数据方向时,SDA 由主机控制,从机接收信号。
在这里插入图片描述
大端(高有效位存在低地址),波形高位先行

ACK NACK

主站发送起始信号,地址,读写信号之后释放SDA控制权,从站开始自动控制SDA信号发送ACK NACK

整体控制逻辑

整体控制逻辑负责协调整个 I2C 外设,控制逻辑的工作模式根据我们配置的“控制寄存器(CR1/CR2)”的参数而改变。在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR1 和 SR2)”,我们只要读取这些寄存器相关的寄存器位,就可以了解 I2C的工作状态。除此之外,控制逻辑还根据要求,负责控制产生 I2C 中断信号、DMA 请求及各种 I2C 的通讯信号(起始、停止、响应信号等)。

IIC使用 读写EEPROM为例

要点

(1) 配置通讯使用的目标引脚为开漏模式;
(2) 使能 I2C 外设的时钟;
(3) 配置 I2C 外设的模式、地址、速率等参数并使能 I2C 外设;
(4) 编写基本 I2C 按字节收发的函数;
(5) 编写读写 EEPROM 存储内容的函数;

硬件IIC

配置IIC复用的GPIO

#配置 GPIO 复用
GPIO_InitTypeDef GPIO_InitStructure;

/* 使能与 I2C 有关的时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB)

/* 配置GPIO为开漏输出模式 */
//配置Gpio初始化结构体略
GPIO_Init(GPIOB, &GPIO_InitStructure);

1.配置IIC模式

I2C_InitTypeDef I2C_InitStructure;

/* I2C 配置 */
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;/*!< 指定工作模式,可选 I2C 模式及 SMBUS 模式 */

/* 高电平数据稳定,低电平数据变化 SCL 时钟线的占空比 */
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; /*指定时钟占空比,可选 low/high = 2:1 及 16:9 模式*/


 /*!< 指定自身的 I2C 设备地址 */
I2C_InitStructure.I2C_OwnAddress1 =I2Cx_OWN_ADDRESS7;/*!< 指定自身的 I2C 设备地址 */

I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ; /*!< 使能或关闭响应(一般都要使能) */

/* I2C 的寻址模式 */
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; /*!< 指定地址的长度,可为 7 位及 10 位 */

I2C_InitStructure.I2C_ClockSpeed = I2C_Speed; /*!< 设置 SCL 时钟频率,此值要低于 400000*/

I2C_Init(I2C1, &I2C_InitStructure);
I2C_Cmd(I2C1, ENABLE);

1.1作为主机端,进行数据发送

即作为 I2C 通讯的主机端时,对外部发送数据的过程

以写一个E2ROM为例
在这里插入图片描述
(1) 控制产生起始信号(S),当发生起始信号后,它产生事件“EV5”,并会对 SR1 寄存器的“SB”位置 1,表示起始信号已经发送;

(2) 紧接着发送设备地址并等待应答信号,若有从机应答,则产生事件“EV6”及“EV8”,这时 SR1 寄存器的“ADDR”位及“TXE”位被置 1,ADDR 为 1 表示地址已经发送,TXE 为 1 表示数据寄存器为空;

(3) 以上步骤正常执行并对 ADDR 位清零后,我们往 I2C 的“数据寄存器 DR”写入要发送的数据,这时TXE位会被重置0,表示数据寄存器非空,I2C外设通过SDA信号线一位位把数据发送出去后,又会产生“EV8”事件,即 TXE 位被置 1,重复这个过程,就可以发送多个字节数据了;

(4) 当我们发送数据完成后,控制 I2C 设备产生一个停止信号§,这个时候会产生EV8_2 事件,SR1 的 TXE 位及 BTF 位都被置 1,表示通讯结束。

假如我们使能了 I2C 中断,以上所有事件产生时,都会产生 I2C 中断信号,进入同一个中断服务函数,到 I2C 中断服务程序后,再通过检查寄存器位来判断是哪一个事件。

IIC STM32固件库函数链接: IIC STM32固件库函数链接

1.1.1发送单字节数据

在这里插入图片描述
AT24C02的单字节时序规定,向它写入数据的时候,第一个字节为内存地址,第二个字节是要写入的数据内容。在发送device地址之后,需要发送eeprom的内部存储地址和需要存的数据

#define EEPROM_I2Cx      I2C1
 /* 具体修改的寄存器位置,查看固件库函数*/

 uint32_t I2C_EE_ByteWrite(u8* pBuffer, u8 WriteAddr){
 
	 /* 产生 I2C 起始信号 */
	 //I2Cx->CR1 |= I2C_CR1_START; 操作I2C中的CR1寄存器
	I2C_GenerateSTART(I2C1, ENABLE);
	
	/*设置超时等待时间*/
	I2CTimeout = I2CT_FLAG_TIMEOUT;//I2CTimeout 常数
	
	/* 检测 EV5 事件并清除标志*/
	while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
	{
		/* 超时,报错*/
		if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(0);
	}
	
	/* 发送 EEPROM 设备地址 */
	I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS,I2C_Direction_Transmitter);
	
	/* 检测 EV6 事件并清除标志*/
	while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
	{
		if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(1);
	}
	
	/* 发送要写入的 EEPROM 内部地址(即 EEPROM 内部存储器的地址) */
	/*  EEPROM 内部存储器读写的规则是第一个数据为要读写的地址,第二个数据是具体读写的数据 */
	I2C_SendData(EEPROM_I2Cx, WriteAddr);
	
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	/* 检测 EV8 事件并清除标志*/
	while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_BYTE_TRANSMITTED))
	{
		if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(2);
	}

	/* 发送一字节要写入的数据 */
	I2C_SendData(EEPROM_I2Cx, *pBuffer);

	I2CTimeout = I2CT_FLAG_TIMEOUT;
	
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	/* 检测 EV8 事件并清除标志*/
	while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_BYTE_TRANSMITTED))
	{
		if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(2);
	}

	/* 发送停止信号 */
	I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);

}

1.1.2发送多字节数据

等待存储设备准备好

这个函数主要实现是向 EEPROM 发送它设备地址,检测 EEPROM 的响应,若EEPROM 接收到地址后返回应答信号,则表示 EEPROM 已经准备好,可以开始下一次通讯。函数中检测响应是通过读取 STM32 的 SR1 寄存器的 ADDR 位及 AF 位来实现的,当I2C 设备响应了地址的时候,ADDR 会置 1,若应答失败,AF 位会置 1。

	//等待 EEPROM 到准备状态
 void I2C_EE_WaitEepromStandbyState(void)
 {
	vu16 SR1_Tmp = 0;
	do {
		/* 发送起始信号 */
		I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);

		/* 读 I2C1 SR1 寄存器 */
		SR1_Tmp = I2C_ReadRegister(EEPROM_I2Cx, I2C_Register_SR1);

		/* 发送 EEPROM 地址 + 写方向 */
		I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS,I2C_Direction_Transmitter);
		}
		// SR1 位 1 ADDR:1 表示地址发送成功,0 表示地址发送没有结束
		// 等待地址发送成功
		
	while (!(I2C_ReadRegister(EEPROM_I2Cx, I2C_Register_SR1) & 0x0002));
	/* 清除 AF 位 */
	I2C_ClearFlag(EEPROM_I2Cx, I2C_FLAG_AF);
	/* 发送停止信号 */
	I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
}
循环单字节发送
uint8_t I2C_EE_ByetsWrite(uint8_t* pBuffer,uint8_t WriteAddr,uint16_t NumByteToWrite)
{
	uint16_t i;
	uint8_t res;
	/*每写一个字节调用一次 I2C_EE_ByteWrite 函数,前文的单字节写入*/
	for (i=0; i<NumByteToWrite; i++)
	{
		/*等待 EEPROM 准备完毕*/
		I2C_EE_WaitEepromStandbyState();
		/*按字节写入数据*/
		res = I2C_EE_ByteWrite(pBuffer++,WriteAddr++);
	}
	return res;
}

1.1.3 EEPROM 的页写入

EEPROM 定义了一种页写入时序,只要告诉 EEPROM 第一个内存地址 address1,后面的数
据按次序写入到 address2、address3… 这样可以节省通讯的时间,加快速度。
在这里插入图片描述
根据页写入时序,第一个数据被解释为要写入的内存地址 address1,后续可连续发送 n个数据,这些数据会依次写入到内存中。其中 AT24C02 型号的芯片页写入时序最多可以一次发送 8 个数据(即 n = 8 ),该值也称为页大小,某些型号的芯片每个页写入时序最多可传输 16 个数据。

但是这种方式还是比较慢,拘泥于页的大小。

//在 EEPROM 的一个写循环中可以写多个字节,但一次写入的字节数不能超过 EEPROM 页的大小,AT24C02 //每页有 8 个字节
//NumByteToWrite:要写的字节数要求 NumByToWrite 小于页大小

uint8_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr,uint8_t NumByteToWrite)
{	
	I2CTimeout = I2CT_LONG_TIMEOUT;
	while (I2C_GetFlagStatus(EEPROM_I2Cx, I2C_FLAG_BUSY))
	{
		if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(4);
	}

	/* 产生 I2C 起始信号 */
	I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);

	I2CTimeout = I2CT_FLAG_TIMEOUT;
	/* 检测 EV5 事件并清除标志 */
	while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
	{
		if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(5);
	}
	
	/* 发送 EEPROM 设备地址 */
	 I2C_Send7bitAddress(EEPROM_I2Cx,EEPROM_ADDRESS,I2C_Direction_Transmitter);

	I2CTimeout = I2CT_FLAG_TIMEOUT;
	
	/* 检测 EV6 事件并清除标志*/
	while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
	{
		if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(6);
	}
	/* 发送要写入的 EEPROM 内部地址(即 EEPROM 内部存储器的地址) */
	I2C_SendData(EEPROM_I2Cx, WriteAddr);
	
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	/* 检测 EV8 事件并清除标志*/
	while (! I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
	{
		if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(7);
	}

	/* 循环发送 NumByteToWrite 个数据 */
	while (NumByteToWrite--)
	{
		/* 发送缓冲区中的数据 */
		I2C_SendData(EEPROM_I2Cx, *pBuffer);
		/* 指向缓冲区中的下一个数据 */
		pBuffer++;
		
		I2CTimeout = I2CT_FLAG_TIMEOUT;
		/* 检测 EV8 事件并清除标志*/
		while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
		{
		if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(8);
		}
	}
	
	/* 发送停止信号 */
	I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
}

1.1.4 EEPROM 的页写入plus

利用 EEPROM 的页写入方式,可以改进前面的“多字节写入”函数,加快传输速度

 // AT24C01/02 每页有 8 个字节
 #define I2C_PageSize 8
 
 void I2C_EE_BufferWrite(u8* pBuffer, u8 WriteAddr,u16 NumByteToWrite)
 {
	u8 NumOfPage=0,NumOfSingle=0,Addr =0,count=0,temp =0;
	
	/*mod 运算求余,若 writeAddr 是 I2C_PageSize 整数倍,运算结果 Addr 值为 0*/
	/*判断目标地址(起始地址)是否为一页的开始*/
	Addr = WriteAddr % I2C_PageSize;

	/*差 count 个数据值,刚好可以对齐到页地址*/
	count = I2C_PageSize - Addr;

	/*计算出要写多少整数页*/
	NumOfPage = NumByteToWrite / I2C_PageSize;
	
	/*mod 运算求余,计算出剩余不满一页的字节数*/
	NumOfSingle = NumByteToWrite % I2C_PageSize;

	// Addr=0,则 WriteAddr 刚好按页对齐 aligned,也就是从这页的第一个byte开始
	// 这样就很简单了,直接写就可以,写完整页后, 把剩下的不满一页的写完即可
	if (Addr == 0) {//从某页的第一位写起
		/* 如果 NumByteToWrite < I2C_PageSize */
		/* 总字数还不满一页 */
		if (NumOfPage == 0) {//总页数不满一页
		I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
		I2C_EE_WaitEepromStandbyState();
		}
		
		/* 如果 NumByteToWrite > I2C_PageSize */
		else {
				/*先把整数页都写了*/
				while (NumOfPage--) {
				I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);//写一页
				I2C_EE_WaitEepromStandbyState();//等待rom准备好
				WriteAddr += I2C_PageSize;	//写完一页之后,下一页的地址
				pBuffer += I2C_PageSize;	//写完一页之后,要传输的源地址也要加
				}
			/*若有多余的不满一页的数据,把它写完*/
				if (NumOfSingle!=0) {
				I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
				I2C_EE_WaitEepromStandbyState();
				}
			}
		}从某页的第一位写起

	// 如果 WriteAddr 不是按 I2C_PageSize 对齐
	// 那就算出对齐到页地址还需要多少个数据,先把这几个数据写完,剩下开始的地址就已经对齐
	else{//不是从某页的第一位写起
		/* 如果 NumByteToWrite < I2C_PageSize */
		if (NumOfPage== 0)
		 {//总数不满一页
			if (NumOfSingle > count) {//count:还需要写多少个字节才能页面对齐
										//NumOfSingle:当前页面空闲的字节数

				// temp 的数据要写到写一页
				temp = NumOfSingle - count;
				I2C_EE_PageWrite(pBuffer, WriteAddr, count);//写count数目的到当前页
				I2C_EE_WaitEepromStandbyState();//等待准备完毕
				WriteAddr += count;//目标地址移动
				pBuffer += count;
				
				I2C_EE_PageWrite(pBuffer, WriteAddr, temp);//在下一页写完剩下的
				I2C_EE_WaitEepromStandbyState();
				}
		else{ /*若 count 比 NumOfSingle 大*/
				I2C_EE_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
				I2C_EE_WaitEepromStandbyState();
			}
		}
		
		/* 如果 NumByteToWrite > I2C_PageSize */
		/* 如果 不止写一页 */
		else {	/* 如果 不止写一页 */
				/*地址不对齐多出的 count 分开处理,不加入这个运算*/
				NumByteToWrite -= count;
				NumOfPage = NumByteToWrite / I2C_PageSize;
				NumOfSingle = NumByteToWrite % I2C_PageSize;
						
				/*先把 WriteAddr 所在页的剩余字节写了*/
				if (count != 0) {
					I2C_EE_PageWrite(pBuffer, WriteAddr, count);
					I2C_EE_WaitEepromStandbyState();
					/*WriteAddr 加上 count 后,地址就对齐到页了*/
					WriteAddr += count;
					pBuffer += count;
					}
	
				/*把整数页都写了*/
				while (NumOfPage--) {
						I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);
						I2C_EE_WaitEepromStandbyState();
						WriteAddr += I2C_PageSize;
						pBuffer += I2C_PageSize;
					}
					/*若有多余的不满一页的数据,把它写完*/
				if (NumOfSingle != 0) {
				I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
				I2C_EE_WaitEepromStandbyState();
					}
				}/*把整数页都写了*/
			}/* 如果 不止写一页 */	
	}

1.2作为主机端,进行数据接收

即作为 I2C 通讯的主机端时,从外部接收数据的过程
在这里插入图片描述
(1) 同主发送流程,起始信号(S)是由主机端产生的,控制发生起始信号后,它产生事件“EV5”,并会对 SR1 寄存器的“SB”位置 1,表示起始信号已经发送;
(2) 紧接着发送设备地址并等待应答信号,若有从机应答,则产生事件“EV6”这时SR1 寄存器的“ADDR”位被置 1,表示地址已经发送。
(3) 从机端接收到地址后,开始向主机端发送数据。当主机接收到这些数据后,会产生“EV7”事件,SR1 寄存器的 RXNE被置 1,表示接收数据寄存器非空,我们读取该寄存器后,可对数据寄存器清空,以便接收下一次数据。此时我们可以控制I2C 发送应答信号(ACK)或非应答信号(NACK),若应答,则重复以上步骤接收数据,若非应答,则停止传输;
(4) 发送非应答信号后,产生停止信号P,结束传输。

 uint8_t I2C_EE_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr,u16 NumByteToRead)
 {
	 I2CTimeout = I2CT_LONG_TIMEOUT;
	 while (I2C_GetFlagStatus(EEPROM_I2Cx, I2C_FLAG_BUSY))
	 {
	 	if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(9);
	 }
	 
	/* 产生 I2C 起始信号 */
	I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
	
	I2CTimeout = I2CT_FLAG_TIMEOUT;

	/* 检测 EV5 事件并清除标志*/
	while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
	{
		if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(10);
	}
	
	/* 发送 EEPROM 设备地址 */
	I2C_Send7bitAddress(EEPROM_I2Cx,EEPROM_ADDRESS,I2C_Direction_Transmitter);

	I2CTimeout = I2CT_FLAG_TIMEOUT;
	/* 检测 EV6 事件并清除标志*/

	while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
	{
		if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(11);
	}	
	/*通过重新设置 PE 位清除 EV6 事件 */
	I2C_Cmd(EEPROM_I2Cx, ENABLE);

	/* 发送要读取的 EEPROM 内部地址(即 EEPROM 内部存储器的地址) */
	/*  EEPROM 内部存储器读写的规则是第一个数据为要读写的地址,第二个数据是具体读写的数据 */
	I2C_SendData(EEPROM_I2Cx, ReadAddr);
		
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	/* 检测 EV8 事件并清除标志*/
	while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_BYTE_TRANSMITTED))
	{
		if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(12);
	}

	/* 产生第二次 I2C 起始信号 */
	I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
	
	I2CTimeout = I2CT_FLAG_TIMEOUT;	
	/* 检测 EV5 事件并清除标志*/
	while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
	{
		if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(13);
	}

	/* 发送 EEPROM 设备地址 */
	I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Receiver);

	I2CTimeout = I2CT_FLAG_TIMEOUT;
	/* 检测 EV6 事件并清除标志*/
	while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
	{
		if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(14);
	}

	/* 读取 NumByteToRead 个数据*/
	while (NumByteToRead)
	{
			/*若 NumByteToRead=1,表示已经接收到最后一个数据了,发送非应答信号,结束传输*/
			if (NumByteToRead == 1)
			{
				/* 发送非应答信号 */
				I2C_AcknowledgeConfig(EEPROM_I2Cx, DISABLE);
				/* 发送停止信号 */
				I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
			}
	
			I2CTimeout = I2CT_LONG_TIMEOUT;
			while (I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)==0)
			{
				if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3);
			}
			{
			/*通过 I2C,从设备中读取一个字节的数据 */
			*pBuffer = I2C_ReceiveData(EEPROM_I2Cx);
			/* 存储数据的指针指向下一个地址 */
			pBuffer++;
			/* 接收数据自减 */
			NumByteToRead--;
			}	
	}	/* 读取 NumByteToRead 个数据*/

	/* 使能应答,方便下一次 I2C 传输 */
	I2C_AcknowledgeConfig(EEPROM_I2Cx, ENABLE);
 }

1.3作为从机端,进行数据接收

在这里插入图片描述

1.4作为从机端,进行数据接收

在这里插入图片描述

GPIO模拟IIC

我之前使用这套方法读取一个外置的RTC模块,参考正点原子的例程,这边就不赘述了。或者之后有机会把例程贴出来哈

1.配置GPIO

2.编写微秒级软延时

3.根据时序编写各个函数

DMA 与IIC

可以使用DMA,对IIC的DR中要传输或者接收的数据进行搬运,DMA请求(当被使能时)仅用于数据传输。发送时数据寄存器变空或接收时数据寄存器变满,则产生DMA请求。DMA请求必须在当前字节传输结束之前被响应。当为相应DMA通道设置的数据传输量已经完成时,DMA控制器发送传输结束信号ETO到I2C接口,并且在中断允许时产生一个传输完成中断。

主发送器:在EOT中断服务程序中,需禁止DMA请求,然后在等到BTF事件后设置停止条件。

主接收器:当要接收的数据数目大于或等于2时,DMA控制器发送一个硬件信号EOT_1,它对应DMA传输(字节数-1)。如果在I2C_CR2寄存器中设置了LAST位,硬件在发送完EOT_1后的下一个字节,将自动发送NACK。在中断允许的情况下,用户可以在DMA传输完成的中断服务程序中产生一个停止条件。

意思是说当DMA产生EOT标志后,(如果开启了EOT相关中断就进中断程序,没有开启就进行软件查询做后续处理)关闭DMA请求,然后等待BTF事件,之后执行STOP操作。 这里的BTF事件就是I2C数据收发过程中的数据字节是否传输完成的的事件
IIC通讯过程 标志位

配置DMA

对DMA进行配置

在IIC的配置中开启DMA

通过设置I2C_CR2寄存器中的DMAEN位可以激活DMA模式。只要TxE位被置位,数据将由DMA从预置的存储区装载进I2C_DR寄存器。为I2C分配一个DMA通道,须执行以下步骤(x是通道号)。

利用DMA发送

  1. 在DMA_CPARx寄存器中设置I2C_DR寄存器地址。数据将在每个TxE事件后从存储器传
    送至这个地址。

  2. 在DMA_CMARx寄存器中设置存储器地址。数据在每个TxE事件后从这个存储区传送至
    I2C_DR。

  3. 在DMA_CNDTRx寄存器中设置所需的传输字节数。在每个TxE事件后,此值将被递减。

  4. 利用DMA_CCRx寄存器中的PL[0:1]位配置通道优先级。

  5. 设置DMA_CCRx寄存器中的DIR位。

  6. 根据应用要求可以配置在整个传输完成一半或全部完成时发出中断请求。

  7. 通过设置DMA_CCTx寄存器上的EN位激活通道。

当DMA控制器中设置的数据传输数目已经完成时,DMA控制器给I2C接口发送一个传输结束的EOT/ EOT_1信号。在中断允许的情况下,将产生一个DMA中断。
如果使用DMA进行发送时,不要设置I2C_CR2寄存器的ITBUFEN位。

利用DMA接收

通过设置I2C_CR2寄存器中的DMAEN位可以激活DMA接收模式。每次接收到数据字节时,将由DMA把I2C_DR寄存器的数据传送到设置的存储区(参考DMA说明)。设置DMA通道进行I2C接收,须执行以下步骤(x是通道号):

  1. 在DMA_CPARx寄存器中设置I2C_DR寄存器的地址。数据将在每次RxNE事件后从此地
    址传送到存储区。
  2. 在DMA_CMARx寄存器中设置存储区地址。数据将在每次RxNE事件后从I2C_DR寄存器
    传送到此存储区。
  3. 在DMA_CNDTRx寄存器中设置所需的传输字节数。在每个RxNE事件后,此值将被递
    减。
  4. 用DMA_CCRx寄存器中的PL[0:1]配置通道优先级。
  5. 清除DMA_CCRx寄存器中的DIR位,根据应用要求可以设置在数据传输完成一半或全部
    完成时发出中断请求。
  6. 设置DMA_CCRx寄存器中的EN位激活该通道。

当DMA控制器中设置的数据传输数目已经完成时,DMA控制器给I2C接口发送一个传输结束的
EOT/ EOT_1信号。在中断允许的情况下,将产生一个DMA中断。
注: 如果使用DMA进行接收时,不要设置I2C_CR2寄存器的ITBUFEN位

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/954297.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux————pxe网络批量装机

目录 一、概述 什么是pxe pxe组件 二、搭建交互式pxe装机 一、配置基础环境 二、配置vsftpd 三、配置tftp 四、准备pxelinx.0文件、引导文件、内核文件 一、准备pxelinux.0 二、准备引导文件、内核文件 五、配置dhcp 一、安装dhcp 二、配置dhcp 六、创建default文…

要用linux,不会shell 基本语法搞不来~

01.变量 1、环境变量 echo $PATH 2、自定义变量 hello"hello_world" echo $hello 3、存储 Linux 命令执行结果作为变量 (2 种方式&#xff0c;推荐使用第二中&#xff0c;第一种是 ~键上面的斜点比较难识别) filesls -al path(pwd)注意点定义变量号两边不能有空…

koa路由自动注册

安装 pnpm install require-directory 路由加载 static initRouters() {// 绝对路径const apiDir ${process.cwd()}/router;// 自动加载路由requireDirectory(module, apiDir, {visit: whenLoadModule});// 判断加载模块是否是路由function whenLoadModule(obj) {if (obj i…

小白带你学习linux的ELK日志收集系统

目录 目录 一、概述 1、ELK由三个组件构成 2、作用 3、为什么使用&#xff1f; 二、组件 1、elasticsearch 2、logstash 3、kibana 三、架构类型 1、ELK 2、ELKK 3、ELFK 4、ELFKK 四、ELK日志收集系统集群实验 1、实验拓扑 2、环境配置 3、 安装node1与node2…

算法设计 || 第12题:12皇后回溯算法(C语言代码)

之前关于8皇后更详细总结&#xff1a; 算法设计 || 实验四 回溯算法-八皇后问题&#xff08;纯手敲保姆级详细讲解小白适用头歌解析&#xff09;_MSY&#xff5e;学习日记分享的博客-CSDN博客 学习的功夫一定要在平时&#xff0c;这样你考试前不必慌张&#xff0c;不用着急&a…

Leetcode54螺旋矩阵

思路&#xff1a;用set记录走过的地方&#xff0c;记下走的方向&#xff0c;根据方向碰壁变换 class Solution:def spiralOrder(self, matrix: list[list[int]]) -> list[int]:max_rows len(matrix)max_cols len(matrix[0])block_nums max_cols * max_rowscount 1i 0j…

这个 web 自动化测试框架真香 ,selenium进阶pro plus版

在 web 自动化测试当中&#xff0c; selenium 架构应该是很难绕过的&#xff0c;很多宣称要超 selenium 的下一代 web 自动化测试框架最终都败下阵来。 不过&#xff0c; selenium 的 api 确实比较复杂&#xff0c;所以也有很多库尝试对他进行上层封装&#xff0c;splinter 是其…

Ubuntu18.04安装cuDNN

注册账号 https://developer.nvidia.com/rdp/cudnn-archive 该网站下载安装包需要先进行注册。登录成功后&#xff0c;找到与CUDA对应的版本。 选择Linux版本进行下载。 下载后的格式为.tar.xz 解压 tar xvJf cudnn-linux-x86_64-8.9.3.28_cuda12-archive.tar.xz配置环境 su…

全新配色 smart 精灵#1 BRABUS性能版于成都车展正式上市

新奢智能纯电汽车品牌smart携旗下全系车型&#xff0c;盛大亮相第二十六届成都国际汽车展览会&#xff08;4号馆H402展台&#xff09;。此次车展上&#xff0c;smart首次公开披露智能驾驶技术迭代战略路线、“南拓西进”全球化市场布局策略&#xff0c;加速成为全球领先的智能纯…

运动耳机怎么选、运动耳机排行榜前十名推荐

对于热爱跑步和运动的人来说&#xff0c;音乐是最好的伴侣&#xff0c;可以消除孤独感和乏味。随着蓝牙无线耳机的出现&#xff0c;耳机的无线化给我们的生活带来了巨大改变&#xff0c;特别是在运动场景下&#xff0c;蓝牙无线耳机的优势更加明显。然而&#xff0c;在选择适合…

网工内推 | 信息安全工程师,五险一金,技术氛围浓厚

01 正佳科技 招聘岗位&#xff1a;信息安全工程师 职责描述&#xff1a; 1、负责运维管理IT基础设施&#xff0c;包括数据中心、网络、安全等&#xff1b; 2、负责公司内外网络的策略调整&#xff0c;安全策略、网络准入等的推进及优化&#xff1b; 3、负责建设、完善公司信息…

python购物程序

本文主要内容&#xff1a;用python实现一个购物程序 功能介绍&#xff1a; 1、显示商品列表 2、根据输入的工资判断是否能买得起选择的商品 3、购物车存放已经购买的商品 #1.购物车程序 # 输入工资 while True:wage input("请输入您的工资:")if not wage.isdigit()…

语音专线如何接入呼叫中心系统

想要了解语音专线是否可以接入呼叫中心系统&#xff0c;首先要分别了解什么是语音专线和什么是呼叫中心系统。语音专线接入呼叫中心系统想要实现什么功能&#xff0c;下面小易就来科普一下。 什么是语音专线&#xff1f;语音专线可以理解为联通、电信、移动运营商提供的一种语音…

IDEA使用Docker插件

修改Docker配置 1.执行命令vim /usr/lib/systemd/system/docker.service&#xff0c;在ExecStart配置的后面追加 -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock ExecStart/usr/bin/dockerd -H fd:// --containerd/run/containerd/containerd.sock -H tcp://0.0.0.0:…

SAP如何添加货币类型。TCODE : OY03

最近公司业务增加了个白俄罗斯的卢布货币收入BYN 类型。系统没有这个类型&#xff08;系统有BYR&#xff09;&#xff0c;于是我找了下资料。添加货币类型步骤如下。或者TCODE&#xff1a; OY03

CSS魔术师Houdini,用浏览器引擎实现高级CSS效果

开门见山&#xff0c;直接上货 &#x1f50d; CSS Houdini是什么&#xff1f; “Houdini”一词引用自“Harry Houdini”&#xff0c;他是一位20世纪的著名魔术师&#xff0c;亦被称为史上最伟大的魔术师、逃脱术师及特级表演者。 我们都知道&#xff0c;浏览器在渲染网页显示样…

MySQL的字符转义

表象 表结构如下: 其中 content 字段存放json之后的数据,这个json数据里面 extra 字段的内容又是一段json,如下: INSERT INTO future.test_escape_character( id, title, content, is_del )VALUES ( 2, 我的博客, {"web_id":31415,"name":"清澄秋…

搭建Ubuntu本地web小游戏网站并通过内网穿透实现公网用户远程访问的步骤指南

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《高效编程技巧》《cpolar》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 前言1. 本地环境服务搭建2. 局域网测试访问3. 内网穿透3.1 ubuntu本地安装cpolar内网穿透3.2 创建隧道3.3 测试公网访…

ActiveMQ配置初探

文章目录 配置wrapper相关配置wrapper是干什么用的MQ的运行内存修改【需修改】修改内容题外话 wrapper.log配置【需修改】引起的问题优化方式 activemq.xml相关配置官网介绍配置管理后台的认证授权【建议修改】配置broker【根据自己需求更改】配置允许jmx监控关闭消息通知持久化…

YOLO V5 和 YOLO V8 对比学习

参考文章&#xff1a; 1、YOLOv5 深度剖析 2、如何看待YOLOv8&#xff0c;YOLOv5作者开源新作&#xff0c;它来了&#xff01;? 3、anchor的简单理解 完整网络结构 YOLO v5和YOLO v8的Head部分 YOLO v8的Head 部分相比 YOLOv5 改动较大&#xff0c;换成了目前主流的解耦头结构…