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

🎬 渡水无言:个人主页渡水无言
❄专栏传送门: 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》
❄专栏传送门: 《freertos专栏》《STM32 HAL库专栏》
⭐️流水不争先,争的是滔滔不绝
📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生
| 省级优秀毕业生获得者 | ZEEKLOG新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生
在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连

目录
3.1.1、添加 pinctrl 节点(配置 PIN 复用)
驱动入口函数 beep_init(第 112–170 行,驱动初始化)
驱动出口函数 beep_exit(第 177–186 行,资源释放)
前言
上一章我们通过pinctrl和GPIO子系统完成了 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 beepApp4.2、运行测试
将上一小节编译出来的beep.ko和beepApp 这两个文件拷贝到 rootfs/lib/modules/4.1.15 目录中。
sudo cp beep.ko /home/duan/linux/nfs/rootfs/lib/modules/4.1.15/ -fsudo 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 子系统的蜂鸣器的实验。