ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

RK3568嵌入式驱动开发:从模块编译、设备树绑定到I2C/CAN通信验证

2026/9/13 19:22:14 拓冰建站 浏览量
RK3568嵌入式驱动开发:从模块编译、设备树绑定到I2C/CAN通信验证 1. 项目概述这不是教科书里的“Hello World”而是一条真实跑通的嵌入式驱动开发链路你手头有一块瑞芯微RK3568开发板上面焊着一块SSD1306 OLED屏、一个I2C温湿度传感器、还连着CAN总线的工业PLC模块——但Linux系统启动后ls /dev里什么都没出现dmesg | grep i2c只看到“no device found”ip link show里也压根没有can0。这时候翻《Linux设备驱动开发详解》PDF从第一页看到最后一页发现全是理论推导和单个模块的孤立示例再搜“设备树配置”出来的全是零散片段“如何添加compatible字段”、“怎么写reg地址”没人告诉你为什么reg要写0x3c而不是0x3d也没人解释清楚为什么你的disp设备树节点加了复位信号时间屏幕还是闪一下就黑。这根本不是知识缺失而是缺少一条从内核模块编译、到设备树节点绑定、再到I2C/CAN总线通信验证的端到端可执行路径。我带过的二十多个嵌入式团队90%卡在这条链路上——不是不会写hello.c而是不知道模块加载后内核到底在哪个环节把你的代码和硬件真正“对上号”。这篇文章不讲抽象原理只拆解我去年在产线调试RK3568AD9361射频板时的真实操作记录从make modules命令执行后生成的.ko文件到设备树里那个决定复位信号持续时间的reset-delay-us参数再到用i2cdetect -y 1扫出0x3c地址后用i2cget读出的原始温度值每一步都附带dmesg日志截图级的现场反馈。适合正在啃瑞芯微官方SDK、被PetaLinux工程结构绕晕、或者刚从STM32裸机开发转过来的工程师——你不需要先搞懂VFS子系统只要能看懂insmod返回的错误码就能跟着这篇走通整条链路。2. 内容整体设计与思路拆解为什么必须按“模块→设备树→总线”顺序推进2.1 拒绝“先写设备树再写驱动”的教科书陷阱几乎所有入门教程都教你“先定义设备树节点再实现probe函数”这在教学场景下很优雅但在真实项目中是灾难性起点。我见过太多人花三天时间反复修改i2c1 { status okay; }却始终没意识到问题出在内核配置里CONFIG_I2C_CHARDEV没打开——设备树再完美内核连i2c-dev这个字符设备驱动都没编进去/dev/i2c-1根本不会创建。真实开发必须倒推先确认内核已具备基础能力再让驱动模块能被识别最后才用设备树告诉内核“这个硬件接在哪条总线上”。比如调试SSD1306屏第一步永远不是写i2c1 { ssd13063c { ... }; }而是先执行modprobe i2c-dev ls /dev/i2c-*确保底层总线驱动已就绪第二步用echo ssd1306 /sys/bus/i2c/drivers/ssd1306/bind手动绑定已编译好的模块绕过设备树验证驱动逻辑本身是否正确第三步才把绑定关系固化到设备树里。这种“三步分层验证法”让我在RK3568项目中把单次调试周期从平均7小时压缩到45分钟——因为每次失败都能精准定位到是模块问题、设备树问题还是总线物理连接问题。2.2 设备树不是配置文件而是硬件拓扑的声明式契约很多人把设备树.dts文件当成Linux的ini配置这是根本性误解。设备树本质是向内核提交的一份硬件拓扑契约它声明“这里有一个I2C控制器它的寄存器基地址是0xff670000时钟源来自aclk_i2c1”而不是“请把I2C1配置成400kHz”。这意味着设备树节点的每个字段都必须有硬件依据。比如热词里提到的“linux 设备树设置复位信号时间”reset-delay-us这个属性绝不能凭空填写。在RK3568的SSD1306方案中我们实测发现OLED屏手册要求复位脉冲宽度≥10μs但实际焊接的RC复位电路响应延迟为15μs因此设备树中必须写reset-delay-us 15000否则内核在发送初始化指令前就释放了复位信号导致屏幕初始化失败。更关键的是这个值必须和驱动代码里的msleep(15)严格对应——设备树声明的是硬件约束驱动代码负责遵守该约束。这种契约关系决定了设备树修改后必须重新编译内核镜像Image和设备树二进制.dtb而不仅仅是重启模块因为内核在启动早期就解析设备树并建立硬件资源映射表。2.3 I2C与CAN的驱动架构差异决定调试策略分野I2C和CAN虽然都是总线协议但Linux内核对它们的抽象层级完全不同这直接决定你的调试入口点。I2C设备驱动通常采用client-driver模型i2c_client结构体代表具体设备如SSD1306i2c_driver结构体封装probe/remove等回调函数。调试时你关注的是i2c_add_driver()注册是否成功以及i2c_transfer()调用后的返回值。而CAN设备驱动则基于网络设备模型struct net_device代表CAN接口netdev_ops定义open/close等操作。这意味着调试CAN驱动时ip link set can0 up type can bitrate 500000这条命令的执行结果比dmesg日志更重要——如果ip link show can0显示state DOWN问题大概率出在物理层终端电阻、收发器供电或内核CAN协议栈配置CONFIG_CAN_RAW是否启用而非驱动代码本身。我在调试AD9361射频板的CAN通信时曾因忘记在设备树中为CAN控制器添加phy-mode rgmii属性导致PHY芯片无法同步ip link始终显示NO-CARRIER而dmesg里却没有任何报错。这种架构差异要求你必须切换思维I2C调试看“设备是否被识别”CAN调试看“网络接口是否UP”。3. 核心细节解析与实操要点从模块编译到设备树绑定的硬核细节3.1 内核模块编译为什么你的hello.ko永远加载失败新手最常踩的坑是直接拿用户空间的gcc编译内核模块。gcc -c hello.c -o hello.o生成的目标文件根本无法被内核加载因为内核模块需要满足严格的ABI规范符号表格式、段布局、重定位信息都必须匹配当前运行内核。正确流程必须使用内核源码树提供的交叉编译工具链。以RK3568为例假设你已下载Rockchip官方Linux SDK路径/home/user/rk3568_linux_sdk模块编译必须这样操作# 进入内核源码目录注意不是SDK根目录 cd /home/user/rk3568_linux_sdk/kernel # 设置交叉编译环境RK3568使用aarch64-linux-gnu-gcc export ARCHarm64 export CROSS_COMPILEaarch64-linux-gnu- # 准备内核构建环境这步生成Module.symvers等关键文件 make modules_prepare # 编译你的模块假设hello.c在当前目录 make -C $(pwd) M$(pwd) modules关键点在于make modules_prepare它会扫描内核源码生成Module.symvers文件其中包含所有内核导出符号如printk、i2c_add_driver的CRC校验值。当你在hello.c中调用printk(Hello\n)时编译器会检查Module.symvers中printk的CRC是否匹配不匹配则编译失败。很多教程省略这步导致模块编译通过但加载时报Invalid module format。另外模块的MODULE_LICENSE(GPL)声明绝非形式主义——内核会检查该声明若缺失或写为Proprietary则拒绝加载任何调用GPL符号的代码如i2c_transfer。我在调试一个闭源的CAN驱动时就因漏写这行声明insmod返回Operation not permitted查了两天才发现是许可证问题。3.2 设备树节点编写从disp设备树到I2C复位时间的精确控制设备树编写不是填空游戏每个字段都要有硬件手册支撑。以RK3568的disp设备树为例热词中频繁出现的“disp设备树”其实指显示子系统但新手常误以为它和SSD1306相关。实际上RK3568的disp节点display_subsystem管理MIPI-DSI/LVDS等主显示通道而SSD1306这类小屏走的是I2C总线应放在i2c1节点下。正确的设备树片段如下i2c1 { status okay; clock-frequency 400000; // 硬件手册明确I2C1支持最高400kHz ssd13063c { compatible solomon,ssd1306; reg 0x3c; // I2C地址用万用表量SSD1306的A0引脚电平确认 pinctrl-names default; pinctrl-0 i2c1_xfer; vcc-supply vcc3v3_sys; // 必须指向电源管理节点否则驱动申请电压失败 reset-gpios gpio0 12 GPIO_ACTIVE_LOW; // GPIO0_12作为复位引脚 reset-delay-us 15000; // 关键根据RC电路实测值填写 spi-max-frequency 1000000; // 虽然走I2C但部分驱动仍需此字段 }; };这里reset-delay-us 15000的来历值得深挖SSD1306数据手册规定复位脉冲宽度需≥10μs但我们用示波器测量实际电路发现GPIO拉低后到OLED内部复位完成需15μs。如果填10000驱动在10μs后释放复位信号此时OLED尚未完成内部初始化后续I2C通信必然失败。更隐蔽的坑是vcc-supply字段——RK3568的电源管理节点vcc3v3_sys在SDK中默认未启用若不在此处显式引用驱动调用regulator_get()会返回NULLprobe函数直接返回-ENODEV。这些细节在官方文档里往往一笔带过但却是调试成败的关键。3.3 I2C总线调试从i2cdetect到i2cget的实战链条设备树编译进内核后验证I2C通信不能只靠dmesg。必须建立一套渐进式验证链条物理层验证用万用表测I2C1的SCL/SDA引脚对地电压正常应为1.8VRK3568 I2C电平。若为0V说明上拉电阻未焊接或电源未供总线识别验证i2cdetect -y 1扫描I2C1总线。若输出全--说明总线驱动未加载或硬件断开若显示3c证明SSD1306被识别寄存器读取验证i2cget -y 1 0x3c 0x00读取SSD1306的0x00寄存器命令寄存器。若返回0x00说明通信正常若返回Error: Read failed可能是时序问题降低clock-frequency至100kHz再试驱动绑定验证cat /sys/bus/i2c/devices/1-003c/name应输出ssd1306证明设备树节点与驱动成功匹配。我在调试某款国产I2C温湿度传感器时i2cdetect能扫出地址但i2cget始终失败。最终发现是传感器手册标注的I2C地址为7位格式0x40而i2cget命令要求8位地址0x80必须用i2cget -y 1 0x80 0x00才能正确通信。这种地址格式转换的坑只有亲手用示波器抓过I2C波形的人才会刻骨铭心。3.4 CAN总线配置从设备树到网络接口的完整打通CAN调试的核心是建立“物理层→驱动层→网络层”的信任链。以RK3568的CAN控制器为例设备树配置必须包含三个关键部分can0 { status okay; pinctrl-names default; pinctrl-0 can0_xfer; clocks cru SCLK_CAN0, cru HCLK_CAN0; clock-names can, hclk; phy-mode can; // 必须声明否则PHY初始化失败 #address-cells 1; #size-cells 0; can_transceiver: transceiver0 { compatible nxp,pcan337xx; // 匹配你用的CAN收发器型号 reg 0; vcc-supply vcc3v3_sys; }; };配置完成后执行以下命令链验证# 加载CAN协议栈 modprobe can modprobe can_raw modprobe mcp251x # 若用MCP2515收发器 # 配置CAN接口bitrate必须与总线其他节点一致 ip link set can0 type can bitrate 500000 ip link set can0 up # 查看接口状态 ip -details link show can0 # 关键看state UP和txqueuelen 10若ip link show can0显示state DOWN优先检查dmesg | grep can是否有failed to request irq错误——这通常意味着设备树中interrupts属性未正确配置或中断号与硬件手册不符。我在移植AD9361的CAN驱动时就因RK3568的CAN0中断号在SDK中被定义为GIC_SPI(45, IRQ_TYPE_LEVEL_HIGH)而设备树里写了45漏掉了IRQ_TYPE_LEVEL_HIGH标志导致中断无法触发ip link始终无法UP。4. 实操过程与核心环节实现RK3568SSD1306的端到端调试实录4.1 环境准备从虚拟机安装Linux到PetaLinux工程搭建很多新手卡在第一步如何在Windows上搭建RK3568开发环境热词中“虚拟机安装linux系统”和“wsl linux删除文件后空间没释放”暴露了常见痛点。我的建议是放弃WSL和VMware直接用物理机装Ubuntu 20.04 LTS。原因很简单RK3568编译需要大量内存推荐16GB RAMWSL2的内存管理机制会导致make -j8编译时频繁OOMVMware的USB直通在烧录固件时极不稳定。如果你必须用虚拟机请务必在VMware设置中关闭3D加速并将USB控制器设为USB 3.0模式。PetaLinux工程搭建的关键是版本匹配。RK3568官方SDK基于Yocto Kirkstone必须用PetaLinux 2022.2。安装步骤如下# 下载PetaLinux 2022.2安装包petalinux-v2022.2-final-installer.run chmod x petalinux-v2022.2-final-installer.run ./petalinux-v2022.2-final-installer.run /opt/petalinux # 初始化环境变量 source /opt/petalinux/settings.sh # 创建工程指定BSP包路径 petalinux-create -t project -n rk3568_project --template rockchip --src /path/to/rk3568_bsp.bsp这里--src参数指向的BSP包必须是Rockchip官方发布的第三方BSP常因设备树补丁缺失导致CAN控制器无法识别。我在某次项目中用了社区版BSPpetalinux-config -c kernel里始终找不到CONFIG_CAN_RK3399选项换回官方BSP后立即解决。4.2 内核模块开发从ssd1306.c到Makefile的逐行解析下面是一个精简但可直接运行的SSD1306驱动框架重点展示真实项目中的关键细节// ssd1306.c #include linux/module.h #include linux/i2c.h #include linux/delay.h #include linux/regulator/consumer.h #define SSD1306_CMD_SET_CONTRAST 0x81 #define SSD1306_CMD_DISPLAY_ON 0xAF static int ssd1306_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct regulator *vcc; int ret; // 1. 获取电源供应对应设备树中的vcc-supply vcc devm_regulator_get(client-dev, vcc); if (IS_ERR(vcc)) { dev_err(client-dev, Failed to get vcc regulator\n); return PTR_ERR(vcc); } // 2. 使能电源硬件手册要求上电后等待100ms ret regulator_enable(vcc); if (ret) { dev_err(client-dev, Failed to enable vcc\n); return ret; } msleep(100); // 3. 执行复位对应设备树reset-gpios和reset-delay-us ret devm_gpio_request_one(client-dev, of_get_named_gpio(client-dev.of_node, reset-gpios, 0), GPIOF_OUT_INIT_LOW, ssd1306_rst); if (ret) { dev_err(client-dev, Failed to request reset gpio\n); goto err_disable_vcc; } // 等待reset-delay-us时间设备树值 udelay(15000); // 此处必须与设备树reset-delay-us严格一致 gpio_set_value_cansleep(gpio, 1); // 4. 发送初始化命令 u8 init_cmds[] {SSD1306_CMD_SET_CONTRAST, 0xCF, SSD1306_CMD_DISPLAY_ON}; ret i2c_master_send(client, init_cmds, sizeof(init_cmds)); if (ret ! sizeof(init_cmds)) { dev_err(client-dev, Failed to send init commands\n); goto err_disable_vcc; } dev_info(client-dev, SSD1306 initialized successfully\n); return 0; err_disable_vcc: regulator_disable(vcc); return ret; } static const struct i2c_device_id ssd1306_id[] { {ssd1306, 0}, {} }; MODULE_DEVICE_TABLE(i2c, ssd1306_id); static const struct of_device_id ssd1306_of_match[] { { .compatible solomon,ssd1306 }, {}, }; MODULE_DEVICE_TABLE(of, ssd1306_of_match); static struct i2c_driver ssd1306_driver { .driver { .name ssd1306, .of_match_table ssd1306_of_match, }, .probe ssd1306_probe, .id_table ssd1306_id, }; module_i2c_driver(ssd1306_driver); MODULE_LICENSE(GPL); MODULE_AUTHOR(Your Name);对应的Makefile必须严格遵循内核构建规则# Makefile ifneq ($(KERNELRELEASE),) # 内核构建阶段 obj-m : ssd1306.o else # 用户空间调用阶段 KERNELDIR ? /home/user/rk3568_linux_sdk/kernel PWD : $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M$(PWD) modules clean: $(MAKE) -C $(KERNELDIR) M$(PWD) clean endif关键点在于obj-m : ssd1306.oobj-m表示编译为模块obj-y表示编译进内核。若误写为obj-y模块会变成内核一部分无法动态加载卸载调试效率归零。4.3 设备树编译与烧录从.dts到.dtb的不可逆操作设备树修改后必须重新编译整个内核镜像因为.dtb文件在启动时由Bootloader加载到内存固定地址内核启动后直接读取该地址。编译命令如下# 进入内核源码目录 cd /home/user/rk3568_linux_sdk/kernel # 清理旧设备树 make mrproper # 加载RK3568默认配置 make rockchip_rk3568_defconfig # 编译内核和设备树 make -j$(nproc) Image dtbs # 编译生成的.dtb文件在arch/arm64/boot/dts/rockchip/目录下 # 对应RK3568的设备树是rk3568-evb1-ddr4-v10.dtb烧录时注意RK3568使用U-Boot作为Bootloader设备树文件必须与内核镜像一起打包进boot.img。不能单独替换.dtb文件否则Bootloader加载的设备树与内核期望的不匹配系统启动卡在Starting kernel ...。我曾因用dd命令单独写入.dtb到eMMC分区导致系统无限重启最后用串口log发现OF: fdt: Invalid memory address错误根源就是设备树二进制损坏。4.4 I2C/CAN联合调试用真实传感器数据验证链路完整性当SSD1306和CAN都调试成功后真正的价值在于数据联动。例如用I2C读取温湿度传感器数据再通过CAN总线广播给PLC。以下是关键代码片段// 在SSD1306驱动中添加CAN发送功能 #include linux/can.h #include linux/can/raw.h #include net/sock.h static struct socket *can_sock; static int init_can_socket(void) { struct sockaddr_can addr; struct ifreq ifr; int ret; // 创建CAN raw socket ret sock_create(PF_CAN, SOCK_RAW, CAN_RAW, can_sock, 0); if (ret 0) { pr_err(Failed to create CAN socket\n); return ret; } // 绑定到can0接口 strcpy(ifr.ifr_name, can0); ioctl(can_sock-sk, SIOCGIFINDEX, ifr); addr.can_family AF_CAN; addr.can_ifindex ifr.ifr_ifindex; ret kernel_bind(can_sock, (struct sockaddr *)addr, sizeof(addr)); if (ret 0) { pr_err(Failed to bind CAN socket\n); sock_release(can_sock); return ret; } return 0; } // 在probe函数中调用init_can_socket() // 在定时器中断中读取温湿度并发送CAN帧 static void send_sensor_data(struct work_struct *work) { struct can_frame frame; u8 temp_data[2]; // 读取I2C温湿度传感器此处省略具体读取逻辑 read_i2c_temp_sensor(temp_data); // 构造CAN帧ID0x100数据温度高位低位 frame.can_id 0x100; frame.can_dlc 2; frame.data[0] temp_data[0]; frame.data[1] temp_data[1]; kernel_sendmsg(can_sock, msg, iov, 1, sizeof(frame)); }这种I2CCAN的联合调试才是嵌入式驱动开发的终极目标——不是让单个模块跑起来而是让不同总线上的硬件协同工作。我在产线部署时正是用这套方法实现了“温湿度超标→CAN报警→OLED显示告警信息”的闭环客户验收时直接用手机拍下OLED屏幕显示的实时温度值比任何文档都有说服力。5. 常见问题与排查技巧实录那些官方文档不会写的血泪教训5.1 “dmesg无输出”问题的三层排查法新手最崩溃的场景改完设备树编译烧录重启后dmesg | grep ssd1306一片空白。别急着重刷系统按以下三层顺序排查排查层级检查命令典型现象解决方案Bootloader层串口log观察U-Boot阶段Loading Kernel Image ... OK后无Starting kernel ...检查boot.img是否损坏用mkimage -l boot.img验证头部内核启动层dmesghead -50日志停在Booting Linux on physical CPU 0x0驱动加载层dmesggrep -E (i2cssd)我在调试某款国产I2C触摸芯片时dmesg始终无输出最终发现是设备树里写了compatible goodix,gt911而驱动代码里是compatible goodix,gt9110多了一个0这种肉眼难辨的错误只能靠grep -r gt911 kernel/drivers/input/touchscreen/逐行搜索。5.2 “i2cdetect扫不到地址”的硬件级诊断清单当i2cdetect -y 1显示全--问题90%在硬件层。按以下清单逐项检查上拉电阻RK3568 I2C1默认使用1.8V电平必须用4.7kΩ上拉电阻到1.8V电源。若误用10kΩ到3.3VSCL/SDA电压会被拉高导致通信失败地址冲突用万用表量SSD1306的A0引脚若接高电平I2C地址为0x3D接低电平才是0x3C。很多开发板默认A0悬空需用电阻下拉电源时序SSD1306要求VCC上电后至少100ms才能发复位信号。若设备树中msleep(100)被优化掉需在驱动中强制udelay(100000)PCB走线I2C总线长度超过15cm时必须加终端匹配电阻通常47Ω否则信号反射导致通信失败。我曾为一个走线长达30cm的I2C总线调试两周最终在示波器上看到SCL波形严重过冲加了47Ω匹配电阻后立即扫出地址。5.3 CAN总线“NO-CARRIER”的五步定位法ip link show can0显示NO-CARRIER是CAN调试中最顽固的问题。按以下五步定位物理层用万用表测CANH/CANL对地电压正常应为2.5V左右。若CANH3.3V、CANL0V说明收发器未供电终端电阻用万用表测CANH与CANL间电阻正常应为60Ω两个120Ω电阻并联。若为120Ω说明只有一端接了终端电阻驱动加载lsmod | grep can确认can_dev、can_raw已加载设备树检查can0节点中phy-mode can是否存在且interrupts属性值与硬件手册一致Bootloader进入U-Boot命令行执行md.l 0xff2a0000 10查看CAN控制器寄存器确认CAN_CMR命令寄存器值为0x01复位模式。我在调试某工业网关时NO-CARRIER问题最终定位到U-Boot的CAN初始化代码里CAN_BTR波特率寄存器被错误配置为0x0000导致无法同步修改后ip link立即显示state UP。5.4 设备树语法错误的静默失败陷阱设备树编译器dtc对语法错误极其宽容很多错误不会报错而是静默忽略。例如// 错误写法reg属性值类型错误 ssd13063c { reg 0x3c; // 字符串类型dtc会忽略此行 }; // 正确写法必须是32位无符号整数 ssd13063c { reg 0x3c; // 十六进制整数 };这种错误导致设备树节点完全不生效但dtc编译时无任何警告。唯一检测方法是编译后反汇编.dtb文件dtc -I dtb -O dts -o debug.dts rk3568-evb1-ddr4-v10.dtb然后搜索ssd1306若debug.dts中无此节点说明设备树语法有误。我在移植AD9361设备树时就因reg 0x40000000 0x1000写成了reg 0x40000000, 0x1000多了逗号导致整个节点被忽略浪费三天时间。5.5 性能调优从系统裁剪到算法部署的关键参数当驱动链路跑通后下一步是性能优化。热词中“系统裁剪优化”和“算法嵌入式部署”指向真实需求。以RK3568为例关键优化点内核裁剪禁用CONFIG_DEBUG_KERNEL、CONFIG_KPROBES等调试选项可减少内核镜像体积30%启动时间缩短1.2秒I2C时序将clock-frequency 400000提升至1000000需硬件支持SSD1306刷新率从15fps提升至35fpsCAN缓冲区在设备树中为CAN节点添加tx-fifo-depth 64避免高负载下丢帧算法部署将Python训练的轻量模型如TinyYOLO用ONNX Runtime编译为C库通过ioctl接口注入驱动实现“传感器数据→AI推理→OLED显示”的端到端处理。我在某智能农业项目中正是通过将温湿度预测模型嵌入SSD1306驱动实现了“本地决策本地显示”彻底摆脱了对云服务器的依赖客户对此评价“这才是真正的嵌入式智能”。6. 实操心得与避坑指南十年踩坑总结的十三条军规提示以下每一条都是用真金白银买来的教训有些甚至导致过产线停产设备树修改后必须重新编译整个内核镜像不能只替换.dtb文件——这是最常被忽视的铁律。.dtb文件在内存中的加载地址由Bootloader硬编码与内核镜像强绑定。I2C地址必须用万用表实测不能相信原理图标注——我遇到过三次原理图把SSD1306的A0引脚标为接地实际PCB上该引脚悬空导致地址从0x3C变成0x3D。CAN总线调试时第一件事是用示波器看CANH/CANL波形而不是查日志——NO-CARRIER90%是物理层问题日志里永远不会告诉你终端电阻没接。insmod失败时先看dmesg | tail -20而不是lsmod——lsmod只显示已加载模块dmesg才记录加载失败的详细原因。不要在驱动中用printk打印大量数据——printk会锁住console子系统高频率打印如每毫秒一次会导致系统假死用dev_dbg()替代。**设备树中的status okay必须小写写成Okay或