STM32F103串口IAP固件升级实战
IAP(In-Application Programming)是一种软件技术,允许用户在不更换硬件的情况下,通过应用程序对微控制器内部的Flash存储器进行读写操作,从而更新其固件。在嵌入式系统中,尤其在产品发布后需要不断更新功能或修复bug的场景下,IAP技术显得尤为重要。STM32F103微控制器基于ARM® Cortex®-M3核心,这是一种32位RISC处理器,具有高性能、低功耗的特点,适
简介:STM32 IAP技术实现了无需外部编程器即可更新微控制器固件的能力。本项目针对STM32F103微控制器,利用串口通信方式实现固件升级,为远程升级提供便利。升级过程包括生成.bin文件、通过串口助手发送文件、接收校验、写入Flash并跳转执行新固件。同时,强调固件升级的安全性和可靠性,采用CRC校验和数字签名等手段,确保升级过程的顺利进行。
1. STM32 IAP概念与重要性
1.1 IAP简介
IAP(In-Application Programming)是一种软件技术,允许用户在不更换硬件的情况下,通过应用程序对微控制器内部的Flash存储器进行读写操作,从而更新其固件。在嵌入式系统中,尤其在产品发布后需要不断更新功能或修复bug的场景下,IAP技术显得尤为重要。
1.2 IAP的重要性
实现IAP功能的系统,可以显著提高产品的灵活性和市场适应性。它不仅能够缩短产品的研发周期,还能在产品上市后通过固件升级的方式快速修复已知问题,或者增加新的功能,从而延长产品的市场寿命。此外,IAP还能够为用户带来更加丰富的个性化体验,因为它提供了可编程和可升级的途径,满足了用户对产品功能不断升级的需求。
1.3 IAP技术的挑战
虽然IAP带来了诸多便利,但同时也带来了一些技术上的挑战,如保证升级过程的稳定性和可靠性、确保系统在升级过程中不会丢失数据或出现功能异常等问题。因此,了解和掌握IAP技术的原理及其实现方法,对于开发人员来说至关重要。接下来,我们将深入探讨STM32 IAP的概念及其在STM32F103微控制器中的实现。
2. STM32F103微控制器简介
2.1 STM32F103微控制器的基本特征
2.1.1 核心架构与性能指标
STM32F103微控制器基于ARM® Cortex®-M3核心,这是一种32位RISC处理器,具有高性能、低功耗的特点,适用于需要高度集成与快速响应的嵌入式应用。其主频可高达72 MHz,能够提供22个中断源、2个内置的12位ADC以及多个通用计时器,使其成为了许多工业、医疗和消费类电子产品的首选。
核心架构与性能指标上,STM32F103系列采用了冯·诺依曼架构,其CPU内核与系统内存共享同一总线接口,易于实现动态内存访问。此外,它还集成了诸如实时看门狗(IWDG)和窗口看门狗(WWDG)、电源管理、温度传感器和多种通信接口等功能,这一切都为IAP提供了良好的硬件支持。
// 示例:STM32F103的时钟初始化代码片段
// RCC: Reset and Clock Control
#include "stm32f1xx_hal.h"
void SystemClock_Config(void) {
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// Initializes the CPU, AHB and APB busses clocks
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
// Initializes the CPU, AHB and APB busses clocks
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
}
该代码块展示了如何初始化STM32F103的时钟系统,通过配置外部高速振荡器(HSE)和PLL来实现所需频率。这是微控制器性能优化的一个关键步骤。
2.1.2 内部资源概览
STM32F103系列拥有多达128KB的闪存和20KB的SRAM,外加可连接外部存储器的接口,为IAP操作提供了充足的空间。此外,内部集成的多种通信接口如I2C、SPI、USART、CAN等为与其他模块或设备的交互提供了便利。
接下来的部分,将详细分析如何利用这些资源,特别是与IAP相关的硬件资源,例如闪存的读写操作和配置,以实现远程固件更新的功能。
2.2 STM32F103的开发环境搭建
2.2.1 开发工具链的安装与配置
开发STM32F103应用通常涉及使用一个集成开发环境(IDE),如Keil MDK、IAR、STM32CubeIDE或Eclipse配合ARM插件。IDE的选择依赖于开发者的偏好、项目需求以及预算。
这里以STM32CubeIDE的安装与配置为例,该IDE基于Eclipse,由ST官方提供,支持STM32全线产品,并集成了STM32CubeMX初始化代码生成器,极大地简化了配置过程。
graph LR
A[开始] --> B[下载STM32CubeIDE]
B --> C[安装IDE]
C --> D[配置交叉编译器]
D --> E[验证安装]
上图展示了一个简化的流程图,说明了安装STM32CubeIDE的基本步骤。
2.2.2 调试环境与编程接口
调试环境对于开发和维护STM32F103项目至关重要。通常使用ST-LINK调试器,该调试器可以集成到大多数STM32开发板上,并且支持多种调试协议如SWD和JTAG。编程接口方面,可以使用ST提供的多种软件包进行闪存编程,例如ST提供的Flash Loader工具。
调试环境的搭建,涉及到硬件和软件两个方面。硬件上,需要确保ST-LINK调试器与目标硬件正确连接。软件上,则需要安装相应的驱动程序,并在IDE中进行调试器的配置。
graph LR
A[开始] --> B[安装ST-LINK驱动]
B --> C[连接硬件]
C --> D[配置IDE调试环境]
D --> E[测试调试功能]
本段落通过流程图形式展示了调试环境搭建的过程,并强调了测试的重要性以确保调试环境的可靠运行。
3. IAP技术详解及固件升级区域设置
3.1 IAP技术的原理与优势
3.1.1 在系统编程的定义
在系统编程(IAP)是一种技术,允许用户在设备上直接更新固件,而无需使用编程器。这意味着可以通过设备的通信接口(如USB、串口、网络等)来上传新的固件版本,无需拆卸或解体设备。
IAP技术在嵌入式系统中非常重要,因为它提供了现场更新的能力,可以修复软件错误、增强功能、增加新的协议支持、或者适应新的标准等。此外,它减少了产品维护成本,并且有助于延长产品的市场寿命。
3.1.2 IAP技术的关键点分析
IAP操作的关键点主要包括以下几点:
-
运行和升级固件的分离: 需要确保运行中的固件与即将下载的固件可以共存,或者具有临时的升级固件存储区域。
-
通信协议的实现: 在系统编程需要一个可靠的数据传输协议来确保固件的完整性和正确性。
-
固件校验与验证: 确保下载的固件未损坏,并且是有效的。
-
升级过程中的异常处理: 能够处理通信错误或固件损坏导致的升级失败情况。
-
安全性和权限控制: 防止未经授权的固件升级,保证升级过程的安全性。
3.2 固件升级区域的规划与设置
3.2.1 Flash存储器的结构与分配
Flash存储器是微控制器中用于存储固件的主要存储介质。STM32F103拥有一个灵活的Flash存储器,其内部结构可以根据需要来分配不同的存储区域。
通常,Flash存储器被分为几个区域:
-
引导程序区域: 存放固件的引导加载程序,它负责在系统启动时检查固件是否需要更新,并负责固件升级过程。
-
用户固件区域: 存放主应用固件,这是微控制器运行的主要程序。
-
备份区域: 存放一个额外的用户固件,用于在主固件更新失败时恢复系统。
-
临时固件区域: 如果使用了IAP,则这个区域用于存放下载的临时固件,直到确认无误后写入用户固件区域。
3.2.2 启动模式与固件升级流程的对接
STM32F103通过几种不同的启动模式来实现固件升级的对接:
-
从主Flash启动: 这是正常的运行模式,微控制器从主固件区域启动执行代码。
-
从系统内存启动: 这个模式下,微控制器首先执行引导加载程序,这个程序负责检测和执行固件升级。
-
从SRAM启动: 这个模式下,微控制器可以从SRAM执行代码,适用于调试或运行测试代码。
固件升级通常涉及将微控制器置于从系统内存启动模式,然后执行引导加载程序来接收新的固件数据。成功接收并校验数据后,引导加载程序会将新固件写入Flash的用户固件区域,并在下次重启时从新固件启动。
代码块解释
为了实现固件升级,需要在引导加载程序中实现一系列功能。以下是一个简化的伪代码块,展示了固件升级的主流程。
void Bootloader_FirmwareUpgrade() {
// Step 1: Check for upgrade command, e.g., from serial communication
if (CheckForUpgradeCommand()) {
// Step 2: Enter system memory mode and reset MCU
EnterSystemMemoryMode();
ResetMCU();
}
// Step 3: Initialize communication interfaces (e.g., UART)
InitializeCommunicationInterfaces();
// Step 4: Receive new firmware data
FirmwareData newFirmware = ReceiveFirmwareData();
// Step 5: Verify the integrity of the new firmware
if (VerifyFirmware(newFirmware)) {
// Step 6: Erase current user firmware area in Flash
EraseUserFirmwareArea();
// Step 7: Write new firmware to Flash
WriteToFlash(newFirmware);
// Step 8: Set flag indicating firmware update completed
SetFirmwareUpdateCompleteFlag();
// Step 9: Reset MCU for the new firmware to take effect
ResetMCU();
} else {
// Handle firmware verification error
HandleVerificationError();
}
}
参数说明
CheckForUpgradeCommand(): 检查是否有升级命令,通常通过串口或其它接口接收。EnterSystemMemoryMode(): 进入系统内存模式,使微控制器可以通过系统内存区域启动。ResetMCU(): 重置微控制器,以准备接收新的固件。InitializeCommunicationInterfaces(): 初始化通信接口,如串口,以便与外部设备通信。ReceiveFirmwareData(): 接收新的固件数据。VerifyFirmware(): 验证固件数据的完整性和正确性。EraseUserFirmwareArea(): 清除当前的用户固件区域,以便写入新的固件。WriteToFlash(): 将新的固件写入Flash存储器。SetFirmwareUpdateCompleteFlag(): 设置一个标志位,表示固件更新已经完成。HandleVerificationError(): 处理固件验证失败的情况。
逻辑分析
此伪代码展示了引导加载程序中的固件更新逻辑。实际实现中,需要根据具体微控制器的特性来编写细节。例如,STM32F103可能会使用特定的库函数来操作Flash存储器,以及特定的通信协议来接收固件数据。此外,固件的校验可能需要实现或使用现有的算法,如CRC校验。
表格展示
下面的表格展示了不同Flash区域的作用以及它们的典型分配大小:
| Flash区域 | 描述 | 典型大小(STM32F103系列) |
|---|---|---|
| 引导程序区域 | 存放引导加载程序,用于固件升级流程 | 4KB |
| 用户固件区域 | 存放主应用固件,是微控制器的主运行程序 | 60KB |
| 备份固件区域 | 存放备份固件,用于固件升级失败时恢复 | 60KB |
| 临时固件区域 | 用于存放临时下载的固件,直到升级完成 | 60KB |
mermaid流程图展示
以下是一个简化的固件升级流程图,展示了在系统编程中各个步骤的顺序:
graph LR
A[检测升级命令] -->|是| B[进入系统内存模式]
A -->|否| C[运行当前固件]
B --> D[重置MCU]
D --> E[初始化通信接口]
E --> F[接收新固件]
F --> G[验证固件]
G -->|成功| H[清除用户固件区]
G -->|失败| I[错误处理]
H --> J[写入新固件到Flash]
J --> K[设置固件更新完成标志]
K --> L[重启MCU]
代码块细节分析
继续上面的逻辑,对于固件写入操作的代码块可能如下:
void WriteToFlash(FirmwareData newFirmware) {
// Assuming newFirmware contains the necessary data and size information
for (uint32_t address = USER_FIRMWARE_START; address < USER_FIRMWARE_END; address += FIRMWARE_WRITE_STEP) {
// Step 1: Write data to the Flash
FLASH_Status flashStatus = FLASH_Write(address, newFirmware.data + (address - USER_FIRMWARE_START), FIRMWARE_WRITE_STEP);
// Step 2: Check if the write operation was successful
if (flashStatus != FLASH_OK) {
// Handle write error
HandleWriteError();
break;
}
}
}
参数说明
USER_FIRMWARE_START和USER_FIRMWARE_END分别为用户固件区域的起始和结束地址。FIRMWARE_WRITE_STEP为每次写入Flash的字节数,通常由Flash的页大小决定。FLASH_Write()是一个假设的函数,用于将数据写入Flash的指定地址。FLASH_OK是Flash写操作成功的状态。
逻辑分析
此函数执行了将固件数据写入Flash存储器的操作。注意,Flash的写入操作通常不能直接按字节写入,需要按照Flash的页(或扇区)大小来写入,这里使用了 FIRMWARE_WRITE_STEP 作为每次写入的大小。
在每次写入后,代码会检查写操作是否成功。如果不成功,它将调用 HandleWriteError() 函数进行错误处理,该函数的具体实现依赖于应用程序的具体需求。通常情况下,错误处理可能包括重试写入操作、记录错误信息、或者进入一个安全模式以便进一步的故障排除。
表格展示
一个典型的STM32F103 Flash页大小的表格如下:
| Flash类型 | 页大小 |
|---|---|
| 主区域 | 1KB |
| 信息区域 | 2KB |
| 系统区域 | 2KB |
在编写实际代码时,开发人员需要参考STM32F103的参考手册来获取准确的页大小信息,并据此调整代码逻辑。
通过这些步骤和代码块,我们可以看到如何使用IAP技术来更新STM32F103微控制器上的固件。每个步骤都需要仔细的设计和实现,以确保固件升级过程可靠且安全。
4. 串口通信在固件升级中的应用
4.1 串口通信基础
4.1.1 串口通信的原理与协议
串口通信,也称为串行通信,是一种常见的数据传输方式。串口(也称为UART,通用异步收发传输器)是一种异步通信协议,意味着它不需要外部设备同步时钟信号即可进行数据传输。数据以位的形式一位接一位地顺序传输,每一帧数据包含起始位、数据位、可选的奇偶校验位和停止位。
在串口通信中,发送方和接收方必须在数据速率(波特率)、停止位、校验位以及数据位数量上达成一致。这允许两个设备间以一种精确的时序进行数据交换。典型的串口通信协议包括RS-232、RS-485和TIA/EIA-485。
串口通信之所以在固件升级中得到广泛应用,是因为它的实现简单、成本低廉且不需要复杂的硬件支持。通过单个传输线,即可实现设备之间的数据交换。
4.1.2 STM32F103串口硬件配置与初始化
在STM32F103微控制器中,串口通信是通过其内置的USART/UART外设实现的。要使用串口,首先需要对其进行配置。配置串口包括设置波特率、数据位、停止位和校验位等参数,并启用其接收和发送功能。
以下是一个简单的代码示例,展示了如何在STM32F103中配置和初始化串口:
#include "stm32f10x.h"
void USART_Configuration(void)
{
// 1. 使能GPIOA和USART1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
// 2. 配置USART1 Tx (PA.09) 为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置USART1 Rx (PA.10) 为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 4. 配置USART1参数
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
// 5. 初始化USART1
USART_Init(USART1, &USART_InitStructure);
// 6. 启用USART1
USART_Cmd(USART1, ENABLE);
}
int main(void)
{
USART_Configuration();
// 其他代码...
return 0;
}
在这段代码中,我们首先使能了GPIOA和USART1的时钟。接着,配置了USART1的Tx(发送)和Rx(接收)引脚。然后,我们设置了串口通信的参数,包括波特率、字长、停止位和奇偶校验位。最后,我们使用这些配置初始化了USART1并启用了它。
4.2 串口通信在固件升级中的实现
4.2.1 升级指令的封装与解析
固件升级通常需要一种机制来触发和控制升级过程。一个典型的实现是在升级指令中封装特定的命令。这些命令可以是简单的状态请求,也可以是更复杂的包含文件大小、校验和等信息的命令。
为了确保数据传输的可靠性,指令的封装往往需要遵循特定的协议,这可能包括帧头、命令类型、数据长度、数据内容和帧尾。接收方负责解析这些指令,并根据解析出的命令执行相应的操作。
例如,一个典型的固件升级指令可能包括以下字段:
- 帧头:标识数据包的开始。
- 指令类型:指示这是一个固件升级的请求。
- 固件大小:指示将要下载的固件的大小。
- 校验和:用于验证固件的完整性。
- 固件数据:固件的实际二进制数据。
- 帧尾:标识数据包的结束。
在STM32F103上解析这样的指令,需要编写相应的代码来按照协议检查和解析帧头、帧尾,并对数据进行校验。如果校验失败,可以请求重新发送数据。
4.2.2 数据传输的校验机制
为了确保固件数据在传输过程中没有出错,必须实施校验机制。常见的校验方法包括循环冗余校验(CRC)和校验和(Checksum)。在发送固件之前,发送方将计算固件数据的校验值,并将该值与固件一起发送给接收方。接收方收到固件后,将使用相同的算法计算固件数据的校验值,并与收到的校验值进行比较。如果两者不匹配,则表明数据在传输过程中已经损坏,需要重新发送数据。
以下是使用CRC校验的一个简单示例代码:
#include "stm32f10x.h"
#include "misc/crc32.h"
// 假设已经将固件数据存储在一个名为firmware_data的数组中
#define FIRMWARE_SIZE 1234 // 固件的大小
uint32_t calculate_crc32(uint8_t *data, uint32_t size)
{
uint32_t crc = 0xFFFFFFFF;
for (uint32_t i = 0; i < size; i++)
{
CRC_CalcBlock((uint32_t *)(&data[i]), 1);
}
return crc ^ 0xFFFFFFFF;
}
int main(void)
{
uint8_t firmware_data[FIRMWARE_SIZE];
uint32_t crc = calculate_crc32(firmware_data, FIRMWARE_SIZE);
// 发送固件数据和CRC值...
// 在接收端验证CRC值...
uint32_t received_crc = 0x00; // 假设这是接收到的CRC值
if (received_crc != crc)
{
// CRC不匹配,请求重新发送固件
}
else
{
// CRC匹配,固件数据完整
}
return 0;
}
在这个示例中,我们定义了一个 calculate_crc32 函数,它接受固件数据和数据大小作为参数,并计算出CRC校验值。在主函数中,我们假设固件数据已经被存储在一个数组中,然后我们计算这些数据的CRC校验值并与接收到的CRC值进行比较。如果CRC校验失败,我们需要请求重新发送固件。
使用校验机制能够确保固件升级过程的可靠性,减少因数据损坏导致的设备故障。在实际应用中,可能需要结合使用多种校验方法来增强安全性和准确性。
5. 固件升级流程与实践要点
5.1 固件升级流程详解
固件升级是确保设备能够适应新标准和功能更新的重要环节。在STM32微控制器中,这一过程可以通过IAP技术实现。本节将详细介绍固件升级的各个步骤,确保开发者能够理解并实践固件更新过程。
5.1.1 固件的生成与打包
固件升级的第一步是生成和打包固件。固件通常是编译好的二进制文件,开发者需要将其打包成特定格式以便于微控制器能够识别和下载。常见的打包工具有STM32CubeProgrammer和ST-Link Utility,这些工具能够帮助开发者生成包含升级文件和元数据的升级包。
5.1.2 固件升级的数据接收与校验
在STM32F103上执行固件升级,数据接收过程通常是通过串口通信完成的。数据校验机制是确保固件完整性的重要环节。常见的校验方法包括CRC校验和校验和。开发者需要在固件升级软件中实现这些校验机制,确保接收到的固件数据没有损坏。
// 伪代码示例:数据接收与校验过程
while (接收数据) {
if (校验数据) {
if (校验成功) {
// 继续接收下一个数据包
} else {
// 报错处理,要求重发数据
}
} else {
// 数据传输错误处理
}
}
5.1.3 固件的写入操作与执行跳转
数据校验通过后,固件的写入操作就显得至关重要。在这个步骤中,要确保新固件被正确写入到指定的Flash存储区域。一旦固件写入完成,需要执行跳转操作,即跳转到新固件的入口点开始执行。
// 伪代码示例:固件写入与跳转
if (固件写入Flash) {
// 写入成功操作
// 清除可能的中断标志
// 设置系统向量表指针
// 使用跳转指令执行新固件
((void(*)())(新固件入口地址))();
} else {
// 固件写入失败处理
}
5.2 固件升级的安全与可靠性措施
为了保证固件升级过程的稳定性和安全性,必须采取一定的预防措施。
5.2.1 升级过程中的异常处理
异常处理机制能够确保升级过程中发生意外情况时,系统能够采取安全措施,如回滚到旧版本固件。异常处理应该包括超时处理、通信错误处理等。
5.2.2 固件升级的安全验证机制
为了防止未授权的固件更新,引入安全验证机制是必要的。这可能包括对固件签名的验证、加密通信等安全措施。
5.3 开发实践要点总结
在固件升级的实际开发过程中,有一些要点值得特别注意。
5.3.1 驱动程序的设计要点
固件升级相关的驱动程序应该简洁、高效,且能够处理各种可能的升级场景。
5.3.2 固件升级协议的制定与遵循
为了确保固件升级过程的标准化,制定并遵循一套固件升级协议是非常重要的。这包括升级指令格式、数据传输协议等。
5.3.3 HAL库与LL库在IAP中的应用差异与选择
STM32的硬件抽象层库(HAL)和低层库(LL)在使用上各有优劣。HAL库提供了更高级别的抽象,而LL库则提供了更接近硬件的操作。开发者在选择使用哪一个库时,需要根据项目需求和资源限制来决定。
本章内容围绕STM32微控制器的固件升级流程与实践要点进行了详尽的介绍。开发者在深入理解本章内容后,应能够掌握固件升级的整个操作过程,并能够在实际项目中灵活应用。
简介:STM32 IAP技术实现了无需外部编程器即可更新微控制器固件的能力。本项目针对STM32F103微控制器,利用串口通信方式实现固件升级,为远程升级提供便利。升级过程包括生成.bin文件、通过串口助手发送文件、接收校验、写入Flash并跳转执行新固件。同时,强调固件升级的安全性和可靠性,采用CRC校验和数字签名等手段,确保升级过程的顺利进行。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐




所有评论(0)