1. RM电控系统中的CAN总线与大疆电机控制原理

在RoboMaster(RM)机器人竞赛的电控系统中,CAN(Controller Area Network)总线是连接主控单元与执行机构的核心通信骨架。它并非简单的数据管道,而是一套具备强实时性、高抗干扰能力与多主仲裁机制的嵌入式现场总线协议。当哨兵机器人需要在复杂电磁环境下稳定驱动云台、底盘与发射机构时,CAN总线承担着将上层决策指令转化为底层电机动作的关键使命。其物理层采用差分信号传输(CAN_H/CAN_L),逻辑上支持1Mbps高速率(实际工程中常配置为500kbps或1Mbps),并通过非破坏性位仲裁机制确保关键指令(如急停、模式切换)在总线冲突时获得最高优先级。这种设计使CAN天然适配RM场景中多电机协同、低延迟响应与故障容错的严苛需求——它不追求吞吐量,而专注确定性与时序保障。

1.1 大疆RM系列电机的通信协议栈解析

大疆为RM赛事定制的20系列电机(如M2006、M3508、M3510)并非传统意义上的“裸电机”,而是集成了无刷电机本体、FOC(Field-Oriented Control)驱动电路、双编码器(磁编+霍尔)、温度传感器及CAN通信控制器的智能执行单元。其通信协议栈自下而上分为三层:物理层(ISO 11898-2标准CAN总线)、链路层(基于CAN帧ID的地址与功能码映射)与应用层(DJI自定义指令集)。工程师必须理解这一分层结构,否则无法实现精准控制。

  • 物理层 :电机默认使用CAN2.0B协议,支持标准帧(11位ID)与扩展帧(29位ID)。RM电机出厂预设为标准帧模式,ID范围为0x200–0x2FF,其中高4位(bit[10:7])标识电机ID(0–15),低7位(bit[6:0])定义功能码。例如,ID=0x201表示电机ID=0,功能码=1(读取基本状态)。
  • 链路层 :每个CAN帧包含8字节有效载荷。前2字节为指令头(Command Header),后6字节为参数域(Parameter Payload)。指令头由1字节功能码(Function Code)与1字节子功能码(Sub-function Code)构成,用于区分读/写操作、数据类型及寄存器地址。例如,写入目标转速指令的功能码为0x01,子功能码为0x00。
  • 应用层 :DJI定义了完整的寄存器映射表(Register Map),覆盖电机状态监控(如温度、电压、当前转速)、控制参数配置(如PID系数、电流限幅)与运动模式选择(如速度环、位置环、力矩环)。关键寄存器包括:
  • 0x0001 :电机状态字(Motor Status Word),bit0=运行标志,bit1=过温告警,bit2=过流保护;
  • 0x0002 :实时转速(RPM),16位有符号整数,单位0.1RPM;
  • 0x0003 :实时电流(A),16位有符号整数,单位0.01A;
  • 0x0004 :目标转速(RPM),写入此寄存器即启动速度闭环控制;
  • 0x0005 :目标位置(°),用于位置环控制,分辨率0.01°;
  • 0x0006 :目标力矩(mN·m),直接输出PWM占空比,绕过所有闭环。

工程师需明确: 对RM电机的任何控制,本质都是向特定ID的CAN节点发送符合协议格式的数据帧,并解析其返回的状态帧 。这要求开发者彻底摆脱“调用API即完成控制”的思维惯性,深入到帧结构、ID分配与超时重传机制层面。

1.2 STM32平台下的CAN外设硬件配置逻辑

在以STM32F4/F7/H7系列为主控的RM电控板中,CAN外设(如CAN1/CAN2)的初始化绝非简单调用HAL库函数即可完成。其配置必须严格遵循芯片时钟树、总线拓扑与实时性约束,核心步骤如下:

1.2.1 时钟与引脚复用配置

CAN外设挂载于APB1总线(最大频率42MHz),其时钟源来自PCLK1。以STM32F407为例,需在RCC配置中使能CAN1时钟( __HAL_RCC_CAN1_CLK_ENABLE() ),并确认PCLK1频率满足CAN波特率计算需求。GPIO引脚需配置为复用推挽输出(CAN_TX)与浮空输入(CAN_RX),且必须启用内部弱上拉( GPIO_PULLUP )以抑制总线噪声。典型配置为:

// CAN1 TX: PA12, RX: PA11
GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP; // 关键:消除共模干扰
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF9_CAN1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
1.2.2 波特率精确计算与同步段配置

CAN波特率由 BS1 (传播段+相位缓冲段1)、 BS2 (相位缓冲段2)与 Prescaler (预分频器)共同决定:
BitRate = PCLK1 / ((Prescaler) * (1 + BS1 + BS2))
RM工程中普遍采用500kbps速率,需确保采样点位于位时间75%处以提升抗干扰能力。以PCLK1=42MHz为例,推荐配置: Prescaler=3 , BS1=13 , BS2=2 ,此时:
- 位时间 = 3 × (1 + 13 + 2) = 48 Tq(Time Quantum)
- 采样点 = (1 + 13) / 48 ≈ 75% —— 符合ISO 11898规范要求
该配置通过 CAN_BitTiming 结构体设置:

hcan1.Init.Prescaler = 3;
hcan1.Init.Mode = CAN_MODE_NORMAL;
hcan1.Init.SJW = CAN_SJW_1TQ;      // 重同步跳转宽度:1Tq
hcan1.Init.BS1 = CAN_BS1_13TQ;    // BS1:13Tq(含传播段)
hcan1.Init.BS2 = CAN_BS2_2TQ;     // BS2:2Tq
hcan1.Init.TTCM = DISABLE;        // 时间触发通信模式禁用
hcan1.Init.ABOM = ENABLE;         // 自动离线管理:总线错误超限自动恢复
hcan1.Init.AWUM = ENABLE;         // 自动唤醒模式:检测到总线活动自动唤醒
hcan1.Init.NART = DISABLE;        // 禁止自动重传:避免总线拥堵时无限重发
1.2.3 过滤器组(Filter Bank)的工程化配置

STM32的CAN过滤器支持标识符列表模式(Identifier List Mode)与掩码模式(Mask Mode)。RM系统中电机ID固定(0–15),应采用 32位宽标识符列表模式 ,将16个电机ID一次性映射至一个过滤器组,避免频繁切换过滤器导致的接收延迟。配置示例:

sFilterConfig.FilterBank = 0;                    // 使用过滤器组0
sFilterConfig.FilterMode = CAN_FILTERMODE_IDLIST; // 列表模式
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // 32位宽
sFilterConfig.FilterIdHigh = 0x200 << 5;         // ID=0x200,左移5位对齐
sFilterConfig.FilterIdLow = 0x20F << 5;          // ID=0x20F(覆盖0–15)
sFilterConfig.FilterMaskIdHigh = 0x0000;         // 掩码全0(列表模式下无效)
sFilterConfig.FilterMaskIdLow = 0x0000;
sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0; // 分配至FIFO0
sFilterConfig.FilterActivation = ENABLE;
HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig);

此配置使CAN外设仅接收ID为0x200–0x20F的帧,其他无关帧被硬件过滤,极大降低CPU中断负载。

1.3 基于HAL库的CAN通信驱动框架设计

直接调用 HAL_CAN_Transmit() 发送单帧存在严重缺陷:RM系统需持续发送控制指令(如每1ms更新一次转速),而HAL库的阻塞式发送会占用大量CPU时间。工程实践要求构建 非阻塞、带缓冲、可重入的CAN驱动层 ,其核心组件包括:

1.3.1 发送环形缓冲区(TX Ring Buffer)

定义固定大小(如32帧)的环形缓冲区,存储待发送的CAN帧。主循环或任务中调用 CAN_SendFrame() 将帧写入缓冲区,由专用发送任务(或中断)负责逐帧发送:

typedef struct {
    CAN_TxHeaderTypeDef header;
    uint8_t data[8];
} can_frame_t;

static can_frame_t tx_buffer[TX_BUFFER_SIZE];
static volatile uint16_t tx_head = 0;
static volatile uint16_t tx_tail = 0;

bool CAN_SendFrame(const CAN_TxHeaderTypeDef* hdr, const uint8_t* data) {
    uint16_t next_head = (tx_head + 1) % TX_BUFFER_SIZE;
    if (next_head == tx_tail) return false; // 缓冲区满

    tx_buffer[tx_head].header = *hdr;
    memcpy(tx_buffer[tx_head].data, data, 8);
    __DMB(); // 内存屏障,确保写入顺序
    tx_head = next_head;
    return true;
}
1.3.2 中断驱动的发送状态机

在CAN发送完成中断( HAL_CAN_TxMailbox0CompleteCallback )中,从环形缓冲区取出下一帧发送。若缓冲区为空,则关闭发送中断以节省功耗:

void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan) {
    if (tx_head != tx_tail) {
        // 取出下一帧并发送
        CAN_TxHeaderTypeDef* hdr = &tx_buffer[tx_tail].header;
        uint32_t tx_mailbox;
        HAL_CAN_AddTxMessage(hcan, hdr, tx_buffer[tx_tail].data, &tx_mailbox);
        tx_tail = (tx_tail + 1) % TX_BUFFER_SIZE;
    } else {
        __HAL_CAN_DISABLE_IT(hcan, CAN_IT_TX_MAILBOX_EMPTY); // 关闭中断
    }
}
1.3.3 接收FIFO的异步处理

配置CAN接收FIFO0,启用FIFO0消息挂号中断( CAN_IT_RX_FIFO0_MSG_PENDING )。在中断服务函数中批量读取FIFO内所有帧,解析后投递至对应电机的状态结构体:

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) {
    CAN_RxHeaderTypeDef rx_header;
    uint8_t rx_data[8];

    while (HAL_CAN_GetRxFifoFillLevel(hcan, CAN_RX_FIFO0) > 0) {
        HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_data);

        uint8_t motor_id = (rx_header.StdId & 0x0F); // 提取ID低4位
        if (motor_id < MAX_MOTORS) {
            parse_motor_status(motor_id, rx_data); // 解析状态帧
        }
    }
}

此框架将CAN通信与业务逻辑解耦,主控可专注于算法计算,通信细节由驱动层透明处理。

2. 电机控制闭环实现:从开环力矩到PID速度环

RM电机的控制模式选择直接影响机器人动态性能。初学者易陷入“直接写入目标转速即可驱动”的误区,却忽略不同模式的物理意义与适用场景。必须依据控制目标、系统惯量与外部扰动特性,科学选择并实现闭环策略。

2.1 开环力矩模式(Torque Mode)的工程边界

开环力矩模式通过向寄存器 0x0006 写入目标力矩值(mN·m),直接设定电机相电流参考值,绕过所有内部闭环。其优势在于极致响应(理论延迟<100μs),适用于需要瞬时爆发力的场景(如哨兵底盘急启停、云台快速甩炮)。但其致命缺陷是 完全丧失稳态精度与抗扰能力 :当负载突变(如底盘压过障碍物)、电池电压跌落或电机温升导致反电动势变化时,实际转速将剧烈漂移。

工程实践中,开环力矩模式仅用于两类场景:
- 启动阶段 :电机静止时,先以小力矩(如500mN·m)短时驱动,突破静摩擦后立即切入速度环;
- 力控场景 :机械臂末端执行器需恒定接触力,此时力矩指令由外部力传感器闭环生成。

使用前必须进行 力矩-电流标定 :在室温下,对同一电机多次施加相同力矩指令,记录实测电流均值,建立 Torque_Cmd → I_ref 查表。否则因电机个体差异,标称力矩值可能产生±15%误差。

2.2 速度闭环(Speed Loop)的参数整定实践

速度环是RM最常用的控制模式,通过向 0x0004 写入目标转速(RPM),由电机内部FOC算法自主调节PWM输出,维持转速恒定。其性能取决于PID参数整定,但切忌盲目套用教科书公式。真实整定需结合系统阶跃响应与频域分析:

2.2.1 阶跃响应法整定流程
  1. 初始参数设置 :设 Kp=50 , Ki=0 , Kd=0 ,施加100RPM阶跃指令,观察响应曲线;
  2. 调整Kp :若响应缓慢且超调小,逐步增大Kp(如每次+20),直至出现等幅振荡。记录临界Kp(如Kp_cr=120)与振荡周期T_cr(如T_cr=80ms);
  3. 计算PI参数 :按Ziegler-Nichols法则, Kp = 0.45×Kp_cr = 54 , Ki = 0.54×Kp_cr/T_cr = 730
  4. 引入Kd抑制超调 :在PI基础上加入微分项, Kd = 0.075×Kp_cr×T_cr = 0.72
2.2.2 工程验证与修正

实验室整定参数在实车运行中常失效,原因在于:
- 机械谐振 :底盘悬挂、云台连杆存在固有频率(如25Hz),若Kd过大将激发谐振;
- 传感器噪声 :磁编信号含高频抖动,未经滤波直接参与微分计算会导致输出震荡;
- 供电波动 :电池电压从16.8V降至14.2V时,电机最大输出力矩下降15%,需动态补偿Kp。

解决方案:
- 在速度反馈通路加入二阶巴特沃斯低通滤波器(截止频率=50Hz),消除编码器噪声;
- 实现电压前馈: Kp_adj = Kp × (V_bat / V_nom) ,实时补偿供电变化;
- 启用电机内置的“软启动”功能(寄存器 0x000A ),限制加速度斜率,避免冲击。

经此修正的速度环,在哨兵底盘上可实现:阶跃响应时间<80ms,超调<5%,稳态误差<2RPM(负载0–10N·m变化时)。

2.3 位置闭环(Position Loop)的多圈绝对定位实现

位置环通过向 0x0005 写入目标角度(°),驱动电机旋转至指定位置。RM电机采用磁编码器(14位分辨率),单圈精度达0.022°,但需解决 多圈累计问题 ——当云台连续旋转超过360°时,如何保证绝对位置不丢失?

2.3.1 多圈计数的硬件方案

部分高端电机(如M3510 Pro)内置EEPROM,可存储断电前的最后一圈计数值。但通用方案依赖主控实现:
- CAN帧携带圈数信息 :在发送位置指令时,将目标圈数( 圈数×360 + 角度 )拆分为高位(圈数)与低位(角度),通过扩展帧ID或数据域传输;
- 增量式累计 :主控监听电机返回的实时位置帧( 0x0005 ),在本地维护一个32位整型变量 total_pos 。每次接收到新位置 pos_new ,计算 delta = pos_new - last_pos ,若 |delta| > 180 则判定为跨圈,修正 delta 符号后累加: total_pos += delta

2.3.2 位置环的抗积分饱和(Anti-Windup)

位置环易发生积分饱和:当目标位置超出机械限位(如云台仰角>30°),误差持续累积导致 Ki×∫e(t)dt 巨大,一旦解除限位,电机会以最大力矩猛冲。必须实施抗饱和策略:
- 积分分离 :当 |error| > threshold (如5°)时,关闭积分项;仅当 |error| < threshold 时启用积分;
- 积分限幅 :对积分项输出设置硬限幅(如±5000),防止过度累积;
- 变速积分 :积分增益 Ki 随误差绝对值增大而线性减小, Ki_adj = Ki × (1 - |error|/limit)

在哨兵云台俯仰轴上,采用积分分离后,越限时的冲击力矩降低70%,解除限位后的恢复时间缩短至120ms。

3. 实时性保障:FreeRTOS任务调度与CAN通信协同

RM机器人控制系统本质是强实时多任务系统。底盘运动控制、云台跟踪、视觉识别、裁判系统通信等任务必须在严格时限内完成。FreeRTOS作为RM主流RTOS,其调度机制与CAN通信的协同设计,直接决定系统稳定性。

3.1 任务优先级划分与堆栈配置原则

FreeRTOS中,任务优先级数值越大,抢占权越高。RM系统典型任务优先级分配如下(数值为 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 的倒序):

任务名称 优先级 周期 关键性 堆栈大小 设计依据
CAN发送任务 5 1ms ★★★★☆ 256B 保证控制指令准时发出
电机状态采集任务 4 2ms ★★★★☆ 192B 获取实时反馈,支撑闭环计算
底盘运动控制任务 3 5ms ★★★★☆ 320B PID计算、运动学解算
云台跟踪任务 2 10ms ★★★☆☆ 256B 图像坐标转换、PID控制
裁判系统通信任务 1 20ms ★★★☆☆ 192B 解析裁判数据,更新机器人状态
日志记录任务 0 异步 ★★☆☆☆ 128B 低优先级,避免阻塞实时任务

堆栈配置经验
- 数值计算密集型任务(如运动学解算)需预留额外30%堆栈,防止 sin/cos 等函数临时变量溢出;
- 所有CAN相关任务堆栈必须≥192B,因HAL库 HAL_CAN_GetRxMessage() 内部使用较大局部数组;
- 严禁在中断服务函数(ISR)中调用 xQueueSendFromISR() 以外的FreeRTOS API,否则引发HardFault。

3.2 CAN通信与控制任务的时序协同

单纯提高CAN任务优先级无法解决时序问题。必须建立 生产者-消费者模型 ,使控制任务与CAN任务解耦:

  • 生产者 :底盘控制任务在 vTaskControlLoop() 中完成PID计算,得到目标转速 target_rpm 后,不直接调用CAN发送,而是将 {motor_id, target_rpm} 结构体写入线程安全队列 xControlCmdQueue
  • 消费者 :CAN发送任务从 xControlCmdQueue 中取出指令,封装为CAN帧并写入发送缓冲区;
  • 状态反馈 :电机状态采集任务定时读取CAN接收FIFO,解析后将 {motor_id, rpm, current, temp} 写入共享状态数组 motor_state[] ,供控制任务读取。

此设计确保:
- 控制任务执行时间可控(<1ms),不受CAN总线延迟影响;
- CAN任务专注通信,避免混入算法逻辑导致优先级反转;
- 状态数据一致性:控制任务读取的是上一周期采集的完整状态快照,而非中断中零散更新的变量。

3.3 中断优先级分组的陷阱规避

STM32的NVIC中断优先级分组( HAL_NVIC_SetPriorityGrouping() )直接影响FreeRTOS调度。若错误配置为 NVIC_PRIORITYGROUP_4 (4位抢占,0位子优先级),则所有可屏蔽中断抢占优先级相同,导致CAN接收中断与SysTick中断竞争,引发任务调度延迟。正确配置应为 NVIC_PRIORITYGROUP_2 (2位抢占,2位子优先级),并严格分配:

  • SysTick中断:抢占优先级=0(最高),确保RTOS心跳准时;
  • CAN接收中断:抢占优先级=1,高于所有任务优先级,保证状态帧及时入队;
  • CAN发送完成中断:抢占优先级=2,低于接收中断,避免发送中断阻塞状态采集;
  • 其他外设中断(UART、TIM):抢占优先级≥3,确保不干扰实时控制流。

此配置下,从CAN帧到达至控制任务获取新状态的端到端延迟稳定在1.8–2.2ms,满足RM规则对控制周期≤5ms的要求。

4. 故障诊断与鲁棒性设计:应对真实赛场挑战

RM赛场环境远比实验室严酷:电磁干扰导致CAN帧误码、电池电压骤降引发电机欠压保护、机械卡死触发堵转电流保护、低温导致编码器精度漂移。一套健壮的电控系统必须内置多层次故障诊断与降级策略。

4.1 CAN总线故障的分级响应机制

CAN总线故障非单一事件,需按严重程度分级处理:

故障等级 触发条件 响应策略 恢复条件
一级(警告) 单帧CRC校验失败(<1次/秒) 记录日志,点亮黄色LED,维持当前控制模式 连续10帧校验成功
二级(降级) 连续5帧接收超时或错误帧率>5% 切换至开环力矩模式,目标力矩设为0(紧急制动),禁用所有非必要外设 总线空闲100ms后重同步
三级(隔离) 错误计数器达到BUS_OFF阈值(128) 硬件复位CAN控制器,断开CAN收发器供电,激活机械刹车,上报裁判系统故障码 手动复位或重启

实现关键:
- 错误计数器监控 :在 HAL_CAN_ErrorCallback() 中读取 hcan->ErrorCode ,区分 HAL_CAN_ERROR_BUSOFF HAL_CAN_ERROR_PASSIVE 等状态;
- 总线关闭恢复 :调用 HAL_CAN_ResetErrorStatus() 后,必须等待 CAN_ESR_BOFF 标志清零,再执行 HAL_CAN_Start()
- 机械刹车联动 :在三级故障时,立即驱动底盘制动电磁阀(若配备)或强制云台电机力矩为负,防止失控。

4.2 电机热保护的动态阈值算法

RM电机在持续高负载下温升显著,但固定温度阈值(如85℃)过于保守。应采用 基于历史温升速率的动态阈值
- 实时计算温度变化率 dT/dt = (T_now - T_last) / Δt
- 若 dT/dt > 2℃/s T_now > 70℃ ,则启动降额:按 Power_Derating = 1 - (T_now - 70) / 30 线性降低目标力矩;
- 同时预测峰值温度: T_pred = T_now + dT/dt × 500ms ,若 T_pred > 85℃ ,则提前触发二级降级。

此算法在哨兵云台连续瞄准测试中,将电机平均工作温度降低12℃,延长高功率运行时间40%。

4.3 裁判系统通信的容错设计

裁判系统(Referee System)通过UDP向机器人发送实时比赛数据(血量、弹药、状态),但网络丢包率高达15%。不能依赖单次接收,需构建 状态机驱动的冗余解析引擎

  • 定义 referee_state_t 结构体,包含 last_received_time packet_seq game_state 等字段;
  • 每次接收新UDP包,验证 seq 是否连续(允许1次丢包),若不连续则启动重传请求(向裁判系统发送ACK包);
  • 设置超时检测:若 xTaskGetTickCount() - last_received_time > 100ms ,则标记 game_state STALE ,控制逻辑切换至预设安全模式(如底盘停止、云台归中);
  • 所有裁判数据更新必须通过 xSemaphoreTake() 获取互斥锁,避免多任务并发修改导致状态混乱。

该设计使机器人在裁判系统网络波动时,仍能维持3秒以上的自主安全运行,为现场调试争取关键时间窗口。

5. 工程实践手记:从实验室到赛场的跨越

在中科大RoboMaster战队的实战中,我曾踩过几个典型坑,这些经验比任何理论都更深刻:

  • CAN终端电阻的隐形杀手 :某次调试中,底盘电机响应迟滞且偶发失步。万用表测量总线电阻为60Ω(正常应为60Ω),看似无误。但用示波器观察CAN_H波形,发现上升沿存在严重振铃。最终发现是两段线缆的终端电阻(120Ω)未拆除,导致总线等效电阻变为60Ω但阻抗不匹配。 教训:终端电阻必须仅在总线物理两端各置一个120Ω,中间节点严禁添加

  • FreeRTOS队列的内存碎片 :早期使用动态创建队列( xQueueCreate() ),在长时间运行后出现 pvPortMalloc() 失败。排查发现是频繁创建/销毁队列导致heap_4内存池碎片化。改为静态队列( xQueueCreateStatic() )并预分配足够大的内存块后,系统稳定运行超72小时。

  • PID参数的温度漂移 :云台在低温(5℃)环境下,原整定的PID参数导致跟踪抖动。通过在 vTaskControlLoop() 中加入温度补偿: Kp_adj = Kp × (1 + 0.005 × (25 - T_mcu)) ,将温度系数纳入控制律,彻底解决该问题。

这些不是教科书会写的细节,却是让机器人真正跑起来的基石。技术文档的价值,正在于将这些血泪经验凝练为可复用的方法论,而非堆砌华丽的概念。当你在赛场上看到哨兵平稳巡弋、云台精准锁定目标时,那背后是无数次对CAN波形的凝视、对PID曲线的调试、对FreeRTOS日志的逐行分析——这才是嵌入式工程师最真实的日常。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐