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赋能】MCP+Skill能力下的前端JS逆向自动化落地(附工具)

【AI赋能】MCP+Skill能力下的前端JS逆向自动化落地(附工具)

项目地址 https://github.com/Fausto-404/js-reverse-automation--skill js-reverse-automation--skill 结合chrome-devtools-mcp的能力并加上Skill的规范,实现JSRPC+Flask+autoDecoder方案的前端JS逆向自动化分析,提升JS逆向的效率 适用场景 * 需要快速落地前端签名/加密参数逆向 * 需要将js逆向逻辑封装为可复用的代码 * 需要与 Burp 配合进行抓包、改包 流程设计思路 针对js逆向中常用的远程调用法进行js逆向(如JSRPC+Mitmproxy、JSRPC+Flask等)中,初始配置阶段中面对的定位加密函数、编写注册代码、编写python代码等繁琐操作,通过引入AI的MCP和Skill技术进行赋能,让AI自动完成函数发现与注册代码生成,最终实现从“半自动”到“高自动”的跨越,人员全程只需下方指令,并最终配置一下burp即可完成JS逆向的全流程。 核心能力 * 基于 MCP 连接真实浏览器,触发并跟踪js加密/签名链路

By Ne0inhk

使用Docker安装Ollama及Open-WebUI完整教程

作者:吴业亮 博客:wuyeliang.blog.ZEEKLOG.net 一、Ollama 简介及工作原理 1. Ollama 简介及原理 * 简介:Ollama 是一款轻量级、开源的大语言模型(LLM)运行工具,旨在简化本地部署和运行大语言模型的流程。它支持 Llama 3、Mistral、Gemini 等主流开源模型,用户无需复杂配置即可在本地设备(CPU 或 GPU)上快速启动模型,适用于开发测试、本地智能应用搭建等场景。 * 工作原理: * 采用模型封装机制,将大语言模型的运行环境、依赖库及推理逻辑打包为标准化格式,实现模型的一键下载、启动和版本管理。 * 通过优化的推理引擎适配硬件架构,支持 CPU 基础运行和 GPU 加速(如 NVIDIA CUDA),减少资源占用并提升响应速度。 * 提供简洁的

By Ne0inhk
Flutter 三方库 flutter_dropzone 的鸿蒙化适配指南 - 掌握万物皆可拖拽的资源流转技术、助力鸿蒙大屏与 Web 应用构建极致直观的文件导入与交互体系

Flutter 三方库 flutter_dropzone 的鸿蒙化适配指南 - 掌握万物皆可拖拽的资源流转技术、助力鸿蒙大屏与 Web 应用构建极致直观的文件导入与交互体系

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 flutter_dropzone 的鸿蒙化适配指南 - 掌握万物皆可拖拽的资源流转技术、助力鸿蒙大屏与 Web 应用构建极致直观的文件导入与交互体系 前言 在 OpenHarmony 鸿蒙应用全场景覆盖、特别是适配鸿蒙桌面模式(Desktop Mode)、折叠屏大屏交互及鸿蒙 Web 版推送的工程实战中,“文件拖拽(Drag and Drop)”已成为提升生产力效率的标配功能。用户希望能够像在 PC 上一样,直接将图片或文档拖入应用窗口即可完成上传。如何实现这种跨越边界的直观交互?flutter_dropzone 作为一个专注于“拖放区域感知与文件流提取”的库,旨在为鸿蒙开发者提供一套标准的拖放治理方案。本文将详述其在鸿蒙端的实战技法。 一、原原理分析 / 概念介绍 1.1 基础原理 flutter_dropzone

By Ne0inhk
【前端小站】CSS 样式美学:从基础语法到界面精筑的实战宝典

【前端小站】CSS 样式美学:从基础语法到界面精筑的实战宝典

半桔:个人主页  🔥 个人专栏: 《前端扫盲》《手撕面试算法》《C++从入门到入土》 🔖阻止了我的脚步的,并不是我所看见的东西,而是我所无法看见的那些东西。 《海上钢琴师》 文章目录 * 前言 * 一. CSS是什么 * 1.1 概念 * 1.2 基本语法 * 二. CSS如何引入HTML * 2.1 内部样式表 * 2.2 行内选择器 * 2.3 外部引入 * 三. CSS选择器 * 3.1 基础选择器 * 3.1.1 标签选择器 * 3.1.2 类选择器 * 3.1.3 id选择器 * 3.

By Ne0inhk