Beep Platform 驱动开发(miscdevice 框架) 文章封面

Beep Platform 驱动开发(miscdevice 框架)

硬件:iMX6ULL ALPHA 开发板,蜂鸣器连接 GPIO5_IO01,低电平触发
内核:Linux 4.14
目标:以 platform driver + miscdevice 框架实现蜂鸣器字符设备驱动


一、硬件说明

参数值
引脚GPIO5_IO01
触发电平低电平有效(GPIO 低 → 蜂鸣器响)
驱动方式NPN 三极管,GPIO 控制基极

二、整体架构与初始化流程

驱动初始化流程图

flowchart TD
    A["DTS 节点<br/>compatible = alientek,beep"] --> B[内核创建 platform_device]
    B --> C{"of_device_id 匹配<br/>beep_of_match"}
    C -->|匹配成功| D[调用 beep_probe]
    C -->|无匹配| Z[跳过]

    D --> E["devm_kzalloc<br/>分配 beep_dev 结构体"]
    E --> F["devm_gpiod_get<br/>申请 GPIO5_IO01<br/>初始逻辑低(蜂鸣器关)"]
    F --> G["配置 miscdevice<br/>minor=MISC_DYNAMIC_MINOR<br/>name=beep, fops=&beep_fops"]
    G --> H["misc_register<br/>主设备号 10,动态次设备号"]
    H --> I["/dev/beep 节点自动创建"]
    I --> J["platform_set_drvdata<br/>保存 beep_dev 指针"]

    style A fill:#dae8fc
    style I fill:#d5e8d4
    style Z fill:#f8cecc

三、miscdevice 框架介绍

Linux 内核提供了 miscdevice(杂项设备)机制,是对字符设备注册流程的高度封装:

对比项手动注册(LED 驱动方式)miscdevice
主设备号自行申请固定为 10
注册流程alloc_chrdev_region + cdev_add + class_create + device_createmisc_register 一行
/dev 节点自动创建自动创建
代码量多少
适用场景需要自定义主设备号、多设备实例简单单一设备

蜂鸣器这类功能简单、只有一个实例的设备,用 miscdevice 更合适。


四、数据结构

struct beep_dev {
    struct gpio_desc  *gpiod;   /* GPIO 描述符,devm_ 自动释放 */
    struct miscdevice  miscdev; /* misc 设备,自动创建 /dev/beep */
};

相比 LED 驱动,结构体极简:

  • 不需要 cdev、class、device、dev_id(miscdevice 内部管理)
  • 不需要手动映射寄存器(改用 gpiod API 操作 GPIO)

数据结构类图

classDiagram
    class beep_dev {
        +gpio_desc  *gpiod
        +miscdevice  miscdev
    }
    class miscdevice {
        +int minor
        +const char *name
        +file_operations *fops
        +device *this_device
    }
    class file_operations {
        +open()
        +write()
        +release()
    }
    class gpio_desc {
        <<kernel内部>>
        +GPIO5_IO01
        +极性: ACTIVE_LOW
    }
    class file {
        +void *private_data
    }

    beep_dev *-- miscdevice : 内嵌
    beep_dev *-- gpio_desc : 持有引用(devm_)
    miscdevice --> file_operations : fops 指向
    file ..> miscdevice : ①misc框架设 private_data=miscdev
    file ..> beep_dev  : ②beep_open改 private_data=beep_dev

五、gpiod API 与 gpio API 对比

本驱动使用新版 gpiod(GPIO descriptor)API,相比旧版 gpio API 有明显优势:

对比项旧版 gpio API新版 gpiod API
获取 GPIOof_get_named_gpiodevm_gpiod_get
设置方向gpio_direction_output在 devm_gpiod_get 时指定
设置电平gpio_set_value(物理电平)gpiod_set_value(逻辑电平)
极性处理手动判断高低自动根据 DTS GPIO_ACTIVE_LOW/HIGH 转换
资源释放手动 gpio_freedevm_ 自动释放

逻辑电平的意义:DTS 中声明 GPIO_ACTIVE_LOW,则:

  • gpiod_set_value(gpiod, 1) → 逻辑高(有效)→ 物理低电平 → 蜂鸣器响
  • gpiod_set_value(gpiod, 0) → 逻辑低(无效)→ 物理高电平 → 蜂鸣器停

代码无需关心硬件极性,可读性更好。


六、DTS 配置

/* imxplore.dts */
/ {
    beep {
        compatible = "alientek,beep";
        pinctrl-names = "default";
        pinctrl-0 = &lt;&amp;beep_pins&gt;;
        beep-gpio = &lt;&amp;gpio5 1 GPIO_ACTIVE_LOW&gt;;
        status = "okay";
    };
};

&amp;iomuxc {
    beep_pins: beep_pins {
        fsl,pins = &lt;
            MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x10b0
        &gt;;
    };
};

与 LED 驱动不同,beep 可以保留 pinctrl 配置,因为 GPIO5_IO01 没有被其他驱动占用。


七、关键代码说明

open/write 调用时序图

sequenceDiagram
    participant App  as 用户态 App
    participant VFS  as VFS 层
    participant Misc as misc 框架(misc_open)
    participant Drv  as beep 驱动(beep_open/write)
    participant GPIO as gpiod 子系统

    Note over App,GPIO: 打开设备
    App  ->> VFS  : open("/dev/beep", O_RDWR)
    VFS  ->> Misc : misc_open(inode, filp)
    Misc ->> Misc : filp->private_data = &miscdev
    Misc ->> Drv  : beep_fops.open(inode, filp)
    Drv  ->> Drv  : miscdev = filp->private_data
    Drv  ->> Drv  : dev = container_of(miscdev, beep_dev, miscdev)
    Drv  ->> Drv  : filp->private_data = dev
    Drv  -->> App : fd 返回

    Note over App,GPIO: 控制蜂鸣器(写 "1" 开响)
    App  ->> VFS  : write(fd, "1", 1)
    VFS  ->> Drv  : beep_fops.write(filp, buf, count, ppos)
    Drv  ->> Drv  : dev = filp->private_data
    Drv  ->> Drv  : copy_from_user → val = 1
    Drv  ->> GPIO : gpiod_set_value(dev->gpiod, 1)
    GPIO ->> GPIO : 逻辑 1 + ACTIVE_LOW → 物理低电平
    GPIO -->> App : 蜂鸣器响

    Note over App,GPIO: 关闭蜂鸣器(写 "0")
    App  ->> VFS  : write(fd, "0", 1)
    VFS  ->> Drv  : beep_fops.write(...)
    Drv  ->> GPIO : gpiod_set_value(dev->gpiod, 0)
    GPIO ->> GPIO : 逻辑 0 + ACTIVE_LOW → 物理高电平
    GPIO -->> App : 蜂鸣器停

7.1 open:通过 miscdevice 获取 beep_dev

static int beep_open(struct inode *inode, struct file *filp)
{
    /*
     * misc 框架在 open 时将 filp-&gt;private_data 设置为 miscdevice 指针,
     * 用 container_of 反推出外层 beep_dev 结构体。
     * 与 LED 驱动的 container_of(inode-&gt;i_cdev, ...) 思路相同,入口不同。
     */
    struct miscdevice *miscdev = filp-&gt;private_data;
    struct beep_dev *dev = container_of(miscdev, struct beep_dev, miscdev);
    filp-&gt;private_data = dev;
    return 0;
}

7.2 probe:devm_gpiod_get 一步完成申请+方向设置

static int beep_probe(struct platform_device *pdev)
{
    struct beep_dev *dev;

    dev = devm_kzalloc(&amp;pdev-&gt;dev, sizeof(*dev), GFP_KERNEL);

    /*
     * "beep" 对应 DTS 属性 "beep-gpio"
     * GPIOD_OUT_LOW = 初始逻辑低 = 物理高 = 蜂鸣器关闭
     */
    dev-&gt;gpiod = devm_gpiod_get(&amp;pdev-&gt;dev, "beep", GPIOD_OUT_LOW);

    dev-&gt;miscdev.minor = MISC_DYNAMIC_MINOR;
    dev-&gt;miscdev.name  = "beep";
    dev-&gt;miscdev.fops  = &amp;beep_fops;

    misc_register(&amp;dev-&gt;miscdev);
    platform_set_drvdata(pdev, dev);
    return 0;
}


八、用户态测试程序

测试程序位于 app/beep_test/beep_test.c:

beep_test on            # 持续响
beep_test off           # 关闭
beep_test beep          # 响一声(默认 200ms)
beep_test beep 500      # 响一声 500ms
beep_test beep 100 3    # 响三声,每声 100ms,间隔 100ms


九、关于 PWM 音量控制

iMX6ULL ALPHA 板使用有源蜂鸣器,内置振荡器,通电即响,音调固定。

  • PWM 占空比可影响平均电压,理论上能微调音量,但效果不明显
  • GPIO5_IO01 不是硬件 PWM 引脚,如需 PWM 控制需改接线
  • 作为报警器使用时,on/off 控制完全够用

关联笔记

延伸阅读

蜂鸣器驱动同时涉及 Platform 框架和 PWM 能力,可按这两条线继续深入。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部