ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Linux内核模块的MediaTek AIoT SoC演示平台开发实践

基于Linux内核模块的MediaTek AIoT SoC演示平台开发实践 前阵子我们团队在准备一套新的演示方案,目标很明确:用最小的成本,把新一代MediaTek AIoT SoC的能力完完整整地展示给客户和开发者看。一开始我们考虑过直接用厂商的SDK demo,但稍微深入一点就发现,SDK里那些例子要么太重,要么太碎,根本没法高效地展示“这颗SoC在真实场景下能干什么”。最后我们敲定的方案是:基于Linux驱动生态,做一套模块化的演示系统——硬件上分成核心板、载板和几个功能模块,软件上以内核驱动模块为底座,再往上叠用户态应用。这篇文章就把这套方案从选型、硬件准备、驱动开发,到踩坑排查的完整过程写出来。这套东西的核心关键词其实就四个:Linux、Modules、MediaTek、AIoT SoCs。但把它们串起来之后,涉及的细节远比想象中多。内核模块怎么写得干净、设备树怎么配、NPU怎么调用、摄像头链路怎么打通、WiFi固件加载失败怎么排查……每一项单独拎出来都够写一篇。这篇文章适合正在做嵌入式Linux项目、准备评估AIoT SoC方案的工程师,也适合方案商里负责预研和demo开发的同事参考。1. 方案整体设计与平台选型思路1.1 为什么要用“Linux驱动模块”来做展示先说结论:用Linux驱动模块来做SoC展示,不是因为Linux本身有多“高级”,而是因为它足够标准、足够透明,而且拆起来最方便。做硬件演示最容易犯的错,就是一上来就搞一个巨大的Qt应用或者安卓App,结果是客户问“这个功能是SoC的什么模块实现的”,答不上来。驱动模块就不一样了:每个硬件外设对应一个内核模块,insmod/rmmod之间就能把硬件功能拆分得清清楚楚。比如要展示SoC的GPIO能力,就加载一个gpio_demo模块;要展示ISP和摄像头链路,就加载一个camera采集模块;要展示NPU推理,就跑一个用户态推理程序,配合内核侧的设备节点。这种“模块化展示”的思路,对客户来说也特别友好。客户拿到板子之后,不需要理解整个系统,只要看“加载了哪个模块,跑起来什么效果”就够了。而且这正好契合MediaTek新一代AIoT SoC的产品定位:这些芯片本身就面向碎片化IoT场景,不同客户需要不同的接口组合、不同的外设组合,模块化演示方案天然匹配这种多样化需求。再说一个很多人忽略的点:Linux内核模块本身就是一套“活文档”。驱动代码里注释怎么写、设备树节点怎么组织、Makefile怎么维护,这些直接构成了一个可阅读、可裁剪、可扩展的参考工程。相比一份几百页的PDF数据手册,工程师更愿意看能编译、能跑的实际代码。1.2 MediaTek AIoT SoC平台选型与优势拆解MediaTek现在主力的AIoT SoC集中在Genio系列,包括Genio 350、Genio 510、Genio 700、Genio 1200等多个档位。我们这次项目用的是Genio 700这一档,原因后面细说。先看一个简单的对比表:型号CPUNPU算力主要定位典型场景Genio 350双核Cortex-A530.6 TOPS入门IoT/智能家居智能音箱、门锁Genio 510双核A78双核A552 TOPS中端边缘AIoT智能零售、工业视觉Genio 700双核A78六核A554 TOPS中高端边缘AIoT智能广告机、边缘计算盒子Genio 1200四核A78四核A554.8 TOPS高性能边缘AIoT智能座舱、服务机器人我们选Genio 700的核心原因有三个:第一是算力与功耗的平衡。4 TOPS的NPU算力虽然不是最顶尖的,但足够跑YOLOv5s、MobileNet这类主流模型,而且整板功耗控制在8W以内,不需要额外的主动散热。对演示场景来说,这很重要——带着一块静音、不烫手的板子去客户现场比什么都强。第二是多媒体接口齐全。Genio 700支持双屏显示(DP HDMI)、MIPI-CSI摄像头输入、多路I2S音频,这意味着它几乎能覆盖所有AIoT交互场景:屏幕显示、摄像头视觉、语音交互。第三是生态兼容性好。MediaTek Genio系列的Linux BSP基于标准内核,遵循Mainline内核的开发习惯,设备树、驱动模型都是标准玩法。对我们这种需要深度定制demo的团队来说,资料透明度和调试自由度是刚需。1.3 模块化演示系统的整体架构整套演示系统我们把它拆成三层:硬件层:核心板 载板 功能子卡(摄像头模组、显示模组、传感器模组、无线模组)。内核层:Linux内核 设备树 各类驱动模块。驱动模块按功能拆分为gpio_demo、camera_demo、pwm_fan、display_demo等。应用层:基于C和Python的用户态程序,包括设备状态监控、NPU推理脚本、RTSP推流服务、远程管理Web面板。这个架构最大的好处是“层层可替换”。客户想看不同的外设组合,不需要改内核,只要改设备树或加载不同的模块;客户想看不同的应用场景,不需要动驱动,只要换应用层脚本。我们在实际规划时,特意控制了三层之间的耦合度。内核模块不依赖用户态程序,用户态程序只通过标准接口(设备节点、sysfs、netlink)与内核通信。这样无论是单独演示某个模块,还是集成跑全流程,都不会互相拖累。2. 硬件准备与Linux开发环境搭建2.1 开发平台与外设配置清单我们这次搭建的Genio 700演示平台,硬件清单大概是这样:核心板:Genio 700 SoM,带LPDDR4X内存和eMMC存储。载板:MediaTek官方EVK载板,引出全部接口。摄像头:SONY IMX258传感器MIPI-CSI模组。显示:DP转HDMI线直连4K显示器。无线模块:MT7921 Wi-Fi 6 BT 5.2模组(M.2接口)。传感器:温湿度传感器(SHT30)挂I2C总线。存储:MicroSD卡用于系统启动和日志记录。这套配置基本把Genio 700的主要特性都覆盖了:多媒体、无线连接、边缘AI、外设控制。而且每一样都是标准接口,不是独占的私有接口——这点很关键,意味着客户参考演示方案做产品设计时,不需要为“演示特供”的硬件付出额外的设计成本。2.2 交叉编译工具链与BSP准备嵌入式Linux开发绕不开交叉编译。Genio 700是ARM64架构,所以交叉编译工具链选择aarch64-linux-gnu系列。我们使用的是MediaTek提供的Yocto BSP,里面自带工具链和内核源码,省去了手动对齐版本的麻烦。基本环境准备步骤如下:# 1. 安装交叉编译工具链(Ubuntu 22.04环境) sudo apt install gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu # 2. 拉取BSP(根据厂商提供的repo地址) git clone https://github.com/MediaTek-Labs/genio-700-bsp.git cd genio-700-bsp # 3. 初始化编译环境(具体命令以BSP的README为准) source setup-environment build # 4. 编译系统镜像 bitbake genio-image-demo关于环境,我强烈建议直接用原生Linux物理机,或者至少给虚拟机分配足够的内存和CPU核心。WSL也可以跑,但有个坑:WSL2的文件系统I/O性能在跨盘操作时下降非常明显,编译内核模块这种大量小文件读写的操作会慢到怀疑人生。如果只能用WSL,建议把整个工程放在WSL内部文件系统(ext4)里,不要放在/mnt/c或/mnt/d这种挂载盘上。编译系统镜像这一步,第一次跑会非常久,因为要下载并编译一大堆依赖。我们团队实际跑下来,新环境首次构建Yocto镜像大约需要2到4小时,取决于网速和机器配置。所以这个时间千万不要浪费,可以先把后续要用的模块代码和测试用例写好。2.3 设备树的理解与配置实践设备树(Device Tree)是嵌入式Linux里绕不开的概念。很多人第一次接触会觉得它像“配置文件”,其实它比配置文件更底层——它描述的是硬件拓扑结构,告诉内核“这块板子上有哪些外设、挂在哪个总线、中断号是多少、GPIO怎么复用”。我们演示板上的设备树节点,简单来说就是围绕Genio 700的SoC dtsi文件做overlay扩展。以GPIO LED为例:pio { led_demo_en: led-demo-en { pins_cmd_dat { pinmux PINMUX_GPIO42__FUNC_GPIO42; slew-rate 1; bias-disable; }; }; }; i2c0 { status okay; sht3044 { compatible sensirion,sht30; reg 0x44; }; };这里有个容易忽略的点:MediaTek平台几乎每个引脚都有多功能复用(pinmux),如果你不在设备树里把引脚配置成GPIO功能,那即使驱动代码里申请了GPIO号,引脚也不会按预期输出电平。这其实是很多“驱动看起来没问题但硬件不动作”的罪魁祸首。我习惯的做法是先看SoC数据手册里的pinmux表格,确认引脚默认功能,再在设备树里显式配置。设备树的改动之后需要重新编译设备树并烧录,编译方法根据BSP不同有所差异,但核心命令一般是:make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- dtbs编译完后会生成一个.dtb文件,替换到boot分区里即可。3. 核心环节:第一个Linux驱动模块的诞生3.1 从“Hello World”到可加载的内核模块所有Linux驱动开发的第一步,永远是写一个可加载的内核模块。不要觉得这个太基础,这一步能把整个工具链、内核头文件、编译环境全部验证一遍。如果这里跑不通,后面写再多驱动都是空中楼阁。我们项目中第一个演示模块是LED控制模块,但我还是建议先从最简单的hello模块开始。下面是完整的模块代码:// hello_demo.c #include linux/module.h #include linux/kernel.h #include linux/init.h static int __init hello_demo_init(void) { pr_info([hello_demo] module loaded\n); return 0; } static void __exit hello_demo_exit(void) { pr_info([hello_demo] module removed\n); } module_init(hello_demo_init); module_exit(hello_demo_exit); MODULE_LICENSE(GPL); MODULE_DESCRIPTION(Hello demo for MediaTek Genio 700);对应的Makefile:obj-m : hello_demo.o KDIR : /path/to/kernel-source ARCH : arm64 CROSS_COMPILE : aarch64-linux-gnu- all: $(MAKE) ARCH$(ARCH) CROSS_COMPILE$(CROSS_COMPILE) -C $(KDIR) M$(PWD) modules clean: $(MAKE) ARCH$(ARCH) CROSS_COMPILE$(CROSS_COMPILE) -C $(KDIR) M$(PWD) clean编译之后会生成hello_demo.ko。然后就是加载测试:# 把hello_demo.ko拷贝到目标板 scp hello_demo.ko rootboard-ip:/root/ # 在目标板上执行 insmod hello_demo.ko dmesg | tail -5 # 应该看到hello_demo module loaded rmmod hello_demo dmesg | tail -5 # 应该看到hello_demo module removed这里有个重要的点是KDIR要指向目标内核的源码目录,而不是主机内核源码目录。很多人图方便直接用/lib/modules/$(uname -r)/build,这在目标板上编译可以,在开发机上交叉编译时必须改成BSP里的内核源码路径。3.2 实战演示:GPIO LED控制模块跑通hello模块之后,就可以做真正有演示效果的GPIO模块了。我们做一个简单的LED闪烁模块,同时暴露一个sysfs节点,让用户态可以手动控制LED开关,这样演示时互动性很强。先写驱动代码:// led_demo.c #include linux/module.h #include linux/kernel.h #include linux/init.h #include linux/gpio/consumer.h #include linux/platform_device.h #include linux/of.h #include linux/delay.h static struct gpio_desc *led_gpio; static ssize_t led_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { bool val; if (kstrtobool(buf, val) 0) gpiod_set_value(led_gpio, val); return count; } static ssize_t led_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, %d\n, gpiod_get_value(led_gpio)); } static DEVICE_ATTR(led, 0644, led_show, led_store); static int led_demo_probe(struct platform_device *pdev) { struct device *dev pdev-dev; int ret; led_gpio devm_gpiod_get(dev, led, GPIOD_OUT_LOW); if (IS_ERR(led_gpio)) { dev_err(dev, failed to get led gpio\n); return PTR_ERR(led_gpio); } ret device_create_file(dev, dev_attr_led); if (ret) { dev_err(dev, failed to create sysfs interface\n); return ret; } dev_info(dev, led_demo probed\n); return 0; } static int led_demo_remove(struct platform_device *pdev) { struct device *dev pdev-dev; device_remove_file(dev, dev_attr_led); return 0; } static const struct of_device_id led_demo_of_match[] { { .compatible mediatek,led-demo }, { } }; MODULE_DEVICE_TABLE(of, led_demo_of_match); static struct platform_driver led_demo_driver { .probe led_demo_probe, .remove led_demo_remove, .driver { .name led_demo, .of_match_table led_demo_of_match, }, }; module_platform_driver(led_demo_driver); MODULE_LICENSE(GPL);对应的设备树节点:pio { led_demo_pins: led-demo-pins { pins_cmd_dat { pinmux PINMUX_GPIO42__FUNC_GPIO42; slew-rate 1; }; }; }; i2c0 { status okay; led_demo: led-demo0 { compatible mediatek,led-demo; reg 0x0; led-gpios pio 42 GPIO_ACTIVE_HIGH; pinctrl-names default; pinctrl-0 led_demo_pins; }; };这里为什么放在I2C0节点下?因为Genio 700上并不是所有GPIO都能直接作为platform device的独立节点存在,挂在某个总线节点下、用reg 0x0占一个虚拟地址是最省事的做法。当然如果你希望它更“正规”,也可以直接放在根节点下并用simple-bus兼容,但只要probe能触发、GPIO能申请成功,演示层面完全够用。设备树编译、烧录之后,加载模块:insmod led_demo.ko # 查看是否probe成功 dmesg | grep led_demo然后通过sysfs控制:echo 1 /sys/devices/platform/soc/11000000.i2c0/led-demo0/led echo 0 /sys/devices/platform/soc/11000000.i2c0/led-demo0/led客户在演示现场看到这个效果,基本就能理解“SoC的GPIO能力”是怎么一回事。3.3 模块化驱动与用户态应用如何配合驱动模块只是底座,真正让演示“有灵魂”的是用户态应用。我们做了一个Python控制脚本,通过subprocess调用sysfs接口控制LED闪烁节奏,同时把状态通过MQTT上报。#!/usr/bin/env python3 import time import subprocess import paho.mqtt.client as mqtt LED_SYSFS /sys/devices/platform/soc/11000000.i2c0/led-demo0/led def set_led(state: int): with open(LED_SYSFS, w) as f: f.write(str(state)) def main(): client mqtt.Client(led-demo-client) client.connect(192.168.1.100, 1883, 60) count 0 while True: val count % 2 set_led(val) client.publish(demo/led/state, val) print(fLED set to {val}) count 1 time.sleep(1) if __name__ __main__: main()这个脚本很简单,但它演示了一个核心思路:内核驱动暴露控制接口,用户态负责业务逻辑,两边通过标准机制(这里是sysfs)通信。后面如果要把控制接口换成ioctl或者netlink,驱动的改动范围也很小,不会推翻整个架构。4. 三大高频演示场景的落地细节4.1 边缘AI推理演示:NPU是怎么跑起来的Genio 700最吸引客户的就是那颗4 TOPS的NPU。我们的NPU演示选择的是跑YOLOv5s做实时目标检测,输入源是USB摄像头或者MIPI摄像头,输出在屏幕上画框。MediaTek Genio平台的NPU支持是通过NeuroPilot SDK暴露的。在Linux下,推理程序可以通过ONNX Runtime配合MediaTek的Execution Provider调用NPU,也可以用厂商自带的Python API。整个链路简单来说是这样的:摄像头采集图像帧。图像帧通过OpenCV或GStreamer转为模型输入张量。ONNX Runtime加载预先转换好的YOLOv5 ONNX模型。推理结果(post-process)解析出目标框坐标和类别。叠加显示到屏幕上。这里有一个关键细节:模型转换。YOLOv5原始PyTorch模型需要先转成ONNX,再经过MediaTek的工具链做量化优化。我们实际测试下来,INT8量化后的模型在NPU上的推理延迟大约在20ms到35ms之间(输入分辨率640x640),帧率完全够用。如果不做量化,直接用FP32模型,推理延迟会高很多,而且NPU的利用率也上不去。推理代码示例:import onnxruntime as ort import numpy as np import cv2 providers [MediaTekExecutionProvider, CPUExecutionProvider] sess ort.InferenceSession(yolov5s_int8.onnx, providersproviders) cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break img cv2.resize(frame, (640, 640)) img img[:, :, ::-1].transpose(2, 0, 1) img np.ascontiguousarray(img, dtypenp.float32) img / 255.0 img np.expand_dims(img, axis0) outputs sess.run(None, {images: img}) # 解析outputs,绘制检测框...演示效果上,我们还特意做了一个仪表盘页面,实时显示CPU/GPU/NPU的占用率,以及当前推理帧率。客户看到NPU占用率明显、CPU占用率很低时,对“硬件加速边缘AI”的感知会强烈得多。4.2 多媒体链路演示:摄像头采集与屏幕显示Genio 700的多媒体能力是第二个演示重点。我们把MIPI摄像头的数据直接采集到屏幕,同时提供RTSP推流,方便客户在局域网内用VLC远程观看。Linux下摄像头采集标准接口是V4L2(Video for Linux 2)。MediaTek的BSP里已经集成了MIPI-CSI的驱动,应用层只需要用标准的V4L2接口打开/dev/video0即可。一个简单的V4L2采集显示流程:# 使用GStreamer直接显示摄像头画面 gst-launch-1.0 v4l2src device/dev/video0 ! \ video/x-raw,width1920,height1080,framerate30/1 ! \ videoconvert ! autovideosink # 推RTSP流(需要先启动RTSP服务端) gst-launch-1.0 v4l2src device/dev/video0 ! \ video/x-raw,width1280,height720,framerate30/1 ! \ x264enc tunezerolatency ! \ rtph264pay ! udpsink host192.168.1.200 port5000实际操作中,需要特别注意MIPI摄像头的初始化顺序。有时候开机后直接打开摄像头会报错“No such device”,这通常不是驱动没加载,而是MIPI-CSI的sensor供电或者时钟没有ready。我遇到这种情况的排查思路是:dmesg | grep imx258检查sensor驱动是否probe成功。检查设备树里MIPI-CSI的电源、时钟配置。确认ISP管线是否正常,有时需要重启ISP服务。另外提一句:在调试摄像头时,v4l2-ctl --list-devices是个非常好用的命令,它能帮你快速确认当前系统中所有V4L2设备节点和对应的驱动信息,比猜测/dev/video0是哪个设备靠谱得多。4.3 无线连接演示:WiFi 6、蓝牙与MQTT远程上报AIoT产品大多离不开无线连接,所以无线模块的演示也很关键。我们平台上的MT7921支持Wi-Fi 6和蓝牙5.2,在Linux下通过标准接口工作。配WiFi的命令其实很简单:# 扫描WiFi nmcli dev wifi list # 连接指定SSID nmcli dev wifi connect DemoAP password demo1234 # 查看IP地址 ip addr show wlan0有了IP之后,就能顺手把前面的MQTT上报跑起来,实现“设备接入网络 - 数据上云”的完整链路。整套演示在客户面前就是:一块板子开机,自动连上WiFi,然后把LED状态、温湿度传感器数据、NPU推理结果全部上报到演示后台,客户在电脑上打开网页就能实时看到。蓝牙的演示稍微复杂一点,核心是处理蓝牙固件和协议栈。如果只是做BLE GATT服务广播,基本上加载完蓝牙驱动后,用bluetoothctl就能搞定:bluetoothctl power on agent on advertise on但如果你需要在BLE上做自定义数据传输,就得在应用层实现GATT服务,这比WiFi路径要麻烦不少。我们目前的做法是直接用一个开源的BLE GATT服务库(BlueZ的D-Bus接口),把温湿度数据包进来,手机端用nRF Connect就能扫描到并读取数据。5. 常见问题与排查技巧实录5.1 内核模块编译失败的经典场景错误现象:编译内核模块时终端报错:error: an error occurred while performing the step: building kernel modules这个错误其实是Makefile环境问题的一个笼统提示,真正的原因要看它上方的详细输出。根据我们的经验,90%的情况是以下三个原因之一:KDIR路径指向错误。如果KDIR不是目标板对应内核的源码目录,而是主机的/usr/src/linux-headers-$(uname -r),那架构完全对不上,编译必然失败。缺少内核模块编译所需的头文件。有的BSP为了减小体积,默认没有安装kernel-dev、kernel-devsrc这些包,Yocto里需要额外加上IMAGE_INSTALL_append kernel-devsrc。交叉编译器版本与内核要求不匹配。比如内核要求GCC 10以上,但系统默认的aarch64工具链还是GCC 9,编译时会报一些奇怪的语法错误。排查建议:先不要急着搜索那个笼统的error字符串,而是把make执行时的完整命令行拿出来,单独手动执行一遍。5.2 网卡固件加载失败:mediatek/wifi_ram_code_mt7961这个错误在Genio平台和MT7921网卡组合下非常常见:mt7921e 0000:04:00.0: direct firmware load for mediatek/wifi_ram_code_mt7961 failed with error -2看到这个提示,说明内核在尝试加载MT7921的RAM code固件,但在指定路径下找不到文件。这跟通常的WiFi网卡固件不是一回事——MT7921除了需要主固件(通常是mt7921_wo.bin等),还需要一个额外的ram code文件,用于辅助WiFi 6功能初始化。解决方法:确认固件文件存在:ls /lib/firmware/mediatek/正常情况下应该有mt7921_wo.bin、WIFI_MT7961_patch_mcu_1_2_hdr.bin、WIFI_RAM_CODE_MT7961_1.bin等文件。如果缺少wifi_ram_code_mt7961,需要从linux-firmware仓库下载对应文件并拷贝到/lib/firmware/mediatek/目录。拷贝完成后执行rmmod mt7921e再modprobe mt7921e重新加载。这个坑特别容易出现在精简版根文件系统上,因为固件文件体积不小,有些镜像构建脚本会默认排除掉。如果你们在自定义Yocto镜像,记得在IMAGE_INSTALL里明确加入linux-firmware-mediatek相关的包。5.3 insmod失败:Unknown symbol 是设备树没配好还有一种高频问题:编译都通过了,insmod时却报一堆Unknown symbol错误。常见原因:该驱动依赖的另一个模块还没有加载。比如摄像头驱动引用了ISP驱动导出的符号,但ISP驱动没insmod。内核配置里关掉了某个需要的子系统。比如CONFIG_GPIO_CDEV没开,GPIO相关的symbol就无法解析。模块之间版本不匹配。内核源码更新后,重新编译了部分模块,但其他第三方模块没有同步重新编译,符号CRC校验失败。遇到这类问题,先nm /path/to/your.ko | grep unknown_symbol看看到底缺什么,再grep CONFIG_XXX /boot/config-$(uname -r)确认内核配置,基本能定位。5.4 设备树节点明明加了却不生效这种情况的典型表现是:ls /sys/devices/platform/soc/下面看不到你添加的节点,或者驱动probe都没有被调用。排查顺序:确认新设备树真的被烧录进去了。别笑,我们真遇到过编译成功但烧错分区的情况。可以cat /proc/device-tree/model看版本信息。检查设备树 overlay 是否被正确应用。如果BSP用的是overlay机制,光改主dts不够,还要确保overlay的加载顺序正确。确认兼容字符串匹配。驱动里of_match_table定义的compatible必须和设备树节点里的compatible完全一致,包括大小写。检查pinctrl配置。如果GPIO引脚被其他模块占用,pinctrl子系统会拒绝你的配置,导致probe失败。5.5 虚拟机环境下性能与设备透传问题如果你是在虚拟机里做这套开发,有几个实际问题要提前心理准备:编译内核模块时,虚拟机磁盘I/O会成为瓶颈。尤其是多个文件并行编译时,机械硬盘的虚拟机可能比物理机慢3到5倍。USB设备透传经常需要额外配置。比如USB摄像头调试时,VMware里需要把USB设备从主机切换到虚拟机,如果你用的是WSL2,USB透传还得借助usbipd-win,配置成本会更高一些。最终调试阶段建议直接跑物理机。内核模块、设备树这类底层调试,在虚拟机上做一半就很容易陷入“是不是虚拟机虚拟化导致的问题”这种假想中,还是直接上真板子最干脆。5.6 常用排查命令速查最后整理一个我们团队日常排查问题最常用的命令速查表,建议收藏:需求命令说明查看内核日志dmesg | tail -50模块加载、驱动报错全靠它查看已加载模块lsmod查看当前内核模块列表查看模块依赖modinfo xxx.ko查看模块的依赖、参数、许可信息查看GPIO状态cat /sys/kernel/debug/gpio确认引脚占用和电平状态查看设备树ls /proc/device-tree/查看实际生效的设备树节点查看中断占用cat /proc/interrupts确认中断是否正确注册测量帧率v4l2-ctl --set-fmt-video配合time命令测采集帧率查看系统资源htop / top演示时给客户看NPU/CPU占用写在最后的一点体会这套基于Linux内核驱动模块的MediaTek AIoT SoC演示平台,从立项到跑通主要功能,前后花了大约三周,其中一半时间都花在各种环境的磨合和固件问题的排查上。我的感受是:做嵌入式Linux项目,别指望一步到位,先跑通最小闭环,再把功能一个个垒上去,是最稳妥的路径。还有个小技巧:在演示系统的根文件系统里,我会把常用的调试工具提前装好并写一个env_check.sh脚本,自动检测内核版本、设备树、模块加载状态、网络状态。每次演示前先跑一遍这个脚本,能提前发现很多“现场翻车”的隐患。客户现场的时间非常宝贵,宁可准备时多花十分钟,也别在现场调试半小时。这个项目的后续扩展方向,我们打算把不同功能模块做成独立的overlay和ko包,配合一个简单的图形化配置界面,让客户能像点菜一样选择自己要的功能组合。希望这篇文章对正在做类似AIoT方案评估、嵌入式Linux驱动开发、或者准备给客户做SoC演示的朋友们有帮助。
返回列表