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

STM32F407实现SD卡的读写功能

文章目录

  • 前言
  • 一、SDIO简介
  • 二、SD卡操作
    • 1.读操作
    • 2.写数据
    • 3.擦除操作
    • 4.最终效果
    • 5.完整工程


前言

在STM32中存储空间是有限的,对于需要存储大量数据的项目就需要外扩存储空间,一般会选择FLASH、EEPROM或者SD卡。SD是这三种中可达空间最大的,所以SD卡在大容量存储中发挥着中要的作用,本次OTA项目是使用了SD卡存储云平台下发的数据流,再将所需的数据存在eflash内,SD卡还可以起到版本恢复的作用。

一、SDIO简介

想要操作SD卡可以通过SPI或者SDIO的方式,本实验使用的是SDIO的方式对SD卡操作,下图是SDIO和MCU接口示意图:
在这里插入图片描述
SD卡使用9-pin接口通信,其中3根电源线、1根时钟线、1根命令线和4根数据线,具体如下:
CLK:时钟线,由SDIO主机产生,即由STM32控制器输出;
CMD:命令控制线,SDIO主机通过该线发送命令控制SD卡,如果命令要求SD卡提供应答,SD卡也是通过该线传输应答信息;
D0-3:数据线,传输读写数据;SD卡可将D0拉低表示忙状态;
VDD、VSS1、VSS2:电源和地信号。

二、SD卡操作

1.读操作

SD数据是以块(Black)形式传输的,SDHC卡数据块长度一般为512字节,数据可以从主机到卡,也可以是从卡到主机。数据块需要CRC位来保证数据传输成功,还要注意:起始位使用0,结束位还使用1。
在这里插入图片描述
上图是SD卡读操作的框图,其中CRC是直接通过硬件的方式进行的。
代码如下:

u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt)
{u8 sta=SD_OK;long long lsector=sector;u8 n;lsector<<=9;if((u32)buf%4!=0){for(n=0;n<cnt;n++){sta=SD_ReadBlock(SDIO_DATA_BUFFER,lsector+512*n,512);//单个sector的读操作memcpy(buf,SDIO_DATA_BUFFER,512);buf+=512;} }else{if(cnt==1)sta=SD_ReadBlock(buf,lsector,512);    	//单个sector的读操作else sta=SD_ReadMultiBlocks(buf,lsector,512,cnt);//多个sector  }return sta;
}SD_Error SD_ReadBlock(u8 *buf,long long addr,u16 blksize)
{	  SD_Error errorstatus=SD_OK;u8 power;u32 count=0,*tempbuff=(u32*)buf;//转换为u32指针 u32 timeout=SDIO_DATATIMEOUT;   if(NULL==buf)return SD_INVALID_PARAMETER; SDIO->DCTRL=0x0;	//数据控制寄存器清零(关DMA) if(CardType==SDIO_HIGH_CAPACITY_SD_CARD)//大容量卡{blksize=512;addr>>=9;}   SDIO_DataInitStructure.SDIO_DataBlockSize= SDIO_DataBlockSize_1b ;//清除DPSM状态机配置SDIO_DataInitStructure.SDIO_DataLength= 0 ;SDIO_DataInitStructure.SDIO_DataTimeOut=SD_DATATIMEOUT ;SDIO_DataInitStructure.SDIO_DPSM=SDIO_DPSM_Enable;SDIO_DataInitStructure.SDIO_TransferDir=SDIO_TransferDir_ToCard;SDIO_DataInitStructure.SDIO_TransferMode=SDIO_TransferMode_Block;SDIO_DataConfig(&SDIO_DataInitStructure);if(SDIO->RESP1&SD_CARD_LOCKED)return SD_LOCK_UNLOCK_FAILED;//卡锁了if((blksize>0)&&(blksize<=2048)&&((blksize&(blksize-1))==0)){power=convert_from_bytes_to_power_of_two(blksize);	SDIO_CmdInitStructure.SDIO_Argument =  blksize;SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN;SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;SDIO_SendCommand(&SDIO_CmdInitStructure);//发送CMD16+设置数据长度为blksize,短响应errorstatus=CmdResp1Error(SD_CMD_SET_BLOCKLEN);	//等待R1响应 if(errorstatus!=SD_OK)return errorstatus;   	//响应错误	}else return SD_INVALID_PARAMETER;	  	 SDIO_DataInitStructure.SDIO_DataBlockSize= power<<4 ;//清除DPSM状态机配置SDIO_DataInitStructure.SDIO_DataLength= blksize ;SDIO_DataInitStructure.SDIO_DataTimeOut=SD_DATATIMEOUT ;SDIO_DataInitStructure.SDIO_DPSM=SDIO_DPSM_Enable;SDIO_DataInitStructure.SDIO_TransferDir=SDIO_TransferDir_ToSDIO;SDIO_DataInitStructure.SDIO_TransferMode=SDIO_TransferMode_Block;SDIO_DataConfig(&SDIO_DataInitStructure);SDIO_CmdInitStructure.SDIO_Argument =  addr;SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_READ_SINGLE_BLOCK;SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;SDIO_SendCommand(&SDIO_CmdInitStructure);//发送CMD17+从addr地址出读取数据,短响应 errorstatus=CmdResp1Error(SD_CMD_READ_SINGLE_BLOCK);//等待R1响应   if(errorstatus!=SD_OK)return errorstatus;   		//响应错误	 if(DeviceMode==SD_POLLING_MODE)						//查询模式,轮询数据	 {INTX_DISABLE();//关闭总中断(POLLING模式,严禁中断打断SDIO读写操作!!!)while(!(SDIO->STA&((1<<5)|(1<<1)|(1<<3)|(1<<10)|(1<<9))))//无上溢/CRC/超时/完成(标志)/起始位错误{if(SDIO_GetFlagStatus(SDIO_FLAG_RXFIFOHF) != RESET)						//接收区半满,表示至少存了8个字{for(count=0;count<8;count++)			//循环读取数据{*(tempbuff+count)=SDIO->FIFO;}tempbuff+=8;	 timeout=0X7FFFFF; 	//读数据溢出时间}else 	//处理超时{if(timeout==0)return SD_DATA_TIMEOUT;timeout--;}} if(SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) != RESET)		//数据超时错误{										   SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT); 	//清错误标志return SD_DATA_TIMEOUT;}else if(SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) != RESET)	//数据块CRC错误{SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL);  		//清错误标志return SD_DATA_CRC_FAIL;		   }else if(SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) != RESET) 	//接收fifo上溢错误{SDIO_ClearFlag(SDIO_FLAG_RXOVERR);		//清错误标志return SD_RX_OVERRUN;		 }else if(SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) != RESET) 	//接收起始位错误{SDIO_ClearFlag(SDIO_FLAG_STBITERR);//清错误标志return SD_START_BIT_ERR;		 }   while(SDIO_GetFlagStatus(SDIO_FLAG_RXDAVL) != RESET)	//FIFO里面,还存在可用数据{*tempbuff=SDIO->FIFO;	//循环读取数据tempbuff++;}INTX_ENABLE();//开启总中断SDIO_ClearFlag(SDIO_STATIC_FLAGS);//清除所有标记}else if(DeviceMode==SD_DMA_MODE){TransferError=SD_OK;StopCondition=0;			//单块读,不需要发送停止传输指令TransferEnd=0;				//传输结束标置位,在中断服务置1SDIO->MASK|=(1<<1)|(1<<3)|(1<<8)|(1<<5)|(1<<9);	//配置需要的中断 SDIO->DCTRL|=1<<3;		 	//SDIO DMA使能 SD_DMA_Config((u32*)buf,blksize,DMA_DIR_PeripheralToMemory); while(((DMA2->LISR&(1<<27))==RESET)&&(TransferEnd==0)&&(TransferError==SD_OK)&&timeout)timeout--;//等待传输完成 if(timeout==0)return SD_DATA_TIMEOUT;//超时if(TransferError!=SD_OK)errorstatus=TransferError;  }   return errorstatus; 
}

在上面的代码中SD_ReadDisk()实际上是调用的是SD_ReadBlock(),该函数先发送 CMD16,用于设置块大小,然后配置 SDIO 控制器读数据的长度,这里我们用到函数 convert_from_bytes_to_power_of_two 求出 blksize 以 2 为底的指数,用于 SDIO 读数据长度设置。然后发送 CMD17(带地址参数 addr),从指定地址读取一块数据。最后,根据我们所设置的模式(查询模式/DMA 模式),从 SDIO_FIFO 读出数据。在这些函数中数据buf的地址必须4字节对齐。

2.写数据

在这里插入图片描述
上图是写数据的程序框图,可以看到左边的CMD,操作SD卡就是通过发送这个CMD来控制的,这里的内容比较多不做很详细的介绍,想要深入了解可以查看手册。
程序代码:

u8 SD_WriteDisk(u8*buf,u32 sector,u8 cnt)
{u8 sta=SD_OK;u8 n;long long lsector=sector;lsector<<=9;if((u32)buf%4!=0){for(n=0;n<cnt;n++){memcpy(SDIO_DATA_BUFFER,buf,512);sta=SD_WriteBlock(SDIO_DATA_BUFFER,lsector+512*n,512);//单个sector的写操作buf+=512;} }else{if(cnt==1)sta=SD_WriteBlock(buf,lsector,512);    	//单个sector的写操作else sta=SD_WriteMultiBlocks(buf,lsector,512,cnt);	//多个sector  }return sta;
}
SD_Error SD_WriteBlock(u8 *buf,long long addr,  u16 blksize)
{SD_Error errorstatus = SD_OK;u8  power=0,cardstate=0;u32 timeout=0,bytestransferred=0;u32 cardstatus=0,count=0,restwords=0;u32	tlen=blksize;						//总长度(字节)u32*tempbuff=(u32*)buf;					if(buf==NULL)return SD_INVALID_PARAMETER;//参数错误  SDIO->DCTRL=0x0;							//数据控制寄存器清零(关DMA)SDIO_DataInitStructure.SDIO_DataBlockSize= 0; ;//清除DPSM状态机配置SDIO_DataInitStructure.SDIO_DataLength= 0 ;SDIO_DataInitStructure.SDIO_DataTimeOut=SD_DATATIMEOUT ;SDIO_DataInitStructure.SDIO_DPSM=SDIO_DPSM_Enable;SDIO_DataInitStructure.SDIO_TransferDir=SDIO_TransferDir_ToCard;SDIO_DataInitStructure.SDIO_TransferMode=SDIO_TransferMode_Block;SDIO_DataConfig(&SDIO_DataInitStructure);if(SDIO->RESP1&SD_CARD_LOCKED)return SD_LOCK_UNLOCK_FAILED;//卡锁了if(CardType==SDIO_HIGH_CAPACITY_SD_CARD)	//大容量卡{blksize=512;addr>>=9;}    if((blksize>0)&&(blksize<=2048)&&((blksize&(blksize-1))==0)){power=convert_from_bytes_to_power_of_two(blksize);	SDIO_CmdInitStructure.SDIO_Argument = blksize;//发送CMD16+设置数据长度为blksize,短响应 	SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN;SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;SDIO_SendCommand(&SDIO_CmdInitStructure);	errorstatus=CmdResp1Error(SD_CMD_SET_BLOCKLEN);	//等待R1响应  if(errorstatus!=SD_OK)return errorstatus;   	//响应错误	 }else return SD_INVALID_PARAMETER;	SDIO_CmdInitStructure.SDIO_Argument = (u32)RCA<<16;//发送CMD13,查询卡的状态,短响应 	SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SEND_STATUS;SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;SDIO_SendCommand(&SDIO_CmdInitStructure);	errorstatus=CmdResp1Error(SD_CMD_SEND_STATUS);		//等待R1响应  if(errorstatus!=SD_OK)return errorstatus;cardstatus=SDIO->RESP1;													  timeout=SD_DATATIMEOUT;while(((cardstatus&0x00000100)==0)&&(timeout>0)) 	//检查READY_FOR_DATA位是否置位{timeout--;  SDIO_CmdInitStructure.SDIO_Argument = (u32)RCA<<16;//发送CMD13,查询卡的状态,短响应SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SEND_STATUS;SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;SDIO_SendCommand(&SDIO_CmdInitStructure);	errorstatus=CmdResp1Error(SD_CMD_SEND_STATUS);	//等待R1响应   if(errorstatus!=SD_OK)return errorstatus;		cardstatus=SDIO->RESP1;													  }if(timeout==0)return SD_ERROR;SDIO_CmdInitStructure.SDIO_Argument = addr;//发送CMD24,写单块指令,短响应 	SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_WRITE_SINGLE_BLOCK;SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;SDIO_SendCommand(&SDIO_CmdInitStructure);	errorstatus=CmdResp1Error(SD_CMD_WRITE_SINGLE_BLOCK);//等待R1响应  if(errorstatus!=SD_OK)return errorstatus;   	 StopCondition=0;									//单块写,不需要发送停止传输指令 SDIO_DataInitStructure.SDIO_DataBlockSize= power<<4; ;	//blksize, 控制器到卡	SDIO_DataInitStructure.SDIO_DataLength= blksize ;SDIO_DataInitStructure.SDIO_DataTimeOut=SD_DATATIMEOUT ;SDIO_DataInitStructure.SDIO_DPSM=SDIO_DPSM_Enable;SDIO_DataInitStructure.SDIO_TransferDir=SDIO_TransferDir_ToCard;SDIO_DataInitStructure.SDIO_TransferMode=SDIO_TransferMode_Block;SDIO_DataConfig(&SDIO_DataInitStructure);timeout=SDIO_DATATIMEOUT;if (DeviceMode == SD_POLLING_MODE){INTX_DISABLE();//关闭总中断(POLLING模式,严禁中断打断SDIO读写操作!!!)while(!(SDIO->STA&((1<<10)|(1<<4)|(1<<1)|(1<<3)|(1<<9))))//数据块发送成功/下溢/CRC/超时/起始位错误{if(SDIO_GetFlagStatus(SDIO_FLAG_TXFIFOHE) != RESET)							//发送区半空,表示至少存了8个字{if((tlen-bytestransferred)<SD_HALFFIFOBYTES)//不够32字节了{restwords=((tlen-bytestransferred)%4==0)?((tlen-bytestransferred)/4):((tlen-bytestransferred)/4+1);for(count=0;count<restwords;count++,tempbuff++,bytestransferred+=4){SDIO->FIFO=*tempbuff;}}else{for(count=0;count<8;count++){SDIO->FIFO=*(tempbuff+count);}tempbuff+=8;bytestransferred+=32;}timeout=0X3FFFFFFF;	//写数据溢出时间}else{if(timeout==0)return SD_DATA_TIMEOUT;timeout--;}} if(SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) != RESET)		//数据超时错误{										   SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT); 	//清错误标志return SD_DATA_TIMEOUT;}else if(SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) != RESET)	//数据块CRC错误{SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL);  		//清错误标志return SD_DATA_CRC_FAIL;		   }else if(SDIO_GetFlagStatus(SDIO_FLAG_TXUNDERR) != RESET) 	//接收fifo下溢错误{SDIO_ClearFlag(SDIO_FLAG_TXUNDERR);		//清错误标志return SD_TX_UNDERRUN;		 }else if(SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) != RESET) 	//接收起始位错误{SDIO_ClearFlag(SDIO_FLAG_STBITERR);//清错误标志return SD_START_BIT_ERR;		 }   INTX_ENABLE();//开启总中断SDIO_ClearFlag(SDIO_STATIC_FLAGS);//清除所有标记  }else if(DeviceMode==SD_DMA_MODE){TransferError=SD_OK;StopCondition=0;			//单块写,不需要发送停止传输指令 TransferEnd=0;				//传输结束标置位,在中断服务置1SDIO->MASK|=(1<<1)|(1<<3)|(1<<8)|(1<<4)|(1<<9);	//配置产生数据接收完成中断SD_DMA_Config((u32*)buf,blksize,DMA_DIR_MemoryToPeripheral);				//SDIO DMA配置SDIO->DCTRL|=1<<3;								//SDIO DMA使能.  while(((DMA2->LISR&(1<<27))==RESET)&&timeout)timeout--;//等待传输完成 if(timeout==0){SD_Init();	 					//重新初始化SD卡,可以解决写入死机的问题return SD_DATA_TIMEOUT;			//超时	 }timeout=SDIO_DATATIMEOUT;while((TransferEnd==0)&&(TransferError==SD_OK)&&timeout)timeout--;if(timeout==0)return SD_DATA_TIMEOUT;			//超时	 if(TransferError!=SD_OK)return TransferError;}  SDIO_ClearFlag(SDIO_STATIC_FLAGS);//清除所有标记errorstatus=IsCardProgramming(&cardstate);while((errorstatus==SD_OK)&&((cardstate==SD_CARD_PROGRAMMING)||(cardstate==SD_CARD_RECEIVING))){errorstatus=IsCardProgramming(&cardstate);}   return errorstatus;
}

可以看到SD_WriteDisk()函数调用的是SD_WriteBlock()函数,执行写数据块命令(CMD24-27)时,主机把一个或多个数据块从主机传送到卡中,同时在每个数据块的末尾传送一个CRC码。一个支持写数据块命令的卡应该始终能够接收由WRITE_BL_LEN定义的数据块。如果CRC校验错误,卡通过SDIO_D信号线指示错误,传送的数据被丢弃而不被写入,所有后续(在多块写模式下)传送的数据块将被忽略。

3.擦除操作

SD_Error SD_Erase(uint32_t startaddr, uint32_t endaddr)
{SD_Error errorstatus = SD_OK;uint32_t delay = 0;__IO uint32_t maxdelay = 0;uint8_t cardstate = 0;/*!< 检查SD卡是否支持擦除操作 */if (((CSD_Tab[1] >> 20) & SD_CCCC_ERASE) == 0){errorstatus = SD_REQUEST_NOT_APPLICABLE;return(errorstatus);}maxdelay = 120000 / ((SDIO->CLKCR & 0xFF) + 2);    //延时,根据时钟分频设计来计算if (SDIO_GetResponse(SDIO_RESP1) & SD_CARD_LOCKED)    //卡上锁{errorstatus = SD_LOCK_UNLOCK_FAILED;return(errorstatus);}/* SDHC卡,地址参数为块地址,每块512字节,SDSC卡地址为字节地址 */if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) {startaddr /= 512;endaddr /= 512;}/*!<  ERASE_GROUP_START (CMD32) and SD_CMD_SD_ERASE_GRP_END(CMD33) */if ((SDIO_STD_CAPACITY_SD_CARD_V1_1 == CardType) || (SDIO_STD_CAPACITY_SD_CARD_V2_0 == CardType) || (SDIO_HIGH_CAPACITY_SD_CARD == CardType)){/*!< Send CMD32 SD_ERASE_GRP_START with startaddr  */SDIO_CmdInitStructure.SDIO_Argument = startaddr;SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_START;SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;SDIO_SendCommand(&SDIO_CmdInitStructure);errorstatus = CmdResp1Error(SD_CMD_SD_ERASE_GRP_START);if (errorstatus != SD_OK){return(errorstatus);}/*!< Send CMD33 SD_ERASE_GRP_END with endaddr  */SDIO_CmdInitStructure.SDIO_Argument = endaddr;SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_END;SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;SDIO_SendCommand(&SDIO_CmdInitStructure);errorstatus = CmdResp1Error(SD_CMD_SD_ERASE_GRP_END);if (errorstatus != SD_OK){return(errorstatus);}}/*!< Send CMD38 ERASE */SDIO_CmdInitStructure.SDIO_Argument = 0;SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_ERASE;SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;SDIO_SendCommand(&SDIO_CmdInitStructure);errorstatus = CmdResp1Error(SD_CMD_ERASE);if (errorstatus != SD_OK){return(errorstatus);}for (delay = 0; delay < maxdelay; delay++){}/*!< 等待SD卡的内部时序操作完成 */errorstatus = IsCardProgramming(&cardstate);while ((errorstatus == SD_OK) && ((SD_CARD_PROGRAMMING == cardstate) || (SD_CARD_RECEIVING == cardstate)))//SD卡编程 || SD卡接收{errorstatus = IsCardProgramming(&cardstate);    //检测SD卡是否在读写操作}return(errorstatus);
}

可以看到上面的函数是通过CMD32、33、38来对SD卡进行擦除的。

4.最终效果

在这里插入图片描述
在上图中可以看到mian函数对SDIO进行了初始化,之后打印出SD卡的基本信息,再写入1024个数据到两个扇区,当按下按键的时候再将两个扇区的数据读取出来并且打印到串口,在串口助手可以看到数据从0到FF之后又从0开始,这是因为我在存数据是定义的变量是u8,所以到达最大值就重新计数了。

5.完整工程

我用夸克网盘分享了「SD卡读写.rar」,点击链接即可保存。
链接:https://pan.quark.cn/s/98bdba7113e4

相关文章:

  • #[特殊字符]Rhino建模教程 · 第一章:正方体建模入门
  • docker 启用portainer,容器管理软件
  • Flowable工程化改造相关文档
  • AI大模型如何重塑科研范式:从“假说驱动”到“数据涌现”
  • 11【模块学习】DS18B20(一):使用学习
  • 免费的内网穿刺工具和免费域名
  • **Windows 系统**的常用快捷键大全
  • C语言实战:用Pygame打造高难度水果消消乐游戏
  • Linux路漫漫
  • 千树万树梨花开
  • 【18】Strongswan encoding详解 message2
  • 面试题:请描述一下你在项目中是如何进行性能优化的?针对哪些方面进行了优化,采取了哪些具体的措施?
  • 【JavaScript】二十一、日期对象
  • 数据结构*集合框架顺序表-ArrayList
  • 网络的起点:深入解析计算机网络中的网络接口层
  • 在JavaScript中实现文件下载完成后自动打开
  • Python multiprocessing模块介绍
  • ns-3中UDP饱和流发包时间间隔设置最合理值
  • Redis + Caffeine打造超速两级缓存架构
  • 未支付订单如何释放库存
  • 最高法知识产权法庭:6年来新收涉外案件年均增长23.2%
  • 三亚一景区发生游客溺亡事件,官方通报:排除他杀
  • 特朗普称已为俄乌问题设最后期限,届时美国态度或生变
  • 央行上海总部:上海个人住房贷款需求回升,增速连续半年回升
  • 下周起上海浦东将投放5000万元消费券,预计分五周发放
  • 官宣一起打造智能汽车品牌后,华为喊话上汽要准备好足够产能