完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
SPI通信模块简介
SPI是英语Serial Peripheral interface 的缩写,顾名思义就是串行外围设备接口。是 Motorola首先在其MC68HCXX系列处理器上定义的。SPI 接口主要应用在 EEPROM,FLASH,实时时钟,AD 转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB 的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,STM32也有SPI接口。
此图与上图一样,只不过比上图更加详细一些。这里多了一个SSPBUF缓存区,无论是读取还是发送内容,均是对缓存区进行操作。由于我们采用的是两线MISO,MOSI进行数据的双向传输,因此我们只能在MISO或者MOSI上一次传输一个bit的内容。 假如我们要求传输8个bits,那么移位寄存器与缓存区必须配合工作。又由于我们将master与slave的时钟接了相同的时钟,因此master与slave会形成一个闭环工作区,即寄存器通过 MOSI 信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过 MISO 信号线返回给主机。这样,两个移位寄存器中的内容就被交换,外设的写操作和读操作是同步完成的。 SPI工作状态 如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。 交换前 交换中 交换后 其实,说实话没有读和写的说法,因为实质上每次SPI是主从设备在交换数据。也就是说,你发一个数据必然会收到一个数据;你要收一个数据必须也要先发一个数据。读写操作的不同只是我们关注点不同,读操作我们只要slave传给master的那一部分的(也就是传输后master的Buffer中的内容),写操作我们只要master传给slave的那一部分的(也就是传输后slave的Buffer中的内容)。 SPI模块中不同的工作区 总体分区简介 1.通讯引脚 2.时钟控制逻辑 3.数据控制逻辑 4.整体控制逻辑 整体控制逻辑部分 寄存器配置说明 SPI_CR1.RXONLY:只接收(Receive Only) SPI_CR1.BIDIOE:双向模式下的输出使能 (Output enable in bidirectional mode) SPI_CR1.BIDIMODE:双向数据模式使能 (Bidirectional data mode enable) SPI_CR1.SPE:SPI使能 (SPI enable) SPI_CR1.SSM:软件从设备管理 (Software slave management) SPI_CR1.SSI:内部从设备选择 (Internal slave select) 总结 1. 整体控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据“控制寄存器(CR1/CR2)”的参数而改变,基本的控制参数包括前面提到的SPI模式、波特率、LSB先行、主从模式、单双向模式等等。 2. 在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR)”,只要读取状态寄存器相关的寄存器位,就可以了解SPI的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生SPI中断信号、DMA请求及控制NSS信号线。 3. 实际应用中,一般不使用STM32 SPI外设的标准NSS信号线,而是更简单地使用普通的GPIO,软件控制它的电平输出,从而产生通讯起始和停止信号。 通讯引脚部分 STM32芯片有多个SPI外设,它们的SPI通讯信号引出到不同的GPIO引脚上,使用时必须配置到这些指定的引脚,以《STM32F10x中文参考》为准。其中SPI1是APB2上的设备,最高通信速率达36Mbtis/s,SPI2、SPI3是APB1上的设备,最高通信速率为18Mbits/s。除了通讯速率,在其它功能上没有差异。 SPI_CR1.BR[2:0]:波特率控制 (Baud rate control) 时钟控制逻辑部分 SCLK线的时钟信号,由波特率发生器根据“控制寄存器CR1”中的BR[0:2]位控制,该位是对PCLK时钟的分频因子,对PCLK的分频结果就是SCLK引脚的输出时钟频率。其中的PCLK频率是指SPI所在的APB总线频率,APB1为PCLK1,APB2为PCLK2。 SPI自定义传输模式 结构体成员变量比较多,这里我们挑取几个重要的成员变量讲解一下:
SPI通道的选择(参考“STM32中文参考手册”) MSB(最高有效位)与LSB(最低有效位) 我们在这个图中看到:NSS寄存器也就是片选引脚被拉低,这说明主从设备之间可以进行正常的通信,且这里我们设置“SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从 MSB 位开始”,那么MISO与MOSI之间的数据传输方向是”MISO的最高位->MOSI的最低位;MOSI的最高位->MISO的最低位”。 从选择(NSS)引脚管理(相当于片选端) ① 有2种NSS模式: 软件NSS模式:可以通过设置SPI_CR1寄存器的SSM位来使能这种模式(见图211)。在这种 模式下NSS引脚可以用作它用,而内部NSS信号电平可以通过写SPI_CR1的SSI位来驱动。 ② 硬件NSS模式,分两种情况: A. NSS输出被使能:当STM32F10xxx工作为主SPI,并且NSS输出已经通过SPI_CR2寄存 器的SSOE位使能,这时NSS引脚被拉低,所有NSS引脚与这个主SPI的NSS引脚相连并 配置为硬件NSS的SPI设备,将自动变成从SPI设备。 当一个SPI设备需要发送广播数据,它必须拉低NSS信号,以通知所有其它的设备它是主 设备;如果它不能拉低NSS,这意味着总线上有另外一个主设备在通信,这时将产生一个 硬件失败错误(Hard Fault)。 B. NSS输出被关闭:允许操作于多主环境。 注:软件NSS与硬件NSS的配置详见“STM32中文参考手册->从选择(NSS)脚管理”。 NSS不同模式寄存器配置简介 SSM:软件从设备管理 (Software slave management) 当SSM被置位时,NSS引脚上的电平由SSI位的值决定。 0:禁止软件从设备管理; 1:启用软件从设备管理。 注:I2S模式下不使用。 SSI:内部从设备选择 (Internal slave select) 该位只在SSM位为’1’时有意义。它决定了NSS上的电平,在NSS引脚上的I/O操作无效。 注:I2S模式下不使用。 NSS两种不同模式的不同之处 1. 硬件NSS,是指SPI自动控制SPI的片选信号,发送数据的时候,输出低电平,不发送的时候,是高电平,这个模式一般不用.因为这种方式只能1个SPI接1个从机,很是蛋疼. 2. 软件模式就是完全软件控制SPI片选,就是一个普通IO控制,你要SPI通信之前,必须先用软件的方式,控制SPI从机的片选为低电平,然后在发送数据.发完后,拉高. 一般用这个模式,因为可以一个SPI控制N多个从机... 基于SPI固件库的正点原子封装库简介 (将STM32开发板配置成主机模式,可以访问SD Card/W25Q64/NRF24L01) 以下几个函数是正点原子根据固件库所配置的常用函数: SPI初始化函数 void SPI2_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );//PORTB时钟使能 RCC_APB1PeriphClockCmd( RCC_APB1Periph_SPI2, ENABLE );//SPI2时钟使能 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);//初始化GPIOB 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设置为双线双向全双工 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构 SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //串行同步时钟的空闲状态为高电平 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //定义波特率预分频的值:波特率预分频值为256 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始 SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式 SPI_Init(SPI2, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器 SPI_Cmd(SPI2, ENABLE); //使能SPI外设 SPI2_ReadWriteByte(0xff);//启动传输 } 我们这里有些疑问的是最后一句语句,这个语句其实并不难理解,就是发送给从机一个0xFF数据吗!但是为什么发送呢?我们联想一下,SPI是一个闭环通信,只有主机发送给从机规定位数的数据从机才可以将相同数量的位发送给主机,这样就实现了主机与从机的数据传输。这里,我们给外设发送一个0xFF无效字节,然后接收外围设备发送的字节。其实,我们预先读取一个字节是为了读取dummy无效字节,这样在后续的数据读取中我们读到的字节均为有效字节。 我们看上述代码,正点原子封装的SPI2_Init()相当于固件库的SPI_Init()+SPI_I2S_ReceiveData。 读操作与写操作 我们看上述代码,正点原子封装的SPI2_Init()相当于固件库的SPI_Init()+SPI_I2S_ReceiveData。 读操作与写操作 //SPIx 读写一个字节 //TxData:要写入的字节 //返回值:读取到的字节 u8 SPI2_ReadWriteByte(u8 TxData) { u8 retry=0; while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位 { retry++; if(retry>200)return 0; } SPI_I2S_SendData(SPI2, TxData); //通过外设SPIx发送一个数据 retry=0; while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) //检查指定的SPI标志位设置与否:接受缓存非空标志位 { retry++; if(retry>200)return 0; } return SPI_I2S_ReceiveData(SPI2); //返回通过SPIx最近接收的数据 } 我们可以这样理解这段程序: while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位 { retry++; if(retry>200)return 0; } 这段程序用于等待SPI2两端的主从设备是否已经建立联系,如果没有成功建立联系,最多轮询200次,如果还没成功建立联系就不必要在建立联系了,直接向SPI2的slave设备发送信息。 while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET) //检查指定的SPI标志位设置与否:接受缓存非空标志位 { retry++; if(retry>200)return 0; } return SPI_I2S_ReceiveData(SPI2); //返回通过SPIx最近接收的数据 再次等待200次,看看发送的信息是否已经被slave接收到,如果还没有,返回最近一次master设备接收的信息。 其实,这里的while循环也有一个延时的作用,我们发送的字节由于要通过串行通信进行传输,因此我们必须等待一小会时间才可以。 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; } 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; } 我们可以看到无论是SPI_I2S_ReceiveData还是SPI_I2S_SendData函数都是操作的master(STM32上的SPI设备)的DR寄存器,再有我们之前提到过的SPI闭环通信,我们可以得知:当我们传输数据的同时也在接收数据,最终相当于将slave(外围设备)寄存器中的内容更新至master(STM32的SPI通信设备)寄存器中。 速度设置函数 //SPI 速度设置函数 //SpeedSet: //SPI_BaudRatePrescaler_2 2分频 //SPI_BaudRatePrescaler_8 8分频 //SPI_BaudRatePrescaler_16 16分频 //SPI_BaudRatePrescaler_256 256分频 void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler) { assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler)); SPI2->CR1&=0XFFC7; // 清除SPI2的CR1寄存器的BR[2:0] SPI2->CR1|=SPI_BaudRatePrescaler; //设置SPI2的CR1寄存器的BR[2:0]分频系数 SPI_Cmd(SPI2,ENABLE); // 使能SPIx } 固件库的库函数中没有可以设置SPIx数据发送速度的函数,我们可以仿造上述函数的编写逻辑自己编写一个控制SPIx数据发送速度的函数。 注:当SPI内部设备为master,外设为slave时,我们可以设置master向slave发送数据的频率;但是当SPI内部设备为slave,外设为master时,master接收数据的速度只能取决于slave发送的速度,此时我们在设置master的发送数据的频率是没有用的。 常用SPI固件库库函数解析 常用SPI固件库库函数解析
初始化SPI之前GPIO口模式的选择 SPIx对应的GPIO端口的引脚 SPI2对应的GPIO模式配置示例 GPIO_InitTyprDef GPIO_InitStructure; 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);//初始化GPIOB 常用SPI固件库函数参数配置(顺序如上) 初始化SPI函数参数 初始化SPI函数参数配置示例 /* Initialize the SPI1 according to the SPI_InitStructure members */ SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; // 双线全双工 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; // 外设为从设备,内设为主设备 SPI_InitStructure.SPI_DatSize = SPI_DatSize_16b; // 传输容量为16bits SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // 空闲电平为低电平 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; // 第二个脉冲沿触发移位寄存器 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; // NSS为软件控制 SPI_InitStructure.SPI_BaudRatePrescaler =SPI_BaudRatePrescaler_128; // 128分频:36MHz/128=281250Hz,这是STM32中的SPI设备向外设发送数据的频率 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; // 从高位开始移位 SPI_InitStructure.SPI_CRCPolynomial = 7; // 设置CRC多项式的值 SPI_Init(SPI1, &SPI_InitStructure); SPI中断配置函数参数 SPI可触发中断类型如上。 SPI的NSS引脚模式设置函数参数
获得SPI状态标志位函数参数 获得指定的中断标志位状态函数参数 SPI固件库库函数配置流程
W25Q128将16M(8M)的容量分为256个块(Block),每个块大小为64K字节,每个块又分为16个扇区(Sector),每个扇区4K个字节。W25Q128的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。这样我们需要给W25Q128开辟一个至少4K的缓存区,这样对SRAM要求比较高,要求芯片必须有4K以上SRAM才能很好的操作。 配置的基本流程 W25Q128内存地址映射 我们看如上地址映射关系可以得出: 共有256个块(64KB),每个块共有16个扇区(4KB),因此flash最小的的内存擦除单元为一个扇区(4K)。 基于正点原子的W25Q128库函数解析
ID读取函数的返回值 #define W25Q80 0XEF13 #define W25Q16 0XEF14 #define W25Q32 0XEF15 #define W25Q64 0XEF16 #define W25Q128 0XEF17 // 用于判断是什么类型的flash芯片 W25128的指令表 //指令表 #define W25X_WriteEnable 0x06 #define W25X_WriteDisable 0x04 #define W25X_ReadStatusReg 0x05 #define W25X_WriteStatusReg 0x01 #define W25X_ReadData 0x03 #define W25X_FastReadData 0x0B #define W25X_FastReadDual 0x3B #define W25X_PageProgram 0x02 #define W25X_BlockErase 0xD8 #define W25X_SectorErase 0x20 #define W25X_ChipErase 0xC7 #define W25X_PowerDown 0xB9 #define W25X_ReleasePowerDown 0xAB #define W25X_DeviceID 0xAB #define W25X_ManufactDeviceID 0x90 #define W25X_JedecDeviceID 0x9F 如何使用SPI的NSS片选引脚实现SPI与W25Q128的连接? 我们可以仔细的分析W25Q128初始化函数: //初始化SPI FLASH的IO口 void W25QXX_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );//PORTB时钟使能 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; // PB12 推挽 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_SetBits(GPIOB,GPIO_Pin_12); // 将PB12设置为NSS软件模式下的片选引脚 W25QXX_CS=1; //高电平代表FLASH不被选中(相当于PBout(12)=1) SPI2_Init(); //初始化SPI SPI2_SetSpeed(SPI_BaudRatePrescaler_2); //设置为36MHz/2=18MHz时钟,高速模式 W25QXX_TYPE=W25QXX_ReadID();//读取FLASH ID } 预定义命令: #define W25QXX_CS PBout(12) //W25QXX的片选信号 我们可能有疑问:为什么片选端都关闭了(W25QXX_CS=1)还能成功读取到Flash的ID呢? 我们可以看ID读取函数的内容: //读取芯片ID //返回值如下: //0XEF13,表示芯片型号为W25Q80 //0XEF14,表示芯片型号为W25Q16 //0XEF15,表示芯片型号为W25Q32 //0XEF16,表示芯片型号为W25Q64 //0XEF17,表示芯片型号为W25Q128 u16 W25QXX_ReadID(void) { u16 Temp = 0; W25QXX_CS=0; // 使能片选位 SPI2_ReadWriteByte(0x90);//发送读取ID命令 SPI2_ReadWriteByte(0x00); SPI2_ReadWriteByte(0x00); SPI2_ReadWriteByte(0x00); Temp|=SPI2_ReadWriteByte(0xFF)<<8; // 使用移位寄存器将ID的高8位移入Temp变量的高8位中 Temp|=SPI2_ReadWriteByte(0xFF); // 使用移位寄存器将ID的低8位移入Temp变量的低8位中 W25QXX_CS=1; // 失能片选位 return Temp; // 返回16位的ID } // 注:由于SPI是闭环通信,因此当我们向W25Q128写入8位数据,W25Q128就会向STM32的内部SPI发送8位数据,这样就达到了SPI内部设备读取W25Q128内容的目的。 一般我们对于W25Q128 flash芯片操作有如下流程: 基于STM32的SPI内部设备与W25Q128 Flash的通信实验 实验的主要原理 其实,以上是我们更新扇区的操作流程,但是我们要知道SPI通信是个闭环通信,因此我们读出扇区数据的时候,实际上已经将4*1024个0xFF写入扇区当中相当于清空了一个扇区。 SPI通信库函数操作流程 SPI通信实验代码示例 #include "w25qxx.h" #include "lcd.h" #include "spi.h" #include "delay.h" #include "usart.h" #include "stm32f10x.h" u8 String[] = "Hello World"; const u8 Size = sizeof(String); int main() { u8 Buffer[Size]; memset(Buffer,0,Size); delay_init(); // 延迟函数初始化 uart_init(115200); // 串口1初始化(TFTLCD初始化一定要伴随着串口的初始化) LCD_Init(); // TFTLCD初始化 LCD_Clear(LIGHTBLUE); // TFTLCD页面为浅蓝色 SPI2_Init(); // SPI2初始化 W25QXX_Init(); // W25Q128初始化 while(1) { POINT_COLOR = RED; // 画笔为红色 while(W25QXX_ReadID() != W25Q128) { LCD_ShowString(0,0,200,16,16,(u8*)"W25Q128 Check Failed"); delay_ms(500); } LCD_ShowString(0,16,200,16,16,(u8*)"W25Q128 Ready"); POINT_COLOR = BLUE; // 画笔为蓝色 LCD_ShowString(0,32,200,16,16,(u8*)"Start to Write..."); W25QXX_Write(String,0,Size); // 写数据 LCD_ShowString(0,48,200,16,16,(u8*)"Finish Writing"); POINT_COLOR = BROWN; // 画笔为棕色 LCD_ShowString(0,64,200,16,16,(u8*)"Start to Read..."); W25QXX_Read(Buffer,0,Size); LCD_ShowString(0,80,200,16,16,(u8*)"Finish Reading"); LCD_ShowString(0,96,200,16,16,(u8*)"Start to Show Information..."); LCD_ShowString(0,112,200,16,16,Buffer); LCD_ShowString(0,128,200,16,16,(u8*)"Finish Showing Information"); } } // TFTLCD初始化函数中有printf串口打印函数因此我们必须初始化串口(所有串口均可),我们主要是为了使用printf串口信息发送函数 TFTLCD初始化函数中有printf串口打印函数因此我们必须初始化串口(所有串口均可),我们主要是为了使用printf串口信息发送函数 注:这里先引用正点原子的例程,等稍后在进行代码例程的更新。 基于STM32的SPI该如何使用? 我们知道STM32的MCU在进行实验的过程中会产生海量的数据,如何存储这些数据成为了STM32的一个大问题,用SRAM中的内存去存储吗(即我们创建一个数组用数组去存储然后将结果通过对Keil MDK的命令行窗口进行操作将数据输出到txt文本文件中)?这极大地浪费了内存而且给数组预留的内存是固定的失去了灵活性。 我们可以将数据存入flash中,SPI通信可以帮助我们,海量的数据不再极大的浪费我们有限的SRAM存储空间(SRAM是用来运行程序时使用的因此SRAM本身的内存并不算太大而且如果我们占用了过多的内存会影响程序运行的效率)。 STM32的SPI通信理论上完成的是如下过程的操作: SPI通信程序操作原理 |
|
|
|
只有小组成员才能发言,加入小组>>
3278 浏览 9 评论
2956 浏览 16 评论
3457 浏览 1 评论
8996 浏览 16 评论
4050 浏览 18 评论
1107浏览 3评论
572浏览 2评论
const uint16_t Tab[10]={0}; const uint16_t *p; p = Tab;//报错是怎么回事?
568浏览 2评论
用NUC131单片机UART3作为打印口,但printf没有输出东西是什么原因?
2301浏览 2评论
NUC980DK61YC启动随机性出现Err-DDR是为什么?
1858浏览 2评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-23 07:15 , Processed in 0.886743 second(s), Total 51, Slave 41 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号