当前位置: 首页 > news >正文

OpenHarmony NAPI实战:从ArkTS应用调用C++驱动控制LED

1. 项目概述与核心价值

最近在捣鼓OpenHarmony设备开发,发现很多开发者朋友在从应用层转向系统层时,会遇到一个典型的“最后一公里”问题:如何在JavaScript/ArkTS应用里,直接调用C/C++的底层硬件驱动来控制一个具体的硬件,比如点亮或熄灭一块开发板上的LED灯?这听起来是个简单的“Hello World”级操作,但在OpenHarmony的南向开发体系里,它恰恰是理解应用与驱动如何“握手”的关键一步。这个项目,就是基于OpenHarmony的NAPI(Native API)框架,搭建一座从上层ArkTS应用到下层C++硬件操作的桥梁,实现对板端LED灯的控制。

如果你是一名OpenHarmony应用开发者,想深入设备侧,了解JS如何“穿透”到硬件;或者你是一名嵌入式背景的工程师,正在学习如何将原有的C/C++驱动代码封装成OpenHarmony标准化的JS接口,那么这个实践会给你提供一个非常清晰的路径。它不仅仅是一个点亮LED的demo,更是一个标准的、可复用的NAPI模块开发范本。通过它,你能掌握NAPI模块的工程创建、接口定义、线程安全、异步回调以及与HDF驱动框架联调的全流程。我把自己在移植和调试过程中遇到的坑、参数传递的细节、以及如何确保跨语言调用稳定性的经验都揉在了里面,希望能帮你少走弯路。

2. 项目整体设计与思路拆解

2.1 为什么是NAPI?

在OpenHarmony中,应用层主要使用ArkTS/JavaScript进行开发,而硬件驱动、高性能计算或复用现有C/C++库则需要使用Native(C/C++)代码。连接这两者的官方桥梁就是NAPI框架。它本质上是一套由Node.js原生模块接口演化而来的规范,提供了一套稳定的ABI(应用二进制接口),使得JS引擎(如Ark引擎)能够安全、高效地调用C/C++函数,并在这两种语言之间进行数据类型的转换。

选择NAPI而不是其他方式(如直接修改系统服务),主要原因有三点:

  1. 标准化与兼容性:NAPI是OpenHarmony官方推荐且持续维护的跨语言交互方案,其API设计考虑了多引擎兼容性,未来升级风险较小。
  2. 性能与安全:NAPI调用发生在应用进程内,避免了进程间通信(IPC)的开销。同时,它通过严格的类型检查和上下文管理,提供了相对安全的访问边界,防止JS代码导致Native层内存崩溃。
  3. 生态友好:对于有Node.js N-API开发经验的开发者来说,学习曲线平缓。其模块化的设计也便于代码的复用和分发。

2.2 系统架构与数据流

本项目的核心架构可以清晰地分为三层:

[ArkTS/JS应用层] -> [NAPI桥接层 (C++)] -> [HDF驱动层 (C)] -> [硬件LED]
  1. 应用层:一个简单的ArkTS UI页面,提供按钮控件。用户点击按钮,触发一个ArkTS函数调用。
  2. NAPI桥接层:这是我们的工作重点。它以一个.so动态库的形式存在,暴露出一系列JS可调用的方法(如turnOnLedturnOffLed)。当这些方法被调用时,NAPI框架将JS参数(如LED编号、亮度值)转换为C/C++类型,并执行我们编写的Native函数。
  3. HDF驱动层:OpenHarmony标准的硬件驱动框架。我们的NAPI模块最终需要通过标准的HDF接口(如HdfIoServiceBindHdfDeviceObject)与LED的驱动进行通信,发送控制指令(如GPIO高低电平设置)。这里我们假设板级厂商已经提供了符合HDF标准的LED驱动。

设计思路的关键点在于:NAPI层不直接操作硬件寄存器,而是作为HDF驱动客户端的调用者。这样保证了驱动模型的统一性和安全性,我们的NAPI模块可以更容易地移植到其他同样使用HDF驱动的OpenHarmony设备上。

2.3 工程结构规划

一个典型的OpenHarmony NAPI工程(以led_controller为例)在源码目录中会这样组织:

applications/sample/led_controller/ ├── index.ets # ArkTS应用入口页面 └── src/main/cpp/ # Native层源码 ├── CMakeLists.txt # 编译构建脚本 ├── include/ # 头文件目录 │ └── led_controller_napi.h ├── src/ # 源文件目录 │ ├── led_controller_napi.cpp # NAPI接口实现 │ └── led_hdf_client.cpp # HDF客户端封装 └── package.json # 模块声明文件(关键!)

package.json是这个NAPI模块的“身份证”,必须正确配置"type": "native",并指明入口文件,系统构建工具(如hb)才会将其识别为Native模块并进行编译。

3. 核心细节解析与实操要点

3.1 NAPI模块的初始化与接口导出

NAPI模块的入口是一个符合napi_module结构体的定义,并在模块加载时自动调用的Init函数。这是所有魔法的起点。

// led_controller_napi.cpp #include “napi/native_node_api.h“ // 1. 定义模块导出的JS函数与其对应的Native函数 static napi_value TurnOnLed(napi_env env, napi_callback_info info) { // 具体实现... } static napi_value TurnOffLed(napi_env env, napi_callback_info info) { // 具体实现... } // 2. 描述导出的属性(即JS对象上的方法) static napi_property_descriptor g_ledControllerDesc[] = { { “turnOnLed“, nullptr, TurnOnLed, nullptr, nullptr, nullptr, napi_default, nullptr }, { “turnOffLed“, nullptr, TurnOffLed, nullptr, nullptr, nullptr, napi_default, nullptr } }; // 3. 模块初始化函数 static napi_value LedControllerInit(napi_env env, napi_value exports) { napi_status status; // 将上述方法描述符挂载到exports对象上 status = napi_define_properties(env, exports, sizeof(g_ledControllerDesc) / sizeof(g_ledControllerDesc[0]), g_ledControllerDesc); if (status != napi_ok) { // 错误处理... return nullptr; } return exports; // 这个exports对象最终会被返回给JS层 } // 4. 定义napi_module结构体 static napi_module g_ledControllerModule = { .nm_version = 1, .nm_flags = 0, .nm_filename = nullptr, .nm_register_func = LedControllerInit, // 指定初始化函数 .nm_modname = “ledController“, // JS中require的模块名(在OH中机制不同,但概念类似) .nm_priv = nullptr, .reserved = {0}, }; // 5. 模块构造函数(使用__attribute__确保在库加载时执行) extern “C“ __attribute__((constructor)) void RegisterLedControllerModule() { napi_module_register(&g_ledControllerModule); }

关键解析与避坑

  • napi_env env:这是每个NAPI函数的第一个参数,代表一个独立的上下文环境。绝对不能跨线程共享或缓存env对象。所有NAPI调用都必须在获取到该env的线程上执行。
  • napi_callback_info info:包含了JS调用时传入的所有参数信息。我们需要使用napi_get_cb_info函数来提取参数个数和具体的值。
  • 模块注册:OpenHarmony的NAPI机制可能与传统Node.js略有不同。上述示例展示了标准的napi_module注册方式。在某些SDK版本中,可能需要使用OH_NODE_API_MODULE宏进行简化注册,务必查阅对应版本的标准文档。
  • 错误处理:每个napi_开头的函数调用后,都应检查其返回的napi_status。良好的错误处理能让你在JS层获得更清晰的异常信息,而不是一个晦涩的“Native Crash”。

3.2 JS与C++之间的数据类型转换

这是NAPI开发中最繁琐但也最重要的一环。JS中的数字、字符串、对象、数组、函数传到C++侧,都对应着不同的NAPI数据类型(napi_value),需要手动转换。

// 示例:解析一个JS调用 ledController.turnOnLed(1, 100); static napi_value TurnOnLed(napi_env env, napi_callback_info info) { size_t argc = 2; napi_value args[2] = {nullptr}; napi_value thisArg; void* data; // 1. 获取调用信息 napi_status status = napi_get_cb_info(env, info, &argc, args, &thisArg, &data); // 检查argc >= 2... // 2. 转换第一个参数(LED编号): JS Number -> C++ int32_t int32_t ledIndex; status = napi_get_value_int32(env, args[0], &ledIndex); if (status != napi_ok || ledIndex < 0) { napi_throw_error(env, nullptr, “Invalid argument: ledIndex must be a non-negative integer.“); return nullptr; } // 3. 转换第二个参数(亮度): JS Number -> C++ int32_t int32_t brightness; status = napi_get_value_int32(env, args[1], &brightness); if (status != napi_ok || brightness < 0 || brightness > 100) { napi_throw_error(env, nullptr, “Invalid argument: brightness must be 0-100.“); return nullptr; } // 4. 调用底层HDF客户端函数 bool result = SetLedBrightness(ledIndex, brightness); // 假设的HDF封装函数 if (!result) { napi_throw_error(env, nullptr, “Failed to control LED via HDF.“); return nullptr; } // 5. 返回结果给JS: C++ bool -> JS Boolean napi_value retValue; status = napi_get_boolean(env, true, &retValue); return retValue; }

实操要点

  • 类型检查先行:在转换前,最好先用napi_typeof判断一下napi_value的实际类型,避免传入错误类型导致崩溃。
  • 内存管理:NAPI提供了napi_create_系列函数来创建JS值。对于字符串和对象,需要注意其生命周期。通常,作为返回值创建的对象,NAPI引擎会负责管理。
  • 异步回调:如果硬件操作是耗时的(虽然LED控制通常很快),应考虑使用异步接口,通过napi_create_async_work创建异步工作项,在complete回调中通过napi_call_function调用JS传入的回调函数或Promiseresolve/reject切记,所有对napi_envnapi_value的操作必须在ExecuteComplete回调指定的线程内完成。

3.3 与HDF驱动层的对接

NAPI模块作为HDF的客户端,其核心任务是找到对应的驱动服务并发送命令。OpenHarmony的HDF提供了服务发现和远程过程调用(RPC)机制。

// led_hdf_client.cpp #include “hdf_base.h“ #include “hdf_log.h“ #include “hdf_sbuf.h“ #include “osal_mem.h“ #include “led_if.h“ // 假设这是厂商提供的LED驱动接口头文件 static struct HdfIoService *g_ledService = nullptr; bool InitLedService() { // 1. 绑定LED驱动服务。 “led_service“ 需与驱动配置(.hcs文件)中的服务名匹配。 g_ledService = HdfIoServiceBind(“led_service“); if (g_ledService == nullptr) { HDF_LOGE(“Failed to bind led service!“); return false; } return true; } bool SetLedBrightness(int32_t ledIndex, int32_t brightness) { if (g_ledService == nullptr) { HDF_LOGE(“Led service not initialized.“); return false; } // 2. 创建并序列化命令数据。HDF使用Sbuf进行数据序列化。 struct HdfSBuf *dataSbuf = HdfSBufObtainDefaultSize(); struct HdfSBuf *replySbuf = HdfSBufObtainDefaultSize(); if (dataSbuf == nullptr || replySbuf == nullptr) { HDF_LOGE(“Failed to obtain sbuf.“); goto ERROR; } // 3. 将参数写入sbuf。顺序和类型必须与驱动侧解析逻辑严格一致。 if (!HdfSbufWriteInt32(dataSbuf, ledIndex) || !HdfSbufWriteInt32(dataSbuf, brightness)) { HDF_LOGE(“Failed to write params to sbuf.“); goto ERROR; } // 4. 向驱动发送命令。CMD_LED_SET_BRIGHTNESS是驱动定义的命令码。 int ret = g_ledService->dispatcher->Dispatch(&g_ledService->object, CMD_LED_SET_BRIGHTNESS, dataSbuf, replySbuf); if (ret != HDF_SUCCESS) { HDF_LOGE(“Dispatch command failed, ret=%d“, ret); goto ERROR; } // 5. 可选:从replySbuf中读取驱动返回的结果 HdfSBufRecycle(dataSbuf); HdfSBufRecycle(replySbuf); return true; ERROR: if (dataSbuf != nullptr) HdfSBufRecycle(dataSbuf); if (replySbuf != nullptr) HdfSBufRecycle(replySbuf); return false; } void DeinitLedService() { if (g_ledService != nullptr) { HdfIoServiceRecycle(g_ledService); g_ledService = nullptr; } }

注意事项

  • 服务名与命令码“led_service“CMD_LED_SET_BRIGHTNESS必须与LED驱动实际发布的服务名和定义的命令码完全一致。这些信息通常在厂商提供的驱动头文件或配置文件中定义。
  • Sbuf序列化:这是HDF进程间通信的数据载体。读写顺序必须一一对应。如果传递复杂结构,需要自行定义序列化/反序列化规则。
  • 错误日志:务必使用HDF_LOGE等宏记录关键错误,这些日志可以通过hilog命令查看,是调试驱动交互问题的最重要依据。
  • 资源释放HdfSBufHdfIoService都是需要手动管理生命周期的资源,使用后必须回收,防止内存泄漏。

4. 实操过程与核心环节实现

4.1 环境准备与工程创建

首先,你需要一个完整的OpenHarmony源码编译环境。这里假设你已按照官方文档搭建好Ubuntu虚拟机,安装了hb等工具。

  1. 确定源码路径与分支:进入你的OpenHarmony源码根目录,并确保分支与你的开发板镜像匹配。
  2. 创建NAPI工程目录:在applications/sample/下创建我们的项目文件夹led_controller
    cd /path/to/openharmony-source mkdir -p applications/sample/led_controller/src/main/cpp/{include,src}
  3. 编写package.json:在led_controller目录下创建package.json,这是最关键的一步,它告诉构建系统这是一个Native模块。
    { “name“: “led_controller“, “version“: “1.0.0“, “description“: “NAPI module for LED control“, “main“: “./src/main/cpp/types/libled_controller/index.d.ts“, “types“: “./src/main/cpp/types/libled_controller/index.d.ts“, “scripts“: {}, “author“: ““, “license“: “Apache-2.0“, “dependencies“: {}, “devDependencies“: {}, “type“: “native“, // 必须为 “native“ “build“: { “sub_type“: “napi“ } }
  4. 编写CMakeLists.txt:在src/main/cpp目录下创建,用于编译C++代码。
    cmake_minimum_required(VERSION 3.16) project(led_controller) # 查找NAPI头文件 find_package(NAPI REQUIRED) # 设置编译目标 set(TARGET_NAME led_controller) add_library(${TARGET_NAME} SHARED src/led_controller_napi.cpp src/led_hdf_client.cpp ) # 链接NAPI库 target_link_libraries(${TARGET_NAME} PUBLIC NAPI::napi) # 链接HDF相关库,具体库名可能因版本而异 target_link_libraries(${TARGET_NAME} PUBLIC hdf_core hdf_utils_base) # 包含头文件目录 target_include_directories(${TARGET_NAME} PRIVATE include)

4.2 ArkTS侧接口定义与调用

Native模块编译后,会在生成的*.so库中暴露接口。我们需要在ArkTS侧通过import native来加载和调用。

  1. 创建.ets声明文件(可选但推荐):在src/main/cpp/types/libled_controller/下创建index.d.ts,提供TypeScript类型提示。
    // index.d.ts export const turnOnLed: (ledIndex: number, brightness: number) => boolean; export const turnOffLed: (ledIndex: number) => boolean;
  2. 编写ArkTS应用页面:在工程根目录创建index.ets
    // index.ets import native from ‘libled_controller.z.so‘; // 注意:.z.so是压缩后的动态库后缀 @Entry @Component struct Index { @State ledStatus: string = ‘Off‘; aboutToAppear() { // 模块加载时,可以初始化HDF服务(如果初始化较慢,可考虑异步) // 这里假设NAPI模块内部已处理初始化 } turnOn() { try { // 调用NAPI导出的Native函数 let result = native.turnOnLed(0, 80); // 控制0号LED,亮度80% if (result) { this.ledStatus = ‘On (80%)‘; console.info(‘LED turned on successfully.‘); } else { console.error(‘Failed to turn on LED.‘); // 可以在这里给用户Toast提示 } } catch (error) { console.error(`JS Exception: ${error.message}`); } } turnOff() { try { let result = native.turnOffLed(0); if (result) { this.ledStatus = ‘Off‘; console.info(‘LED turned off successfully.‘); } else { console.error(‘Failed to turn off LED.‘); } } catch (error) { console.error(`JS Exception: ${error.message}`); } } build() { Column({ space: 20 }) { Text(‘LED Control via NAPI‘) .fontSize(30) Text(`Current Status: ${this.ledStatus}`) .fontSize(20) Button(‘Turn On LED‘) .width(‘60%‘) .height(50) .onClick(() => this.turnOn()) Button(‘Turn Off LED‘) .width(‘60%‘) .height(50) .onClick(() => this.turnOff()) } .width(‘100%‘) .height(‘100%‘) .justifyContent(FlexAlign.Center) } }
    关键点import的路径‘libled_controller.z.so‘是约定俗成的。构建系统会自动将我们编译的led_controller.so打包并重命名为这个格式。如果模块名不匹配,会导致加载失败。

4.3 编译、烧录与调试

  1. 配置产品与组件:在你的产品配置文件(如vendor/xxx/yyy/config.json)中,确保添加了“led_controller“这个组件。
    { “subsystem“: “applications“, “components“: [ { “component“: “led_controller“, “features“: [] } ] }
  2. 全量编译
    hb set # 选择你的开发板产品 hb build -f # 全量编译
    编译成功后,你可以在out/.../packages/phone/system/app/下找到对应的HAP安装包。
  3. 烧录与运行:将镜像烧录到开发板,启动后安装HAP包,即可运行应用。
  4. 日志查看
    • JS层日志:在ArkTS中使用console.info/error打印的日志,可以通过DevEco Studio的调试器或hdc shell hilog | grep JSApp查看。
    • C++/NAPI层日志:在Native代码中使用HDF_LOGEOH_LOG_ERROR(取决于日志框架)打印的日志,使用hdc shell hilog | grep -E “led_controller|HDF“过滤查看。
    • 崩溃分析:如果应用崩溃,使用hdc shell crash命令查看崩溃栈,能精确定位到是NAPI函数中的哪一行出了问题。

5. 常见问题与排查技巧实录

在实际开发中,我踩过不少坑,这里把典型问题和解决方法记录下来。

5.1 模块加载失败:Cannot find module ‘libxxx.z.so‘

  • 现象:应用启动时直接崩溃,日志报错找不到模块。
  • 排查步骤
    1. 检查package.json:确认“type“: “native“已设置。
    2. 检查CMakeLists.txt:确认编译目标名称(TARGET_NAME)与package.json中的“name“字段没有直接关系,但最终生成的.so文件会被重命名为lib{name}.so。确保你的“name“led_controller,且编译能生成libled_controller.so
    3. 检查组件配置:确认产品的config.json中已添加该组件。
    4. 检查编译输出:在out/{product}/lib.unstripped/目录下查找是否生成了libled_controller.so。如果没有,说明编译未成功包含你的模块。
  • 根本原因:构建系统没有正确识别和处理你的Native模块。99%的问题出在package.json的配置或组件依赖上。

5.2 NAPI函数调用导致应用闪退

  • 现象:点击按钮后应用立即闪退。
  • 排查步骤
    1. 查看崩溃栈:第一时间连接hdc,执行hdc shell crash,找到最新的崩溃记录。栈信息会明确指向发生崩溃的so库和函数偏移地址。结合addr2line工具和带符号的so文件(在lib.unstripped目录下),可以定位到具体的C++代码行。
    2. 检查参数转换:这是最常见的原因。在NAPI函数开头,仔细检查napi_get_cb_info的返回值,以及后续napi_get_value_xxx的类型转换。确保JS传入的参数类型和数量与C++侧期望的一致。添加严格的参数校验和错误抛出
    3. 检查线程安全:是否在非UI线程(如napi_create_async_workExecute回调里)错误地使用了来自最初调用线程的napi_env?记住,env不能跨线程使用。异步工作完成后,必须在Complete回调(它会在JS引擎线程执行)里调用napi_call_function或返回Promise
    4. 检查空指针:在C++侧,对任何从NAPI获取的napi_value或自己创建的对象,在使用前都要判断是否为nullptr,尤其是在napi_create_系列函数调用之后。
  • 技巧:在Native代码中多使用HDF_LOGEprintf(临时调试)在关键步骤打印日志,结合hilog查看执行流,能快速缩小问题范围。

5.3 HDF服务绑定失败或Dispatch失败

  • 现象:NAPI函数执行了,但LED没有反应,Native日志显示“Failed to bind service““Dispatch command failed“
  • 排查步骤
    1. 确认驱动已加载:在设备上执行hdc shell lsmod | grep led或查看/dev/目录下是否有对应的设备节点,确认LED驱动内核模块已正确加载。
    2. 确认服务名:检查你的HDF客户端代码中HdfIoServiceBind使用的服务名(如“led_service“),是否与LED驱动配置文件(.hcs文件)中serviceName字段完全一致。大小写敏感
    3. 确认命令码:检查Dispatch函数使用的命令码(如CMD_LED_SET_BRIGHTNESS),是否与驱动侧IO_SERVICE_INTF中定义的方法ID一致。驱动和客户端必须共用同一套命令码定义头文件。
    4. 检查Sbuf序列化:确保驱动侧读取Sbuf参数的顺序、类型与客户端写入的顺序、类型完全匹配。一个int32_t对应一个HdfSbufWriteInt32。这是最容易出错的地方,建议为每个命令编写独立的序列化/反序列化辅助函数。
    5. 权限问题:检查你的应用是否有访问该硬件设备的权限。需要在应用的config.json中申请对应的“reqPermissions“

5.4 性能与最佳实践建议

  1. 减少跨语言调用:每次JS到C++的调用都有开销。如果需要进行多次硬件操作,考虑在NAPI侧设计一个批量操作的接口,而不是让JS循环调用单个接口。
  2. 异步化耗时操作:即使是一个简单的GPIO操作,理论上也应使用异步模式。这符合ArkUI的响应式设计哲学,避免阻塞UI线程。使用napi_create_async_work封装你的HDF调用。
  3. 资源释放:在模块卸载函数(如果有)或类析构函数中,确保释放HdfIoService和任何分配的全局资源。虽然进程退出时系统会回收,但良好的习惯能避免潜在问题。
  4. 代码复用:将HDF客户端操作封装成一个独立的C++类,NAPI模块只负责类型转换和调用这个类。这样,当需要支持其他硬件或更换驱动框架时,NAPI层改动最小。

这个项目麻雀虽小,五脏俱全。完成它,你不仅能让一个LED听你指挥,更重要的是打通了OpenHarmony南向开发中“应用-中间件-驱动”的关键链路。下次当你需要为新的传感器、执行器或者任何自定义硬件添加JS API时,这套方法论就可以直接套用。开发过程中,耐心阅读官方文档的Native API章节,善用hilog日志,多写防御性代码,你会发现NAPI开发并没有想象中那么神秘。

http://www.jsqmd.com/news/825488/

相关文章:

  • Maven组件发布实战:从distributionManagement配置到CI/CD集成
  • AI智能体工作流引擎:从原理到实践,构建高效多智能体协作系统
  • 基于大数据的智能电网负荷预测系统的研究与实现
  • 硬件项目前面板制作:三明治层压与乙烯基贴纸法详解
  • Coral开发板SPI通信实战:从协议原理到MAX31855传感器驱动
  • 2026届最火的五大AI辅助写作神器横评
  • 基于8位MCU双核架构的医疗级心律监护器设计与实践
  • C3SQL:基于大语言模型的文本到SQL生成工具实战指南
  • Eurorack模块面板隐藏式LED技术:Sticker标签实现一体化美学设计
  • 英伟达Blackwell架构解析:如何将大模型训练成本降低一个数量级
  • 基于Adafruit CLUE与BLE CSC服务构建自行车传感器数据采集系统
  • SoC安全验证挑战与Jasper SPV解决方案解析
  • 原生三件套构建极简个人主页:零依赖Web开发实践
  • Claude大模型与Home Assistant融合:打造具备认知智能的家庭自动化系统
  • 基于凸轮从动件机制的自动化装置:从机械原理到软硬件实现
  • 量子通信中的级联环图码技术解析
  • 盘点2026年Q2衡水钢板租赁服务商:为何推荐北京顺建源建筑设备租赁有限公司? - 2026年企业推荐榜
  • BurpSuite中文汉化终极指南:3步打造专业安全测试环境
  • 2026年靠谱的人本机床轴承/长城机床轴承可靠供应商推荐 - 行业平台推荐
  • 智能Shell脚本框架:提升运维自动化脚本的可维护性与工程化实践
  • html-anything 仓库全面介绍
  • 基于情感分析与提示工程的智能对话机器人架构设计与实现
  • 2026年当下,江苏企业如何甄选实力派拓客系统服务商? - 2026年企业推荐榜
  • 基于CircuitPython的互动雪花球:从传感器滤波到状态机设计的嵌入式实践
  • 基于MC9RS08KA与MC9S08JM60的心律监护器设计与实践
  • Arm SME2架构矩阵计算加速原理与优化实践
  • NIPPON KINZOKU加强推广环保型产品 “L-Core”:通过表面改性技术实现高导电性的功能性不锈钢
  • GenSwarm:LLM驱动的多机器人代码自动生成系统
  • 基于Python的网页自动化工具zo2:从原理到实战的完整指南
  • Fast Planner里的ESDF地图是怎么算距离的?一个2D小例子带你搞懂