完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1)实验平台:正点原子STM32mini 开发板
2)摘自《正点原子STM32 不完全手册(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子 第二十章 ADC 实验 本章我们将向大家介绍 STM32 的 ADC 功能。在本章中,我们将使用 STM32 的 ADC1 通 道 1 来采样外部电压值,并在 TFTLCD 模块上显示出来。本章将分为如下几个部分: 20.1 STM32 ADC 简介 20.2 硬件设计 20.3 软件设计 20.4 下载验证 20.1 STM32 ADC 简介 STM32 拥有 1~3 个 ADC(STM32F101/102 系列只有 1 个 ADC),这些 ADC 可以独立使用, 也可以使用双重模式(提高采样率)。STM32 的 ADC 是 12 位逐次逼近型的模拟数字转换器。 它有 18 个通道,可测量 16 个外部和 2 个内部信号源。各通道的 A/D 转换可以单次、连续、扫 描或间断模式执行。ADC 的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中。 模拟看 门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阀值。 STM32F103 系列最少都拥有 2 个 ADC,我们选择的 STM32F103RCT 包含有 3 个 ADC。 STM32 的 ADC 最大的转换速率为 1Mhz,也就是转换时间为 1us(在 ADCCLK=14M,采样周期 为 1.5 个 ADC 时钟下得到),不要让 ADC 的时钟超过 14M,否则将导致结果准确度下降。 STM32 将 ADC 的转换分为 2 个通道组:规则通道组和注入通道组。规则通道相当于你正 常运行的程序,而注入通道呢,就相当于中断。在你程序正常执行的时候,中断是可以打断你 的执行的。同这个类似,注入通道的转换可以打断规则通道的转换, 在注入通道被转换完成之 后,规则通道才得以继续转换。 通过一个形象的例子可以说明:假如你在家里的院子内放了 5 个温度探头,室内放了 3 个 温度探头;你需要时刻监视室外温度即可,但偶尔你想看看室内的温度;因此你可以使用规则 通道组循环扫描室外的 5 个探头并显示 AD 转换结果,当你想看室内温度时,通过一个按钮启 动注入转换组(3 个室内探头)并暂时显示室内温度,当你放开这个按钮后,系统又会回到规则通 道组继续检测室外温度。从系统设计上,测量并显示室内温度的过程中断了测量并显示室外温 度的过程,但程序设计上可以在初始化阶段分别设置好不同的转换组,系统运行中不必再变更 循环转换的配置,从而达到两个任务互不干扰和快速切换的结果。可以设想一下,如果没有规 则组和注入组的划分,当你按下按钮后,需要从新配置 AD 循环扫描的通道,然后在释放按钮 后需再次配置 AD 循环扫描的通道。 上面的例子因为速度较慢,不能完全体现这样区分(规则通道组和注入通道组)的好处,但 在工业应用领域中有很多检测和监视探头需要较快地处理,这样对 AD 转换的分组将简化事件 处理的程序并提高事件处理的速度。 STM32 其 ADC 的规则通道组最多包含 16 个转换,而注入通道组最多包含 4 个通道。关于 这两个通道组的详细介绍,请参考《STM32 参考手册的》第 155 页,第 11 章。 STM32 的 ADC 可以进行很多种不同的转换模式,这些模式在《STM32 参考手册》的第 11 章也都有详细介绍,我们这里就不在一一列举了。我们本章仅介绍如何使用规则通道的单次转 换模式。 STM32 的 ADC 在单次转换模式下,只执行一次转换,该模式可以通过 ADC_CR2 寄存器 的 ADON 位(只适用于规则通道)启动,也可以通过外部触发启动(适用于规则通道和注入通 道),这是 CONT 位为 0。 以规则通道为例,一旦所选择的通道转换完成,转换结果将被存在 ADC_DR 寄存器中, EOC(转换结束)标志将被置位,如果设置了 EOCIE,则会产生中断。然后 ADC 将停止,直 到下次启动。 接下来,我们介绍一下我们执行规则通道的单次转换,需要用到的 ADC 寄存器。第一个 要介绍的是 ADC 控制寄存器(ADC_CR1 和 ADC_CR2)。ADC_CR1 的各位描述如图 22.1.1 所 示: 图 20.1.1 ADC_CR1 寄存器各位描述 这里我们不再详细介绍每个位,而是抽出几个我们本章要用到的位进行针对性的介绍,详 细的说明及介绍,请参考《STM32 参考手册》第 11 章的相关章节。 ADC_CR1 的 SCAN 位,该位用于设置扫描模式,由软件设置和清除,如果设置为 1,则 使用扫描模式,如果为 0,则关闭扫描模式。在扫描模式下,由 ADC_SQRx 或 ADC_JSQRx 寄 存器选中的通道被转换。如果设置了 EOCIE 或 JEOCIE,只在最后一个通道转换完毕后才会产 生 EOC 或 JEOC 中断。 ADC_CR1[19:16]用于设置 ADC 的操作模式,详细的对应关系如图 20.1.2 所示: 图 20.1.2 ADC 操作模式 本章我们要使用的是独立模式,所以设置这几位为 0 就可以了。接着我们介绍 ADC_CR2, 该寄存器的各位描述如图 20.1.3 所示: 图 20.1.3 ADC_CR2 寄存器操作模式 该寄存器我们也只针对性的介绍一些位:ADON 位用于开关 AD 转换器。而 CONT 位用于 设置是否进行连续转换,我们使用单次转换,所以 CONT 位必须为 0。CAL 和 RSTCAL 用于 AD 校准。ALIGN 用于设置数据对齐,我们使用右对齐,该位设置为 0。 EXTSEL[2:0]用于选择启动规则转换组转换的外部事件,详细的设置关系如图 20.1.4 所示: 图 20.1.4 ADC 选择启动规则转换事件设置 我们这里使用的是软件触发(SWSTART),所以设置这 3 个位为 111。ADC_CR2 的 SWSTART 位用于开始规则通道的转换,我们每次转换(单次转换模式下)都需要向该位写 1。 AWDEN 为用于使能温度传感器和 Vrefint。STM32 内部的温度传感器我们将在下一节介绍。 第二个要介绍的是 ADC 采样事件寄存器(ADC_SMPR1 和 ADC_SMPR2),这两个寄存器 用于设置通道 0~17 的采样时间,每个通道占用 3 个位。ADC_SMPR1 的各位描述如图 20.1.5 所示 图 20.1.5 ADC_SMPR1 寄存器各位描述 ADC_SMPR2 的各位描述如下图 20.1.6 所示: 图 20.1.6 ADC_SMPR2 寄存器各位描述 对于每个要转换的通道,采样时间建议尽量长一点,以获得较高的准确度,但是这样会降 低 ADC 的转换速率。ADC 的转换时间可以由以下公式计算: Tcovn=采样时间+12.5 个周期 其中:Tcovn 为总转换时间,采样时间是根据每个通道的 SMP 位的设置来决定的。例如, 当 ADCCLK=14Mhz 的时候,并设置 1.5 个周期的采样时间,则得到:Tcovn=1.5+12.5=14 个周 期=1us。 第三个要介绍的是 ADC 规则序列寄存器(ADC_SQR1~3),该寄存器总共有 3 个,这几个 寄存器的功能都差不多,这里我们仅介绍一下 ADC_SQR1,该寄存器的各位描述如图 20.1.7 所 示: 图 20.1.7 ADC_ SQR1 寄存器各位描述 L[3:0]用于存储规则序列的长度,我们这里只用了 1 个,所以设置这几个位的值为 0。其 他的 SQ13~16 则存储了规则序列中第 13~16 通道的编号(编号范围:0~17)。另外两个规则序 列寄存器同 ADC_SQR1 大同小异,我们这里就不再介绍了,要说明一点的是:我们选择的是 单次转换,所以只有一个通道在规则序列里面,这个序列就是 SQ1,通过 ADC_SQR3 的最低 5 位(也就是 SQ1)设置。 第四个要介绍的是 ADC 规则数据寄存器(ADC_DR)。规则序列中的 AD 转化结果都将被存 在这个寄存器里面,而注入通道的转换结果被保存在 ADC_JDRx 里面。ADC_DR 的各位描述 如图 20.1.8: 图 20.1.8 ADC_ JDRx 寄存器各位描述 这里要提醒一点的是,该寄存器的数据可以通过 ADC_CR2 的 ALIGN 位设置左对齐还是 右对齐。在读取数据的时候要注意。 最后一个要介绍的 ADC 寄存器为 ADC 状态寄存器(ADC_SR),该寄存器保存了 ADC 转 换时的各种状态。该寄存器的各位描述如图 20.1.9 所示: 图 20.1.9 ADC_ SR 寄存器各位描述 这里我们要用到的是 EOC 位,我们通过判断该位来决定是否此次规则通道的 AD 转换已经 完成,如果完成我们就从 ADC_DR 中读取转换结果,否则等待转换完成。 通过以上介绍,我们了解了 STM32 的单次转换模式下的相关设置,本章我们使用 ADC1 的通道 1 来进行 AD 转换,这里需要说明一下,使用到的库函数分布在 stm32f1xx_adc.c 文件和 stm32f1xx_adc.h 文件中。下面讲解其详细设置步骤: 1)开启 PA 口时钟和 ADC1 时钟,设置 PA1 为模拟输入。 STM32F103ZET6 的 ADC 通道 1 在 PA1 上,所以,我们先要使能 PORTA 的时钟,然后设 置 PA1 为模拟输入。同时我们要把 PA1 复用为 ADC,所以我们要使能 ADC1 时钟。 使能 GPIOA 时钟和 ADC1 时钟都很简单,具体方法为: __HAL_RCC_ADC1_CLK_ENABLE(); //使能 ADC1 时钟 __HAL_RCC_GPIOA_CLK_ENABLE(); //开启 GPIOA 时钟 初始化 GPIOA1 为模拟输入,方法也多次讲解,关键代码为: GPIO_InitTypeDef GPIO_Initure; GPIO_Initure.Pin=GPIO_PIN_1; //PA1 GPIO_Initure.Mode=GPIO_MODE_ANALOG; //模拟 GPIO_Initure.Pull=GPIO_NOPULL; //不带上下拉 HAL_GPIO_Init(GPIOA,&GPIO_Initure); 2)初始化 ADC,设置 ADC 时钟分频系数,分辨率,模式,扫描方式,对齐方式等信息。 在 HAL 库中,初始化 ADC 是通过函数 HAL_ADC_Init 来实现的,该函数声明为: HAL_StatusTypeDef HAL_ADC_Init(ADC_HandleTypeDef* hadc); 该函数只有一个入口参数 hadc,为 ADC_HandleTypeDef 结构体指针类型,结构体定义为: typedef struct { ADC_TypeDef *Instance; //ADC1/ ADC2/ ADC3 ADC_InitTypeDef Init; //初始化结构体变量 DMA_HandleTypeDef *DMA_Handle; //DMA 方式使用 HAL_LockTypeDef Lock; __IO HAL_ADC_StateTypeDef State; __IO uint32_t ErrorCode; }ADC_HandleTypeDef; 该结构体定义和其他外设比较类似,我们着重看第二个成员变量 Init 含义,它是结构体 ADC_InitTypeDef 类型,结构体 ADC_InitTypeDef 定义为: typedef struct { uint32_t DataAlign; //对齐方式:左对齐还是右对齐:ADC_DATAALIGN_RIGHT uint32_t ScanConvMode; //扫描模式 DISABLE uint32_t ContinuousConvMode;//开启连续转换模式或者单次转换模式 DISABLE uint32_t NbrOfConversion; //规则序列中有多少个转换 1 uint32_t DiscontinuousConvMode;//不连续采样模式 DISABLE uint32_t NbrOfDiscConversion;//不连续采样通道数 0 uint32_t ExternalTrigConv; //外部触发方式 ADC_SOFTWARE_START }ADC_InitTypeDef; 我们直接把每个成员变量含义注释在结构体定义的后面,请大家仔细阅读上面注释。 这里我们需要说明一下,和其他外设一样,HAL 库同样提供了 ADC 的 MSP 初始化函数, 一般情况下,时钟使能和 GPIO 初始化都会放在 MSP 初始化函数中。函数声明为: void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc); 4)开启 AD 转换器。 在设置完了以上信息后,我们就开启 AD 转换器了(通过 ADC_CR2 寄存器控制)。 HAL_ADC_Start(&ADC1_Handler); //开启 ADC 5)配置通道,读取通道 ADC 值。 在上面的步骤完成后,ADC 就算准备好了。接下来我们要做的就是设置规则序列 1 里面的 通道,然后启动 ADC 转换。在转换结束后,读取转换结果值值就是了。 设置规则序列通道以及采样周期的函数是: HAL_StatusTypeDef HAL_ADC_ConfigChannel(ADC_HandleTypeDef* hadc, ADC_ChannelConfTypeDef* sConfig); 该函数有两个入口参数,第一个就不用多说了,接下来我们看第二个入口参数 sConfig,它 是 ADC_ChannelConfTypeDef 结构体指针类型,结构体定义如下: typedef struct { uint32_t Channel; //ADC 通道 uint32_t Rank; //规则通道中的第几个转换 uint32_t SamplingTime; //采样时间 }ADC_ChannelConfTypeDef; 该结构体有四个成员变量,对于 STM32F1 只用到前面三个。Channel 用来设置 ADC 通道, Rank 用来设置要配置的通道是规则序列中的第几个转换,SamplingTime 用来设置采样时间。 使用实例为: ADC1_ChanConf.Channel=ch; //通道 ADC1_ChanConf.Rank=1; //第 1 个序列,序列 1 ADC1_ChanConf.SamplingTime=ADC_SAMPLETIME_239CYCLES_5; //采样时间 HAL_ADC_ConfigChannel(&ADC1_Handler,&ADC1_ChanConf); //通道配置 配置好通道并且使能 ADC 后,接下来就是读取 ADC 值。这里我们采取的是查询方式读取, 所以我们还要等待上一次转换结束。此过程 HAL 库 提 供 了 专 用 函 数 HAL_ADC_PollForConversion,函数定义为: HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef* hadc, uint32_t Timeout); 等待上一次转换结束之后,接下来就是读取 ADC 值,函数为: uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef* hadc); 这两个函数的使用方法都比较简单,这里我们就不累赘了。 这里还需要说明一下 ADC 的参考电压,战舰 STM32 开发板使用的是 STM32F103ZET6, 该芯片有外部参考电压:Vref-和 Vref+,其中 Vref-必须和 VSSA 连接在一起,而 Vref+的输入 范围为:2.4~VDDA。战舰 STM23 开发板通过 P7 端口,设置 Vref-和 Vref+设置参考电压,默 认的我们是通过跳线帽将 Vref-接到 GND,Vref+接到 VDDA,参考电压就是 3.3V。如果大家想 自己设置其他参考电压,将你的参考电压接在 Vref-和 Vref+上就 OK 了。本章我们的参考电压 设置的是 3.3V。 通过以上几个步骤的设置,我们就能正常的使用 STM32 的 ADC1 来执行 AD 转换操作了。 20.2 硬件设计 本实验用到的硬件资源有: 1) 指示灯 DS0 2) TFTLCD 模块 3) ADC 4) 杜邦线 前面两个均已介绍过,而 ADC 属于 STM32 内部资源,实际上我们只需要软件设置就可以 正常工作,不过我们需要在外部连接其端口到被测电压上面。本章,我们通过 ADC1 的通道 1 (PA1)来读取外部电压值,MiniSTM32 开发板没有设计参考电压源在上面,但是板上有几个 可以提供测试的地方:1,3.3V 电源。2,GND。3,后备电池。注意:这里不能接到板上 5V 电源上去测试,这可能会烧坏 ADC!。 因为要连接到其他地方测试电压,所以我们需要一根杜邦线,或者自备的连接线也可以, 一头插在 PA1 排针上(在 P3 上),另外一头就接你要测试的电压点(确保该电压不大于 3.3V 即可)。如果是测量外部电压,则还需要和开发板共地,开发板上有很多 GND 的排针,随便连 接一个共地即可。 20.3 软件设计 打开实验工程可以发现,我们在 FWLIB 分组下面新增了 stm32f1xx_hal_adc.c 源文件,同 时会引入对应的头文件 stm32f1xx_hal_adc.h。ADC 相关的库函数和宏定义都分布在这两个文件 中。同时,我们在 HARDWARE 分组下面新建了 adc.c,也引入了对应的头文件 adc.h。这两个 文件是我们编写的 adc 相关的初始化函数和操作函数。 打开 adc.c,代码如下: ADC_HandleTypeDef ADC1_Handler; //ADC 句柄 //初始化 ADC //ch: ADC_channels //通道值 0~16 取值范围为:ADC_CHANNEL_0~ADC_CHANNEL_16 void MY_ADC_Init(void) { ADC_CLKInit.PeriphClockSelection=RCC_PERIPHCLK_ADC; //ADC 外设时钟 ADC_CLKInit.AdcClockSelection=RCC_ADCPCLK2_DIV6; //分频因子 6 时钟为 72M/6=12MHz HAL_RCCEx_PeriphCLKConfig(&ADC_CLKInit); //设置 ADC 时钟 ADC1_Handler.Instance=ADC1; ADC1_Handler.Init.DataAlign=ADC_DATAALIGN_RIGHT; //右对齐 ADC1_Handler.Init.ScanConvMode=DISABLE; //非扫描模式 ADC1_Handler.Init.ContinuousConvMode=DISABLE; //关闭连续转换 ADC1_Handler.Init.NbrOfConversion=1;//1 个转换在规则序列中就是只转换规则序列 1 ADC1_Handler.Init.DiscontinuousConvMode=DISABLE; //禁止不连续采样模式 ADC1_Handler.Init.NbrOfDiscConversion=0; //不连续采样通道数为 0 ADC1_Handler.Init.ExternalTrigConv=ADC_SOFTWARE_START; //软件触发 HAL_ADC_Init(&ADC1_Handler); //初始化 HAL_ADCEx_Calibration_Start(&ADC1_Handler); //校准 ADC } //ADC 底层驱动,引脚配置,时钟使能 //此函数会被 HAL_ADC_Init()调用 //hadc:ADC 句柄 void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc) { GPIO_InitTypeDef GPIO_Initure; __HAL_RCC_ADC1_CLK_ENABLE(); //使能 ADC1 时钟 __HAL_RCC_GPIOA_CLK_ENABLE(); //开启 GPIOA 时钟 GPIO_Initure.Pin=GPIO_PIN_1; //PA1 GPIO_Initure.Mode=GPIO_MODE_ANALOG; //模拟 GPIO_Initure.Pull=GPIO_NOPULL; //不带上下拉 HAL_GPIO_Init(GPIOA,&GPIO_Initure); } //获得 ADC 值 //ch: 通道值 0~16,取值范围为:ADC_CHANNEL_0~ADC_CHANNEL_16 //返回值:转换结果 u16 Get_Adc(u32 ch) { ADC_ChannelConfTypeDef ADC1_ChanConf; ADC1_ChanConf.Channel=ch; //通道 ADC1_ChanConf.Rank=1; //第 1 个序列,序列 1 ADC1_ChanConf.SamplingTime=ADC_SAMPLETIME_239CYCLES_5; //采样时间 HAL_ADC_ConfigChannel(&ADC1_Handler,&ADC1_ChanConf); //通道配置 HAL_ADC_Start(&ADC1_Handler); //开启 ADC HAL_ADC_PollForConversion(&ADC1_Handler,10); //轮询转换 return (u16)HAL_ADC_GetValue(&ADC1_Handler); //返回最近一次 ADC1 规则组的转换结果 } //获取指定通道的转换值,取 times 次,然后平均 //times:获取次数 //返回值:通道 ch 的 times 次转换结果平均值 u16 Get_Adc_Average(u32 ch,u8 times) { u32 temp_val=0; u8 t; for(t=0;t temp_val+=Get_Adc(ch); delay_ms(5); } return temp_val/times; } 此部分代码就 3 个函数,Adc_Init 函数用于初始化 ADC1。这里基本上是按我们上面的步 骤来初始化的,我们用标号①~④标示出来步骤。这里我们仅开通了 1 个通道,即通道 1。第二 个函数 Get_Adc,用于读取某个通道的 ADC 值,例如我们读取通道 1 上的 ADC 值,就可以通 过 Get_Adc(ADC_Channel_1)得到。最后一个函数 Get_Adc_Average,用于多次获取 ADC 值, 取平均,用来提高准确度。 头文件 adc.h 代码比较简单,主要是三个函数申明。接下来我们看看 main 函数内容: int main(void) { u16 adcx; float temp; HAL_Init(); //初始化 HAL 库 Stm32_Clock_Init(RCC_PLL_MUL9); //设置时钟,72M delay_init(72); //初始化延时函数 uart_init(115200); //初始化串口 usmart_dev.init(84); //初始化 USMART LED_Init(); //初始化 LED LCD_Init(); //初始化 LCD MY_ADC_Init(); //初始化 ADC1 通道 1 POINT_COLOR=RED; LCD_ShowString(30,50,200,16,16,"Mini STM32"); LCD_ShowString(30,70,200,16,16,"ADC TEST"); LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK"); LCD_ShowString(30,110,200,16,16,"2019/11/15"); POINT_COLOR=BLUE;//设置字体为蓝色 LCD_ShowString(30,130,200,16,16,"ADC1_CH1_VAL:"); LCD_ShowString(30,150,200,16,16,"ADC1_CH1_VOL:0.000V");//先显示小数点 while(1) { adcx=Get_Adc_Average(ADC_CHANNEL_1,20);//获通道 1 的转换值,20 次取平均 LCD_ShowxNum(134,130,adcx,4,16,0); //显示 ADCC 采样后的原始值 temp=(float)adcx*(3.3/4096); //获取计算后的带小数的实际电压值,比如 3.1111 adcx=temp; //赋值整数部分给 adcx 变量,因为 adcx 为 u16 整形 LCD_ShowxNum(134,150,adcx,1,16,0); //显示电压值的整数部分,3.1111 的话,这里就是显示 3 temp-=adcx; //把已经显示的整数部分去掉,留下小数部分,比如 3.1111-3=0.1111 temp*=1000; //小数部分乘以 1000,例 如:0.1111 就转换为 111.1,相当于保留三位小数。 LCD_ShowxNum(150,150,temp,3,16,0X80); //显示小数部分(前面转换为了整形显示),这里显示的就是 111. LED0=!LED0; delay_ms(250); } } 此部分代码,我们在 TFTLCD 模块上显示一些提示信息后,将每隔 250ms 读取一次 ADC 通道 0 的值,并显示读到的 ADC 值(数字量),以及其转换成模拟量后的电压值。同时控制 LED0 闪烁,以提示程序正在运行。 20.4 下载验证 在代码编译成功之后,我们通过下载代码到 ALIENTEK MiniSTM32 开发板上,可以看到 LCD 显示如图 20.4.1 所示: 图 20.4.1 ADC 实验测试图 图中,我们已经拔了 RMT 和 PA1 的跳线帽,PA1 处于浮空状态,容易受干扰,所以电压 是不定的,这个是正常的现象。然后用杜邦线连接 PA1 到其他地方即可进行 AD 测试,但是一 定别接到超过 3.3V 的电压上面去,否则可能烧坏 ADC! 通过这一章的学习,我们了解了 STM32 ADC 的使用,但这仅仅是 STM32 强大的 ADC 功 能的一小点应用。STM32 的 ADC 在很多地方都可以用到,其 ADC 的 DMA 功能是很不错的, 建议有兴趣的大家深入研究下 STM32 的 ADC,相信会给你以后的开发带来方便。 |
|
相关推荐
|
|
991 浏览 0 评论
AD7686芯片不传输数据给STM32,但是手按住就会有数据。
970 浏览 2 评论
2080 浏览 0 评论
如何解决MPU-9250与STM32通讯时,出现HAL_ERROR = 0x01U
1177 浏览 1 评论
hal库中i2c卡死在HAL_I2C_Master_Transmit
1599 浏览 1 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-22 19:18 , Processed in 0.488603 second(s), Total 64, Slave 46 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号