IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> SPI:数据通道有俩,数据寄存器就一个 -> 正文阅读

[嵌入式]SPI:数据通道有俩,数据寄存器就一个

SPI:串行外围设备接口,主要应用在EEPROM、FLASH、实时时钟、AD转换器、数字信号处理器和数字信号解码器之间。高速全双工,同步通信,只占四根引脚:MISO 主设备数据输入,从设备数据输出;MOSI 主设备数据输出,从设备数据输入;SCLK 时钟信号,由主设备产生;CS 从设备片选信号,由主设备控制。

一、功能

在这里插入图片描述在这里插入图片描述MISO:主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据
MOSI:主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据
SCK:串口时钟,作为主设备的输出,从设备的输入,主设备的SPI时钟输入到从设备中以保证时钟同步
NSS:从设备选择。这是一个可选的引脚,用来选择主/从设备。它的功能是用来作为“片选引脚”,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突
其实四根引脚作用相对好理解:MISO字面意思就是master input servent out即主机输入,从机输出线,也就说如果咱把MCU配置成主机模式,那么这根引脚就是用来接收其他从机外设的返回信号的;如果把MCU配置成从机模式,那么这根引脚就是MCU给其他主机外设返回数据用的;即找准自己的定位。
当一个SPI设备需要发送广播数据,它必须拉低NSS信号,以通知所有其它的设备它是主设备;如果它不能拉低NSS,这意味着总线上有另外一个主设备在通信,这时将产生一个硬件失败错误(Hard Fault)。
在这里插入图片描述

移出的同时移入,写出的同时接收:SPI通信时发生的事情,看一下SPI数据寄存器

在这里插入图片描述SPI的数据寄存器是16位的,我们可以选择8位或16位收发,上图显然是选择了8位进行收发,即每次传递8位数据。咱这MCU的SPI模块里有个DR寄存器,另一边外设的SPI模块里也有个DR寄存器,如果我们MCU当主机,外设当从机,然后主设备向从设备写入数据即发送数据:那么主设备里的DR寄存器从高位开始一位一位的取出(这里以先发高位为例子,向左移,左边为高位,每取出来一个高位,低位就会空出来一位),取出来后送给从设备,从设备则把收到的位放到自己DR寄存器的低位,难道这不会把从设备DR寄存器里面的数据给覆盖掉吗?不会的,因为在接收来自主设备的位数据同时,从设备的DR寄存器也在向左移动(即把自己的高位取出来送到主设备空出来的低位),把自己的低位给空出来,这空出来的低位就是用来接收主设备发过来的数据的。总而言之,主从设备共用一个时钟,在时钟控制下俩设备的DR寄存器都往左移把高位取出来,低位空出来,这就导致主设备向从设备发送(写入)数据时,还会收到从设备发送过来的数据,如果主机想读取从机的DR寄存器,主机需要向从机随便发送一些数据从而使从机把自己的DR数据发给主机。这就导致发送(写入)数据和接收(读取)数据是绑定在一起了

SPI中断:上面的原理框图中的CR2寄存器控制

在这里插入图片描述在这里插入图片描述CR2可以看出SPI有:TXEIE(发送寄存器为空,触发中断,即发送完后对应的标志位TXE会置位,从而触发中断),RXNEIE(接收缓冲区非空即在接收数据或者收满了,同样是对应的RXEN置位,触发中断),ERRIR(收发时出现了错误,触发中断好报警),SSOE(多主机模式下用的),TXDMAEN(召唤DMA把其他地方的数据搬运到SPI的发送寄存器),RXDMAEN(召唤DMA及时把SPI的接收寄存器的内容搬运到其他地方去)
在这里插入图片描述理解一下这个DMA和SPI的配合用法:
为了达到最大通信速度,需要及时往SPI发送缓冲器填数据,同样接收缓冲器中的数据也必须及时读走以防止溢出。为了方便高速率的数据传输,SPI实现了一种采用简单的请求/应答的DMA机制。当SPI_CR2寄存器上的对应使能位被设置时,SPI模块可以发出DMA传输请求。发送缓冲器和接收缓冲器亦有各自的DMA请求。
发送时,在每次TXE被设置为’1’时发出DMA请求(TXE是发送寄存器为空的标志位,说明此时DMA可以搬运数据到发送寄存器了,而TXDMAEN表示允许DMA往SPI搬运),DMA控制器则写数据至SPI_DR寄存器,TXE标志因此而被清除。接收时,在每次RXNE被设置为’1’时发出DMA请求(RXEN是SPI的接受寄存器满了的标志位,说明此时DMA可以搬运SPI的接受寄存器内容到其他地方了),DMA控制器则从SPI_DR寄存器读出数据,RXNE标志因此而被清除。(注:SPI只有一个DR寄存器即接受寄存器和发送寄存器都是指DR,叙述中只是为了方便才这样区分说法)
在发送模式下,当DMA已经传输了所有要发送的数据(DMA_ISR寄存器的TCIF标志变为’1’)后,可以通过监视BSY标志以确认SPI通信结束,这样可以避免在关闭SPI或进入停止模式时,破坏最后一个数据的传输。因此软件需要先等待TXE=1,然后等待BSY=0

发送缓冲器空闲标志(TXE) :此标志为’1’时表明发送缓冲器为空,可以写下一个待发送的数据进入缓冲器中。当写入SPI_DR时,TXE标志被清除。接收缓冲器非空(RXNE) :此标志为’1’时表明在接收缓冲器中包含有效的接收数据。读SPI数据寄存器可以清除此标志。忙(Busy)标志:BSY标志由硬件设置与清除(写入此位无效果),此标志表明SPI通信层的状态。当它被设置为’1’时,表明SPI正忙于通信。

SPI参数配置:上面的原理框图中的CR1寄存器

在这里插入图片描述在这里插入图片描述在这里插入图片描述这个寄存器功能还挺多的,只简单看看重要的几个吧,以后需要的话再看:DFF(SPI一次发8位还是16位),RXONLY(是否是全双工模式),SSM(是否软件控制NSS位),LSBFIRST(发送是先发高位数据还是低位数据),SPE(SPI使能),BR[2:0](波特率控制,波特率本质由SPI时钟决定,因此这个就是SPI时钟的分频系数),MSTR(配置当前设备为主机还是从机),CPOL(时钟极性,决定着空闲时SCK的高平状态),CPHA(时钟相位,即从第几个时钟边沿开始正式采样(获取)数据)。所以说CR1寄存器决定着:是否打开SPI,是否采用全双工模式,这是主机还是从机,每次发几位数据,先发高位还是低位,传输波特率是多少,时钟空闲时是低电平还是高电平,从第一个还是第二个时钟边沿获取数据等等。

SPI状态反映:原理框图中的SR寄存器

在这里插入图片描述在这里插入图片描述SPI的状态有:BSY(是否处于忙碌状态),OVR(溢出错误),TXE(发送缓冲是否为空),RXEN(接收缓冲是否为空)等等。

在这里插入图片描述

时钟信号的相位和极性

SPI_CR寄存器的CPOL和CPHA位,能够组合成四种可能的时序关系。CPOL(时钟极性)位控制在没有数据传输时时钟的空闲状态电平,此位对主模式和从模式下的设备都有效。如果CPOL被 清’0’,SCK引脚在空闲状态保持低电平;如果CPOL被置’1’,SCK引脚在空闲状态保持高电平。
如果CPHA(时钟相位)位被置’1’,SCK时钟的第二个边沿(CPOL位为0时就是下降沿,CPOL位 为’1’时就是上升沿)进行数据位的采样,数据在第二个时钟边沿被锁存。如果CPHA位被清’0’,SCK时钟的第一边沿(CPOL位为’0’时就是下降沿,CPOL位为’1’时就是上升沿)进行数据位采样,数据在第一个时钟边沿被锁存。
例如,对于CPHA=1,CPOL=0 的图,CPOL=0决定着当SPI空闲时SCK线会处于高电平,开始工作时会从高电平跳变到低电平,工作完后又保持高电平;CPHA=1决定着从SCK的第2个边沿信号开始获取数据线上的数据;另外还需注意:SPI是在SCK跳变沿处采集数据线上的电平数据的,即SCK跳变时对应到的数据线上的电平高低就是采集到的数据内容

二、工作过程

配置成主模式
在这里插入图片描述比如说我们把MCU配置成主机模式:首先要选时钟吧(咱MCU要是主机的话,这个时钟就是MCU自己产生,然后通过这个SCK引脚连到其他从机的SCK引脚,保证时钟同步),选完时钟后要分频吧(即波特率BR[:]位),然后时钟极性要考虑吧,先传高位还是低位,每次传几位要考虑吧,很重要的一点是咱这个设备是配置成主机还是从机呢,配置成主机后怎么选中其他从机呢。
在这里插入图片描述配置成主机了,主机向从机发送数据:一开始待发送的数据是存在数据缓冲区(即数据寄存器中)中,当数据进入移位寄存器开始发送时(即开始移位了,一位一位的移到从机寄存器里去),TXE标志位置位表明发送缓冲区清空了,可以准备下次向缓冲区装载数据了,要是设置了发送中断还是触发中断。什么时候才会发送呢?当你对SPI的DR寄存器写入值,就会自动发送出去了,这一点和USART是一样的主机接收从机返回的数据或者说主机读取从机中的数据主机需要先向从机随便发送一些数据,然后检查自己的RXNE位是否置位了,置位就说明从机已经返回数据了,主机此时可以去读取数据寄存器中数据了

配置成从模式
在这里插入图片描述配置成从机了,那么时钟就不是自己产生了,而是通过SCK引脚接收主机SCK引脚传来的时钟信号,那么从机的波特率配置位(就是时钟分频系数)也没用了,因为波特率已经在主机那边配置过了。那么从机这边的SPI配置主要是配合主机那边的SPI配置,比如从机时钟极性和相位要和主机的一致,数据每次传几位、从高位传还是低位传也和主机一致等等。从机如何发送数据呢?由主机发起,只有当从机的MOSI引脚接收到数据时(即主机向从机发送了数据),从机才会通过自己的MISO引脚向主机发送数据,同样的如果从机把数据都从缓冲区(即数据寄存器)送到了移位寄存器,从机的TXE标志位会置位。从机如何接收数据呢?也是由主机发起,主机会主动向从机传输数据

缓冲区(即数据寄存器)到移位寄存器是并行的,SPI传输数据是串行的!缓冲区指的是数据寄存器,传输用的寄存器是移位寄存器,存在个缓冲区到移位寄存器的过程

当写入数据至发送缓冲器时,发送过程就开始了。在接收时,接收到的数据被存放在一个内部的接收缓冲器中;在发送时,在被发送之前,数据将首先被存放在一个内部的发送缓冲器中。对SPI_DR寄存器的读操作,将返回接收缓冲器的内容;写入SPI_DR寄存器的数据将被写入发送缓冲器中:也就是说,如果调用SPI的读取数据函数(这个函数本质不过是把DR寄存器内容给取出来,函数内部其实就是 SPIx->DR; 返回了SPI的DR数据内容),这样居然就能触发使得RXEN复原!!!!

//MCU作为主机来读数据

//在采样时钟的最后一个边沿,当数据被从移位寄存器传送到接收缓冲器时,设置RXNE标志(接
//收缓冲器非空);它表示数据已经就绪,可以从SPI_DR寄存器读出;
if(主机的接收标志RXEN被置位了)
{
   调用读取函数SPI2_ReadWriteByte()来读取DR数据内容;
}

//读取DR的函数
u8 SPI2_ReadWriteByte(u8 TxData)
{		
	...
	...					    
	return SPI_I2S_ReceiveData(SPI2); //读取DR数据寄存器内容			    
}
//函数内部其实就是 SPIx->DR; 返回了SPI的DR数据内容,这样居然就能触发使得RXEN复原!!!!
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx)
{
  /* Check the parameters */
  assert_param(IS_SPI_ALL_PERIPH(SPIx));
  
  /* Return the data in the DR register */
  return SPIx->DR;  //读完后能触发RXEN置位   读出SPI_DR寄存器即可清除RXNIE标志位。
}

同样的,如果向DR中写入数据(本质也不过是对DR赋值),就能触发TXE的置位。(这个还能理解,毕竟是修改了DR的值,能检测到,所以能触发TXE置位,但是为啥上面只是对DR读一下都能触发有关寄存器置位呢)

if(TXE == 1)  //在试图写发送缓冲器之前,需确认TXE标志应该为’1’
{
   SPI_I2S_SendData(SPI2, TxData); //向SPI的DR中写入数据,就会直接开始SPI传输了!!!
}

void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data)
{
  /* Check the parameters */
  assert_param(IS_SPI_ALL_PERIPH(SPIx));
  
  /* Write in the DR register the data to be sent */
  SPIx->DR = Data;  //写入本质不过是赋值,写完后能触发TXE置位     写入SPI_DR寄存器即可清除TXE位。
}

向SPI的DR中写入数据,就会直接开始SPI传输了!!!一旦传输开始,如果下一个将发送的数据被放进了发送缓冲器,就可以维持一个连续的传输流(软件必须保证在SPI主设备开始数据传输之前在发送寄存器中写入要发送的数据)。在试图写发送缓冲器之前,需确认TXE标志应该为’1’

主从机区分
NSS:从设备选择。这是一个可选的引脚,用来选择主/从设备。它的功能是用来作为“片选引脚”,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。从设备的NSS引脚可以由主设备的一个标准I/O引脚来驱动。

硬件NSS模式NSS输出被使能,当STM32F10xxx工作为主SPI,并且NSS输出已经通过SPI_CR2寄存器的SSOE位使能,这时NSS引脚被拉低,所有NSS引脚与这个主SPI的NSS引脚相连并配置为NSS的SPI设备,将自动变成从SPI设备。当一个SPI设备需要发送广播数据,它必须拉低NSS信号,以通知所有其它的设备它是主设备,此时,所有的SPI设备,如果它们的NSS引脚连接到主设备的NSS引脚,则会检测到低电平,如果它们被设置为NSS硬件模式,就会自动进入从设备状态;如果它不能拉低NSS,这意味着总线上有另外一个主设备在通信,这时将产生一个硬件失败错误(Hard Fault)。当配置为主设备、NSS配置为输入引脚(MSTR=1,SSOE=0)时,如果NSS被拉低,则这个SPI设备进入主模式失败状态:即MSTR位被自动清除,此设备进入从模式。

软件NSS模式:可以通过设置SPI_CR1寄存器的SSM位来使能这种模式(见图211)。在这种模式下NSS引脚可以用作它用,而内部NSS信号电平可以通过写SPI_CR1的SSI位来驱动也就是软件模式下和硬件模式下功能是一样的,只不过一个通过硬件引脚控制,一个通过软件赋值进行控制:硬件模式下使能CR2中的SSOE位能够使NSS引脚拉低,软件模式下施恩那个CR1中的SSI位能够使NSS引脚拉低
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

SPI_NSS 设置 NSS 信号由硬件(NSS 管脚)还是软件控制,这里我们通过软件控制 NSS 引脚电平,而不是硬件自动控制,所以选择 SPI_NSS_Soft。MCU的PB12、13、14、15引脚连接到了W25Qxx设备的四个SPI引脚,不就相当于PB12、13、14、15这四个引脚是MCU的SPI引脚了,把程序烧入到MCU,意味着我们现在只是对MCU进行了SPI配置?对W25Qxx外设配置SPI的话需要再单独烧入程序到W25Qxx中?这也是为什么后面程序中把PB12、13、14、15引脚配置成了主机模式,然后能作为主机去选中W25Qxx设备,主动去写入和读取W25Qxx的数据。这说明MCU是主机,W25Qxx已经是从机了,但在配置程序中我们没有看到将W25Qxx配置成从机的语句,只看到有个配置成主机的语句。也就是说,编写的程序是把MCU作为主机了,并采取了软件NSS模式来选择从机(由于没有对W25Qxx烧录程序即没有配置,默认是配合主机MCU的),接着再配置主机的CR1的SSI位使NSS拉低(只有主机才能把NSS拉低),就把与NSS相连的设备都当作从机了!那么怎么区分主机要找哪个从机呢?毕竟所有从机的CS引脚都连到了主机的NSS引脚,应该是如果从机的CS引脚此时也拉低了,就表示被选中了?“”多从机的SPI正常的应用会有多根SS线(类似片选线),不同的片选连接不同的从机的SS,想要选择哪个从机只要拉低相应的SS线就可以了“ —— 这句话中的拉低是谁拉低?主机拉低自己的NSS,从机也要拉低自己的CS才行,但从机又咋知道啥时候要拉低呢?是不是还需要个设备号呢?
NSS引脚告诉你其他外设都是从机(排座次),MUC的PB12连到从机的CS引脚,才决定着要选中哪个外设(常规模式下)

在这里插入图片描述常规模式:主机里有多个CS引脚,另外三根线可以复用,但CS引脚会变得比较多,不能连太多从机
在这里插入图片描述看到这里,我突然意识到,这里MCU和W25Qxx采用的是常规模式啊!!只不过因为只有一个外设,导致只看到了MCU上的一个CS引脚,如果还有其他外设,只需再从MCU上选出一个引脚比如PB10吧,连到这第二个外设的CS引脚就行了!然后如果MCU主机把PB10拉低就意味着选中了这第二个外设。是自己搞糊涂了啊!

CRC校验
在这里插入图片描述DMA和SPI配合使用的时序图,锻炼看图能力吧,视需要看

在这里插入图片描述在这里插入图片描述W25Q128 是华邦公司推出的大容 量 SPI FLASH 产品,W25Q128 的容量为 128Mb,该系列还有 W25Q80/16/32/64 等。ALIENTEK
所选择的 W25Q128 容量为 128Mb,也就是 16M 字节。W25Q128 将 16M 的容量分为 256 个块(Block),每个块大小为 64K 字节,每个块又分为16 个扇区(Sector),每个扇区 4K 个字节。W25Q128 的最小擦除单位为一个扇区,也就是每次必须擦除 4K 个字节。这样我们需要给 W25Q128 开辟一个至少 4K 的缓存区,这样对 SRAM 要求比较高,要求芯片必须有 4K 以上 SRAM 才能很好的操作。W25Q128 的擦写周期多达 10W 次,具有 20 年的数据保存期限,支持电压为 2.7~3.6V,W25Q128 支持标准的 SPI,还支持双输出/四输出的 SPI,最大 SPI 时钟可以到 80Mhz(双输出时相当于 160Mhz,四输出时相当于 320M),更多的 W25Q128 的介绍,请参考 W25Q128 的DATASHEET。
在这里插入图片描述在这里插入图片描述

程序

//SPI的SCK、MISO、MOSI三个引脚的初始化
void SPI2_Init(void)
{
 	GPIO_InitTypeDef GPIO_InitStructure;
    SPI_InitTypeDef  SPI_InitStructure;

	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOB, ENABLE );//使能引脚的时钟,配置IO来配合SPI功能
	RCC_APB1PeriphClockCmd(	RCC_APB1Periph_SPI2,  ENABLE );//SPI时钟来自APB1,使能时钟	
 
	//四根线:PB13、14、15分别对应SCK、MISO、MOSI,片选引脚对应到PB12,用软件控制PB12的电平状态即可是否选中CS引脚
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //PB13/14/15  都配置成复用推挽输出也是可以的
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化

 	GPIO_SetBits(GPIOB,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);  //PB13/14/15引脚电平都拉高,使处于空闲状态
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //SPI有很多个模式,比如全双工、单工等,这里选择全双工
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		//这台设备设置成主机?MCU是主机,W25QXX是从机???
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//每次传输8位
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;		//空闲时SCK为高电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;	//从第二个边沿开始采样数据
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		//软件控制NSS
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;	//波特率预分频的值,这个随便设置一下默认值,后面又改了
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	// 从高位开始传输
	SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC值计算的多项式
	SPI_Init(SPI2, &SPI_InitStructure);  //初始化
 
	SPI_Cmd(SPI2, ENABLE); //使能
	
	SPI2_ReadWriteByte(0xff);//通过SPI随便写入一个数据,就是启动了SPI的传输过程

}   
void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)
{
    assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));
	SPI2->CR1&=0XFFC7;
	SPI2->CR1|=SPI_BaudRatePrescaler;	//
	SPI_Cmd(SPI2,ENABLE); 

} 

void W25QXX_Init(void)
{	
    GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOB, ENABLE );//使能PB口时钟

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;  
// PB12 推挽,这个端口是W25Qxx芯片的CS片选引脚,接到单片机的PB12引脚了,通过PB12的电平控制是否片选中W25Qxx芯片
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  //PB112设置为推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOB, &GPIO_InitStructure);
 	GPIO_SetBits(GPIOB,GPIO_Pin_12); //为高,表示不选中
 
    W25QXX_CS=1;				//拉高外设W25Qxx芯片的CS引脚使得不选中,重复了一次
	SPI2_Init();		   	//其他三个引脚的初始化以及SPI的参数配置
	SPI2_SetSpeed(SPI_BaudRatePrescaler_2);//设置时钟分频系数,决定SPI时钟频率,设置为18M,高速模式
	W25QXX_TYPE=W25QXX_ReadID();//读取设备ID号

} 
  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-07-21 21:42:18  更:2022-07-21 21:42:22 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 22:31:30-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码