☰
嵌入式Linux驱动实验笔记:IMX6ULL Platform+Misc设备树驱动(Beep+LED+KEY)
2026/10/11 5:32:23 网站建设 项目流程

前言

本次基于正点原子IMX6ULL开发板,使用platform框架 + miscdevice + 设备树DTS完成字符设备驱动开发。整套代码复用同一套驱动骨架,仅少量修改就可以实现蜂鸣器、LED、按键驱动,掌握一套模板快速开发外设。

环境:Ubuntu交叉编译环境,linux-imx-rel_imx_4.1.15_2.1.0_ga内核,nfs根文件系统,minicom串口。

一、基础知识点

  1. Platform平台驱动:把硬件资源(引脚、中断)放到设备树,驱动通过compatible字符串匹配设备树节点,实现设备和驱动分离。
  2. Misc设备(杂项设备):属于字符设备,主设备号固定为10,自动分配次设备号,不用手动管理cdev、class、device_create,简化字符设备代码。
  3. 设备树DTS:描述硬件引脚信息,编译生成dtb,内核启动解析dtb。⚠老版本dtc不支持//单行注释,只能/* */。
  4. mdev:根文件系统没有udev,insmod加载驱动后,需要mdev -s扫描sysfs,自动在/dev创建设备节点。

二、实验1:蜂鸣器 Beep驱动

1. 设备树节点(添加在根节点/下)

mybeep { compatible = "pute,mybeep"; gpio-beep = <&gpio1 1 0>; status = "okay"; };
  • compatible:匹配关键字,驱动of_match_table必须完全一致
  • gpio-beep:自定义属性,驱动of_get_named_gpio读取GPIO编号

2. 驱动核心逻辑

  • 框架:platform_driver,module_platform_driver注册
  • GPIO方向:输出gpio_direction_output(gpio_num, 默认电平),两个参数:引脚号 + 初始电平
  • 文件操作:.write回调。APP调用write,copy_from_user把用户层数据传到内核,gpio_set_value控制蜂鸣器高低电平。
  • misc注册:misc_register,自动在sys/class生成类。

3. APP测试程序

  • open标志:O_WRONLY只写
  • write发送字符1/0,内核判断,控制蜂鸣器响/关闭

4. 踩坑记录

  1. device_create创建设备节点失败:改用misc简化代码,不用手动class_create
  2. 加载驱动ls /dev看不到节点:nfs文件系统无udev,执行mdev -s生成/dev设备文件
  3. APP打开报错No such file or directory:
    • 检查交叉编译器,必须arm-linux-gnueabihf-gcc,不能x86编译器
    • 确认/dev/beep_misc存在
  4. write函数坑:echo 1 > /dev/xxx传递的是ASCII字符'1',不是数字1,直接判断if(1 == val)不会触发,需要判断字符'1'

三、实验2:LED驱动

改动(在Beep驱动模板基础上修改)

LED和Beep同为输出外设,代码几乎完全一样:

  1. 设备树新增节点,compatible改成"pute,myled",定义led的gpio属性
  2. probe内:gpio_direction_output()设置输出
  3. fops:依旧使用.write回调
  4. APP:O_WRONLY,write发送0/1控制亮灭

核心:LED、BEEP都是输出型外设,共用write模型。

四、实验3:KEY按键驱动【重点,和BEEP区分】

1. 设备树节点(复用板载KEY0原有pinctrl,避免pinctrl重复定义报错)

mykey0 { compatible = "pute,mykey"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_key0>; gpio-mykey0 = <&gpio1 18 1>; status = "okay"; };

⚠坑:不要重复新建pinctrl节点,同一个PAD引脚不能同时在两组pinctrl,会报DTS语法错误。

IMX6ULL pad宏前缀:MX6UL_PAD_,不是MX6ULL_PAD,多写L直接编译dtb报错。

2. 驱动改动(基于beep模板,4处核心修改)

  1. GPIO设置:输出 →输入
    • Beep:gpio_direction_output(gpio, 1);2个参数
    • KEY:gpio_direction_input(gpio);仅1个参数,不需要电平
  2. 文件操作回调替换
    • Beep:.write,用户→内核,写输出
    • KEY:.read,内核→用户,读取引脚状态
  3. 回调函数逻辑替换
    • Beep:copy_from_user+gpio_set_value()
    • KEY:gpio_get_value()读取引脚电平 +copy_to_user把按键状态传给应用层
  4. 匹配字符串、全局变量名、misc设备名配套修改

3. APP程序改动

  • open标志:O_RDONLY只读
  • while循环调用read,读取内核传回的按键状态字符,打印按下/松开

4. 驱动完整改动片段

// 替换write为read static ssize_t key_read(struct file *fp, char __user *puser, size_t n, loff_t *off) { char ch; int val; val = gpio_get_value(keygpiono); if(val == 0) ch = '1'; //按键按下 else ch = '0'; //松开 copy_to_user(puser, &ch, 1); return 1; } static struct file_operations fops = { .owner = THIS_MODULE, .read = key_read, }; //probe里面gpio设置 gpio_direction_input(keygpiono);

五、通用编译&开发板操作流程(三个外设通用)

Ubuntu编译驱动ko

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs #编译设备树 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules #编译驱动模块

Ubuntu交叉编译APP

arm-linux-gnueabihf-gcc xxx_app.c -o xxx_app

开发板minicom操作

insmod xxx_drv.ko mdev -s # 自动生成/dev下设备节点 ls /dev/xxx_misc ./xxx_app rmmod xxx_drv

六、DTS编译踩坑汇总

  1. 老dtc不识别//注释,只能用/* */
  2. 所有符号< > ;必须英文半角,复制粘贴引入中文符号直接报语法错误
  3. pinctrl节点必须写在&iomuxc{}内部
  4. 同一个引脚不能定义两组pinctrl,会冲突
  5. i.MX6ULL引脚宏:MX6UL_PAD_xxxx,不要写成MX6ULL_PAD
  6. 报错行号经常错位:实际错误在上一行少;或者}

七、核心对比总结表

外设GPIO方向驱动回调数据流向APP打开方式
Beep蜂鸣器输出writeAPP → 内核 → 硬件O_WRONLY
LED灯输出writeAPP → 内核 → 硬件O_WRONLY
KEY按键输入read硬件 → 内核 → APPO_RDONLY

八、学习总结

  1. platform + misc 是嵌入式Linux简单字符设备的万能模板,输出外设用write,输入外设用read。
  2. 设备树只描述硬件资源,驱动通过compatible匹配,实现软硬分离。
  3. nfs根文件系统缺少udev,必须手动执行mdev -s才能在/dev看到设备节点。
  4. 区分copy_from_user和copy_to_user:
    • copy_from_user:用户空间 → 内核空间(写操作)
    • copy_to_user:内核空间 → 用户空间(读操作)
  5. GPIO API区分:
    • gpio_direction_output(gpio,val):输出,两个参数
    • gpio_direction_input(gpio):输入,仅一个参数

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询