当前位置: 首页 > article >正文

I2C协议—读写EEPROM(24Cxx为例)

STM32 I2C协议详解与应用实践-CSDN博客

实际项目中移植IIC协议及读写EEprom代码:



/* AT24C08,写次数达100万次

SOC ,2500次充放电次数,需要500万次记录,需存储均衡
每页

*/

#include "FreeRTOS.h"
#include "task.h"
#include "24C08.h"
#include "CortrolMain.h"


//#define	OP_READ	0xa1		// ?÷?tμ??·ò??°?áè?2ù×÷,0xa1?′?a1010 0001B
#define	EE_ADDR 0xa0		// ?÷?tμ??·ò??°D′è?2ù×÷,0xa1?′?a1010 0000B



#include "include.h"
#define EE_SCL_PIN  PTB2   //模拟IIC的SCL信号  1.修改引脚即可修改IIC接口
#define EE_SDA_PIN  PTC13   //模拟IIC的SDA信号

#define EE_SDA_IN()   GPIO_PDDR_REG(GPIOX[EE_SDA_PIN>> 5]) &= ~(1 << (EE_SDA_PIN&0x1f));  //GPIO_PinSetDir(EE_SDA_PIN, 0);	//输入
#define EE_SDA_OUT()  GPIO_PDDR_REG(GPIOX[EE_SDA_PIN>> 5]) |= (1 << (EE_SDA_PIN&0x1f)); //GPIO_PinSetDir(EE_SDA_PIN, 1);	//输出

#define EE_SCK_OUT()  GPIO_PDDR_REG(GPIOX[EE_SCL_PIN>> 5]) |= (1 << (EE_SCL_PIN&0x1f)); //GPIO_PinSetDir(EE_SDA_PIN, 1);	//输出


#define EE_IIC_SCL    PTB2_OUT                   //SCL      2.修改引脚即可修改IIC接口        
#define EE_IIC_SDA    PTC13_OUT                   //SDA	 
#define EE_READ_SDA   PTC13_INT                    //输入SDA 



/******************************************************************************
*函  数:void EE_IIC_Delay(void)
*功 能:IIC延时
*参  数:无
*返回值:无
*备  注: 移植时只需要将EE_IIC_Delay()换成自己的延时即可
*******************************************************************************/	
void EE_IIC_Delay(uint8_t us)
{
    for(int i = 0; i < 20; i++)    
    {
        __asm("NOP");//core bus 160M  情况下大概IIC速率 400K
    }    
}
/******************************************************************************
*函  数:void IIC_Init(void)
*功 能:IIC初始化
*参  数:无
*返回值:无
*备  注:无
*******************************************************************************/

void EE_IIC_Init(void)
{			
    gpio_init(EE_SCL_PIN,GPO,1) ;
    gpio_init(EE_SDA_PIN,GPO,1) ;
    EE_SCK_OUT();
    EE_SDA_OUT();
    EE_IIC_SCL=1;
    EE_IIC_SDA=1;        
}
	
void EE_IIC_Start(void)
{
	EE_SDA_OUT(); //sda线输出 
	EE_IIC_SDA=1;	
	EE_IIC_SCL=1;
	EE_IIC_Delay(4);
 	EE_IIC_SDA=0; //START:when CLK is high,DATA change form high to low 
	EE_IIC_Delay(4);
	EE_IIC_SCL=0; //钳住I2C总线,准备发送或接收数据 
}

	  
void EE_IIC_Stop(void)
{
	EE_SDA_OUT(); //sda线输出
	EE_IIC_SCL=0;
	EE_IIC_SDA=0; //STOP:when CLK is high DATA change form low to high
    EE_IIC_Delay(4);
	EE_IIC_SCL=1; 
	EE_IIC_SDA=1; //发送I2C总线结束信号
    EE_IIC_Delay(4);							   	
}


uint8_t EE_IIC_WaitAck(void)
{
	uint8_t ucErrTime=0;
	EE_SDA_IN(); //SDA设置为输入  (从机给一个低电平做为应答) 
	EE_IIC_SDA=1;EE_IIC_Delay(1);	   
	EE_IIC_SCL=1;EE_IIC_Delay(1);;	 
	while(EE_READ_SDA)
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			EE_IIC_Stop();
			return 1;
		}
	}
	EE_IIC_SCL=0; //时钟输出0 	   
	return 0;  
} 



void EE_IIC_Ack(void)
{
	EE_IIC_SCL=0;
	EE_SDA_OUT();
	EE_IIC_SDA=0;
	EE_IIC_Delay(1);
	EE_IIC_SCL=1;
	EE_IIC_Delay(2);
	EE_IIC_SCL=0;
}


void EE_IIC_NAck(void)
{
	EE_IIC_SCL=0;
	EE_SDA_OUT();
	EE_IIC_SDA=1;
	EE_IIC_Delay(1);
	EE_IIC_SCL=1;
	EE_IIC_Delay(1);
	EE_IIC_SCL=0;
}					 				     

	  
void EE_IIC_SendByte(uint8_t data)
{                        
    uint8_t t;   
    EE_SDA_OUT(); 	    
    EE_IIC_SCL=0; //拉低时钟开始数据传输
    for(t=0;t<8;t++)
    {              
        EE_IIC_SDA=(data&0x80)>>7;
        EE_IIC_Delay(1);			
        EE_IIC_SCL=1;
        data<<=1;
        EE_IIC_Delay(1);
        EE_IIC_SCL=0;	   
    }
    EE_IIC_Delay(1);
} 	


void EE_IIC_PageSendByte(uint16_t data)
{                        
    uint8_t t,DataIn;   
    EE_SDA_OUT(); 	    
    EE_IIC_SCL=0; //拉低时钟开始数据传输
    DataIn=data&0xff;
    for(t=0;t<8;t++)
    {              
        EE_IIC_SDA=(DataIn&0x80)>>7;
        EE_IIC_Delay(1);			
        EE_IIC_SCL=1;
        data<<=1;
        EE_IIC_Delay(1);
        EE_IIC_SCL=0;	   
    }
    DataIn=(data>>8)&0xff;
    for(t=0;t<8;t++)
    {              
        EE_IIC_SDA=(DataIn&0x80)>>7;
        EE_IIC_Delay(1);			
        EE_IIC_SCL=1;
        data<<=1;
        EE_IIC_Delay(1);
        EE_IIC_SCL=0;	   
    }
    
    
    EE_IIC_Delay(1);
} 

	
uint8_t EE_IIC_ReadByte(uint8_t ack)
{
	uint8_t i,receive=0;
	EE_SDA_IN(); //SDA设置为输入模式 等待接收从机返回数据
    for(i=0;i<8;i++ )
	{
        EE_IIC_SCL=0; 
        EE_IIC_Delay(1);
        EE_IIC_SCL=1;
        receive<<=1;
        if(EE_READ_SDA)receive++; //从机发送的电平
        EE_IIC_Delay(1); 
    }					 
    if(ack)
        EE_IIC_Ack(); //发送ACK 
    else
        EE_IIC_NAck(); //发送nACK  
    return receive;
}


uint8_t EE_IIC_ReadByteFromSlave(uint8_t I2C_Addr,uint8_t reg,uint8_t *buf)
{
	EE_IIC_Start();	
	EE_IIC_SendByte(I2C_Addr);	 //发送从机地址
	if(EE_IIC_WaitAck()) //如果从机未应答则数据发送失败
	{
		EE_IIC_Stop();
		return 1;
	}
	EE_IIC_SendByte(reg); //发送寄存器地址
	EE_IIC_WaitAck();	  
	
	EE_IIC_Start();
	EE_IIC_SendByte(I2C_Addr+1); //进入接收模式			   
	EE_IIC_WaitAck();
	*buf=EE_IIC_ReadByte(0);	   
    EE_IIC_Stop(); //产生一个停止条件
	return 0;
}


uint8_t EE_EE_IIC_SendByteToSlave(uint8_t I2C_Addr,uint8_t reg,uint8_t data)
{
	EE_IIC_Start();
	EE_IIC_SendByte(I2C_Addr); //发送从机地址
	if(EE_IIC_WaitAck())
	{
		EE_IIC_Stop();
		return 1; //从机地址写入失败
	}
	EE_IIC_SendByte(reg); //发送寄存器地址
    EE_IIC_WaitAck();	  
	EE_IIC_SendByte(data); 
	if(EE_IIC_WaitAck())
	{
		EE_IIC_Stop(); 
		return 1; //数据写入失败
	}
	EE_IIC_Stop(); //产生一个停止条件
    
	return 0;
}

uint8_t EE_IIC_WriteByteToSlave(uint8_t I2C_Addr,uint8_t reg,uint8_t data)
{
	EE_IIC_Start();
	EE_IIC_SendByte(I2C_Addr); //发送从机地址
	if(EE_IIC_WaitAck())
	{
		EE_IIC_Stop();
		return 1; //从机地址写入失败
	}
	EE_IIC_SendByte(reg); //发送寄存器地址
    EE_IIC_WaitAck();	  
	EE_IIC_SendByte(data); 
	if(EE_IIC_WaitAck())
	{
		EE_IIC_Stop(); 
		return 1; //数据写入失败
	}
	EE_IIC_Stop(); //产生一个停止条件
    
	return 0;
}
uint8_t EE_IIC_ReadMultByteFromSlave(uint8_t dev, uint8_t reg, uint8_t length, uint8_t *data)
{
    uint8_t count = 0;
	uint8_t temp;
	EE_IIC_Start();
	EE_IIC_SendByte(dev); //发送从机地址
	if(EE_IIC_WaitAck())
	{
		EE_IIC_Stop(); 
		return 1; //从机地址写入失败
	}
	EE_IIC_SendByte(reg); //发送寄存器地址
    EE_IIC_WaitAck();	  
	EE_IIC_Start();
	EE_IIC_SendByte(dev+1); //进入接收模式	
	EE_IIC_WaitAck();
    for(count=0;count<length;count++)
	{
		if(count!=(length-1))
            temp = EE_IIC_ReadByte(1); //带ACK的读数据
		else  
            temp = EE_IIC_ReadByte(0); //最后一个字节NACK
        
		data[count] = temp;
	}
    EE_IIC_Stop(); //产生一个停止条件
    return 0;
}


/******************************************************************************
*函  数:uint8_t IICwriteBytes(uint8_t dev, uint8_t reg, uint8_t length, uint8_t* data)
*功 能:将多个字节写入指定设备 指定寄存器
*参  数:dev     目标设备地址
reg	   寄存器地址
length  要写的字节数
*data   要写入的数据将要存放的指针
*返回值:1成功 0失败
*备  注:无
*******************************************************************************/ 
uint8_t EE_IIC_WriteMultByteToSlave(uint8_t dev, uint8_t reg, uint8_t length, uint8_t* data)
{   
 	uint8_t count = 0;
	EE_IIC_Start();
	EE_IIC_SendByte(dev); //发送从机地址
	if(EE_IIC_WaitAck())
	{
		EE_IIC_Stop();
		return 1; //从机地址写入失败
	}
    EE_IIC_SendByte(reg&0xff); //发送寄存器地址,就是页地址
    EE_IIC_WaitAck();	  
	for(count=0;count<length;count++)
	{
		EE_IIC_SendByte(data[count]); 
		if(EE_IIC_WaitAck()) //每一个字节都要等从机应答
		{
			EE_IIC_Stop();
			return 1; //数据写入失败
		}
	}
	EE_IIC_Stop(); //产生一个停止条件
    
	return 0;
}


uint8_t EEPROM_PageWrite(uint8 PageNum, uint8_t length, uint8_t* data)
{
    uint8 Addr,PageAddr,i;
    uint16 j;
  //每页16个字节,24c08一共64页,页地址是页号*16
  /*
  if(PageNum<16)//0~15
  {
    PageAddr=16*PageNum;
    Addr=0xA0;
     EE_IIC_WriteMultByteToSlave(Addr,PageAddr,length,data) ;
  }else if(PageNum>=16 && PageNum<=31)
  {
      Addr=0xA0+((((PageNum*16)>>8)&0x03)<<1);
      PageAddr=(PageNum*16)&0xff;
     EE_IIC_WriteMultByteToSlave(Addr,PageAddr,length,data) ;
    
  }else if(PageNum>=32 && PageNum<=47)
  {
      Addr=0xA0+((((PageNum*16)>>8)&0x03)<<1);
      PageAddr=(PageNum*16)&0xff;
     EE_IIC_WriteMultByteToSlave(Addr,PageAddr,length,data) ;
    
    
  }else if(PageNum>=48 && PageNum<=63)
  {
     Addr=0xA0+((((PageNum*16)>>8)&0x03)<<1);
      PageAddr=(PageNum*16)&0xff;
     EE_IIC_WriteMultByteToSlave(Addr,PageAddr,length,data) ;
    
  }*/
  
     //cht- eeprom进行写的时候,MP进行接地处理
    EEPROMMP(0);
//    vTaskDelay( 2/portTICK_RATE_MS );
    EE_IIC_Delay(20);
    DisableInterrupts;
    Addr=0xA0+((((PageNum*16)>>8)&0x07)<<1);
    PageAddr=(PageNum*16)&0xff;
    EE_IIC_WriteMultByteToSlave(Addr,PageAddr,length,data) ;
    EnableInterrupts;
    for(i=0;i<6;i++)
    {
        for(j=0;j<500;j++)
        {
            EE_IIC_Delay(1);
        }
//        vTaskDelay( 1/portTICK_RATE_MS );
    }
    EEPROMMP(1);//cht-恢复高电平,可以读
    return 1;
}

uint8_t EEPROM_PageRead(uint8 PageNum, uint8_t length, uint8_t* data)
{
    uint8 Addr,PageAddr;
  //每页16个字节,24c08一共64页,页地址是页号*16
  
  /*if(PageNum<16)//0~15
  {
    PageAddr=16*PageNum;
    Addr=0xA0;
    EE_IIC_ReadMultByteFromSlave(Addr,PageAddr,length,data) ;
   
  }else if(PageNum>=16 && PageNum<=31)
  {
    // PageAddr=(16*PageNum)%256;
    Addr=0xA0+((((PageNum*16)>>8)&0x03)<<1);
      PageAddr=(PageNum*16)&0xff;
      EE_IIC_ReadMultByteFromSlave(Addr,PageAddr,length,data) ;
    
  }else if(PageNum>=32 && PageNum<=47)
  {
          Addr=0xA0+((((PageNum*16)>>8)&0x03)<<1);
      PageAddr=(PageNum*16)&0xff;
      EE_IIC_ReadMultByteFromSlave(Addr,PageAddr,length,data) ;
    
    
  }else if(PageNum>=48 && PageNum<=63)
  {
          Addr=0xA0+((((PageNum*16)>>8)&0x03)<<1);
      PageAddr=(PageNum*16)&0xff;
      EE_IIC_ReadMultByteFromSlave(Addr,PageAddr,length,data) ;
    
  }*/
    Addr=0xA0+((((PageNum*16)>>8)&0x07)<<1);
    PageAddr=(PageNum*16)&0xff;
    EE_IIC_ReadMultByteFromSlave(Addr,PageAddr,length,data) ;
    return 1; 

}

#define _24C08	        4
#define PAGE_SIZE		16
#define PAGE_SIZE_REM	0x0F
#define MEM_SIZE		0x03ff

#define MAX_ADDR	( (MEM_SIZE+1)*DEV_24CXX_NUM-1 )
//芯片常量
#define DEV_24CXX_MAIN_ADDR	0xA0
#define DEV_24CXX_READ	    0x01
#define DEV_24CXX_WRITE	    0x00
#define IIC_SEND_ACK	    0x10
#define IIC_SEND_NOACK	    0x11

#define IIC_STATE_NOACK	    0x20
#define IIC_STATE_ACK	    0x21

uint16 HardAddr;
void _24CXXX_AddrProcess(unsigned int Addr)
{
    HardAddr = 0x00;
}


void _24CXXX_WriteByte(unsigned int Addr,unsigned char Data)
{
	_24CXXX_AddrProcess(Addr);
	Addr &= MEM_SIZE;	
	
	EE_IIC_Start();
	EE_IIC_SendByte(DEV_24CXX_MAIN_ADDR|(HardAddr<<1)|DEV_24CXX_WRITE);
	EE_IIC_SendByte( ((unsigned char)(Addr>>8)) );
	EE_IIC_SendByte( ((unsigned char)Addr) );
	EE_IIC_SendByte( Data );
	EE_IIC_Stop();

}



unsigned char _24CXXX_ReadByte(unsigned int Addr)
{
	unsigned char temp;
	_24CXXX_AddrProcess(Addr);
	Addr &= MEM_SIZE;
	
	EE_IIC_Start();
	EE_IIC_SendByte(DEV_24CXX_MAIN_ADDR|(HardAddr<<1)|DEV_24CXX_WRITE);
	EE_IIC_SendByte( ((unsigned char)(Addr>>8)) );
	EE_IIC_SendByte( ((unsigned char)Addr) );
	EE_IIC_Start();
	EE_IIC_SendByte(DEV_24CXX_MAIN_ADDR|(HardAddr<<1)|DEV_24CXX_READ);
	temp = EE_IIC_ReadByte(IIC_SEND_NOACK);
	EE_IIC_Stop();
	return temp;
}




void _24CXXX_WriteBytes(unsigned int StartAddr,unsigned char *Data,int DataNum)
{
	unsigned char Num;
	unsigned int i;
	unsigned int y;
	unsigned int yy;
	_24CXXX_AddrProcess(StartAddr);
	yy = StartAddr & MEM_SIZE;

	y = yy&PAGE_SIZE_REM;	//=StartAddr/PAGE_SIZE
	y = PAGE_SIZE - y;	    //要写的第一页中有多少个数据


	if( y <= PAGE_SIZE )	//写第一页(可能不到一页,以使页对齐)
	{
		EE_IIC_Start();
		EE_IIC_SendByte( DEV_24CXX_MAIN_ADDR|(HardAddr<<1)|DEV_24CXX_WRITE );
		EE_IIC_SendByte( ((unsigned char)(yy>>8)) );
		EE_IIC_SendByte( ((unsigned char)yy) );
		for(i=0;i<y;i++)
		{
			if( i>=DataNum )
			{
				break;
			}
			EE_IIC_SendByte( *(Data++) );			
		}
	}
	IIC_Stop();
	//DelayMs(10);
    vTaskDelay( 20/portTICK_RATE_MS );
	DataNum -= y;		//还有多少数据未写
	i = 0;
	Num = 0;			//用于记录已经写了多少页
	while( DataNum>0 )	//数据未写完
	{
		if( i == 0 )
		{
			yy = StartAddr+y+Num*PAGE_SIZE;
			_24CXXX_AddrProcess(yy);
			yy = yy & MEM_SIZE;
			EE_IIC_Start();
			EE_IIC_SendByte( DEV_24CXX_MAIN_ADDR|(HardAddr<<1)|DEV_24CXX_WRITE );
			EE_IIC_SendByte( ((unsigned char)(yy>>8)) );
			EE_IIC_SendByte( ((unsigned char)(yy)) );
		}
		EE_IIC_SendByte( *(Data++) );
		
		i++;
		if(i>=PAGE_SIZE )	//如果一页写完
		{
			i = 0;
			Num++;
			IIC_Stop();
            vTaskDelay( 20/portTICK_RATE_MS );
		}
		DataNum--;
	}
	if(i!=0x0000)	//如果写结束时,并未到达页的最后,则需要补上停止位
	{
		EE_IIC_Stop();
        vTaskDelay( 20/portTICK_RATE_MS );
	}	
}



void _24CXXX_ReadBytes(unsigned int StartAddr,unsigned char *Data,int DataNum)
{
	unsigned char Num;
	unsigned int i;
	unsigned int y;
	unsigned int yy;

	_24CXXX_AddrProcess(StartAddr);
	yy = StartAddr & MEM_SIZE;

	y = yy&PAGE_SIZE_REM;	//=StartAddr/PAGE_SIZE
	y = PAGE_SIZE - y;				//要写的第一页中有多少个数据


	if( y <= PAGE_SIZE )	//写第一页(可能不到一页,以使页对齐)
	{
		EE_IIC_Start();
		EE_IIC_SendByte( DEV_24CXX_MAIN_ADDR|(HardAddr<<1)|DEV_24CXX_WRITE );
		EE_IIC_SendByte( ((unsigned char)(StartAddr>>8)) );
		EE_IIC_SendByte( ((unsigned char)StartAddr) );
		EE_IIC_Start();
		EE_IIC_SendByte(DEV_24CXX_MAIN_ADDR|(HardAddr<<1)|DEV_24CXX_READ);
		for(i=0;i<y;i++)
		{
			if( i>=DataNum )
			{
				break;
			}
			if( (i==DataNum-1)||(i==y-1) )	//如果是该页的最后一个数据,则NOACK
				*Data = EE_IIC_ReadByte(IIC_SEND_NOACK);
			else
				*Data = EE_IIC_ReadByte(IIC_SEND_ACK);
			Data++;			
		}
	}
	EE_IIC_Stop();
	//DelayUs(20);

	DataNum -= y;		//还有多少数据未写
	i = 0;
	Num = 0;			//用于记录已经写了多少页
	while( DataNum>0 )	//数据未写完
	{
		if( i == 0 )
		{
			yy = StartAddr+y+Num*PAGE_SIZE;
			_24CXXX_AddrProcess(yy);
			yy = yy & MEM_SIZE;
			EE_IIC_Start();
			EE_IIC_SendByte( DEV_24CXX_MAIN_ADDR|(HardAddr<<1)|DEV_24CXX_WRITE );
			EE_IIC_SendByte( (unsigned char)(yy>>8) );
			EE_IIC_SendByte( (unsigned char)(yy) );
			EE_IIC_Start();
			EE_IIC_SendByte(DEV_24CXX_MAIN_ADDR|(HardAddr<<1)|DEV_24CXX_READ);
		}
		if( (i==PAGE_SIZE-1)||(DataNum==1) )	//如果是该页或要读的最后一个数据,则NOACK
			*Data = EE_IIC_ReadByte(IIC_SEND_NOACK);
		else
			*Data = EE_IIC_ReadByte(IIC_SEND_ACK);
		Data++;
		
		i++;
		if(i>=PAGE_SIZE )	//如果一页写完
		{
			i = 0;
			Num++;
			EE_IIC_Stop();
			//DelayUs(20);
		}
		DataNum--;
	}
	if(i!=0x0000)	//如果写结束时,并未到达页的最后,则需要补上停止位
	{
		EE_IIC_Stop();
	}
	
}


区分硬件IIC与软件IIC

一般使用软件模拟IIC,,可移植性高


一:模拟IIC与硬件IIC定义?


模拟I2C一般是用GPIO管脚,用软件控制管脚状态以模拟I2C通信波形。
硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用。

二:优缺点


1.硬件I2C的效率要远高于软件的,而软件I2C由于不受管脚限制,接口比较灵活。
2.模拟I2C 是通过GPIO,软件模拟寄存器的工作方式,而硬件(固件)I2C是直接调用内部寄存器进行配置。如果要从具体硬件上来看,可以去看下芯片手册。因为固件I2C的端口是固定的,所以会有所区别。

三:如何区分它们

可以看底层配置,比如IO口配置,如果配置了IO口的功能(IIC功能)那就是固件IIC,否则就是模拟

可以看IIC写函数,看里面有木有调用现成的函数或者给某个寄存器赋值,如果有,则肯定是固件IIC功能,没有的话肯定是数据一个bit一个bit模拟发生送的,肯定用到了循环,则为模拟。

根据代码量判断,模拟的代码量肯定比固件的要大。


http://www.kler.cn/a/537847.html

相关文章:

  • CSGHub高效管理|解锁DeepSeek R1蒸馏模型 :高效推理的新选择
  • 数据库5(MySQL版)
  • harmonyOS生命周期详述
  • 【starrocks学习】之将starrocks表同步到hive
  • Java版本与JDK版本
  • Python----Python高级(网络编程:网络基础:发展历程,IP地址,MAC地址,域名,端口,子网掩码,网关,URL,DHCP,交换机)
  • VSCode 换行符问题
  • Deepseek的起源与发展
  • (六)QT——布局&常用控件——基本的用户输入界面
  • HTTP报文格式
  • 蓝桥杯---力扣题库第38题目解析
  • html css网页制作成品——HTML+CSS爷爷不泡茶的茶网页设计(7页)附源码
  • IDEA安装离线插件(目前提供了MavenHelper安装包)
  • npm中央仓库
  • pycharm ai插件
  • element-plus 解决el-dialog背后的页面滚动问题,及其内容有下拉框出现错位问题
  • 21.[前端开发]Day21-HTML5新增内容-CSS函数-BFC-媒体查询
  • < 评论 > 阿里云 与 腾讯云 国内的轻量应用服务器(VPS)产品对比
  • 【韩顺平linux】部分上课笔记整理
  • 星网锐捷 DMB-BS LED屏信息发布系统taskexport接口处存在敏感信息泄露
  • 机器学习专业毕业设计选题灵感集锦:选题建议
  • C++STL(四)——vector模拟
  • Web自动化测试:如何生成高质量的测试报告
  • Element UI 表单源码原理
  • (六)C++的函数模板与类模板
  • 使用 Nginx 搭建代理服务器(正向代理 HTTPS 网站)指南