完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
扫一扫,分享给好友
|
|
相关推荐
1个回答
|
|
一 . 需求分析阶段
1.1 引入 随着近几年电子产品的高速发展,出现了各式各样的便携式产品,他们的发展趋势必将是更小、更轻,功能更强大。那么在产品的开发过程中,需要在满足需求中性能指标后,尽可能的优化产品功耗。 我们做不到 让马儿跑,又让马儿不吃草,但是可以合理的规划它跑的路线和时间段。 1.2 需求分析举例 以某公司手环为例:(以下故事纯属瞎编) 老板偶然看到别人带了个智能手环挺好,然后回来就决定自己也要做手环,吩咐产品经理去调研市面上的手环(竞品分析),产品经理通过调研发现小米的手环功能最多,待机最长,价格最便宜。然后就开始根据市面总体情况编写需求文档啦,我要求成本200块以内,待机20天,有记步等功能,外形多高多宽。然后交给工程师小李了。 小李一看,我擦,成本200,待机还要这么长,还要这么多功能,外形大小还限制了。要求真多,但也没办法,接下来就要根据需求进行软硬件的设计,首先我们先分析下小米手环的设计。 小米手环的硬件设计方面: 低功耗OLED显示屏(相对TFT LCD 数码管等功耗更低) 快速稳定的蓝牙4.0主控芯片(低功耗主控) 大电量,长续航(高能量高密度,锂聚合物电池) 传感器,采用功耗低,精度高的传感器芯片 硬件方面,器件的选型属于硬标准,为你低功耗的产品设计提供可能。 小米手环的软件设计方面: 软件上主要在使用方式上进行优化,例如检测不到运动芯片进入低功耗,屏幕熄灭,当运动的时候才进行触发等。 通过上面的例子我们也都发现,低功耗产品设计=低功耗硬件设计+低功耗软件设计(策略,使用方式) 二. 低功耗的硬件上设计 一个产品的设计,在硬件方面我们主要考虑哪些方面? (1)说道主控芯片:我们以前接触过的MSP430,好多公司选它大都是因为它的低功耗特性。 但是仅几年stm32 L系列发展的势头更猛,我们下面说的低功耗都是基于stm32L系列进行讲解。 (2)电源管理:LDO(低静态电流,输出可关断)DC/DC(高转化率,输出可关断) 注:静态电流就是不工作的时候不耗电,输出可关断就是可以cpu控制(用cpu管脚进行控制关掉)在我们选择电源管理芯片的时候一定要特别注意,有的芯片负载能力强,但是功耗高,有的功耗低,但是负载能力差,需要根据需求进行选定。可适当配合cpu进行关断处理。 (3)外设模块(休眠模式可配置,超低休眠电流,超低待机电流,中断唤醒功能) (4)外部接口 输入:超低功耗外围器件 输出:MOS管控制输出,避免长期供电。 注:低功耗外围器件选型可参考之前的功耗PPT 电容,按键,LED,存储器,模拟电路等。 三. stm32选型 1.stm32产品线 stm32的各个系列都有在低功耗方面做处理,但是想要更低的功耗,肯定是优先选择L系列。 手册内容: STM32L4系列MCU可以根据微处理器运行时不同的应用需求来适时调整电压从而实现功耗的动态平衡。该功能适用于STOP模式下的低功耗外设(LP UART、LP定时器)、安全和保密特性、大量智能外设,以及诸如运算放大器、比较器、LCD、12位DAC和16位ADC(硬件过采样)等先进的低功耗模拟外设。 功耗方面,L系列到底在哪些方面优于F系列? 1>低功耗模式更多 stm32F1 和stm32L系列模式对比 2>低功耗模式下提供了更多 功能选择 3>低功耗模式下支持更多的唤醒源 4>实测,L4的漏电流确实比F1系列要小很多 等,不详细展开说明。 更为灵活的设计也必然会让我们在软件设计上更为灵活。 四 . stm32低功耗处理关键点 以stm32L476为例,决定 STM32L476 系统功耗的主要是三个因素:稳压器(voltage regulator)、CPU 工作频率、芯片自身低功耗的处理,下面分别对三个因素进行阐述。 1.稳压器 L4 使用两个嵌入式线性稳压器为所有数字电路、待机电路以及备份时钟域供电,分别是主稳压器(main regulator,下文简称 MR)和低功耗稳压器(low-power regulator,下文简称 LPR)。稳压器在复位后处于使能状态,根据应用模式,选择不同的稳压器对 Vcore 域供电。其中,MR 的输出电压可以由软件配置为不同的范围(Range 1 和 Rnage 2)。
注:不同的供电范围,外设应用上会有限制(ADC USB) 2.CPU 工作频率 通过降低 CPU 的主频达到降低功耗的目的: MR 工作在 Range 1 正常模式时,SYSCLK 最高可以工作在 80M; MR 工作在 Range 2 时,SYSCLK 最高不能超过 26 M; 低功耗运行模式和低功耗休眠模式,即 Vcore 域由 LPR 供电,SYSCLK 必须小于 2M。 注:在频率修改的时候一定要注意供电范围,及外设的限制。 3.芯片本身的低功耗处理 芯片本身定义了一系列的休眠模式,如 Sleeep、Stop、Standby 和 Shutdown,前面的四种模式功耗逐渐降低,实质是芯片内部通过关闭外设和时钟来实现。 参考:低功耗管理系统从设计上分离运行模式和休眠模式,独立管理,运行模式用于变频和变电压,休眠调用芯片的休眠特性。对于多数芯片和开发来说,可能并不需要考虑变频和变电压,仅需关注休眠模式。 五.stm32L 低功耗模式 我们下面主要讲解sleep模式和stop模式,其他模式作为了解。 5.1 低功耗运行模式(RUN LP): 内核和外设都可以保持运行状态 进入低功耗运行模式必须:
内核逻辑由低功耗稳压器供电,以降低静态电流; 在低功耗睡眠模式下,可关闭闪存(掉电模式和时钟门控)。当处理器从SRAM1或SRAM2 执行时,它还可在低功耗运行模式下关闭; 系统时钟频率最大限于2 MHz。可选择MSI内部RC振荡器,因为它支持多种频率范围,低 功耗睡眠闪存关闭时MCU总消耗很小,在100 kHz可低至18 µA 5.2 睡眠模式(SLEEP):内核停止运行,外设保持运行
退出:任意的NVIC识别到的外设中断都可以唤醒内核 WFE (由事件唤醒) 事件是指在没有在NVIC中使能的外设中断,或者被配置为事件模式的EXTI中 断。 退出:一旦事件产生,即可唤醒内核,不会有进入/退出中断而产生的延迟
Sleep on Exit: SLEEPONEXIT=1:MCU在退出某个低优先级的ISR之后进入 SLEEP模式
此可以关闭不使用的外设的时钟,以降低功耗 5.3低功耗睡眠模式:内核停止运行,外设保持运行 进入低功耗睡眠模式必须:
5.4停止模式:内核停止,VCORE范围内的时钟都停止,PLL, MSI, HSI和HSE都被禁止,SRAM和寄存器的内容保留 进入停止模式必须:
进入停止模式可选:
达48 MHz的MSI,能够在1 µs内唤醒。 在这些停止模式下,所有高速振荡器(HSE,MSI,HSI)都停止,而低速振荡器(LSE, LSI)可保持活动。外设可设置为活动的,需要时可使用HSI时钟,能够在一些特定事件 (如UART字符重复或I2C地址识别)下唤醒设备。 Stop2模式可实现专门机制,使保持电流尽可能低,同时允许非常快速的唤醒,从SRAM唤醒 需要5 µs,或从闪存唤醒需要8 µs。 5.5待机模式:内部电源变换器关闭,VCORE范围内全部断电, PLL, MSI, HSI和HSE都被禁止,SRAM和寄存器内容不保留,RTC寄存器,RTC备份寄存器和待机电路保持原状 在待机模式下所有I/O口都保持高阻状态,除了:
进入待机模式可选:
每个模式都有自己特点,根据自己的需求,进行模式的适配。 在上面的模式中也不断的提到,在什么模式下,什么外设可用,什么时钟不可用,要用什么中断源唤醒,无非都是ST在芯片设计的时候将电源的管理部分进行更为灵活的设计,才能为我们提供这么多的动态接口。那么ST的内部电源管理还是需要进行了解的。 六.stm32L freertos 下低功耗模式 1.sleep模式 freertos 进入空闲后,进入sleep模式,sleep模式下任何中断都会导致退出sleep模式,滴答定时器也会导致唤醒,这肯定不是我们想要的,但是freertos的系统调度依赖于滴答时钟,那么我们的系统就会崩溃,那么对这段时间进行下补偿就好了,我们可以使用LPtime进行时间补偿,freertos使用滴答时钟完成了这个功能,我们通过系统函数得到下个任务到来的时间,然后设置滴答定时器在那个时候来中断唤醒系统,当然也会存在其他中断唤醒的情况,滴答时钟进行计时,之后的时间减去睡眠之前记录的时间,就可以轻松得到补偿时间。 这个操作,依赖于我们最先就配置好了时钟,滴答时钟进行精准的计时,那假如我在整个系统运行中想要把整个主频降低该怎么办? 我们在动态改变整个系统的时钟的时候肯定要先把整个任务调度和中断停掉,这个时候再更换时钟,就神不知鬼不觉了,当然这种方式肯定要快而且不能频繁操作,不然我们的整个系统的调度时间都要被延后了。 2.freertos 下stop模式 模仿tickless进行的实现。串口唤醒,中断唤醒及lptime进行时间补偿的计时。 重要的是时钟的恢复,不然整个系统会跑乱。 配合RTC进行唤醒 关键点1:时钟的选择(配置和恢复) 时钟设置: stop模式是会停掉时钟的,唤醒后要进行时钟的恢复,这样freertos才能正常的进行调度。(恢复的时钟要和设置的前后保持一致) 关键点2:设置串口中断唤醒及RTC唤醒 我们知道,低功耗模式的操作是在空闲时间进行的,我们肯定不能睡过,不然就错过了下一任务的执行,我们通过 RTC唤醒中断,当下一个任务到来的时候进行周期唤醒,也就是获得最大的stop时间,这个时间我们可以通过freertos的接口进行获取到。 但是我们的串口数据不是规律到来的,所以还要配置下串口中断唤醒,但是最为麻烦的是,从stop开始,到串口唤醒,这段时间是不知道的,freertos是通过滴答定时器进行计时的,但是我们stop模式下只有LSE和LSI时钟可用。一种方法就是将滴答时钟改为以上时钟,这样stop的时候就不会影响计时了,还有就是通过LPtime进行定时。 大致流程如下:
注:LPtime的定时时间要精准,不然整个调度过程就会出错。 关键点三:滴答时钟寄存器 在时间补偿的过程中,我们要停掉滴答时钟,滴答定时器重装载,恢复时钟等操作,滴答定时器的寄存器需要理解。 了解sysTick定时器: Systick(系统节拍)定时器,它属于NVIC的一部分,且可以产生SysTick异常。Systick为简单的向下计数的24位计数器,可以使用处理器内部时钟或者外部参考时钟。 在操作系统中,需要一个周期性的中断来定期触发OS内核。如用于任务管理和上下文切换,处理器也可以在不同时间片内处理不同任务。处理器设计还需要确保运行在非特权等级的应用任务无法禁止该定时器,否则任务可能会禁止SysTick定时器并锁定整个系统。 systick中存在4个寄存器: 如何停止定时器,如果重装载值,这里不过多说明,查看相应的寄存器 补偿步骤:
附件: LPuart stop下唤醒中断配置: void LPUART_PrepareToStopMode(void) { uint8_t ubReceivedChar; /* Empty RX Fifo before entering Stop mode 2 (Otherwise, characters already present in FIFO will lead to immediate wake up */ while (LL_LPUART_IsActiveFlag_RXNE(LPUART1)) { /* Read Received character. RXNE flag is cleared by reading of RDR register */ ubReceivedChar = LL_LPUART_ReceiveData8(LPUART1); } ubReceivedChar = ubReceivedChar; /* Clear OVERRUN flag */ LL_LPUART_ClearFlag_ORE(LPUART1); /* Make sure that no LPUART transfer is on-going */ while(LL_LPUART_IsActiveFlag_BUSY(LPUART1) == 1) { } /* Make sure that LPUART is ready to receive */ while(LL_LPUART_IsActiveFlag_REACK(LPUART1) == 0) { } /* Configure LPUART1 transfer interrupts : */ /* Clear WUF flag and enable the UART Wake Up from stop mode Interrupt */ LL_LPUART_ClearFlag_WKUP(LPUART1); LL_LPUART_EnableIT_WKUP(LPUART1); /*STOP2模式下使能时钟*/ LL_LPUART_EnableClockInStopMode(LPUART1); /* Enable Wake Up From Stop */ LL_LPUART_EnableInStopMode(LPUART1); } stop唤醒后,时钟恢复: void SYSCLKConfig_STOP(void) { RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; RCC_OscInitTypeDef RCC_OscInitStruct = {0}; uint32_t pFLatency = 0; /* Enable Power Control clock */ __HAL_RCC_PWR_CLK_ENABLE(); /* Get the Oscillators configuration according to the internal RCC registers */ HAL_RCC_GetOscConfig(&RCC_OscInitStruct); /* Enable PLL */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_NONE; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { } /* Get the Clocks configuration according to the internal RCC registers */ HAL_RCC_GetClockConfig(&RCC_ClkInitStruct, &pFLatency); /* Select PLL as system clock source and keep HCLK, PCLK1 and PCLK2 clocks dividers as before */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, pFLatency) != HAL_OK) { } } 睡眠前第一个函数及唤醒后第一个函数: void OS_PreSleepProcessing(uint32_t xExpectedIdleTime) { tick_sleep = xExpectedIdleTime; LPUART_PrepareToStopMode(); //配置串口唤醒 HAL_RTCEx_DeactivateWakeUpTimer(&RTCHandle); //配置RTC唤醒 HAL_RTCEx_SetWakeUpTimer_IT(&RTCHandle, (uint32_t)((tick_sleep)*2.048), RTC_WAKEUPCLOCK_RTCCLK_DIV16); // Wakeup Time Base = 16 /(~32.000KHz) = ~0.5 ms count_start(); //重置计数 HAL_PWREx_EnterSTOP1Mode(PWR_STOPENTRY_WFI); //进入低功耗 uart_wkp = 0; } void OS_PostSleepProcessing(uint32_t ulExpectedIdleTime) { time_count_sys = LL_LPTIM_GetCounter(LPTIM2)+4; count_stop(); if(uart_wkp != 1) { //表示为RTC唤醒 rtc_wakeup_flag =1; } else { rtc_wakeup_flag =0; uart_wkp = 0; } SYSCLKConfig_STOP(); LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM1_CLKSOURCE_LSI); LL_LPTIM_Enable(LPTIM1); LL_LPTIM_SetAutoReload(LPTIM1, 65535); LL_LPTIM_StartCounter(LPTIM1, LL_LPTIM_OPERATING_MODE_CONTINUOUS);//恢复时钟 } lptimer2初始化: static void MX_LPTIM2_Init(void) { LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_LPTIM2); //LL_LPTIM_Enable(LPTIM1); /* USER CODE END LPTIM1_Init 1 */ LL_LPTIM_SetClockSource(LPTIM2, LL_LPTIM_CLK_SOURCE_INTERNAL); LL_LPTIM_SetPrescaler(LPTIM2, LL_LPTIM_PRESCALER_DIV32); LL_LPTIM_SetPolarity(LPTIM2, LL_LPTIM_OUTPUT_POLARITY_REGULAR); LL_LPTIM_SetUpdateMode(LPTIM2, LL_LPTIM_UPDATE_MODE_IMMEDIATE); LL_LPTIM_SetCounterMode(LPTIM2, LL_LPTIM_COUNTER_MODE_INTERNAL); LL_LPTIM_TrigSw(LPTIM2); LL_LPTIM_SetInput1Src(LPTIM2, LL_LPTIM_INPUT1_SRC_GPIO); LL_LPTIM_SetInput2Src(LPTIM2, LL_LPTIM_INPUT2_SRC_GPIO); } 主要的时钟补偿函数: __weak void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime ) { uint32_t ulReloadValue, ulCompleteTickPeriods, ulSysTickCTRL; /* 确保滴答定时器的重装值不会溢出(不超过最大计数值) */ if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks ) { xExpectedIdleTime = xMaximumPossibleSuppressedTicks; } /* 停止滴答定时器*/ portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT; /* 根据参数xExpectedIdleTime来计算滴答定时器的重装载值 */ ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG + ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) ); /* Enter a critical section but don't use the taskENTER_CRITICAL() method as that will mask interrupts that should exit sleep mode. */ __DSB(); __ISB(); /* If a context switch is pending or a task is waiting for the scheduler to be unsuspended then abandon the low power entry. */ if( eTaskConfirmSleepModeStatus() == eAbortSleep ) //再次判断是否能够进入低功耗 { /* Restart from whatever is left in the count register to complete this tick period. */ portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG; /* Restart SysTick. */ portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT; /* Reset the reload register to the value required for normal tick periods. */ portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL; /* Re-enable interrupts - see comments above __disable_interrupt() call above. */ } else { /* Set the new reload value. */ portNVIC_SYSTICK_LOAD_REG = ulReloadValue; /* Clear the SysTick count flag and set the count value back to zero. */ portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL; /* Restart SysTick. */ portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT; /* Sleep until something happens. configPRE_SLEEP_PROCESSING() can set its parameter to 0 to indicate that its implementation contains its own wait for interrupt or wait for event instruction, and so wfi should not be executed again. However, the original expected idle time variable must remain unmodified, so a copy is taken. */ configPRE_SLEEP_PROCESSING(xExpectedIdleTime); configPOST_SLEEP_PROCESSING( xExpectedIdleTime ); /* 停止滴答定时器 */ ulSysTickCTRL = portNVIC_SYSTICK_CTRL_REG; portNVIC_SYSTICK_CTRL_REG = ( ulSysTickCTRL & ~portNVIC_SYSTICK_ENABLE_BIT ); /* Re-enable interrupts - see comments above __disable_interrupt() call above. */ if(rtc_wakeup_flag ==1) { rtc_wakeup_flag = 0; ulCompleteTickPeriods = tick_sleep; //rtc正常周期的时钟补偿 } else { if(time_count_sys > xExpectedIdleTime) { time_count_sys = xExpectedIdleTime; } ulCompleteTickPeriods = time_count_sys;//串口中断的时钟补偿 } /* 重新启动滴答定时器,滴答定时器的重装载值设置为正常值*/ portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL; portENTER_CRITICAL(); { portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT; vTaskStepTick( ulCompleteTickPeriods ); portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL; } portEXIT_CRITICAL(); } } rtc初始化:(唤醒+时钟功能) static void MX_RTC_Init(void) { /* USER CODE BEGIN RTC_Init 0 */ LL_EXTI_InitTypeDef EXIT_InitStructure; /* USER CODE END RTC_Init 0 */ LL_RTC_TimeTypeDef RTC_TimeStruct = {0}; LL_RTC_DateTypeDef RTC_DateStruct = {0}; /* Peripheral clock enable */ LL_RCC_EnableRTC(); /* RTC interrupt Init */ NVIC_SetPriority(RTC_WKUP_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0)); NVIC_EnableIRQ(RTC_WKUP_IRQn); /* USER CODE BEGIN RTC_Init 1 */ LL_RTC_DisableWriteProtection(RTC); LL_RTC_WAKEUP_Disable(RTC); /* Wait until it is allow to modify wake up reload value */ while (LL_RTC_IsActiveFlag_WUTW(RTC) != 1) { } LL_RTC_WAKEUP_SetAutoReload(RTC, 0); LL_RTC_WAKEUP_SetClock(RTC, LL_RTC_WAKEUPCLOCK_DIV_16); /* Enable wake up counter and wake up interrupt */ LL_RTC_EnableIT_WUT(RTC); LL_RTC_WAKEUP_Enable(RTC); LL_RTC_EnableWriteProtection(RTC); /* Reset Internal Wake up flag */ LL_RTC_ClearFlag_WUT(RTC); /*初始化RTC唤醒对应的EXIT_LINE_20*/ LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_20); EXIT_InitStructure.Line_0_31 = LL_EXTI_LINE_20; EXIT_InitStructure.LineCommand = ENABLE; EXIT_InitStructure.Mode = LL_EXTI_MODE_IT ; EXIT_InitStructure.Trigger = LL_EXTI_TRIGGER_RISING; LL_EXTI_Init(&EXIT_InitStructure); /* USER CODE END RTC_Init 1 */ /** Initialize RTC and set the Time and Date */ RTCHandle.Instance = RTC; RTCHandle.Init.HourFormat = RTC_HOURFORMAT_24; RTCHandle.Init.AsynchPrediv = RTC_ASYNCH_PREDIV; RTCHandle.Init.SynchPrediv = RTC_SYNCH_PREDIV; RTCHandle.Init.OutPut = RTC_OUTPUT_DISABLE; RTCHandle.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH; RTCHandle.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN; if( HAL_RTC_Init(&RTCHandle) != HAL_OK) { /* Initialization Error */ } /** Initialize RTC and set the Time and Date */ if(LL_RTC_BAK_GetRegister(RTC, LL_RTC_BKP_DR0) != 0x32F2){ RTC_TimeStruct.Hours = 10; RTC_TimeStruct.Minutes = 0; RTC_TimeStruct.Seconds = 0; LL_RTC_TIME_Init(RTC, LL_RTC_FORMAT_BCD, &RTC_TimeStruct); RTC_DateStruct.WeekDay = LL_RTC_WEEKDAY_TUESDAY; RTC_DateStruct.Month = 10; RTC_DateStruct.Day = 1; RTC_DateStruct.Year = 19; LL_RTC_DATE_Init(RTC, LL_RTC_FORMAT_BCD, &RTC_DateStruct); LL_RTC_BAK_SetRegister(RTC,LL_RTC_BKP_DR0,0x32F2); } /** Initialize RTC and set the Time and Date */ /* USER CODE BEGIN RTC_Init 2 */ /* USER CODE END RTC_Init 2 */ } |
|
|
|
只有小组成员才能发言,加入小组>>
3707个成员聚集在这个小组
加入小组3014 浏览 0 评论
航顺(HK)联合电子发烧友推出“近距离体验高性能Cortex-M3,免费申请价值288元评估板
4002 浏览 1 评论
3987 浏览 0 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-7-21 15:47 , Processed in 0.473953 second(s), Total 44, Slave 38 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号