news 2026/9/8 11:18:28

设备树与Linux驱动开发:从原理到RK3568实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
设备树与Linux驱动开发:从原理到RK3568实战

1. 设备树到底在解决什么问题

很多刚接触嵌入式Linux的朋友,第一次看到“设备树”(Device Tree)这三个字,第一反应是:这玩意儿跟我平时写的驱动有什么关系?我直接在内核源码里改平台设备注册信息不就完了,为什么要引入这么一层“中间商”?

先说结论:设备树本质上是把“硬件长什么样”和“驱动怎么写”这两件事彻底剥离开。在早期ARM Linux内核里,几乎每一块开发板的硬件配置信息都是直接写死在arch/arm/mach-xxx目录下的C代码里。什么意思呢?就是每次出一块新板子,都得往内核里塞一段平台设备注册代码,把板载资源的地址、中断号、时钟、GPIO等信息用C语言结构体描述一遍,然后调platform_device_register()注册进去。改动一次硬件,就要改一次内核源码,重新编译整个内核。早期ARM SoC厂商少,板子种类有限,这套做法虽然丑,但勉强能转。等到SoC种类爆发式增长,一个内核要同时支持几百种板卡时,这种硬编码方式彻底成了灾难。

设备树的思路很朴素:用一套独立的、与内核代码完全无关的纯文本描述文件,把硬件信息写清楚。内核在启动时解析这份文件,动态生成平台设备列表,驱动再用统一的接口去读取硬件资源。这样一来,换板子就只需要换一个.dts文件,内核代码一行都不用动。这个解耦思路,跟PC世界里ACPI表的作用是一样的。所以网上常有人说“设备树就是ARM版的ACPI”,我觉得这个类比非常准确。

从驱动开发者的视角看,设备树引入后最大的变化是:你写驱动时不再关心具体板子上哪个寄存器对应哪个外设,而是专注处理“当遇到compatible属性匹配到的设备时,如何初始化它”。硬件细节全部由设备树描述,驱动只需要知道如何消费这些描述。理解了这个本质,后面学起来就会顺畅很多。

2. 设备树语法与关键属性

2.1 从一颗LED灯开始理解基本语法

别一上来就去看全量语法手册,那东西能把人劝退。我建议从最简单的例子切入——点一颗LED灯。这颗LED用GPIO控制,对应设备树节点长这样:

/ { leds { compatible = "gpio-leds"; status = "okay"; power_led { label = "power"; gpios = <&gpio4 0 GPIO_ACTIVE_HIGH>; default-state = "on"; }; }; };

这个节点信息量不小,拆开慢慢看。根节点“/”下面挂了一个“leds”子节点,compatible属性告诉内核:这个节点由gpio-leds驱动接管。子节点power_led则具体描述了一颗LED灯:label是它的名字,gpios指定它接在gpio4控制器第0号引脚上,高电平有效。default-state是初始化状态。

仔细观察,设备树其实是一种“树形键值对集合”。每个节点是一个硬件对象,节点名通常是设备类型,同级节点通过unit address(比如gpio@ff7b0000)来区分。每个属性和值则描述这个硬件对象的具体细节。比如reg属性是一组地址信息,通常配合#address-cells和#size-cells告诉内核“地址占几个32位字,长度占几个32位字”。

2.2 几个新手最容易写错的属性

第一个是reg和ranges。reg描述设备自身的寄存器空间地址和长度,ranges描述父总线地址到子总线地址的翻译关系。很多新手会混淆这两个:reg告诉内核“我需要的寄存器在哪”,ranges告诉内核“从CPU视角看,子节点地址和父节点地址怎么换算”。尤其涉及PCIe、外部存储器控制器这类桥接设备时,ranges一旦写错,子设备就全部识别不了。

第二个是interrupts和interrupt-parent。热词里提到了DPU驱动、GPU驱动开发,绝大多数外设驱动都会用到中断。设备树里描述中断要分两步:先用interrupt-parent声明“我用哪个中断控制器”,再用interrupts声明具体的中断号和触发方式。经常有新手只写了interrupts却漏了interrupt-parent,结果驱动注册时拿不到有效中断号,中断申请失败的报错查半天查不出来。

第三个是clocks。瑞芯微RK3568这样的大SoC,几乎每个外设都要做时钟门控。设备树里一般用clocks属性和assigned-clock-rates来指定外设工作时钟及频率。比如UART想要跑1.5M波特率,光改驱动里波特率参数是不够的,必须确保设备树里uart时钟源频率配置正确,否则算出来的分频系数是错的,串口输出全是乱码。

第四个是status属性。经常出现的情况是:硬件明明板载了某个控制器,但它默认在设备树里被写成了status = "disabled"。比如某些SoC同一个I2C控制器可能复用多个引脚组,板厂根据实际接线决定用哪组,没用到的就disabled掉。遇到“内核说设备不存在”的时候,先检查status是不是okay。这个排查思路能解决一半的外设不工作问题。

2.3 dtsi与dts的关系

看瑞芯微RK3568的SDK时,你会发现arch/arm64/boot/dts/rockchip/目录下有一堆文件:rk3568.dtsi、rk3568-evb.dts、rk3568-evb1-ddr4-v10.dts…… 很多人刚开始直接懵了,到底哪个是正在用的?

这里涉及一个关键概念:dtsi是SoC公共描述文件,描述芯片整体资源(CPU核心、中断控制器、各IP控制器寄存器基址、时钟树、内部总线结构等)。dts是具体板卡描述文件,描述这块板子上实际接了哪些外设、使用了SoC的哪些引脚功能。dts通过#include引用dtsi,叠加自身板级配置。改变板载外设就改dts,换SoC才动dtsi。

RK3568这种平台,一颗SoC会被做成各种不同规格的开发板和商业板卡,所以你会看到rk3568-evb、rk3568-evb1-ddr4-v10、rk3568-rock-3a等下划线后缀各异的dts。它们都是同一个思路:公共芯片信息放在rk3568.dtsi,板级差异放在各自的dts里。编译时内核的build系统会扫描这些dts,生成对应的dtb文件。到底该选哪个,要看你手上板子是哪个型号、DDR是DDR3还是DDR4、屏幕接口是哪路LVDS还是MIPI DSI。板厂提供的内核源码里,一般有一个默认defconfig和对应的dts文件,照着板子型号匹配即可。

注意:修改dts文件以后,必须重新编译生成dtb。很多新手改了dts却发现不生效,最后发现是编译产物没更新,或者烧录时烧错了dtb分区。这个很低级,但确实非常常见。

3. 驱动怎么和设备树对上号

3.1 compatible匹配的完整流程

设备树节点写得再漂亮,驱动不认也是白搭。驱动与设备树的“接头暗号”就是compatible属性。以GPIO LED驱动为例,内核源码里drivers/leds/leds-gpio.c的驱动匹配表长这样:

static const struct of_device_id gpio_leds_of_match[] = { { .compatible = "gpio-leds", }, {} }; MODULE_DEVICE_TABLE(of, gpio_leds_of_match); static struct platform_driver gpio_led_driver = { .probe = gpio_led_probe, .remove = gpio_led_remove, .driver = { .name = "leds-gpio", .of_match_table = gpio_leds_of_match, }, }; module_platform_driver(gpio_led_driver);

内核启动时,设备树解析出的每个platform_device都会被拿来跟驱动的of_match_table做匹配。只要compatible字符串完全一致,驱动就绑定了这个设备,probe函数会被调用。匹配不上的话,设备就处于“有设备没驱动”的状态,在/dev下自然看不到对应节点。

这里要提示一个关键点:compatible字符串必须做到板级DTS和驱动源码严格一致,一个字符都不能差。很多时候驱动不probe,问题就出在DTS里写的是"gpio-led",驱动里匹配的是"gpio-leds",差个s就查半天。

3.2 在驱动中读取设备树资源的常用API

匹配只是第一步,probe函数里要真正拿到硬件资源,还得借助OF API。这里列几个最常用的,每一个都有明确的使用场景。

读取reg属性对应的资源,用platform_get_resource或更上层的devm_platform_ioremap_resource。这两个函数会从platform_device里抽取内存区域,直接返回映射后的虚拟地址。过去我们会直接用of_iomap(node, index),不过在新内核里更推荐platform系列接口,它会自动处理资源生命周期管理。

读取GPIO信息,用devm_gpiod_get或of_get_named_gpio。前者是新的GPIO descriptor接口,后者是老式的整型GPIO编号接口。同样是拿GPIO,descriptor接口更安全,因为它替你关注了GPIO是否有效、方向是否正确、active-low是否翻转等问题。DTS里写的GPIO_ACTIVE_LOW会被descriptor接口自动消化掉,而老接口需要自己手动判断有效电平。

读取中断信息,直接用platform_get_irq或of_irq_get。拿到IRQ号之后,再用request_irq或devm_request_threaded_irq注册中断处理函数。注册的时机要小心:必须在设备正常工作前把中断注册好,否则硬件信号到了,内核找不到处理函数,轻则丢中断,重则触发“irq X nobody cared”错误。

读取clocks信息,用devm_clk_get配合clk_prepare_enable。设备树里指定了clocks属性后,驱动侧按clock名字获取时钟句柄,使能时钟,外设才能正常工作。RK3568的PCIe控制器、GMAC网卡这类高速外设,时钟配置尤其重要,漏使能一个core clock,整个控制器直接挂掉。

读取自定义属性,用of_property_read_u32、of_property_read_string等系列函数。DTS里经常会有max-speed = <1000>phy-mode = "rgmii"这类自定义扩展,驱动侧用这些接口读取。读取之前最好用of_property_read_bool判断属性是否存在,防止读一个不存在的属性返回负值还没察觉。

3.3 一个完整的平台驱动骨架

把上面这些串起来,一个规范的平台驱动骨架长这样:

#include <linux/module.h> #include <linux/platform_device.h> #include <linux/of.h> #include <linux/gpio/consumer.h> #include <linux/interrupt.h> struct my_device_priv { void __iomem *base; struct gpio_desc *enable_gpio; int irq; }; static irqreturn_t my_device_isr(int irq, void *data) { struct my_device_priv *priv = data; u32 val = ioread32(priv->base + 0x10); /* 处理中断事件 */ return IRQ_HANDLED; } static int my_device_probe(struct platform_device *pdev) { struct resource *res; struct device *dev = &pdev->dev; struct my_device_priv *priv; int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; platform_set_drvdata(pdev, priv); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); priv->base = devm_ioremap_resource(dev, res); if (IS_ERR(priv->base)) return PTR_ERR(priv->base); priv->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); if (IS_ERR(priv->enable_gpio)) return PTR_ERR(priv->enable_gpio); priv->irq = platform_get_irq(pdev, 0); if (priv->irq < 0) return priv->irq; ret = devm_request_irq(dev, priv->irq, my_device_isr, 0, dev_name(dev), priv); if (ret) return ret; gpiod_set_value(priv->enable_gpio, 1); dev_info(dev, "init done\n"); return 0; } static int my_device_remove(struct platform_device *pdev) { struct my_device_priv *priv = platform_get_drvdata(pdev); gpiod_set_value(priv->enable_gpio, 0); return 0; } static const struct of_device_id my_device_of_match[] = { { .compatible = "myvendor,my-device", }, {} }; MODULE_DEVICE_TABLE(of, my_device_of_match); static struct platform_driver my_device_driver = { .probe = my_device_probe, .remove = my_device_remove, .driver = { .name = "my_device", .of_match_table = my_device_of_match, }, }; module_platform_driver(my_device_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("My Device Driver");

这个骨架里已经涵盖了资源映射、GPIO获取、中断申请、设备私有数据保存等核心逻辑。实际项目里,框架基本都是一样的,变化的只是硬件操作细节和业务逻辑。把这段代码弄熟,再套用到具体外设上,思路一通全通。

4. RK3568实战:修改设备树并让驱动跑起来

4.1 为什么RK3568有那么多设备树,到底怎么选

这个问题在热词里反复出现,特别是在OpenHarmony、Ubuntu这类第三方系统适配中更加突出。RK3568的SDK里设备树文件非常多,因为同一颗SoC会被多个评估板、参考板、量产板使用,每个板子的DDR颗粒、显示屏、网口个数、PCIe通道分配可能都不一样。

选设备树的原则只有一条:找到与你的硬件设计最接近的那个dts,然后在它基础上微调。如果是买来的开发板,板厂一般会在SDK的dts文件名里体现板子型号,直接匹配即可。如果自己画了底板,那就基于官方的evb dts改:去掉没有的器件节点,加上新增的器件节点,修改GPIO引脚复用配置。

判断设备树是否生效的方法也很简单:内核启动日志里,搜索“Kernel command line”,确认bootargs里指定的dtb路径。再看“Machine model”或者产品型号打印,确认实际加载的是哪块板级的compatible字符串。如果这些信息都对应不上,说明烧录的dtb不对,或者u-boot环境变量CONFIG_DEFAULT_FDT_FILE设错了。

4.2 修改Ubuntu下RK3568设备树并重新编译

真实场景里,很多朋友是在Ubuntu主机上做RK3568支持的开发。流程分三步:修改dts、编译dtb、烧录确认。

第一步,找到需要修改的dts。在SDK内核目录下执行:

find arch/arm64/boot/dts/rockchip/ -name "rk3568*.dts"

按板卡型号筛选出目标文件。比如你的板子是RK3568 EVB1 DDR4版本,那就是rk3568-evb1-ddr4-v10.dts。找到后用文本编辑器打开,定位到需要修改的节点。

第二步,编译。最简单的做法是使用SDK自带的构建脚本,但仅想验证dts修改效果时,可以直接在SDK顶层目录执行:

export ARCH=arm64 export CROSS_COMPILE=aarch64-linux-gnu- make rockchip_linux_defconfig make dtbs

编译完成后,在arch/arm64/boot/dts/rockchip/目录下会生成同名.dtb文件。把这个dtb文件替换到根文件系统/boot分区或者独立dtb分区即可,具体取决于你的Android/Ubuntu系统分区布局。用RK开发工具烧录时,通常会把kernel.img和resource.img分开处理,dtb可能打包在resource.img或者boot.img里。每个SDK版本略有差异,烧录前先确认打包脚本。

第三步,验证。这一步很多人会跳过,但强烈建议别省。有两种验证姿势:一种是启动后用root权限查看/sys/firmware/devicetree/base目录,这是设备树的运行时视图,按节点路径逐层展示属性。你在dts里改的内容,只要编译烧录正确,一定会体现在这里。另一种是看/proc/device-tree,这是/sys/firmware/devicetree/base的符号链接,本质相同。比如你改了LED默认状态,直接cat /sys/firmware/devicetree/base/leds/power_led/default-state,比对值是否与预期一致。

提示:修改dts后务必同步更新内核的DTC版本。RK3568的平台一般不需要旧版本DTC支持复杂宏展开,但如果从老SDK升级,建议用SDK内置的dtc工具编译,避免因为DTC版本差异导致编译告警或二进制约束不一致,投影到实际设备上出现“属性读不到”这种怪异问题。

4.3 一个硬件修改的完整推演

假设我的板子上新增了一个I2C温度传感器,地址是0x48,挂载在I2C2总线。设备树的修改逻辑是这样的:首先确认SoC的I2C2控制器节点位置。RK3568的i2c2节点在rk3568.dtsi里已经定义好了,默认状态可能是disabled,因为同一组I2C引脚可能被复用成GPIO。所以第一步是把它的status改成okay,然后在它下面新建一个子节点:

&i2c2 { status = "okay"; clock-frequency = <400000>; lm75: temperature-sensor@48 { compatible = "national,lm75"; reg = <0x48>; }; };

reg = <0x48>表示设备的I2C从机地址。驱动侧通过i2c_driver匹配compatible后,在probe里用i2c_smbus_read_word_data之类的接口读取温度寄存器即可。整个过程,驱动代码完全不需要知道这个传感器挂在哪个I2C总线上,也不需要管引脚复用怎么配置,这些都由设备树消化掉了。这,就是设备树真正的价值。

4.4 串口芯片驱动为什么老出问题

热词里CP2102、CH340、FT232R这几个串口芯片驱动出现的频率非常高。先说结论:这几颗USB转串口芯片在Linux主线内核里都自带驱动,正常情况下插上就能用,不需要额外安装驱动。CP2102对应cp210x.ko,CH340对应ch341.ko,FT232R对应ftdi_sio.ko。你看到“驱动装不上”的帖子,大部分是两类问题。

第一类是系统压根没识别到USB设备。插上芯片后执行dmesg看内核日志,如果只有USB enumerate但不产生ttyUSB节点,可能是usbserial驱动没自动绑定。此时手动执行modprobe cp210x再看一次。如果还没动静,检查USB线是不是纯充电线,很多廉价USB线只有电源线没有数据线,这个问题经常被忽视。

第二类是识别到了但打开串口失败。这里重点检查操作权限:dailout、dialout、uucp等用户组权限决定了非root用户能否访问串口设备。执行ls -l /dev/ttyUSB0,如果权限是crw-rw---- root dialout,那你的账号需要加入dialout组,然后重新登录shell。这个步骤论坛里翻来覆去讲,但是几乎每天都有新朋友问。

5. 设备树与驱动开发中的常见坑

5.1 编译报错“unit address mismatch”怎么办

DTC编译器对设备树的检查非常严格。比如你把节点名写成i2c@ff7b0000,但reg属性写的地址是<0xff7a0000>,DTC会直接报“node has a unit name, but no reg property”或者“unit address mismatch”。这类错误绝大多数是手误,检查reg值和节点名的地址部分是否一致即可。

还有一种情况是节点名合法,但重复定义了相同unit address。比如两个节点都叫gpio@ff7b0000,DTC也会报错。这种重复往往来自dtsi文件层层include之后的定义冲突,排查方法是编译时加上DTC_FLAGS=-@或者打开编译日志详细输出,定位到出错的具体文件与行号。

5.2 驱动probe不执行,先别急着怀疑驱动代码

驱动probe不执行是设备树开发里最高频的问题。我的排查顺序一般是:先看/sys/bus/platform/devices下有没有对应的设备节点。如果没有,说明设备树解析阶段就没生成设备,问题在DTS。如果有设备但没有驱动绑定,检查compatible是否完全匹配。如果设备和驱动都有但是probe报错,看dmesg里probe失败的具体返回值,-EPROBE_DEFER要特别注意,它表示当前依赖的资源还没准备好,内核会自动重试,通常是因为某个clock、regulator或pin controller还没注册完成。

有一个容易被忽略的点:platform_driver的id_table。有些驱动同时支持OF匹配和非OF匹配,代码里写了两套匹配表。如果of_match_table里的compatible和DTS对不上,但id_table里另一个兼容名对得上,驱动也会绑定,但probe函数拿到的资源格式可能完全不同。对于新代码,建议只用OF匹配,逻辑简单可控。

5.3 引脚复用配置不当导致的功能失灵

瑞芯微平台最让人头疼的就是IOMUX引脚复用。RK3568的每个引脚往往有多个功能,比如GPIO3_C2这个引脚,既可以是普通GPIO,也可以是I2C3_SCL,还可以是UART1_TX。默认状态在芯片数据手册里有定义,但在Linux下具体启用哪个功能,由设备树pinctrl子系统统一控制。

最常见的坑是:同一个引脚在多个节点里都被引用。比如A节点用了GPIO3_C2做LED控制,B节点又声明这个引脚是I2C3的SCL,pinctrl子系统在初始化时可能会报冲突警告,导致其中一个功能失效。排查方法是打开内核的pinctrl调试开关,在debugfs的pinctrl目录下查看每个pin的当前mux状态与是否有claim冲突。

5.4 热词里的WSL和虚拟机

热词里有一条“适用于 Linux 的 Windows 子系统必须更新到最新版本才能继续”,这就是WSL的典型报错。很多人想用WSL做嵌入式Linux开发,省去装虚拟机的麻烦,但WSL本质上是一个受限的Linux子系统,对USB设备直通、串口访问、GPIO调试等支持并不完善。做纯软件编译、脚本验证、内核模块交叉编译可以,但要做设备树调试、驱动烧录验证,还是建议用真实Linux物理机或者专门的虚拟机USB透传方案。

如果你的开发调试跑在WSL里,遇到USB串口映射不到/ttyUSB0的现象,先不要怀疑驱动代码,大概率是WSL没有把Windows的COM端口转发到Linux子系统。新版WSL支持usbipd-win方案,可以把Windows宿主机上的USB设备共享给WSL2使用,但配置复杂,稳定性也一般。生产环境别折腾这个,久经考验的方案还是实体机装Linux。

5.5 设备树里改了参数但不生效的几个原因

这个场景在Ubuntu改RK3568设备树时最典型。我整理一个对照表,方便你自查:

现象常见原因检查手段
修改的属性完全没变dtb没重新编译或者没烧录对比/sys/firmware/devicetree/base下的实时值
修改了dtsi但没生效dts里用status = "disabled"覆盖了dtsi配置搜索dts里是否包含同节点的status属性
修改了引脚功能但外设仍异常引脚复用冲突,另一个节点抢占了同一引脚cat /sys/kernel/debug/gpio 查看引脚占用
新加的节点在/sys下看不到设备树编译没问题但compatible没有驱动匹配看dmesg里是否有“no driver”提示
修改clock频率后外设不工作时钟树配置无误但驱动内部使用了旧频率检查驱动是读设备树属性还是使用硬编码

5.6 内核文档里找不到答案时怎么自救

设备树和驱动的调试,很多时候不是网上没有答案,而是你知道的不够多。碰到磕磕绊绊的问题,首要动作是什么?打开内核源码。设备树绑定文档在Documentation/devicetree/bindings目录下,每个子系统都有独立的yaml格式说明。驱动源码里的of_match_table和probe函数写得很清楚,比任何论坛帖子都可靠。再看一下drivers/gpio、drivers/pinctrl、drivers/clk这些基础子系统在对应平台下的实现,很多疑难杂症都能从底层实现里找到线索。

另一个被很多人忽略的利器是trace。内核的tracepoint机制里,有一个of事件组,可以跟踪设备树解析过程。打开tracefs后执行以下命令:

mount -t tracefs nodev /sys/kernel/tracing echo 1 > /sys/kernel/tracing/events/of/enable cat /sys/kernel/tracing/trace

这样可以直观看到每个节点的解析耗时和错误信息,对于定位设备树大文件的性能问题以及节点解析中断问题非常有帮助。

6. 从驱动开发角度看整个生态

写到这里,我想聊聊对设备树和驱动开发这件事的整体感受。很多人一上来就背语法、抄驱动模板,结果换个平台就抓瞎。设备树本质上是描述硬件的一种“数据契约”,驱动是消费这份契约的“服务方”。“数据契约”设计得好不好,直接决定了驱动好不好写。

所以每次拿到一块新板子,我习惯先花半小时仔细读它的dts文件,梳理清楚整块板卡的硬件拓扑:CPU通过哪些总线连了哪些外设,哪些控制器被使能、哪些被禁用,GPIO和中断脚位分配是否合理,时钟频率是否匹配外设需求。这个过程就像装修前先看户型图,没有这一步,后面做设计全是空中楼阁。

再聊聊生态。设备树在ARM Linux里已经是绝对主流,而且随着RISC-V的兴起,设备树这套机制也被完整继承了下来。x86平台虽然传统上不用设备树,但在一些特定嵌入式场景也开始尝试。对整个行业来说,设备树不是某个芯片厂商的私有协议,而是所有跑Linux的嵌入式平台的通用交流语言。学会设备树与驱动开发,本质上学会了跨平台的硬件抽象思维方式,这门手艺长期有效。

从热词里还能看出,很多人关心设备树到底怎么选、怎么改、怎么和系统适配,这说明大部分开发者的痛点并不在驱动代码本身,而在于对整个启动流程、编译流程、烧录流程的链路理解。这个链路就是:dts -> dtb -> bootloader加载 -> 内核解析 -> platform_device生成 -> driver匹配绑定 -> probe初始化。任何一环断了,你看到的现象都是“设备不工作”,但根因可能千差万别。把这条链路彻底吃透,比背一千行驱动代码都有用。

我给新人的建议很简单:不要急着追新框架、新工具,先把一块最简单的外设,比如GPIO按键或者LED灯,从dts修改到驱动编写,完整走通一遍。这一遍里你会踩到pin control、platform device、OF API、中断注册、设备生命周期管理这些几乎所有后续开发都会用到的核心概念。把这个闭环打通,后面学USB、PCIe、MIPI DSI等复杂驱动,都是同样的套路,沿着这个思路套上去就能上手。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 11:17:34

OpenHarmony设备树DTS修改实战:从定位到编译验证全流程

鸿蒙开发越深入&#xff0c;就越绕不开设备树。尤其是当你拿到一块新板子&#xff0c;或者想在一个官方开发板上外接一个不太常见的传感器、屏幕、模组时&#xff0c;十有八九最后都会落到同一个问题上&#xff1a;DTS怎么改&#xff1f;我见过不少人卡在这一步卡了很久——不是…

作者头像 李华
网站建设 2026/9/8 11:16:08

YOLOv8+MMAction2行人动作识别:双阶段检测与识别实战

简介&#xff1a;一份结合YOLOv8目标检测与MMAction2时序模型的行人动作检测可运行源码&#xff0c;面向智能视频监控、行为分析等场景的计算机视觉开发者和研究人员&#xff0c;解决视频中行人定位与行为分类的联动问题。资源共11个文件&#xff0c;涵盖py算法源码、mp4演示视…

作者头像 李华
网站建设 2026/9/8 11:15:05

Qt集成Tesseract OCR:Windows 64位编译与项目配置完整指南

简介&#xff1a;面向需要在Windows 64位环境下使用Qt进行OCR功能开发的工程师&#xff0c;这份编译好的Tesseract库可直接嵌入开发流程&#xff0c;免去从源码编译、依赖修补的繁琐过程。压缩包共916个文件、大小约39.32MB&#xff0c;其中包含546个头文件、72个DLL动态库、50…

作者头像 李华
网站建设 2026/9/8 11:13:54

S7-1200 PLC与MCGS触摸屏的自动售货机控制系统设计与联调实战

我刚做完一个自动售货机的联机项目&#xff0c;主控用的西门子S7-1200 PLC&#xff0c;上位显示用的昆仑通态MCGS7.7触摸屏&#xff0c;从硬件选型、程序框架到现场联调&#xff0c;整个过程走下来踩了不少坑&#xff0c;也攒了不少心得。这篇文章就把这套方案的完整实现过程拆…

作者头像 李华
网站建设 2026/9/8 11:13:40

AI视频广告实战指南:从脚本到成品的全流程制作方法

1. AI视频广告到底是什么&#xff0c;它和传统视频制作差在哪里 AI视频广告并不是一个模糊的概念&#xff0c;而是指“用生成式AI工具&#xff0c;从脚本、文案、画面、配音到剪辑&#xff0c;尽量用自动化方式完成一支广告视频”的完整流程。很多人一听到“AI一键生成”&#…

作者头像 李华