STM32_IIC

1、IIC简介

        I2C,即Inter IC Bus。是由Philips公司开发的一种串行通用数据总线,主要用于近距离、低速的芯片之间的通信;有两根通信线:SCL(Serial Clock)用于通信双方时钟的同步、SDA(Serial Data)用于收发数据;具有同步,半双工,带数据应答,支持总线挂载多设备(一主多从、多主多从)等特点。

        IIC总线是一种多主机总线,连接在IIC总线上的器件分为主机和从机,主机有权发起和结束一次通信,而从机只能被主机呼叫;当总线上有多个主机同时启用总线时,IIC也具备冲突检测和仲裁的功能来防止错误产生;每个连接到IIC总线上的器件都有一个唯一的地址(一般是7bit),且每个器件都可以作为主机也可以作为从机(同一时刻只能有一个主机),总线上的器件增加和删除不影响其它器件正常工作;IIC总线在通信时,总线上发送数据的器件为发送器,接收数据的器件为接收器。

        所有I2C设备的SCL连在一起,SDA连在一起;设备的SCL和SDA均要配置成开漏输出模式;SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右。

        在STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担;支持多主机模型;支持7位/10位地址模式;支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz);支持DMA;兼容SMBus协议。

        STM32F103C8T6 硬件I2C资源:I2C1、I2C2

        对于串口这样的异步时序来说,软件实现非常麻烦,硬件实现非常简单,所以串口的实现基本全都倒向硬件了;而对IIC这样的同步时序来说,软件实现反而简单灵活,硬件实现,相比之下,不能完全让人省心,所以IIC的实现,软件模拟的情况还是比较多的。

        考虑到硬件IIC也有很多独有的优势,比如执行效率比较高,可以节省软件资源,功能比较强大,可以实现完整的多主机通信模型,时序波形规整,通信速率快等,所以硬件IIC也是有相应的应用场景的。

2、IIC结构图

        以下结构图基于STM32F103xxx

         这里的数据收发的核心部分是数据寄存器和数据移位寄存器,当我们需要发送数据时,可以把一个字节的数据写到数据寄存器DR,当移位寄存器没有数据移位时,这个数据寄存器的值就是进一步转到移位寄存器这里,在移位的过程中,我们就可以直接把下一个数据放在数据寄存器里等着了,一旦数据发送完成,下一个数据就可以无缝连接,继续发送。当数据由数据寄存器转到移位寄存器时,就会置状态寄存器的值TXE位为1,表示发送寄存器为空。

        在接收时,也是这一路,输入的数据,一位一位的从引脚移入到移位寄存器里,当一个字节的数据收齐之后,数据就整体从移位寄存器转到数据寄存器,同时置标志位RXNE,表示接收寄存器非空,这时就可以把数据从数据寄存器读出来了。

        基本框图

3、IIC时序

3.1 IIC时序基本单元

        起始条件:SCL高电平期间,SDA从高电平切换到低电平

        终止条件:SCL高电平期间,SDA从低电平切换到高电平

        发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节 。

        接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA) 

        发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答

        接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA) 

3.2 IIC时序

3.2.1 指定地址写

        对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)

        这里这个指定设备,通过从机地址来确定,这里这个指定地址就是某个设备内部的寄存器地址。

3.2.2  当前地址读

        对于指定设备(Slave Address),在当前地址指针指示的地址下,读取从机数据(Data)

        在这个时序图中,主机发送第一个字节,指定读之后,第2个字节读写的方向就要反过来了,控制权交给从机,由从机来发送数据,这时主机无法去指定是由从机哪个寄存器发出的数据,那么这里这个当前地址指针指示的地址就很重要了。在从机中,所有的寄存器都被分配到了一个线性区域中,并且会有一个单独的指针变量指示着其中一个寄存器,这个指针上电一般默认0地址,并且每写入和读出一个字节后,这个指针就会自动自增一次,移动到下一个位置,那么在调用当前地址读的时序时,主机没有指定要读哪个地址,从机就会返回当前指针指向的寄存器的值。

3.2.3 指定地址读

        对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)

        这里先指定从机地址是1101000,读写标志位是0,代表我要进行写的操作。经常从机应答之后,再发送一个字节,第二个字节用来指定地址,这个数据就写入到了从机的地址指针中了,也就是从机接受到这个数据之后,他的寄存器指针就指向了0x19这个位置,之后再重复一个起始条件,因为指定读写标志位只能是跟着起始条件的第一个字节,如果想要切换读写方向,只能再来个起始条件,然后起始条件后,重新寻址并且指定读写标志位,此时读写标志位是1,代表我要开始读了,这时候接收到的就是0x19下的数据。

        写入的地址会存在地址指针里面,所以这个地址并不会因为时序的停止而消失。

4、操作流程

4.1 主机发送

        指定地址写:首先初始化之后,总线默认空闲状态,STM32默认是从模式,为了产生一个起始条件,STM32需要写入控制寄存器(这个要看一下手册的寄存器描述),之后STM32由从模式转为主模式,控制完硬件电路之后,要检查标志位,来看看硬件有没有达到我们想要的状态,在这里起始条件之后会发生EV5事件,这个EV5事件就可以把它当成标志位(这里使用EV几事件,而不写具体标志位,是因为有的事件会产生多个标志位,这里的EV几事件就是包含了多个标志位的大标志位,在库函数中也会有对应),检查到起始条件已发送的情况下就可以发送一个字节的从机地址了,从机地址需要写到数据寄存器DR中,写入DR后,硬件电路会把这个字节发送到移位寄存器中,再把这一个字节发送到IIC总线上,之后硬件会自动接收应答并判断,如果没有应答,硬件会置应答失败的标志位,然后这个标志位可以申请中断来提醒我们,在寻址完成之后,会发生EV6事件(代表主模式下地址发送结束),EV6事件结束之后是EV8_1事件(TXE标志位=1,移位寄存器空,数据寄存器空),这时需要我们写入数据寄存器DR进行数据发送了,一旦写入数据寄存器之后,因为移位寄存器也是空,所以DR会立刻转到移位寄存器进行发送,这时就是EV8事件(移位寄存器非空,数据寄存器空),这时就是移位寄存器正在发数据的状态,所以流程这里,数据1的时序就发生了,之后应该是写入了下一个数据,数据2此刻应该被写入到数据寄存器里等着了,然后接收应答位之后,数据2就转入移位寄存器进行发送,此时的状态是移位寄存器非空,数据寄存器空,所以这是EV8事件又发生了,之后重复该过程,一旦我们检测要EEV8事件,就可以写入下一个数据了,最后当我们想要发送的数据写完之后,这时就没有新的数据写入数据寄存器了,当移位寄存器当前的数据移位完成时,此时就是移位寄存器空,数据寄存器也空的状态,这个事件就是这里的EV8_2事件,当检测到EV8_2时,就可以产生终止条件了,产生终止条件在控制寄存器中有相应的位可以控制,到这里,一个完整的时序就发送完成了。

4.2 主机接收 

        从七位主接收来看,起始,从机地址+读,接收应答,然后就是,接收数据,发送应答,最后一个数据给非应答,之后终止。从这个时序看,这是当前地址读的一个时序。

5、示例代码

5.1 软件读写IIC

#include "stm32f10x.h"                  // Device header
#include "Delay.h"     

//#define SCL_PORT		GPIOB
//#define SCL_PIN		GPIO_Pin_10

//对端口和引脚的封装,方便后续修改和移植
void MyI2C_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
	//I2C时序可以稍微慢一点,但是如果快了,那就要看一下手册对时序时间的要求
	Delay_us(10);
	
}

void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
	//I2C时序可以稍微慢一点,但是如果快了,那就要看一下手册对时序时间的要求
	Delay_us(10);
	
}

uint8_t MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
	Delay_us(10);
	return BitValue;
}

void MyI2C_Init(void)
{
	//软件读取I2C只要gpio的库函数就可以了,I2C的库函数就不用看了
	
	//任务一,将SCL和SDA都初始化为开漏输出模式
	//任务二,将SCL和SDA都置高电平
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	//配置端口
	//先定义一个结构体变量
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;	//开漏输出,开漏输出模式仍然可以输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;	
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//速度50MHz
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	//释放总线,SCL和SDA处于高电平,此时I2C总线处于空闲状态
	GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);
	
	
}

void MyI2C_Start(void)
{
	//根据I2C时序要求,这里兼顾了开始时序和Sr期间时序
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(1);
	
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(0);
}	

void MyI2C_Stop(void)
{
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(1);
}

void MyI2C_SendByte(uint8_t Byte)
{
//	MyI2C_W_SDA(Byte & 0x80);	//取出数据的最高位,SDA是高位先行
//	//释放SCL,读走放在SDA的数据
//	MyI2C_W_SCL(1);
//	//再拉低SCL,就可以放下一位数据了
//	MyI2C_W_SCL(0);
	
	uint8_t i;
	for (i = 0; i < 8; i ++)
	{
		MyI2C_W_SDA(Byte & (0x80 >> i));	//0x80 >> i,表示0x80右移i位
		MyI2C_W_SCL(1);
		MyI2C_W_SCL(0);
	}
}

uint8_t MyI2C_ReceiveByte(void)
{
	uint8_t i, Byte = 0x00;
	
	//主机释放SDA,从机把数据放到SDA
	MyI2C_W_SDA(1);

	for (i = 0; i < 8; i ++)
	{
		//主机释放SCL,SCL高电平,主机就能读取数据了
		MyI2C_W_SCL(1);
		if (MyI2C_R_SDA() == 1)
		{
			Byte |= (0x80 >> i);
		}
		//再次拉低SCL,这时从机就会把数据放在SDA上
		MyI2C_W_SCL(0);
	}
	return Byte;
	
}

void MyI2C_SendAck(uint8_t AckBit)
{
	//函数进来时,SCL低电平,主机把AckBit放到SDA上
	MyI2C_W_SDA(AckBit);	
	//SCL高电平,从机读取应答
	MyI2C_W_SCL(1);
	//SCL低电平,进入下一个时序单元
	MyI2C_W_SCL(0);
}

uint8_t MyI2C_ReceiveAck(void)
{
	uint8_t AckBit;
	//函数进来时,SCl低电平
	//主机释放SDA,防止从机干扰,同时从机应答位放到SDA
	MyI2C_W_SDA(1);
	//SCL高电平,主机读取应答位
	MyI2C_W_SCL(1);
	AckBit = MyI2C_R_SDA();
	//SCL低电平,进入下一个时序单元
	MyI2C_W_SCL(0);
	
	return AckBit;
	
}

5.2 硬件读写IIC

//MyI2C_Init();
	//用硬件来配置I2C外设,对I2C2外设进行初始化,来替换之前用软件实现的MyI2C_Init();	
	//第一步,开启I2C外设和对应GPIO口的时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	//第二步,把I2C外设对应的GPIO口初始化为复用开漏模式
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;	//复用开漏输出,开漏是I2C硬件要求,复用就是GPIO的控制权要交给硬件外设
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;	
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	//速度50MHz
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	
	//第三步,使用结构体,对整个I2C进行配置
	I2C_InitTypeDef I2C_InitStructure;
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;	//I2C的模式,这里选择是I2C
	I2C_InitStructure.I2C_ClockSpeed = 50000;	//配置SCL的时钟频率,数值越大,SCL频率越高,数据传输就越快,这里写的是50kHz
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;	//时钟占空比,只有在时钟频率大于100kHz,也就是进入到快速状态时才有用,在小雨100kHz的标准速度下,占空比是标准的1:1
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;	//应答位配置,这里给enable,默认是给应答的
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;	//这里是指定STM32作为从机,可以响应几位的地址,这里选择7位地址
	I2C_InitStructure.I2C_OwnAddress1 = 0x00;	//自身地址1,这个也是stm32作为从机使用的,用于指定stm32的自身地址,方便别的主机呼叫它,这里暂时不需要做从机被别人使唤,随便给一个,只要不和总线上其它设备的地址重复就可以了
	I2C_Init(I2C2, &I2C_InitStructure);
	//第四步,I2C_Cmd,使能I2C
	I2C_Cmd(I2C2, ENABLE);
//封装指定地址写和指定地址读的时序
//指定地址写寄存器
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
//	MyI2C_Start();
//	MyI2C_SendByte(MPU6050_ADDRESS);	//从机地址+读写位
//	MyI2C_ReceiveAck();
//	//发送指定寄存器地址
//	MyI2C_SendByte(RegAddress);
//	MyI2C_ReceiveAck();
//	//发送指定要写入指定寄存器地址下的数据
//	MyI2C_SendByte(Data);
//	MyI2C_ReceiveAck();
//	//终止时序
//	MyI2C_Stop();
	
	uint32_t Timeout;
	
	//控制外设电路,实现指定地址写的时序,来替换上面的WriteReg
	I2C_GenerateSTART(I2C2, ENABLE);	//生成起始条件
	//对于非阻塞的程序,在函数结束之后,都要等待相应的标志位,来确保这个函数的操作执行到位了
	//对照PPT流程图,等待EV5的到来,stm32默认为从机,发送起始条件后变为主机
	Timeout = 10000;
	while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS)	//监测EV5事件是否发生了
	//在程序中如果while死循环等待用多了,一旦总线出问题了,就很容易造成整个程序卡死,还要设计一个超时退出的机制
	{
		Timeout --;
		if (Timeout == 0)
		{
			break;	//使用break跳出这个循环,使用return跳出整个函数
			//在实际项目中,如果想让代码更加完善,这里不能只是简单的break了
			//这里还应该做一些相应的错误处理操作,比如说打印错误日志、进行系统复位
			//或者说,如果项目设计危险的机械结构,就要评估一下,是不是应该进行紧急停机的操作
		}
	}
//	MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);	//这一句就等同与上面的等待事件和超时退出的结合
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);	//发送从机地址,第三个参数是方向,也就是从机地址的最低位,读写位
	//在这个库函数中,发送数据都自带了接收应答的过程,同样,接收数据也自带了发送应答的过程,如果应答错误,硬件会通过中断和标志位来提示我们,所以这里发送地址后,应答位就不需要处理了
	//等待EV6事件
	while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);
	//写入DR,发送数据
	I2C_SendData(I2C2, RegAddress);
	//等待EV8事件
	while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING) != SUCCESS);
	//发送数据
	I2C_SendData(I2C2, Data);
	//等待事件,这里这个是最后一个字节,要等待EV8_2事件
	while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS);
	
	I2C_GenerateSTOP(I2C2, ENABLE);
}

//指定地址读
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
//	MyI2C_Start();
//	MyI2C_SendByte(MPU6050_ADDRESS);	//从机地址+读写位
//	MyI2C_ReceiveAck();
//	//发送指定寄存器地址
//	MyI2C_SendByte(RegAddress);
//	MyI2C_ReceiveAck();
//	
//	//转入读的时序,就必须重新指定读写位,就必须重新起始
//	MyI2C_Start();
//	MyI2C_SendByte(MPU6050_ADDRESS | 0x01);		//原从机地址,读写位为1
//	MyI2C_ReceiveAck();		//接收应答后,总线控制权就正式交给从机了
//	Data = MyI2C_ReceiveByte();
//	//主机接收后,要给从机发送一个应答
//	//参数给0,就是给从机应答,给1,就是不给从机应答;想继续读多个字节,那就要给应答,从机收到应答后就会继续发送数据
//	MyI2C_SendAck(1);
//	MyI2C_Stop();
	
	//控制外设电路,来实现指定地址读的时序,来替换上面的ReadReg
	I2C_GenerateSTART(I2C2, ENABLE);	//生成起始条件
	while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);	//监测EV5事件是否发生了
	//在程序中如果while死循环等待用多了,一旦总线出问题了,就很容易造成整个程序卡死,还要设计一个超时退出的机制
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);	//发送从机地址,第三个参数是方向,也就是从机地址的最低位,读写位
	while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);

	//写入DR,发送数据
	I2C_SendData(I2C2, RegAddress);
	while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS);
	
	I2C_GenerateSTART(I2C2, ENABLE);	//重复生成起始条件
	while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS);	//监测EV5事件是否发生了
	
	I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);	//第三个参数改为Receiver之后,函数内部就会自动把MPU6050_ADDRESS这个地址的最低位置1了,就不需要手动来改了
	while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS);	
	//在接收最后一个字节之前,也就是EV7_1事件那里,需要提前把ACK置0,STOP置1,如果只需要读取一个字节,那在EV6事件之后就要立刻ACK置0,STOP置1,要是设置晚了,时序上就会多一个字节出来
	I2C_AcknowledgeConfig(I2C2, DISABLE);
	I2C_GenerateSTOP(I2C2, ENABLE);
	
	//等待EV7事件,等EV7事件产生后,一个字节的数据就已经在DR里面了,我们读取DR即可拿出这一个字节
	while (I2C_CheckEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS);
	Data = I2C_ReceiveData(I2C2);
	
	//ack再次置1,我们的想法是,默认状态下ACK就是1,给从机应答,在接收最后一个字节之前,临时把ACK置0,给非应答。
	//所以在接收函数的最后,要回复默认的ACk = 1,这个流程是为了方便指定地址收多个字节
	I2C_AcknowledgeConfig(I2C2, ENABLE);
	
	return Data;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

echarts渐变色与css渐变色互转(两个坐标点转角度)

前言 用于 echarts 的小伙伴都知道&#xff0c;他使用的渐变色写法和 css 的写法不一样。css 中直接使用角度定义渐变的方向&#xff0c;而 echarts 使用的是两个坐标点来进行标识方向&#xff08;线性渐变&#xff09;。 本文主要针对线性渐变的转换 那怎么在 css 中使用 e…

BrainGPT1,一个帮你b站点歌放视频的多模态多轮对话模型

BrainGPT1&#xff0c;一个帮你b站点歌放视频的多模态多轮对话模型 返回论文目录 项目地址 模型地址 作者&#xff1a;华东师范大学&#xff0c;计算机科学与技术学院&#xff0c;智能教育研究院的小怪兽会微笑。 介绍 BrainGPT1是一个工具调用多轮对话模型&#xff0c;与G…

[机器学习]GPT LoRA 大模型微调,生成猫耳娘

往期热门专栏回顾 专栏描述Java项目实战介绍Java组件安装、使用&#xff1b;手写框架等Aws服务器实战Aws Linux服务器上操作nginx、git、JDK、VueJava微服务实战Java 微服务实战&#xff0c;Spring Cloud Netflix套件、Spring Cloud Alibaba套件、Seata、gateway、shadingjdbc…

BU01板卡引脚

概述 BU01 是一款高速采集卡&#xff0c;主要用于高带宽数据采集及传输&#xff0c;应用领域多为数据中 心及数据采集领域。 端口提供60Gbps 传输带宽&#xff0c;可兼容2 个SFP万兆网口&#xff0c;和1 个40GE QSFP 光 口。和主机通信采用的是PCIE 2.0 x8 模式&#xff0c;最…

C++哈希的应用:位图 布隆过滤器 哈希切割

目录 位图 bitset 构造空间 将某个位变为0 将某个位变为1 检查是否存在 完整代码 拓展问题一 ​编辑 拓展问题二 布隆过滤器 判断是否存在 使用场景 哈希切割 拓展问题一 拓展问题二 位图 问题&#xff1a;有四十个亿未排序的不重复的无符号整数&#xff0c;此…

算法导论 总结索引 | 第三部分 第十四章:数据结构的扩张

1、通过存储 额外信息的方法来扩张一 种标准的数据结构&#xff0c;然后对这种数据结构&#xff0c;编写新的操作来支持所需的应用。因为添加的信息 必须要能被该数据结构上的常规操作更新和维护 2、通过扩张红黑树构造出的两种数据结构&#xff1a;14.1介绍 一种支持一般动态…

对boot项目拆分成cloud项目的笔记

引言&#xff1a;这里我用的是新版本的技术栈 spring-boot-starter-parent >3.2.5 mybatis-spring-boot-starter >3.0.3 mybatis-plus-boot-starter >3.5.5 spring-cloud-dependencies …

给Docker一个辈分(备份),免得无后...

定期备份所有 Docker 镜像 Linux 脚本 创建一个名为 backup_all_docker_images.sh 的脚本文件&#xff0c;内容如下&#xff1a; #!/bin/bash# 定义变量 BACKUP_DIR"/backup/docker" TIMESTAMP$(date "%Y%m%d%H%M") BACKUP_FILE"${BACKUP_DIR}/doc…

vx小程序初学

小程序初学 在我还没接触到微信小程序之前&#xff0c;通常使用轮播要么手写或使用swiper插件去实现&#xff0c;当我接触到微信小程序之后&#xff0c;我看到了微信小程序的强大之处&#xff0c;让我为大家介绍一下吧&#xff01; swiper与swiper-item一起使用可以做轮播图 …

Facebook开户 | Facebook二不限户

Facebook二不限户的正确使用方法 Facebook 二不限是指 Facebook 国内二不限户&#xff0c;是通过代理开出来的一种特殊账户&#xff0c;️需要广告主准备主页。 其特点是&#xff1a;限主页、不限域名、额度没解限&#xff0c;解限后则不限额度。 相比于三不限户&#xff0c;…

Keras 3.0强势回归,助力深度学习

大家好&#xff0c;Keras的简洁代码风格一直受到开发者的青睐&#xff0c;自从Keras宣布支持Pytorch和Jax后&#xff0c;开发者们迎来了新的选择。 本文将介绍Keras 3.0的实用技巧&#xff0c;以一个典型的编码器-解码器循环神经网络为例&#xff0c;展示如何利用子类化API构建…

【Ubuntu】100 系统字体安装和更改

系统&#xff1a;Ubuntu18.04LTS 1 Why we need&#xff1f; 写这篇经验贴的原因&#xff1a; ①我需要装一下中文字体&#xff08;Qt要用&#xff09;&#xff1b; ②想调一下字体大小和默认中文字体的样式 2 装第三方字体 Step1&#xff1a;安装软件Font Manager sudo ap…

AI数据分析:用kimi生成一个正弦波数学动画

正弦波公式: ƒ(x) a * sin(x x0) b 公式中&#xff1a; a: 决定正弦函数振动幅度的大小&#xff1b; x0:表示x开始比0拖后的弧度值&#xff1b; b&#xff1a;表示函数偏离X轴的距离&#xff1b; 对于难以理解的学生来说&#xff0c;可以用动画把这个公式直观的展现出…

数据结构05:树与二叉树 习题02[C++]

考研笔记整理&#xff0c;本篇作为二叉树的入门习题&#xff0c;供小伙伴们参考~&#x1f95d;&#x1f95d; 之前的博文链接在此&#xff1a;数据结构05&#xff1a;树与二叉树[C]-CSDN博客~&#x1f95d;&#x1f95d; 第1版&#xff1a;王道书的课后习题~&#x1f9e9;&am…

曲面细分技术在AI去衣中的创新应用

引言&#xff1a; 随着人工智能技术的飞速发展&#xff0c;其在图像处理领域的应用日益广泛。其中&#xff0c;AI去衣技术因其独特的应用场景而备受瞩目。在这一技术的发展过程中&#xff0c;曲面细分技术发挥了至关重要的作用。本文将深入探讨曲面细分技术在AI去衣中的作用及其…

[AI OpenAI] 推出ChatGPT Edu

一种负担得起的解决方案&#xff0c;帮助大学将AI负责任地引入校园。 我们宣布推出ChatGPT Edu&#xff0c;这是一个专为大学设计的ChatGPT版本&#xff0c;旨在负责任地向学生、教职员工、研究人员和校园运营部署AI。ChatGPT Edu由GPT-4o提供支持&#xff0c;能够跨文本和视觉…

【计算机毕设】设计与实现基于SpringBoot的在线文档管理系统 - 源码免费(私信领取)

免费领取源码 &#xff5c; 项目完整可运行 &#xff5c; v&#xff1a;chengn7890 诚招源码校园代理&#xff01; 1. 研究目的 在当今信息爆炸的时代&#xff0c;文档管理对于任何组织都至关重要。基于SpringBoot的在线文档管理系统的设计旨在为用户提供一个便捷、高效、安全的…

unityBIM

Revit模型到Unity勉强能用 1、Revit直接导出FBX&#xff0c;然后拖到unity里面 2、通过Navisworks导出FBX&#xff0c;拖到unity里面。 我什么都还没做&#xff0c;只建立了一个空的URP效果&#xff0c;把FBX拖进去&#xff0c;挂了一个相机控制器&#xff0c;效果勉强看得过…

IP路由策略1

控制层面:路由协议传递路由信息的流量--对应的方向 数据层面:设备间具体访问时请求的流量--对应方向 控制层面方向与数据层面方向一定相反 在控制层面流量进或出的接口上&#xff0c;抓取流量后&#xff0c;修改其中参数或删除该信息&#xff0c;最终起到影响路由器路由表的生…

React UseMemo源码分析

useMemo useMemo 是 React 提供的内置 Hooks&#xff0c;主要作用就是缓存&#xff0c;如果依赖项没有变化&#xff0c;Memo 方法不会再次执行&#xff0c;计算量比较高的方法可以使用&#xff0c;从而提高用户体验。本文将通过一个例子跟踪 Memo 的创建、更新流程。 App.js …