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;	//速度50MHzGPIO_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,从机把数据放到SDAMyI2C_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,防止从机干扰,同时从机应答位放到SDAMyI2C_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;	//速度50MHzGPIO_Init(GPIOB, &GPIO_InitStructure);//第三步,使用结构体,对整个I2C进行配置I2C_InitTypeDef I2C_InitStructure;I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;	//I2C的模式,这里选择是I2CI2C_InitStructure.I2C_ClockSpeed = 50000;	//配置SCL的时钟频率,数值越大,SCL频率越高,数据传输就越快,这里写的是50kHzI2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;	//时钟占空比,只有在时钟频率大于100kHz,也就是进入到快速状态时才有用,在小雨100kHz的标准速度下,占空比是标准的1:1I2C_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,使能I2CI2C_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;//控制外设电路,实现指定地址写的时序,来替换上面的WriteRegI2C_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();//控制外设电路,来实现指定地址读的时序,来替换上面的ReadRegI2C_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;
}

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

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

相关文章

友善RK3399v2平台利用rkmpp实现硬件编解码加速

测试VPU 编译mpp sudo apt update sudo apt install gcc g cmake make cd ~ git clone https://github.com/rockchip-linux/mpp.git cd mpp/build/linux/aarch64/ sed -i s/aarch64-linux-gnu-gcc/gcc/g ./arm.linux.cross.cmake sed -i s/aarch64-linux-gnu-g/g/g ./arm.lin…

如何学习ai agent?

如何学习Agent&#xff0c;推荐阅读《动手做AI Agent》这本书。 推荐理由&#xff1a; 1&#xff1a;一本书能够全方位了解并探索Agent的奥秘&#xff01; &#xff08;1&#xff09;Agent的发展进程。 &#xff08;2&#xff09;可以帮我们做哪些事&#xff1a;自动办公&am…

码随想录算法训练营第二十四天| 77. 组合

77. 组合 - 力扣&#xff08;LeetCode&#xff09; class Solution {ArrayList<Integer> path new ArrayList<>();ArrayList<List<Integer>> result new ArrayList<>();public List<List<Integer>> combine(int n, int k) {if(n &…

15-通过JS代码处理窗口滚动条

selenium并不是万能的&#xff0c;页面上有些操作无法实现时&#xff0c;就需要借助JS代码来完成了。selenium提供了一个方法&#xff1a;execute_script()&#xff0c;可以执行JS脚本代码。 比如&#xff1a;当页面上的元素超过一屏后&#xff0c;想操作屏幕下方的元素&#x…

[leetcode hot 150]第一百零八题,将有序数组转换为二叉搜索树

题目&#xff1a;给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你将其转换为一棵 平衡二叉搜索树。 给定一个有序的整数数组,我们需要构建一棵平衡的二叉搜索树。平衡二叉树是指任意一个节点的左右子树的高度差不超过1。 由于给定的数组是有序的…

Prime1 - 提权的另一种解法,彻底搞懂OpenSSL解密渗透提权,超强思路版。

提权枚举 现在我们直接从低权限用户开始&#xff1b;我们先按照提权步骤&#xff0c;简单的系统枚举 虽然我们知道可以利用系统版本低进行内核提权&#xff0c;内核提权虽然比较快比较方便&#xff0c;但也比较暴力&#xff0c;缺点非常明显&#xff1b;很容易导致系统服务中…

【云原生】Kubernetes----POD控制器

目录 引言 一、Pod控制器概述 二、Pod控制器的种类 &#xff08;一&#xff09;ReplicaSet &#xff08;二&#xff09;Deployment &#xff08;三&#xff09;StatefulSet &#xff08;四&#xff09;DaemonSet &#xff08;五&#xff09;Job 三、使用POD控制器 &a…

【Seafile】Seafile容器版文件删除后存储空间不释放问题解决

Seafile是一款非常优秀的网盘系统&#xff0c;我们可以根据官方文档&#xff0c;在本地虚拟机研究Seafile免费版的安装和使用&#xff0c;安装建议采用使用docker容器的方式。 不过在使用过程中&#xff0c;刚接触的小伙伴可能会遇到这样的问题&#xff1a; 删除网盘里面的文…

C++设计模式-状态模式

文章目录 28. 状态模式 运行在VS2022&#xff0c;x86&#xff0c;Debug下。 28. 状态模式 状态模式让一个对象的行为随着内部状态的改变而改变&#xff0c;而该对象也像换了类一样。应用&#xff1a;如在游戏开发中&#xff0c;游戏有不同场景&#xff0c;如主菜单、开始、战斗…

Houdini的PythonScript基本使用

前言 Houdini内置了Python脚本和相应的编辑器, 很多时候想灵活的制作各种Houdini工具, 基本是必须用到 Python。Houdini官方的python提供了非常完善的接口, 比如可以创建各种节点&#xff0c;连接各种节点&#xff0c;遍历节点各种数据&#xff0c;遍历节点参数等等。 Houdin…

word避免画质画质模糊方法

问题描述&#xff1a;   近期写文档时会高频率贴图&#xff0c;粘图过程中发现Word会自动压缩图片画质&#xff0c;而且压缩得很严重&#xff0c;下面是一幅图被压缩前后的画质对比 &#xff08;图片压缩前&#xff09; &#xff08;图片压缩后&#xff09; 解决方案&#x…

基于JSP的九宫格日志网站

你好呀&#xff0c;我是学长猫哥&#xff01;如果有需求可以文末加我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSP技术 工具&#xff1a;浏览器/服务器&#xff08;B/S&#xff09;结构 系统展示 首页 管理员功能模块 用户功能模块 摘要 本…

GPT-4o VS GPT-3.5 完胜

前言&#xff1a; 最近&#xff0c;GPT-4o已经限时免费开放了&#xff0c;试了一下&#xff0c;然后&#xff0c;说我的时间到了&#xff0c;然后&#xff0c;有给我转到3.5&#xff0c;正好遇到一个问题做一下对吧&#xff0c;感觉4O完胜啊。3.5还是很好胡诌&#xff0c;也就…

[Algorithm][动态规划][子序列问题][最长定差子序列][最长的斐波那契子序列的长度]详细讲解

目录 1.最长定差子序列1.题目链接2.算法原理详解3.代码实现 2.最长的斐波那契子序列的长度1.题目链接2.算法原理详解3.代码实现 1.最长定差子序列 1.题目链接 最长定差子序列 2.算法原理详解 思路&#xff1a; 确定状态表示 -> dp[i]的含义 以i位置元素为结尾的所有子序列…

11.2 选择排序

目录 11.2 选择排序 11.2.1 算法特性 11.2 选择排序 选择排序&#xff08;selection sort&#xff09;的工作原理非常简单&#xff1a;开启一个循环&#xff0c;每轮从未排序区间选择最小的元素&#xff0c;将其放到已排序区间的末尾。 设数组的长度为 &#x1d45b;…

华东师范大学研究团队《Ecology Letters 》揭示植物如何改变其物候以响应全球变化

自工业革命以来&#xff0c;人类活动导致多种环境因子同时发生变化&#xff0c;包括气候变暖、降水模式改变、氮沉降增加和大气CO2升高。这些变化预计会影响植物生命周期事件的季节时序—植物物候&#xff08;Nature Reviews Earth & Environment | 傅伯杰院士团队发文阐述…

[C][栈帧]详细讲解

目录 1.栈帧1.进程地址空间2.栈帧说明 2.认识相关寄存器3.认识相关汇编命令4.过程理解5.栈帧总结6.补充 1.栈帧 1.进程地址空间 .进程地址空间 2.栈帧说明 调用函数&#xff0c;形成栈帧函数返回&#xff0c;释放栈帧局部变量是存放在栈区上的栈区内存的使用习惯是&#xff…

BPTT算法详解:深入探究循环神经网络(RNN)中的梯度计算【原理理解】

引言 在深度学习领域中&#xff0c;我们经常处理的是独立同分布&#xff08;i.i.d&#xff09;的数据&#xff0c;比如图像分类、文本生成等任务&#xff0c;其中每个样本之间相互独立。然而&#xff0c;在现实生活中&#xff0c;许多数据具有时序结构&#xff0c;例如语言模型…

什么是PLAB?

接上文PLAB---》 可以看到和TLAB很像&#xff0c;PLAB即 Promotion Local Allocation Buffers。用在年轻代对象晋升到老年代时。 在多线程并行执行YGC时&#xff0c;可能有很多对象需要晋升到老年代&#xff0c;此时老年代的指针就"热"起来了&#xff0c;于是搞了个…

函数的创建和调用

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 提到函数&#xff0c;大家会想到数学函数吧&#xff0c;函数是数学最重要的一个模块&#xff0c;贯穿整个数学学习过程。在Python中&#xff0c;函数…