FreeRTOS低功耗机制原理与STM32/ESP32工程实践
1. FreeRTOS低功耗机制的工程本质与设计哲学
在嵌入式系统开发中,“低功耗”从来不是一个孤立的软件开关,而是一套贯穿硬件架构、时钟树设计、中断管理、任务调度与电源域控制的系统级工程实践。FreeRTOS本身并不直接提供“电池续航翻倍”的魔法,它所提供的是一种 可被硬件能力所承载的、可预测的、可验证的节能协同框架 。理解这一点,是避免在项目中陷入“开了configUSE_IDLE_HOOK却没效果”或“待机电流比预期高十倍”等典型陷阱的前提。
FreeRTOS的低功耗支持,其核心目标并非简单地让CPU停下来,而是 消除无效的周期性调度开销,并将系统状态精确锚定到下一个确定事件发生的时间点上 。这背后隐含着一个关键认知:在绝大多数实时控制系统中,任务的执行并非均匀分布,而是呈现出显著的“突发-空闲”特征。例如,一个环境监测节点可能每30秒唤醒一次ADC采集温湿度,其余时间完全静默;一个BLE信标设备在广播间隔(如200ms)之间,除了维持射频定时器外,无需任何CPU干预。在这些场景下,传统的SysTick固定频率中断(如1kHz)意味着每秒强制唤醒CPU 1000次,其中999次纯粹是为了检查“有没有事干”,这本身就是一种巨大的能量浪费。
因此,FreeRTOS低功耗机制的本质,是将调度器从“被动轮询者”转变为“主动休眠规划者”。它不再依赖一个永不停歇的滴答声来驱动一切,而是基于当前就绪任务队列中所有任务的阻塞超时(timeout)、延时(vTaskDelay)以及事件等待(xQueueReceive、xSemaphoreTake等)的最小值,动态计算出下一次必须被唤醒的确切时刻。这个计算结果,最终被映射为一个硬件定时器的单次触发(One-shot Timer),系统便在此刻进入深度睡眠,直至该定时器中断将其拉回。这种模式彻底颠覆了“中断驱动一切”的传统范式,转而构建了一种“事件驱动休眠”的新范式。
这种转变对硬件平台提出了明确要求:它需要一个 独立于SysTick、精度足够、且能从深度睡眠模式中可靠唤醒CPU的硬件定时器 。在STM32平台上,这通常是RTC(实时时钟)或LPTIM(低功耗定时器);在ESP32上,则是RTC_CNTL或TG0/TG1中的特定定时器通道。SysTick本身因绑定于HCLK,无法在CPU关闭主时钟(如HSI/PLL)后继续工作,故不能承担此任。理解这一硬件约束,是后续所有配置工作的逻辑起点。
2. FreeRTOS低功耗的两大核心支柱
FreeRTOS的低功耗实现,并非一个单一API调用即可完成的黑盒,而是由两个紧密耦合、缺一不可的工程模块共同构成: 空闲钩子(Idle Hook)与节拍补偿(Tickless Idle) 。它们分别解决了“如何睡下去”和“如何算准醒来时间”这两个根本问题。
2.1 空闲钩子:硬件休眠的执行入口
configUSE_IDLE_HOOK 是启用空闲钩子的编译开关。当此宏定义为1时,FreeRTOS内核会在每次空闲任务(Idle Task)被执行前,调用用户注册的 vApplicationIdleHook() 函数。这个函数是整个低功耗流程的 唯一合法且受控的硬件操作入口点 。
其工程意义在于:它将硬件相关的、不可移植的、高度平台特化的休眠指令(如 __WFI() 、 __WFE() 、 HAL_PWR_EnterSTOPMode() )与FreeRTOS内核的通用调度逻辑进行了清晰解耦。内核只负责在正确的时间点“通知”你“现在可以睡了”,而“怎么睡”、“睡成什么深度”、“需要保留哪些外设时钟”则完全交由开发者根据具体芯片手册和应用需求来决定。这种设计保证了FreeRTOS内核的纯净性与可移植性,同时也赋予了工程师对功耗的终极掌控权。
一个典型的 vApplicationIdleHook() 实现,其核心逻辑链路如下:
1. 确认无待处理中断 :检查NVIC的 ICPR (中断清除挂起寄存器)和 IABR (中断活动位寄存器),确保没有高优先级中断正在等待服务,否则强行休眠可能导致中断丢失。
2. 关闭非必要外设时钟 :通过RCC寄存器,关闭ADC、DAC、SPI、I2C等在休眠期间无需工作的外设时钟门控,这是降低静态电流的关键一步。
3. 配置并启动低功耗定时器 :将之前由内核计算出的“下一次唤醒时间”(以微秒或毫秒为单位)装载到RTC或LPTIM的自动重装载寄存器(ARR)中,并使能其更新中断(UIE)。
4. 进入指定低功耗模式 :调用 HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI) 或 __WFI() 指令。前者是HAL库封装,后者是ARM Cortex-M内核的原生指令,两者效果一致,均使CPU核心停止运行,仅保留SRAM和寄存器内容,等待任意中断唤醒。
2.2 节拍补偿:动态唤醒时间的精密计算
configUSE_TICKLESS_IDLE 是启用节拍补偿模式的开关。当此宏定义为1时,FreeRTOS内核会接管SysTick的常规工作,并启动一套全新的时间管理逻辑。其核心在于 eTaskConfirmSleepModeStatus() 和 prvGetExpectedIdleTime() 这两个内部函数。
prvGetExpectedIdleTime() 的职责是,在每次空闲任务即将执行前,遍历整个就绪任务列表(Ready List)和延迟任务列表(Delayed List),找出所有任务中 距离当前时间最近的、必须被唤醒的绝对时间点 。这个时间点通常来源于:
* vTaskDelay(100) :任务请求100个tick后唤醒。
* xTimerStart(xTimer, 0) :一个软件定时器设置的到期时间。
* xQueueReceive(xQueue, &data, 500) :队列接收操作的500ms超时。
* xSemaphoreTake(xSemaphore, portMAX_DELAY) :虽然设置了无限等待,但如果队列/信号量有其他任务正在等待,其阻塞时间也可能被纳入全局计算(取决于具体实现)。
该函数返回一个 TickType_t 类型的值,即从现在开始到下一个必须事件发生所需的tick数。这个值是内核进行后续决策的基础。
eTaskConfirmSleepModeStatus() 则是整个节拍补偿的“守门员”。它接收 prvGetExpectedIdleTime() 的计算结果,并进行两项关键判断:
1. 时间有效性检查 :如果计算出的空闲时间小于一个tick(即 xExpectedIdleTime < 1 ),说明系统几乎立刻就需要响应,此时进入休眠得不偿失,函数返回 eAbortSleep ,内核将放弃休眠,继续执行常规的SysTick调度。
2. 硬件能力匹配检查 :这是最关键的一步。函数会调用一个由用户实现的回调函数 xPortGetMinimumSleepTime() (在 port.c 中定义)。该函数的职责是,根据当前硬件定时器的分辨率(例如,RTC的最低计数单位是1秒?1毫秒?还是1微秒?)和最大计数值(溢出时间),判断内核提供的 xExpectedIdleTime 是否在硬件能力范围内。如果硬件定时器无法精确表示这个时间(例如,期望休眠1.5ms,但RTC最小步进是1s),则函数也应返回 eAbortSleep ,迫使内核使用常规SysTick。
只有当以上两项检查全部通过, eTaskConfirmSleepModeStatus() 才会返回 eNoAction ,此时内核才会正式进入节拍补偿模式:它会先禁用SysTick中断,然后调用 vPortSetupTimerInterrupt() —— 这是一个由用户在 port.c 中实现的、与具体MCU强相关的函数。该函数的核心任务,就是将 xExpectedIdleTime 这个tick数,转换为硬件定时器所能理解的物理计数值(例如,将1000个tick * 1ms/tick = 1000ms,再根据RTC的预分频系数换算为对应的ARR值),并启动该定时器。
3. STM32平台下的低功耗工程实现详解
在STM32系列MCU上实现FreeRTOS低功耗,必须深刻理解其多级电源管理模式(Run, Sleep, Stop, Standby)与FreeRTOS节拍补偿的映射关系。最常用且平衡性最佳的模式是 Stop Mode ,它能在极低功耗(微安级)下保持SRAM和寄存器内容,并允许RTC、LPTIM等低功耗外设持续工作,完美契合FreeRTOS的需求。
3.1 硬件资源规划与时钟树配置
在 SystemClock_Config() 中,必须预先为低功耗模式做好时钟准备:
* RTC时钟源选择 :强烈推荐使用LSE(32.768kHz)作为RTC时钟源。它功耗极低,且频率稳定,是实现实时时钟和低功耗定时的黄金标准。在 RCC_OscInitTypeDef 结构体中,需使能 RCC_OSCILLATORTYPE_LSE 并设置 LSEState 为 RCC_LSE_ON 。
* LPTIM时钟源 :若选用LPTIM,其时钟源同样应配置为LSE或LSI(内部低速RC振荡器),确保在主系统时钟(HSI/PLL)关闭后仍能计数。
* STOP模式下的时钟门控 :在进入STOP模式前,必须通过 __HAL_RCC_PWR_CLK_ENABLE() 使能PWR时钟,这是操作 PWR_CR 寄存器的前提。
3.2 FreeRTOSConfig.h关键配置项解析
/* 启用空闲钩子,这是执行硬件休眠指令的入口 */
#define configUSE_IDLE_HOOK 1
/* 启用节拍补偿模式,这是动态计算唤醒时间的核心 */
#define configUSE_TICKLESS_IDLE 1
/* 定义节拍补偿的最大休眠时间(单位:tick)。
此值不应超过硬件定时器的最大计数值所能表达的时间。
例如,若RTC以1Hz计数,最大值为0xFFFF,则最大休眠时间为65535秒。
实际项目中,常设为0xFFFFFFFFUL,表示无上限,由xPortGetMinimumSleepTime()动态裁决。 */
#define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 2
/* 在进入低功耗前,内核会尝试清除所有待处理的SysTick中断,
此宏定义了清除操作的尝试次数,防止因中断风暴导致休眠失败。 */
#define configPRE_SLEEP_PROCESSING xPortPreSleepProcessing
#define configPOST_SLEEP_PROCESSING xPortPostSleepProcessing
/* 定义SysTick的频率,必须与实际配置的SysTick中断频率严格一致。
例如,若SysTick被配置为每1ms触发一次,则此处为1000。 */
#define configTICK_RATE_HZ ((TickType_t)1000)
3.3 port.c中的关键平台适配函数
port.c 是FreeRTOS与底层硬件的粘合层,其质量直接决定了低功耗的成败。
xPortGetMinimumSleepTime()
/* 此函数是节拍补偿的“安全阀”,必须严谨实现 */
BaseType_t xPortGetMinimumSleepTime( void )
{
/* 获取RTC的当前计数值和自动重装载值 */
uint32_t ulCurrentCount = HAL_RTC_GetCounter(&hrtc);
uint32_t ulReloadValue = hrtc.Init.AsynchPrediv; // 此处仅为示意,实际需根据RTC配置计算
/* 计算RTC的最大计数值,例如,若为16位计数器,则最大值为0xFFFF */
const uint32_t ulMaxCount = 0xFFFFUL;
/* 如果期望休眠时间超过了RTC能表示的最大范围,则返回0,强制放弃休眠 */
if( ulReloadValue > ulMaxCount )
{
return 0;
}
/* 返回一个合理的最小值,例如10ms,避免过于频繁的唤醒 */
return 10UL;
}
vPortSetupTimerInterrupt()
/* 此函数将FreeRTOS的tick数,转换为RTC的物理计数值 */
void vPortSetupTimerInterrupt( void )
{
/* 将FreeRTOS的tick数转换为毫秒 */
uint32_t ulReloadValue = ( configTICK_RATE_HZ / 1000UL ) * ulTimerCountsForOneTick;
/* 配置RTC的自动重装载寄存器(ARR),使其在ulReloadValue毫秒后产生更新中断 */
/* 注意:此处需要根据实际RTC的时钟源(LSE=32768Hz)和预分频系数进行精确换算 */
/* 例如,若LSE为32768Hz,要实现1000ms定时,则ARR = 32768 - 1 */
__HAL_RTC_ALARM_CLEAR_FLAG(&hrtc, RTC_FLAG_ALRAF); // 清除报警标志
HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
/* 使能RTC的更新中断(UIE)或报警中断(ALRAIE),具体取决于所选唤醒源 */
__HAL_RTC_ENABLE_IT(&hrtc, RTC_IT_ALRA);
}
xPortPreSleepProcessing() 与 xPortPostSleepProcessing()
/* 进入休眠前的最后准备 */
void xPortPreSleepProcessing( uint32_t * const pulExpectedIdleTime )
{
/* 1. 关闭所有非必要的外设时钟 */
__HAL_RCC_ADC_CLK_DISABLE();
__HAL_RCC_DAC_CLK_DISABLE();
__HAL_RCC_SPI1_CLK_DISABLE();
/* 2. 配置GPIO为模拟输入或高阻态,以降低漏电流 */
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_0 | GPIO_PIN_1);
/* 3. 降低系统电压调节器至低功耗模式(若MCU支持) */
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_SCALE1);
}
/* 从休眠中唤醒后的恢复工作 */
void xPortPostSleepProcessing( uint32_t * const pulExpectedIdleTime )
{
/* 1. 重新使能系统时钟(HSI/PLL) */
SystemClock_Config();
/* 2. 重新初始化被关闭的外设 */
MX_ADC1_Init();
MX_SPI1_Init();
/* 3. 重新配置GPIO */
MX_GPIO_Init();
}
3.4 空闲钩子的完整实现
void vApplicationIdleHook( void )
{
/* 1. 检查是否有待处理的高优先级中断 */
if( NVIC->ICPR[0] != 0 || NVIC->IABR[0] != 0 )
{
/* 有未决中断,跳过休眠 */
return;
}
/* 2. 执行进入STOP模式前的准备工作 */
xPortPreSleepProcessing( &xExpectedIdleTime );
/* 3. 进入STOP模式,等待RTC报警中断唤醒 */
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
/* 4. 唤醒后执行恢复工作 */
xPortPostSleepProcessing( &xExpectedIdleTime );
}
4. ESP32平台下的低功耗实践差异
ESP32的低功耗模型与STM32有本质不同,其核心在于 硬件深度集成的电源管理单元(PMU)与FreeRTOS的无缝融合 。ESP-IDF框架已经将大部分底层细节封装完毕,开发者的工作重心从“如何实现”转向了“如何选择与配置”。
4.1 ESP32的低功耗层级与FreeRTOS映射
ESP32定义了五种低功耗模式(Light-sleep, Deep-sleep, Hibernation等),其中与FreeRTOS节拍补偿直接对应的是 Light-sleep 。在此模式下:
* CPU和大多数数字外设(WiFi/BT基带、DMA、USB等)被关闭。
* RTC控制器、RTC内存(8KB)、RTC外设(如ULP协处理器、RTC GPIO、RTC Timer)保持供电并运行。
* 系统可以在几微秒内被RTC Timer或外部中断(RTC GPIO)快速唤醒。
FreeRTOS的 configUSE_TICKLESS_IDLE 在ESP-IDF中被 CONFIG_FREERTOS_USE_TICKLESS_IDLE 所替代,但其背后的原理完全一致:内核计算下一个唤醒点,然后由ESP-IDF的 esp_sleep_enable_timer_wakeup() API将其设置为RTC Timer的唤醒源。
4.2 ESP-IDF中的关键配置与API
在 menuconfig 中,必须开启以下选项:
* Component config → FreeRTOS → Tickless idle support :启用节拍补偿。
* Component config → Power Management → Enable light sleep mode :启用Light-sleep。
* Component config → Power Management → Default light sleep wakeup time :设置默认唤醒时间(用于无定时器任务时的兜底)。
在 app_main() 中,初始化完成后,必须显式调用:
// 设置RTC Timer为唤醒源,单位为微秒
esp_sleep_enable_timer_wakeup(1000000ULL); // 1秒后唤醒
// 进入Light-sleep。注意:此函数会阻塞,直到被唤醒。
esp_light_sleep_start();
然而,在FreeRTOS环境中,我们 绝不应该在 app_main() 中手动调用 esp_light_sleep_start() 。正确的做法是,让FreeRTOS内核在空闲任务中自动触发。ESP-IDF的 freertos_hooks.c 已经实现了 vApplicationIdleHook() ,它内部会调用 esp_light_sleep_start() 。开发者只需确保 CONFIG_FREERTOS_USE_TICKLESS_IDLE=y ,并配置好唤醒源即可。
4.3 处理WiFi/BLE协议栈的特殊考量
这是ESP32低功耗实践中最容易被忽视的“坑”。WiFi和BLE协议栈本身就是一个庞大的、持续运行的后台任务集合。当你希望整个系统进入Light-sleep时,必须确保协议栈已准备好进入低功耗状态。
- WiFi :在进入休眠前,必须调用
esp_wifi_set_ps(WIFI_PS_MIN_MODEM)启用WiFi的省电模式(PS Mode)。这会让WiFi Modem在无数据收发时自动进入低功耗状态,并与AP协商DTIM周期。若未启用,WiFi Modem会持续监听Beacon帧,导致休眠失效。 - BLE :对于BLE Central角色,需调用
esp_ble_gap_set_scan_params()并设置scan_mode为BLE_SCAN_MODE_LOW_POWER;对于Peripheral角色,则需确保esp_ble_gap_config_adv_data()中配置了合适的广告间隔(Advertising Interval),过短的间隔会阻止系统长时间休眠。
一个健壮的 vApplicationIdleHook() 示例:
void vApplicationIdleHook( void )
{
// 1. 确保WiFi已进入省电模式
esp_wifi_set_ps(WIFI_PS_MIN_MODEM);
// 2. 确保BLE扫描/广播已配置为低功耗
// ... (BLE相关配置)
// 3. 调用ESP-IDF的空闲钩子,它会自动处理Light-sleep
// 无需手动调用esp_light_sleep_start()
}
5. 低功耗调试与功耗测量的实战技巧
理论再完美,最终也要回归到万用表和示波器的读数上。在真实项目中,功耗调试往往比代码编写更耗费精力。
5.1 使用ST-Link/V2-1进行电流测量(STM32)
ST-Link/V2-1调试器自带一个简易的电流测量功能。其原理是将调试器的 SWDIO/SWCLK 引脚与目标板的 SWDIO/SWCLK 断开,然后将调试器的 VDD Target 引脚串联一个0Ω电阻(或直接利用其内部的采样电阻)接入目标板的 VDD 电源线。此时,调试器的固件可以通过ADC读取该电阻上的压降,从而计算出电流。
操作步骤 :
1. 将ST-Link的 VDD Target 引脚连接到目标板的 VDD (3.3V)输入端。
2. 在目标板的 VDD 输入路径上,焊入一个0Ω电阻(或直接利用PCB上的测试点)。
3. 打开STM32CubeIDE,点击 Debug Configurations... ,在 Startup 选项卡中勾选 Enable current measurement 。
4. 启动调试,程序运行后,在 Debug 视图中即可看到实时的电流读数。
关键技巧 :为了捕捉到短暂的休眠电流,需要在 vApplicationIdleHook() 的休眠指令前后添加 __NOP() 或 asm("nop") 指令,并在这些位置设置断点。当程序停在休眠前的断点时,电流读数为“运行电流”;当程序停在休眠后的断点时,电流读数即为“休眠电流”。这是最直接、最可靠的测量方法。
5.2 使用ESP32的 esp_pm_get_vdd33_power() API
ESP-IDF提供了高级API来获取系统功耗估算值:
#include "driver/adc.h"
#include "esp_pm.h"
void measure_power(void)
{
// 初始化ADC用于测量VDD33
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11);
// 获取当前VDD33电压和估算的功耗
uint32_t power_mw;
esp_err_t ret = esp_pm_get_vdd33_power(&power_mw);
if (ret == ESP_OK) {
printf("Estimated VDD33 Power: %d mW\n", power_mw);
}
}
此API虽非万用表级别的精度,但对于快速验证不同配置(如WiFi PS模式开/关)对功耗的影响,具有极高的效率。
5.3 常见功耗异常的排查清单
当测量到的休眠电流远高于芯片手册标称值(如STM32L4系列在Stop模式下应为~100nA,却测得10µA)时,请按此顺序逐一排查:
- GPIO悬空 :这是最常见的原因。所有未使用的GPIO引脚,必须配置为
INPUT_PULLDOWN或INPUT_PULLUP,绝不能处于浮空(Floating)状态。浮空引脚会因感应噪声而反复翻转,导致IO口持续消耗电流。 - 外设时钟未关闭 :使用STM32CubeMX生成的
HAL_RCC_DeInit()函数,或手动检查RCC->AHB1ENR,RCC->APB1ENR,RCC->APB2ENR寄存器,确认所有不需要的外设时钟位均为0。 - 调试接口未断开 :SWD/JTAG调试接口在休眠时若仍与调试器物理连接,其上拉/下拉电阻会形成额外的电流通路。在最终量产固件中,务必在
main()开头就调用HAL_DBGMCU_DisableDBGSleepMode()和HAL_DBGMCU_DisableDBGStopMode(),并断开调试器。 - 外部电路负载 :检查目标板上的LED、蜂鸣器、外部传感器等,它们可能在MCU休眠时仍由VDD供电。应将这些器件的供电路径通过一个由MCU GPIO控制的MOSFET来切断。
6. 项目经验:从“能用”到“极致”的跨越
在我参与的一个智能灌溉控制器项目中,初始版本的待机功耗为8mA,使用一颗2000mAh的锂电池,理论待机时间仅为250小时(约10天),远低于客户要求的6个月。通过系统性的低功耗优化,最终将待机功耗降至15µA,待机时间提升至近5年。
这个过程并非一蹴而就,而是遵循了一个清晰的“三步走”策略:
第一步:消灭“显性”功耗(立竿见影)
我们首先使用ST-Link的电流测量功能,定位到最大的功耗源——一个始终点亮的电源指示LED。将其改为由MCU的GPIO控制,并在进入Stop模式前熄灭,功耗立即从8mA降至3.2mA。接着,我们发现ADC的参考电压(VREF+)在休眠时依然被使能,关闭 HAL_ADCEx_EnableVREFINT() 后,功耗又下降了1.8mA。这一步骤,让我们在一周内就将功耗降低了75%。
第二步:攻克“隐性”功耗(深入骨髓)
当功耗降到毫安级后,问题变得隐蔽。我们使用高精度万用表(Keithley 2110)进行测量,并将所有GPIO引脚逐一配置为 INPUT_ANALOG (模拟输入,无上下拉),发现功耗奇迹般地降到了200µA。这证实了我们的怀疑:有多个GPIO引脚处于浮空状态,形成了微小的漏电流。我们修改了 MX_GPIO_Init() 函数,为所有未使用的引脚添加了 GPIO_NOPULL 配置,并在 xPortPreSleepProcessing() 中再次强制设置,最终将这部分漏电流彻底消除。
第三步:拥抱“协议栈”功耗(终极挑战)
最后的15µA,来自WiFi模块。尽管我们已启用了 WIFI_PS_MIN_MODEM ,但ESP32的WiFi PHY层在DTIM间隔内仍会周期性地唤醒监听Beacon。我们最终的解决方案是,让设备在大部分时间里完全关闭WiFi,仅在每天固定时间(如凌晨2点)短暂唤醒,连接服务器同步数据,然后再次进入Deep-sleep。这需要在 app_main() 中,将WiFi初始化、连接、数据传输、断开、进入Deep-sleep的整个流程,封装在一个独立的任务中,并由一个RTC Alarm定时器来触发。这个方案虽然牺牲了实时性,却将功耗真正降到了芯片的物理极限。
这个项目让我深刻体会到,FreeRTOS的低功耗支持,其价值不仅在于那几行配置宏,更在于它提供了一个 可被工程化、可被量化、可被层层拆解的系统性优化框架 。它迫使你去审视每一根GPIO线、每一个时钟门、每一个外设寄存器,最终让你对整个硬件平台的理解,达到一种庖丁解牛般的透彻。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)