现代CMake的设计理念和使用

一口Linux 2021-10-30 10:37

击上方“一口Linux”,选择“星标公众号

干货福利,第一时间送达!

本文来源:https://ukabuer.me/blog/more-modern-cmake/

对于 C/C++的开发者而言,当涉及到复杂的第三方依赖时,工程的管理往往会变得十分棘手,尤其是还需要支持跨平台开发时。CMake 做为跨平台的编译流程管理工具,为第三方依赖查找和引入,编译系统创建,程序测试以及安装都提供了成熟的解决方案。编写一次 CMakeLists.txt 文件,执行同样的命令,在不同系统上都可以完成可执行程序或者链接库的创建。在熟悉 CMake 后,这种编译体验我认为勉强能赶上 Rust, Go 这些现代语言的一半,还有一半则是差在包管理上,这方面暂且不提。当然,如果只是做做算法题,完全不需要用到 CMake 这样复杂的工具,简单使用 gcc, clang 就可以满足需求了。

CMake 和 C++一样,随着多年的发展,其设计也得到了许多改进,并且和旧版本相比产生了重要的差异,从而有了现代 CMake 的说法。传统的 CMake 使用方式也没有什么问题,但就和现代 C++一样,现代的 CMake 使用方式在一些概念上更清晰,对开发者也更友好,更不容易出错。

# 一个现代CMake工程的简单例子
cmake_minimum_required(VERSION 3.12)
project(myproj)

find_package(Poco REQUIRED COMPONENTS Net Util)

add_executable(MyEXE)
target_source(MyEXE PRIVATE "main.cpp")
target_link_library(MyEXE PRIVATE Poco::Net Poco::Util)
target_compile_definition(MyEXE PRIVATE std_cxx_14)

Target 和围绕 Target 的配置

一个 C/C++工程通常都是为了生成可执行程序或者链接库,在现代 CMake 里他们被统称为target,创建命令分别是add_library()add_executable()。其中链接库的类型又分为很多种,最常用的就是SHARED以及STATIC,在命令中加入关键词进行声明:add_library(MyLib SHARED),第一个参数为target的名称,后续的配置都需要用到这个名字。

CMakeLists.txt中可以有多个target,相关配置大多围绕这些 target 进行。比如指定target的源文件:

target_source(MyLib PRVIATE "main.cpp" "func.cpp")

在 CMake 中,PRIVATE关键词用于描述参数的“应用范围”,此外还有INTERFACEPUBLIC两种可能的值,在下一小节会对他们进行详细介绍,此处可以暂时无视。

将一个已有的项目改造为 CMake 工程时,通常会有较多的源文件,可以使用 CMake 的file命令进行遍历拿到全部的源文件:

file(GLOB_RECURSE SRCS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)

命令第一个参数GLOB_RECURSE表明递归的查找子文件夹,第二个参数SRCS则是存储结果的变量名,第三个参数为目标文件的匹配模式,找到符合条件的 cpp 文件后,他们的路径会以字符串数组的形式保存在 SRCS 变量中,使用方式如下:

target_source(MyLib PRIVATE ${SRCS})

除了源码,配置target时通常还需要指定头文件目录:

target_include_directories(MyLib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/)

编译时需要的语言特性:

target_compile_features(MyLib PRIVATE std_cxx_14)

以及编译时的宏定义:

target_compile_definitions(MyLib PRIVATE LogLevel=3)

如果你有一些参数想直接传给底层的编译器(比如 gcc, clang, cl),可以使用

target_compile_options(MyLib PRIVATE -Werror -Wall -Wextra)

上面通过target_source这些target_*形式的命令进行的配置都是只对指定 target 有效的。而在传统 CMake 中,这些配置通常都是以全局变量的形式定义,比如使用include_directories()set_cxx_flags()等命令,传统方式的问题是灵活度低,当存在多个 target 时无法进行分别配置,导致某个 target 的属性意外遭到污染,因此现代 CMake 基于 target 的配置方式就和引入了 namespace 一样,管理起来更省心。

Build Specification 和 Usage Requirement

软件开发中依赖是十分常见的,C/C++通过 include 头文件的方式引入依赖,在动态或静态链接后可以调用依赖实现。一个可执行程序可能会依赖链接库,链接库也同样可能依赖其他的链接库。此时一个棘手的问题是,使用者如何知道使用这些外部依赖库需要什么条件?比方说,其头文件的代码可能需要开启编译器 C++17 的支持、依赖存在许多动态链接库时可能只需要链接其中的一小部分、有哪些间接依赖需要安装、间接依赖的版本要求是什么……

对于这些问题,最简单粗暴的解决方案即文字说明,依赖库的作者可以在某个 README、网站、甚至在头文件里说明使用要求,但这种方式效率显然是很低下的。

CMake 提供的解决方案是,在对 target 进行配置时,可以规定配置的类型,分为 build specification 和 usage requirement 两类,会影响配置的应用范围。Build specification 类型的配置仅在编译的时候需要满足,通过PRIVATE关键字声明;Usage requirement 类型的配置则是在使用时需要满足,即在其他项目里,使用本项目已编译好的 target 时需要满足,这种类型的配置使用INTERFACE关键词声明。在实际工程中,有很多配置在编译时以及被使用时都需要被满足的,这种配置通过PUBLIC关键词进行声明。

下面来看一个例子,我们编写了一个 library,在编译时静态链接了 Boost,在我们的实现文件中使用了 c++14 的特性,并用到了 Boost 的头文件和函数。随后我们对外发布了这个库,其中有头文件和预编译好的动态链接库。尽管我们的实现代码里用了 C++14,但在对外提供的头文件中只用到 C++03 的语法,也没有引入任何 Boost 的代码。这种情况下,当其他工程在使用我们的 library 时,其使用的编译器不需要开启 C++14 的支持,开发环境下也不需要安装 Boost。我们 library 的 CMake 配置中可以这么写:

target_compile_features(MyLib PRIVATE cxx_std_14)
target_link_libraries(MyLib PRIVATE Boost::Format)

此处用 PRIVATE 说明 c++14 的支持只在编译时需要用到,Boost 库的链接也仅在编译时需要。但如果我们对外提供的头文件中也使用了 C++14,那么就需要使用 PUBLIC 修饰,改为:

target_compile_features(MyLib PUBLIC cxx_std_14)
target_link_libraries(MyLib PRIVATE Boost::Format)

当 library 是 header-only 时,我们的工程是不需要单独编译的,因此也就没有 build specification,通过INTERFACE修饰配置即可

target_compile_features(MyLib INTERFACE cxx_std_14)

需要注意的是,Usage requirement 类型的配置,即通过INTERFACE或是PUBLIC修饰的配置是会传递的,比如 LibA 依赖 LibB 后,会继承 LibB 的 usage requirement,此后 LibC 依赖 LibB 时,LibA 和 libB 的 usage requirement 都会继承下来,这在存在多级依赖时是非常有用的。

现在的一个问题是,我们写好的这些 target, 还有他们的PRIVATEINTERFACE以及PUBLIC属性,使用者如何才能知道呢?

寻找和使用链接库

对于使用者而言,一大问题是如何找到依赖以及了解如何使用依赖。C/C++标准没有规范库的安装位置和安装形式,通过 CMake 提供的方案寻找依赖,不光可以定位到头文件目录和链接库路径,还能够获取到库的 usage requirement。

在 CMake 中寻找第三方库的命令为find_package,其背后的工作方式有两种,一种基于 Config File 的查找,另一种则是基于 Find File 的查找。在执行find_package时,实际上 CMake 都是在找这两类文件,找到后从中获取关于库的信息。

通过 Config file 找到依赖

Config File 是依赖的开发者提供的 cmake 脚本,通常会随预编译好的二进制一起发布,供下游的使用者使用。在 Config file 里,会对库里包含的 target 进行描述,说明版本信息以及头文件路径、链接库路径、编译选项等 usage requirement。

CMake 对 Config file 的命名是有规定的,对于find_package(ABC)这样一条命令,CMake 只会去寻找ABCConfig.cmake或是abc-config.cmake。CMake 默认寻找的路径和平台有关,在 Linux 下寻找路径包括/usr/lib/cmake以及/usr/lib/local/cmake,在这两个路径下可以发现大量的 Config File,一般在安装某个库时,其自带的 Config file 会被放到这里来。

在 Windows 下没有安装库的规范,也因此没有这样的目录,库可能被安装在各种奇奇怪怪的地方。此外,在 Linux 下,库也可能没有被安装在上述这些默认位置,在这些情况下,CMake 也提供了解决方案,对于find_package(Abc)命令,如果 CMake 没有找到 Config file,使用者可以提供Abc_DIR变量,CMake 会到Abc_DIR指向的路径寻找 Config file。

通过 Find file 找到依赖

Config file 看似十分美好,由开发者编写 CMake 脚本,使用者只要能找到 Config file 即可获取到库的 usage requirement。但现实是,并不是所有的开发者都使用 CMake,很多库并没有提供供 CMake 使用的 Config file,但此时我们还可以使用 Find file。

对于find_package(ABC)命令,如果 CMake 没有找到 Config file,他还会去试着寻找FindABC.cmake。Find file 在功能上和 Config file 相同,区别在于 Find file 是由其他人编写的,而非库的开发者。如果你使用的某个库没有提供 Config file,你可以去网上搜搜 Find file 或者自己写一个,然后加入到你的 CMake 工程中。

一个好消息是 CMake 官方为我们写好了很多 Find file,在CMake Documentation这一页面可以看到,OpenGL,OpenMP,SDL 这些知名的库官方都为我们写好了 Find 脚本,因此直接调用 find_package 命令即可。但由于库的安装位置并不是固定的,这些 Find 脚本不一定能找到库,此时根据 CMake 报错的提示设置对应变量即可,通常是需要提供安装路径,这样就可以通过 Find file 获取到库的 usage requirement。不论是 Config file 还是 Find file,其目的都不只是找到库这么简单,而是告诉 CMake 如何使用这个库。

坏消息是有更大部分库 CMake 官方也没有提供 Find file,这时候就要自己写了或者靠搜索了,写好后放到本项目的目录下,修改CMAKE_MODULE_PATH这个 CMAKE 变量:

list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_SOURCE_DIR}/cmake)

这样${CMAKE_SOURCE_DIR}/cmake目录下的 Find file 就可以被 CMake 找到了。

不过一个新的问题是,Config file 以及 Find file 究竟要怎么写?

Imported Target

在 C/C++工程里,对于依赖,我们最基本的要求就是知道他们的链接库路径和头文件目录,通过 CMake 的find_libraryfind_path两个命令就可以完成任务:

find_library(MPI_LIBRARY
NAMES mpi
HINTS "${CMAKE_PREFIX_PATH}/lib" ${MPI_LIB_PATH}
# 如果默认路径没找到libmpi.so,还会去MPI_LIB_PATH找,下游使用者可以设置这个变量值
)
find_path(MPI_INCLUDE_DIR
NAMES mpi.h
PATHS "${CMAKE_PREFIX_PATH}/include" ${MPI_INCLUDE_PATH}
# 如果默认路径没找到mpi.h,还会去MPI_INCLUDE_PATH找,下游使用者可以设置这个变量值
)

于是在早期 CMake 时代,依赖的开发者在 cmake 脚本里通过全局变量来声明这两个东西。比如名为 Abc 的库,其开发者在他的 cmake 脚本里会创建Abc_INCLUDE_DIRSAbc_LIBRARIES两个变量供下游使用者使用。这种命令尽管不是官方强制要求的,但大家都遵守了这个习惯,到了今天,很多库为了兼容旧 CMake 的使用方式,仍然提供这样的全局变量。

在现代 CMake 中,cmake 脚本提供一个 target 显然会更好,因为 target 具备属性,我们不光是要找到库,还需要了解库的使用方式,使用 target 除了头文件目录和链接库路径,我们还可以拿到更多关于库的信息。

因此现代 CMake 提供了一种特别的 target,Imported Target,创建命令为add_library(Abc STATIC IMPORTRED),用于表示在项目外部已经存在、无需编译的依赖,命令的第二个参数用于说明类型,比如是静态库或动态库等。对于 Imported Target 的名字,似乎开发者们都喜欢使用 namespace 的方式,比如Boost::FormatBoost::Asio等。同样的,对于一个 CMake 脚本,可以有多个 Imported Target。

我们可以像对待普通 target 一样,对 Imported Target 调用target_link_libraries等命令来说明他的 usage requirement。但其实还有另一种配置方式,上文提到过可以通过PRIVATEINTERFACEPUBLIC用于修饰 target 属性,这实际上可看作是一种语法糖。在 CMake 中,target 的大多属性都有对应的 private 以及 interface 两个版本的变量。比如通过
target_include_directories
命令配置头文件目录时,当使用PRIVATE修饰时,值被写入 target 的 INCLUDE_DIRECTORIES变量;使用INTERFACE修饰时,值写入
INTERFACE_INCLUDE_DIRECTORIES
变量;而使用PUBLIC时,则会写入两个变量。在 CMake 中,我们可以不使用 target 命令,而是直接使用set_target_properties修改这些值的变量。

对于 Imported Target,当库已经事先编译好时,我们需要通过一个特殊的变量,IMPORTED_LOCATION,来指明动态链接库的具体位置。这个变量就可以通过set_target_properties进行设置,在实际生产环境下,由于存在 Release 以及 Debug 环境的区别,IMPORTED_LOCATION实际上也存在多个版本,比如IMPORTED_LOCATION_RELEASE以及IMPORTED_LOCATION_DEBUG,都进行设置后,在对应的环境下,CMake 会根据这些变量为下游使用者选择正确的链接库。

# spdlog库的Imported Target
set_target_properties(spdlog::spdlog PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX"
IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/spdlog/spdlog.lib"
)

使用 Imported Target 的另一个好处是,我们在引入一个依赖时只需要 link 其 Imported Target,不再需要手动加入其头文件目录了。因为依赖的头文件目录已经在其 target 的INTERFACE属性里了,而INTERFACE属性是可传递的,于是:

find_package(spdlog REQUIRED)
add_executable(MyEXE)
target_source(MyExe "main.cpp")
target_link_libraries(MyExe SPDLog::spdlog)

无需
target_include_directories
,spdlog 的头文件目录自动会加进来。

find_package 的处理

回到find_package这个命令,这个命令可以指定很多参数,比如指定版本,指定具体的模块等等。以 SFML 多媒体库为例,其包含了 network 模块,audio 模块,graphic 模块等等,但我很多时候只用到 graphic 模块,那么其他的模块对应的链接库不需要被链接,于是 CMake 脚本可以这么写

# 要求大版本号为2的SFML库的graphic模块
find_package(SFML 2 COMPONENTS graphics REQUIRED)
# SFML提供的target名字为sfml-graphics
target_link_libraries(MyEXE PRIVATE sfml-graphics)

对于find_package命令,这些版本、模块等参数在 Config file 或是 Find file 中显然是需要处理的,在版本不匹配,模块不存在的情况下应该对下游使用者进行提示。这一方面 CMake 官方也为依赖开发者做了考虑,提供了
FindPackageHandleStandardArgs
这个模块,在 CMake 脚本中 include 此模块后,就可以使用
find_package_handle_standard_args
命令,来告知 CMake 如何获取当前 package 的版本变量,如何知道是否找到了库,比如下面针对 RapidJSON 的 cmake 脚本:

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(RapidJSON
REQUIRED_VARS RapidJSON_INCLUDE_DIR
VERSION_VAR RapidJSON_VERSION
)

这段脚本声明了当前库的版本值应该从RapidJSON_VERSION这个变量拿,而RapidJSON_INCLUDE_DIR这个变量可以用于表明有没有找到库。在执行这段脚本时,CMake 先去判断RapidJSON_INCLUDE_DIR这个变量是否为空,如果为空说明没找到库,CMake 会直接对下游使用者报错提示;如果此变量不为空,并且下游使用者在调用find_package时传入了版本号,CMake 则会从RapidJSON_VERSION变量中取值进行对比,如果版本不满足也报错提示。

使用 CMake 来编译

CMake 生成好编译环境后,底层的 make, ninja, MSBuild 编译命令都是不一样的,但 CMake 提供了一个统一的方法进行编译:

cmake --build .

使用--buildflag,CMake 就会调用底层的编译命令,在跨平台时十分方便。

对于 Visual Studio,其 Debug 和 Release 环境是基于 configuration 的,因此CMAKE_BUILD_TYPE变量无效,需要在 build 时指定:

cmake --build . --config Release

CMake 的缺陷

CMake 的缺陷是很明显的,入门成本很高,其语法的设计也很糟糕,find_package这些函数不会返回结果,而是对全局变量或是 target 产生副作用,函数的行为不查阅文档是很难预测的。并且在 CMake 中,变量,target,字符串的区分不明确,很容易让人感到迷惑,不知道什么时候应该使用${}去读取值。

此外,官方网站上的教程也十分落后,尽管可用,但并没有使用现代 CMake 方式创建工程。推荐看本文最后给的资料而不是官网上的 Tutorial。

之后有空了再介绍 Config file 的具体创建方式、库的 install 还有基于 ctest 的测试,不过希望在我更新之前就能有更好的替代工具诞生吧。


end



一口Linux 


关注,回复【1024】海量Linux资料赠送


精彩文章合集

文章推荐

【专辑】ARM
【专辑】粉丝问答
【专辑】所有原创
专辑linux入门
专辑计算机网络
专辑Linux驱动
【干货】嵌入式驱动工程师学习路线
【干货】Linux嵌入式所有知识点-思维导图


点击“阅读原文”查看更多分享,欢迎点分享、收藏、点赞、在看

一口Linux 写点代码,写点人生!
评论
  • 80,000人到访的国际大展上,艾迈斯欧司朗有哪些亮点?感未来,光无限。近日,在慕尼黑electronica 2024现场,ams OSRAM通过多款创新DEMO展示,以及数场前瞻洞察分享,全面展示自身融合传感器、发射器及集成电路技术,精准捕捉并呈现环境信息的卓越能力。同时,ams OSRAM通过展会期间与客户、用户等行业人士,以及媒体朋友的深度交流,向业界传达其以光电技术为笔、以创新为墨,书写智能未来的深度思考。electronica 2024electronica 2024构建了一个高度国际
    艾迈斯欧司朗 2025-01-16 20:45 187浏览
  • 近期,智能家居领域Matter标准的制定者,全球最具影响力的科技联盟之一,连接标准联盟(Connectivity Standards Alliance,简称CSA)“利好”频出,不仅为智能家居领域的设备制造商们提供了更为快速便捷的Matter认证流程,而且苹果、三星与谷歌等智能家居平台厂商都表示会接纳CSA的Matter认证体系,并计划将其整合至各自的“Works with”项目中。那么,在本轮“利好”背景下,智能家居的设备制造商们该如何捉住机会,“掘金”万亿市场呢?重认证快通道计划,为家居设备
    华普微HOPERF 2025-01-16 10:22 194浏览
  • Ubuntu20.04默认情况下为root账号自动登录,本文介绍如何取消root账号自动登录,改为通过输入账号密码登录,使用触觉智能EVB3568鸿蒙开发板演示,搭载瑞芯微RK3568,四核A55处理器,主频2.0Ghz,1T算力NPU;支持OpenHarmony5.0及Linux、Android等操作系统,接口丰富,开发评估快人一步!添加新账号1、使用adduser命令来添加新用户,用户名以industio为例,系统会提示设置密码以及其他信息,您可以根据需要填写或跳过,命令如下:root@id
    Industio_触觉智能 2025-01-17 14:14 84浏览
  • 百佳泰特为您整理2025年1月各大Logo的最新规格信息,本月有更新信息的logo有HDMI、Wi-Fi、Bluetooth、DisplayHDR、ClearMR、Intel EVO。HDMI®▶ 2025年1月6日,HDMI Forum, Inc. 宣布即将发布HDMI规范2.2版本。新规范将支持更高的分辨率和刷新率,并提供更多高质量选项。更快的96Gbps 带宽可满足数据密集型沉浸式和虚拟应用对传输的要求,如 AR/VR/MR、空间现实和光场显示,以及各种商业应用,如大型数字标牌、医疗成像和
    百佳泰测试实验室 2025-01-16 15:41 189浏览
  • 随着消费者对汽车驾乘体验的要求不断攀升,汽车照明系统作为确保道路安全、提升驾驶体验以及实现车辆与环境交互的重要组成,日益受到业界的高度重视。近日,2024 DVN(上海)国际汽车照明研讨会圆满落幕。作为照明与传感创新的全球领导者,艾迈斯欧司朗受邀参与主题演讲,并现场展示了其多项前沿技术。本届研讨会汇聚来自全球各地400余名汽车、照明、光源及Tier 2供应商的专业人士及专家共聚一堂。在研讨会第一环节中,艾迈斯欧司朗系统解决方案工程副总裁 Joachim Reill以深厚的专业素养,主持该环节多位
    艾迈斯欧司朗 2025-01-16 20:51 146浏览
  • 一个易用且轻量化的UI可以大大提高用户的使用效率和满意度——通过快速启动、直观操作和及时反馈,帮助用户快速上手并高效完成任务;轻量化设计则可以减少资源占用,提升启动和运行速度,增强产品竞争力。LVGL(Light and Versatile Graphics Library)是一个免费开源的图形库,专为嵌入式系统设计。它以轻量级、高效和易于使用而著称,支持多种屏幕分辨率和硬件配置,并提供了丰富的GUI组件,能够帮助开发者轻松构建出美观且功能强大的用户界面。近期,飞凌嵌入式为基于NXP i.MX9
    飞凌嵌入式 2025-01-16 13:15 219浏览
  • 2024年是很平淡的一年,能保住饭碗就是万幸了,公司业绩不好,跳槽又不敢跳,还有一个原因就是老板对我们这些员工还是很好的,碍于人情也不能在公司困难时去雪上加霜。在工作其间遇到的大问题没有,小问题还是有不少,这里就举一两个来说一下。第一个就是,先看下下面的这个封装,你能猜出它的引脚间距是多少吗?这种排线座比较常规的是0.6mm间距(即排线是0.3mm间距)的,而这个规格也是我们用得最多的,所以我们按惯性思维来看的话,就会认为这个座子就是0.6mm间距的,这样往往就不会去细看规格书了,所以这次的运气
    wuliangu 2025-01-21 00:15 57浏览
  • 随着智慧科技的快速发展,智能显示器的生态圈应用变得越来越丰富多元,智能显示器不仅仅是传统的显示设备,透过结合人工智能(AI)和语音助理,它还可以成为家庭、办公室和商业环境中的核心互动接口。提供多元且个性化的服务,如智能家居控制、影音串流拨放、实时信息显示等,极大提升了使用体验。此外,智能家居系统的整合能力也不容小觑,透过智能装置之间的无缝连接,形成了强大的多元应用生态圈。企业也利用智能显示器进行会议展示和多方远程合作,大大提高效率和互动性。Smart Display Ecosystem示意图,作
    百佳泰测试实验室 2025-01-16 15:37 194浏览
  •  光伏及击穿,都可视之为 复合的逆过程,但是,复合、光伏与击穿,不单是进程的方向相反,偏置状态也不一样,复合的工况,是正偏,光伏是零偏,击穿与漂移则是反偏,光伏的能源是外来的,而击穿消耗的是结区自身和电源的能量,漂移的载流子是 客席载流子,须借外延层才能引入,客席载流子 不受反偏PN结的空乏区阻碍,能漂不能漂,只取决于反偏PN结是否处于外延层的「射程」范围,而穿通的成因,则是因耗尽层的过度扩张,致使跟 端子、外延层或其他空乏区 碰触,当耗尽层融通,耐压 (反向阻断能力) 即告彻底丧失,
    MrCU204 2025-01-17 11:30 152浏览
  • 实用性高值得收藏!! (时源芯微)时源专注于EMC整改与服务,配备完整器件 TVS全称Transient Voltage Suppre,亦称TVS管、瞬态抑制二极管等,有单向和双向之分。单向TVS 一般应用于直流供电电路,双向TVS 应用于电压交变的电路。在直流电路的应用中,TVS被并联接入电路中。在电路处于正常运行状态时,TVS会保持截止状态,从而不对电路的正常工作产生任何影响。然而,一旦电路中出现异常的过电压,并且这个电压达到TVS的击穿阈值时,TVS的状态就会
    时源芯微 2025-01-16 14:23 186浏览
  • 现在为止,我们已经完成了Purple Pi OH主板的串口调试和部分配件的连接,接下来,让我们趁热打铁,完成剩余配件的连接!注:配件连接前请断开主板所有供电,避免敏感电路损坏!1.1 耳机接口主板有一路OTMP 标准四节耳机座J6,具备进行音频输出及录音功能,接入耳机后声音将优先从耳机输出,如下图所示:1.21.2 相机接口MIPI CSI 接口如上图所示,支持OV5648 和OV8858 摄像头模组。接入摄像头模组后,使用系统相机软件打开相机拍照和录像,如下图所示:1.3 以太网接口主板有一路
    Industio_触觉智能 2025-01-20 11:04 117浏览
  • 电竞鼠标应用环境与客户需求电竞行业近年来发展迅速,「鼠标延迟」已成为决定游戏体验与比赛结果的关键因素。从技术角度来看,传统鼠标的延迟大约为20毫秒,入门级电竞鼠标通常为5毫秒,而高阶电竞鼠标的延迟可降低至仅2毫秒。这些差异看似微小,但在竞技激烈的游戏中,尤其在对反应和速度要求极高的场景中,每一毫秒的优化都可能带来致胜的优势。电竞比赛的普及促使玩家更加渴望降低鼠标延迟以提升竞技表现。他们希望通过精确的测试,了解不同操作系统与设定对延迟的具体影响,并寻求最佳配置方案来获得竞技优势。这样的需求推动市场
    百佳泰测试实验室 2025-01-16 15:45 310浏览
  • 日前,商务部等部门办公厅印发《手机、平板、智能手表(手环)购新补贴实施方案》明确,个人消费者购买手机、平板、智能手表(手环)3类数码产品(单件销售价格不超过6000元),可享受购新补贴。每人每类可补贴1件,每件补贴比例为减去生产、流通环节及移动运营商所有优惠后最终销售价格的15%,每件最高不超过500元。目前,京东已经做好了承接手机、平板等数码产品国补优惠的落地准备工作,未来随着各省市关于手机、平板等品类的国补开启,京东将第一时间率先上线,满足消费者的换新升级需求。为保障国补的真实有效发放,基于
    华尔街科技眼 2025-01-17 10:44 205浏览
  • 本文介绍瑞芯微开发板/主板Android配置APK默认开启性能模式方法,开启性能模式后,APK的CPU使用优先级会有所提高。触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。源码修改修改源码根目录下文件device/rockchip/rk3562/package_performance.xml并添加以下内容,注意"+"号为添加内容,"com.tencent.mm"为AP
    Industio_触觉智能 2025-01-17 14:09 118浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦