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

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

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

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

专栏传送门: 《freertos专栏》《STM32 HAL库专栏
⭐️流水不争先,争的是滔滔不绝

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

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

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

目录

前言

一、硬件原理分析

二、蜂鸣器驱动核心原理

三、实验程序编写

3.1、设备树修改总流程

3.1、修改设备树文件

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

3.1.2、添加蜂鸣器设备节点

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

3.2、编译设备树

 3.3、蜂鸣器驱动程序编写

3.3.1、驱动程序执行流程图

3.3.2、驱动代码

3.3.3、分析驱动代码

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

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

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

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

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

3.4、测试APP

3.4.1测试APP执行流程图

3.4.2、程序编写

四、运行测试

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/84GPIO 电平控制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 子系统的蜂鸣器的实验。

Read more

【粉丝福利社】构建自主AI深入A2A协议的智能体开发

【粉丝福利社】构建自主AI深入A2A协议的智能体开发

💎【行业认证·权威头衔】 ✔ 华为云天团核心成员:特约编辑/云享专家/开发者专家/产品云测专家 ✔ 开发者社区全满贯:ZEEKLOG博客&商业化双料专家/阿里云签约作者/腾讯云内容共创官/掘金&亚马逊&51CTO顶级博主 ✔ 技术生态共建先锋:横跨鸿蒙、云计算、AI等前沿领域的技术布道者 🏆【荣誉殿堂】 🎖 连续三年蝉联"华为云十佳博主"(2022-2024) 🎖 双冠加冕ZEEKLOG"年度博客之星TOP2"(2022&2023) 🎖 十余个技术社区年度杰出贡献奖得主 📚【知识宝库】 覆盖全栈技术矩阵: ◾ 编程语言:.NET/Java/Python/Go/Node… ◾ 移动生态:HarmonyOS/iOS/Android/小程序 ◾ 前沿领域:

By Ne0inhk
人工智能:自然语言处理在教育领域的应用与实战

人工智能:自然语言处理在教育领域的应用与实战

人工智能:自然语言处理在教育领域的应用与实战 学习目标 💡 理解自然语言处理(NLP)在教育领域的应用场景和重要性 💡 掌握教育领域NLP应用的核心技术(如智能问答、作业批改、个性化学习) 💡 学会使用前沿模型(如BERT、GPT-3)进行教育文本分析 💡 理解教育领域的特殊挑战(如多学科知识、学生认知差异、数据隐私) 💡 通过实战项目,开发一个智能问答系统应用 重点内容 * 教育领域NLP应用的主要场景 * 核心技术(智能问答、作业批改、个性化学习) * 前沿模型(BERT、GPT-3)在教育领域的使用 * 教育领域的特殊挑战 * 实战项目:智能问答系统应用开发 一、教育领域NLP应用的主要场景 1.1 智能问答 1.1.1 智能问答的基本概念 智能问答是通过自然语言与用户进行交互,回答用户问题的程序。在教育领域,智能问答的主要应用场景包括: * 课程问答:回答课程相关的问题(如“什么是机器学习”

By Ne0inhk

Claude AI注册避坑指南:5分钟搞定海外手机号验证(附最新解决方案)

Claude AI 注册实战:从验证难题到高效上手的完整路径 最近几个月,身边不少朋友和同事都在讨论一个现象:想体验一下那个以“安全”和“长上下文”著称的Claude AI,却在注册的第一步——手机号验证——就卡住了。这确实是个挺让人头疼的体验,明明技术产品就在眼前,却因为一个看似简单的步骤而无法触及。对于国内的开发者、产品经理或是AI爱好者来说,这种“看得见却用不上”的感觉尤其强烈。这篇文章,就是为你准备的。我们不谈空泛的理论,只聚焦于一个核心目标:如何绕过那些常见的障碍,顺利、安全地完成Claude账户的注册与初步设置,并为你梳理清楚后续高效使用的关键点。整个过程,力求在5分钟内给你一个清晰的行动路线。 1. 理解注册流程的核心关卡与常见误区 在动手操作之前,我们先花点时间拆解一下Claude的注册流程,特别是那个让很多人“折戟”的环节。这能帮你避开很多不必要的试错,直接找到有效的路径。 Claude的官方注册流程,本质上和大多数国际主流互联网服务类似:邮箱验证 -> 手机号验证 ->

By Ne0inhk
人工智能:注意力机制与Transformer模型实战

人工智能:注意力机制与Transformer模型实战

人工智能:注意力机制与Transformer模型实战 1.1 本章学习目标与重点 💡 学习目标:掌握注意力机制的核心原理、经典注意力算法,以及Transformer模型的架构设计与实战应用。 💡 学习重点:理解自注意力与多头注意力的计算逻辑,学会使用TensorFlow搭建Transformer模型,完成机器翻译任务。 1.2 注意力机制的核心思想 1.2.1 为什么需要注意力机制 💡 传统的RNN和LSTM在处理长序列时,存在长距离依赖捕捉能力不足和并行计算效率低的问题。注意力机制的出现,解决了这两个核心痛点。 注意力机制的本质是让模型学会“聚焦”——在处理序列数据时,自动分配不同的权重给输入序列中的各个元素,重点关注与当前任务相关的信息,弱化无关信息的干扰。 比如在机器翻译任务中,翻译“我爱中国”时,模型会给“我”“爱”“中国”分配不同的注意力权重,从而更精准地生成对应的英文翻译。 1.2.2 注意力机制的基本框架 💡 注意力机制的计算通常包含**查询(Query)、键(Key)、值(

By Ne0inhk