ARM Linux 驱动开发篇---基于 pinctrl+GPIO 子系统的蜂鸣器驱动开发(设备树版)--- Ubuntu20.04

🎬 渡水无言个人主页渡水无言

专栏传送门 : 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》

专栏传送门 : 《freertos专栏》 《STM32 HAL库专栏

⭐️流水不争先,争的是滔滔不绝

📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生

| 省级优秀毕业生获得者 | csdn新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生

在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连

目录

前言

一、硬件原理分析

二、蜂鸣器驱动核心原理

三、实验程序编写

3.1、设备树修改总流程

3.1、修改设备树文件

[3.1.1、添加 pinctrl 节点(配置 PIN 复用)](#3.1.1、添加 pinctrl 节点(配置 PIN 复用))

3.1.2、添加蜂鸣器设备节点

[3.1.3、检查 PIN 是否被其他外设占用](#3.1.3、检查 PIN 是否被其他外设占用)

3.2、编译设备树

3.3、蜂鸣器驱动程序编写

3.3.1、驱动程序执行流程图

3.3.2、驱动代码

3.3.3、分析驱动代码

[设备结构体设计(对应代码第 33--42 行)](#设备结构体设计(对应代码第 33–42 行))

[私有数据传递(对应代码第 54 行)](#私有数据传递(对应代码第 54 行))

[write 函数(第 66--87 行,核心控制逻辑)](#write 函数(第 66–87 行,核心控制逻辑))

[驱动入口函数 beep_init(第 112--170 行,驱动初始化)](#驱动入口函数 beep_init(第 112–170 行,驱动初始化))

[驱动出口函数 beep_exit(第 177--186 行,资源释放)](#驱动出口函数 beep_exit(第 177–186 行,资源释放))

3.4、测试APP

3.4.1测试APP执行流程图

3.4.2、程序编写

四、运行测试

[4.1、编译驱动程序和测试 APP](#4.1、编译驱动程序和测试 APP)

6.2、运行测试

总结


前言

上一章我们通过pinctrlGPIO子系统完成了 LED 灯驱动开发,而蜂鸣器驱动的核心逻辑和 LED 完全一致 ------ 都是控制 GPIO 输出高低电平。本章将以蜂鸣器为例,巩固 pinctrl+GPIO 子系统的使用方法,同时熟悉不同 GPIO 引脚(SNVS_TAMPER1)的设备树配置和驱动适配。


一、硬件原理分析

  • 该电路使用 PNP 型三极管 8550 驱动蜂鸣器,由SNVS_TAMPER1引脚控制。
  • SNVS_TAMPER1输出低电平时,三极管导通,蜂鸣器通电鸣叫。
  • SNVS_TAMPER1输出高电平时,三极管截止,蜂鸣器断电停止鸣叫

实验硬件:I.MX6ULL-ALPHA 开发板;

内核版本:4.1.15;

硬件原理如上图。

二、蜂鸣器驱动核心原理

蜂鸣器驱动的开发流程和 LED 完全一致,核心三步:

1、设备树中添加 SNVS_TAMPER1 引脚的 pinctrl 复用配置;

2、设备树中创建蜂鸣器节点,绑定 GPIO 信息;

3、基于字符设备驱动框架编写驱动 + 测试 APP(复用 LED 驱动逻辑)。

三、实验程序编写

3.1、设备树修改总流程

3.1、修改设备树文件

LED 灯使用SNVS_TAMPER1引脚(),需在设备树中完成 3 项配置:

添加 pinctrl 节点。

添加beep 设备节点。

检查引脚冲突。

3.1.1、添加 pinctrl 节点(配置 PIN 复用)

打开开发板设备树imx6ull-alientek-emmc.dts,在&iomuxc节点的imx6ul-evk子节点下,新增pinctrl_beep子节点,配置 SNVS_TAMPER1 的复用和电气属性

复制代码
pinctrl_beep: beepgrp {
    fsl,pins = <
        MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x10B0 /* beep:复用为GPIO5_IO01 */
    >;
};
  • MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01:SNVS_TAMPER1 引脚复用为 GPIO5_IO01(该宏定义在imx6ull-pinfunc-snvs.h中);
  • 0x10B0:电气属性值(上下拉、驱动能力等,和 LED 配置一致)。

3.1.2、添加蜂鸣器设备节点

在设备树根节点/下创建beep节点,绑定 pinctrl 和 GPIO 信息:

复制代码
beep {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "atkalpha-beep"; /* 驱动匹配标识,自定义 */
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_beep>; /* 绑定pinctrl节点 */
    beep-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>; /* GPIO5_IO01,默认高电平(不叫) */
    status = "okay";
};

pinctrl-0 = <&pinctrl_beep>:关联上一步创建的 pinctrl 节点,内核自动初始化 PIN;

beep-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>:指定蜂鸣器使用 GPIO5 第 1 号引脚,高电平触发(需和硬件逻辑匹配)。

3.1.3、检查 PIN 是否被其他外设占用

和 LED 驱动一样,需检查 SNVS_TAMPER1 引脚是否被其他外设占用:

搜索SNVS_TAMPER1,确认无其他 pinctrl 节点使用该引脚;

搜索gpio5 1,确认 GPIO5_IO01 未被其他设备节点占用;

若有冲突,屏蔽对应配置行(加/* */)。

3.2、编译设备树

设备树编写完成以后使用如下命令重新编译设备树:

复制代码
make dtbs

再把新生成的imx6ull-alientek-emmc.dtb 文件复制到tftp里,使用如下命令:

复制代码
sudo cp arch /arm/boot/dts/imx6ull-alientek-emmc.dtb/home/duan/linux/tftp/ -f

然后使用新编译出来的 imx6ull-alientek-emmc.dtb 文件启动 Linux 系统。

启动成功以后进入"/proc/device-tree"目录中 查看"gpioled"节点是否存在,使用如下命令:

复制代码
cd /proc/device-tree

如下图所示:

可以看到beep节点是存在的。

3.3、蜂鸣器 驱动程序编写

设备树准备好以后就可以编写驱动程序了,本期实验是在之前博客的led实验基础上修改而来。新建名为"5_gpioled"文件夹,然后在 5_gpioled 工程创建好以后新建 gpioled.c 文件,驱动程序基于字符设备驱动框架,核心是通过 GPIO 子系统 API 操作 GPIO,无需直接操作寄存器。

3.3.1、驱动程序执行流程图

3.3.2、驱动代码

复制代码
  1 #include <linux/types.h>          // 基本类型定义
  2 #include <linux/kernel.h>         // 内核核心函数(printk)
  3 #include <linux/delay.h>          // 延时函数
  4 #include <linux/ide.h>            // IDE相关宏
  5 #include <linux/init.h>           // 模块初始化/退出宏
  6 #include <linux/module.h>         // 模块核心头文件
  7 #include <linux/errno.h>          // 错误码定义
  8 #include <linux/gpio.h>           // GPIO子系统核心头文件
  9 #include <linux/cdev.h>           // 字符设备驱动核心头文件
 10 #include <linux/device.h>         // 设备类/设备节点创建
 11 #include <linux/of.h>             // 设备树核心头文件
 12 #include <linux/of_address.h>     // 设备树地址解析
 13 #include <linux/of_gpio.h>        // 设备树GPIO解析函数
 14 #include <asm/mach/map.h>         // 内存映射
 15 #include <asm/uaccess.h>          // 用户/内核空间数据拷贝
 16 #include <asm/io.h>               // IO操作函数
 17 
 18 /***************************************************************
 19 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
 22 版本 : V1.0
 23 描述 : 蜂鸣器驱动程序。
 26 ***************************************************************/
 27 #define BEEP_CNT        1         /* 设备号个数 */
 28 #define BEEP_NAME       "beep"    /* 设备名 */
 29 #define BEEPOFF         0         /* 关蜂鸣器 */
 30 #define BEEPON          1         /* 开蜂鸣器 */
 31 
 32 /* beep设备结构体:封装驱动所有资源 */
 33 struct beep_dev {
 34     dev_t devid;            /* 设备号 */
 35     struct cdev cdev;       /* cdev */
 36     struct class *class;    /* 类 */
 37     struct device *device;  /* 设备 */
 38     int major;              /* 主设备号 */
 39     int minor;              /* 次设备号 */
 40     struct device_node *nd; /* 设备节点 */
 41     int beep_gpio;          /* beep使用的GPIO编号 */
 42 };
 43 
 44 struct beep_dev beep; /* 定义beep设备结构体 */
 45 
 46 /*
 47  * @description : 打开设备
 48  * @param -- inode : 传递给驱动的inode
 49  * @param - filp : 设备文件,保存私有数据
 50  * @return : 0 成功;其他 失败
 51  */
 52 static int beep_open(struct inode *inode, struct file *filp)
 53 {
 54     filp->private_data = &beep; /* 绑定设备结构体到文件私有数据 */
 55     return 0;
 56 }
 57 
 58 /*
 59  * @description : 向设备写数据(核心:控制蜂鸣器开关)
 60  * @param - filp : 设备文件
 61  * @param - buf : 用户空间写入的数据
 62  * @param - cnt : 数据长度
 63  * @param - offt : 文件偏移
 64  * @return : 0 成功;负值 失败
 65  */
 66 static ssize_t beep_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
 67 {
 68     int retvalue;
 69     unsigned char databuf[1];
 70     unsigned char beepstat;
 71     struct beep_dev *dev = filp->private_data; /* 获取设备结构体 */
 72 
 73     /* 从用户空间拷贝数据到内核空间 */
 74     retvalue = copy_from_user(databuf, buf, cnt);
 75     if (retvalue < 0) {
 76         printk("kernel write failed!\r\n");
 77         return -EFAULT;
 78     }
 79 
 80     beepstat = databuf[0]; /* 获取蜂鸣器状态(1=开,0=关) */
 81     if (beepstat == BEEPON) {
 82         gpio_set_value(dev->beep_gpio, 0); /* 打开蜂鸣器(根据硬件逻辑调整电平) */
 83     } else if (beepstat == BEEPOFF) {
 84         gpio_set_value(dev->beep_gpio, 1); /* 关闭蜂鸣器 */
 85     }
 86     return 0;
 87 }
 88 
 89 /*
 90  * @description : 关闭/释放设备
 91  * @param - filp : 要关闭的设备文件
 92  * @return : 0 成功;其他 失败
 93  */
 94 static int beep_release(struct inode *inode, struct file *filp)
 95 {
 96     return 0;
 97 }
 98 
 99 /* 设备操作函数集 */
100 static struct file_operations beep_fops = {
101     .owner = THIS_MODULE,
102     .open = beep_open,
103     .write = beep_write,
104     .release = beep_release,
105 };
106 
107 /*
108  * @description : 驱动入口函数(加载驱动时执行)
109  * @param : 无
110  * @return : 0 成功;负值 失败
111  */
112 static int __init beep_init(void)
113 {
114     int ret = 0;
115 
116     /* 1、获取设备树节点:beep */
117     beep.nd = of_find_node_by_path("/beep");
118     if (beep.nd == NULL) {
119         printk("beep node not find!\r\n");
120         return -EINVAL;
121     } else {
122         printk("beep node find!\r\n");
123     }
124 
125     /* 2、解析设备树的beep-gpio属性,获取GPIO编号 */
126     beep.beep_gpio = of_get_named_gpio(beep.nd, "beep-gpio", 0);
127     if (beep.beep_gpio < 0) {
128         printk("can't get beep-gpio\r\n");
129         return -EINVAL;
130     }
131     printk("beep-gpio num = %d\r\n", beep.beep_gpio);
132 
133     /* 3、配置GPIO为输出模式,默认高电平(关闭蜂鸣器) */
134     ret = gpio_direction_output(beep.beep_gpio, 1);
135     if (ret < 0) {
136         printk("can't set gpio!\r\n");
137     }
138 
139     /* 4、注册字符设备驱动(标准流程) */
140     /* 4.1 创建设备号(手动/自动分配) */
141     if (beep.major) {
142         beep.devid = MKDEV(beep.major, 0);
143         register_chrdev_region(beep.devid, BEEP_CNT, BEEP_NAME);
144     } else {
145         alloc_chrdev_region(&beep.devid, 0, BEEP_CNT, BEEP_NAME);
146         beep.major = MAJOR(beep.devid);
147         beep.minor = MINOR(beep.devid);
148     }
149     printk("beep major=%d,minor=%d\r\n", beep.major, beep.minor);
150 
151     /* 4.2 初始化cdev */
152     beep.cdev.owner = THIS_MODULE;
153     cdev_init(&beep.cdev, &beep_fops);
154 
155     /* 4.3 添加cdev到内核 */
156     cdev_add(&beep.cdev, beep.devid, BEEP_CNT);
157 
158     /* 4.4 创建类 */
159     beep.class = class_create(THIS_MODULE, BEEP_NAME);
160     if (IS_ERR(beep.class)) {
161         return PTR_ERR(beep.class);
162     }
163 
164     /* 4.5 创建设备(生成/dev/beep节点) */
165     beep.device = device_create(beep.class, NULL, beep.devid, NULL, BEEP_NAME);
166     if (IS_ERR(beep.device)) {
167         return PTR_ERR(beep.device);
168     }
169     return 0;
170 }
171 
172 /*
173  * @description : 驱动出口函数(卸载驱动时执行)
174  * @param : 无
175  * @return : 无
176  */
177 static void __exit beep_exit(void)
178 {
179     /* 注销字符设备驱动 */
180     cdev_del(&beep.cdev);
181     unregister_chrdev_region(beep.devid, BEEP_CNT);
182 
183     /* 销毁设备和类 */
184     device_destroy(beep.class, beep.devid);
185     class_destroy(beep.class);
186 }
187 
188 module_init(beep_init);
189 module_exit(beep_exit);
190 MODULE_LICENSE("GPL");
191 MODULE_AUTHOR("zuozhongkai");
行号范围 核心功能 关键说明
33--42 设备结构体定义 封装驱动所有资源,Linux 驱动核心设计
54 绑定私有数据 传递设备上下文,替代全局变量
74 内核 / 用户空间数据拷贝 必须用 copy_from_user,禁止直接访问用户指针
82/84 GPIO 电平控制 GPIO 子系统 API,无需操作寄存器
117 获取设备树节点 of_find_node_by_path 匹配设备树节点
126 解析 GPIO 属性 of_get_named_gpio 转换 GPIO 编号
134 配置 GPIO 输出 gpio_direction_output 设置输出模式
141--168 字符设备注册流程 创建设备号→初始化 cdev→创建类 / 设备
177--186 资源释放 与 init 反向操作,避免内存泄漏
188--191 模块注册 + 协议声明 内核识别驱动的关键,必须声明 GPL 协议

3.3.3、分析驱动代码

设备结构体设计(对应代码第 33--42 行)

在设备结构体beep_dev中加入 beep_gpio 这个成员变量,此成员变量保存蜂鸣器 所使用的 GPIO 编号。

复制代码
33 struct beep_dev {
34     dev_t devid;            /* 设备号 */
35     struct cdev cdev;       /* cdev */
36     struct class *class;    /* 类 */
37     struct device *device;  /* 设备 */
38     int major;              /* 主设备号 */
39     int minor;              /* 次设备号 */
40     struct device_node *nd; /* 设备节点 */
41     int beep_gpio;          /* beep使用的GPIO编号 */
42 };
私有数据传递(对应代码第 54 行)

第 54 行,将设备结构体变量beep 设置为 filp 的私有数据 private_data

复制代码
filp->private_data = &beep;

这种将设备结构体设置为 filp 私有数据的方法在 Linux 内核驱动里面非常常见,方便 write 里取用,这样在后续操作函数中方便地获取设备上下文,避免使用全局变量。

write 函数(第 66--87 行,核心控制逻辑)
复制代码
66 static ssize_t beep_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
67 {
68     int retvalue;
69     unsigned char databuf[1];
70     unsigned char beepstat;
71     struct beep_dev *dev = filp->private_data; /* 获取设备结构体 */
72 
73     /* 从用户空间拷贝数据到内核空间 */
74     retvalue = copy_from_user(databuf, buf, cnt);
75     if (retvalue < 0) {
76         printk("kernel write failed!\r\n");
77         return -EFAULT;
78     }
79 
80     beepstat = databuf[0]; /* 获取蜂鸣器状态(1=开,0=关) */
81     if (beepstat == BEEPON) {
82         gpio_set_value(dev->beep_gpio, 0); /* 打开蜂鸣器 */
83     } else if (beepstat == BEEPOFF) {
84         gpio_set_value(dev->beep_gpio, 1); /* 关闭蜂鸣器 */
85     }
86     return 0;
87 }

第 71 行:从filp->private_data恢复设备结构体指针,是open函数的反向操作,实现上下文传递。

第 74 行:使用copy_from_user拷贝用户空间数据到内核空间 ------ 这是 Linux 内核的强制规范,禁止直接访问用户空间指针(避免内存越界、权限问题)。

第 82/84 行:通过gpio_set_value控制 GPIO 电平,完全基于 GPIO 子系统 API,无需操作底层寄存器。

注意:电平值(0/1)需根据硬件原理图调整,本实验中蜂鸣器为(低电平触发鸣叫,高电平关闭)。

驱动入口函数 beep_init(第 112--170 行,驱动初始化)
复制代码
112 static int __init beep_init(void)
113 {
114     int ret = 0;
115 
116     /* 1、获取设备树节点:beep */
117     beep.nd = of_find_node_by_path("/beep");
118     if (beep.nd == NULL) {
119         printk("beep node not find!\r\n");
120         return -EINVAL;
121     } else {
122         printk("beep node find!\r\n");
123     }
124 
125     /* 2、解析设备树的beep-gpio属性,获取GPIO编号 */
126     beep.beep_gpio = of_get_named_gpio(beep.nd, "beep-gpio", 0);
127     if (beep.beep_gpio < 0) {
128         printk("can't get beep-gpio\r\n");
129         return -EINVAL;
130     }
131     printk("beep-gpio num = %d\r\n", beep.beep_gpio);
132 
133     /* 3、配置GPIO为输出模式,默认高电平(关闭蜂鸣器) */
134     ret = gpio_direction_output(beep.beep_gpio, 1);
135     if (ret < 0) {
136         printk("can't set gpio!\r\n");
137     }
138 
139     /* 4、注册字符设备驱动(标准流程) */
140     /* 4.1 创建设备号(手动/自动分配) */
141     if (beep.major) {
142         beep.devid = MKDEV(beep.major, 0);
143         register_chrdev_region(beep.devid, BEEP_CNT, BEEP_NAME);
144     } else {
145         alloc_chrdev_region(&beep.devid, 0, BEEP_CNT, BEEP_NAME);
146         beep.major = MAJOR(beep.devid);
147         beep.minor = MINOR(beep.devid);
148     }
149     printk("beep major=%d,minor=%d\r\n", beep.major, beep.minor);
150 
151     /* 4.2 初始化cdev */
152     beep.cdev.owner = THIS_MODULE;
153     cdev_init(&beep.cdev, &beep_fops);
154 
155     /* 4.3 添加cdev到内核 */
156     cdev_add(&beep.cdev, beep.devid, BEEP_CNT);
157 
158     /* 4.4 创建类 */
159     beep.class = class_create(THIS_MODULE, BEEP_NAME);
160     if (IS_ERR(beep.class)) {
161         return PTR_ERR(beep.class);
162     }
163 
164     /* 4.5 创建设备(生成/dev/beep节点) */
165     beep.device = device_create(beep.class, NULL, beep.devid, NULL, BEEP_NAME);
166     if (IS_ERR(beep.device)) {
167         return PTR_ERR(beep.device);
168     }
169     return 0;
170 }

设备树解析(第 117/126 行):

第 117 行of_find_node_by_path("/beep"):通过绝对路径找到设备树中定义的beep节点,是驱动与设备树绑定的第一步;

第 126 行of_get_named_gpio(beep.nd, "beep-gpio", 0):解析设备树中beep-gpio属性,将<&gpio5 1 GPIO_ACTIVE_HIGH>转换为内核可识别的 GPIO 编号(本实验中为 129)。

GPIO 初始化(第 134 行):gpio_direction_output将 GPIO 配置为输出模式,并设置默认电平为高(关闭蜂鸣器),替代了裸机开发中直接配置GPIOx_GDIR寄存器的操作。

字符设备注册(第 141--168 行):遵循 Linux 字符设备驱动的标准流程 ------ 创建设备号→初始化 cdev→添加 cdev→创建类→创建设备,最终自动生成/dev/beep设备节点,用户层可直接操作。

驱动出口函数 beep_exit(第 177--186 行,资源释放)
复制代码
177 static void __exit beep_exit(void)
178 {
179     /* 注销字符设备驱动 */
180     cdev_del(&beep.cdev);
181     unregister_chrdev_region(beep.devid, BEEP_CNT);
182 
183     /* 销毁设备和类 */
184     device_destroy(beep.class, beep.devid);
185     class_destroy(beep.class);
186 }

核心原则:与beep_init的操作完全反向,确保驱动卸载时释放所有占用的内核资源(设备号、cdev、类、设备节点),避免内存泄漏。

第 180 行:删除 cdev 结构体,注销字符设备;

第 181 行:释放申请的设备号;

第 184 行:销毁/dev/beep设备节点;

第 185 行:销毁设备类。

无 GPIO 释放:GPIO 子系统会自动管理 GPIO 资源,无需手动释放,体现了内核子系统的「自动化管理」优势。

3.4、测试APP

3.4.1测试APP执行流程图

3.4.2、程序编写

复制代码
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: beepApp.c
描述	   	: beep测试APP。
其他	   	: 无
使用方法	 :./beepApp /dev/beep  0 关闭蜂鸣器
		     ./beepApp /dev/beep  1 打开蜂鸣器
***************************************************************/

#define BEEPOFF 	0
#define BEEPON 	1

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd, retvalue;
	char *filename;
	unsigned char databuf[1];
	
	if(argc != 3){
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];

	/* 打开beep驱动 */
	fd = open(filename, O_RDWR);
	if(fd < 0){
		printf("file %s open failed!\r\n", argv[1]);
		return -1;
	}

	databuf[0] = atoi(argv[2]);	/* 要执行的操作:打开或关闭 */

	/* 向/dev/beep文件写入数据 */
	retvalue = write(fd, databuf, sizeof(databuf));
	if(retvalue < 0){
		printf("BEEP Control Failed!\r\n");
		close(fd);
		return -1;
	}

	retvalue = close(fd); /* 关闭文件 */
	if(retvalue < 0){
		printf("file %s close failed!\r\n", argv[1]);
		return -1;
	}
	return 0;
}

测试 APP 代码和 LED 几乎一样,仅修改命名和提示信息,核心逻辑为:

校验命令行参数(必须传入设备路径和控制指令);

打开/dev/beep设备文件;

向驱动写入控制指令(1 = 开,0 = 关);

关闭设备文件。

四、运行测试

4.1、编译驱动程序和测试 APP

编写 Makefile 文件,本次实验的 Makefile 文件和之前的led实验基本一样,只是将 obj-m 变量的值改为beep.o,Makefile 内容如下所示:

复制代码
KERNELDIR := /home/duan/linux/linux-imx-rel_imx_4.1.15_2.1.1_ga_alientek_v2.2
CURRENT_PATH := $(shell pwd)
 
obj-m :=beep.o
 
build: kernel_modules
 
kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

输入如下命令编译出驱动模块文件:

复制代码
make -j32

编译成功以后就会生成一个名为"beep.ko"的驱动模块文件。

编译测试 APP

输入如下命令编译测试beepApp.c 这个测试程序:

复制代码
arm-linux-gnueabihf-gcc beepApp.c -o beepApp

4.2、运行测试

将上一小节编译出来的beep.ko和beepApp 这两个文件拷贝到 rootfs/lib/modules/4.1.15 目录中。

复制代码
sudo cp beep.ko /home/duan/linux/nfs/rootfs/lib/modules/4.1.15/ -f

sudo cp beepApp /home/duan/linux/nfs/rootfs/lib/modules/4.1.15/ -f

进入到目录 lib/modules/4.1.15 中,输入如下命令加载beep.ko 驱动模块:

复制代码
depmod //第一次加载驱动的时候需要运行此命令
modprobe beep.ko //加载驱动

驱动加载成功以后会在终端中输出一些信息,如下图所示:

从上图可以看出,beep 这个节点找到了,并且 GPIO5_IO01 这个 GPIO 的编号为 129。

使用 beepApp 软件来测试驱动是否工作正常,输入如下命令打开蜂鸣器:

复制代码
./beepApp /dev/beep 1 //打开蜂鸣器

输入上述命令,查看 I.MX6U-ALPHA 开发板上的蜂鸣器是否有鸣叫,如果鸣叫的话说明驱动工作正常。在输入如下命令关闭蜂鸣器:

复制代码
./beepApp /dev/beep 0 //关闭蜂鸣器

输入上述命令以后观察 I.MX6U-ALPHA 开发板上的蜂鸣器是否停止鸣叫。如果要卸载驱动的话输入如下命令即可:

复制代码
rmmod beep.ko

总结

本期博客完成了基于 pinctrl+GPIO 子系统的蜂鸣器的实验。

相关推荐
AiGuoHou12 小时前
Debian/Ubuntu 各个版本一键更换国内镜像源
linux·ubuntu·国内源·debian·镜像源·换源
蓝羽天空2 小时前
Ubuntu 24.04 安装 Docker
linux·ubuntu·docker
T0uken2 小时前
WSL:离线配置 Ubuntu 开发环境
linux·运维·ubuntu
�羡阳丶2 小时前
ubuntu22.04+5060显卡双系统安装,各种黑屏踩坑记录
linux·经验分享·ubuntu
b_xinjun11202 小时前
树莓派 Ubuntu 24.04.3 LTS 安装 OpenClaw 操作说明
linux·ubuntu·arcgis
硅基导游3 小时前
Linux内核观测与跟踪的利器BPF环境测试
linux·服务器·性能监控·bpf
YXXY3133 小时前
Linux进程概念(四)
linux
了一梨4 小时前
[T113] 交叉编译 OpenCV 4.5.2 + face 模块
linux·笔记·opencv
我是谁??4 小时前
在 Rocky Linux 9 无桌面环境中通过 SSH 安装 KVM 虚拟机(Rocky9含 XFCE 桌面/xubuntu20)完整指南
linux·服务器·ssh