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

在 VSCode 中本地运行 DeepSeek,打造强大的私人 AI

在 VSCode 中本地运行 DeepSeek,打造强大的私人 AI

本文将分步向您展示如何在本地安装和运行 DeepSeek、使用 CodeGPT 对其进行配置以及开始利用 AI 来增强您的软件开发工作流程,所有这些都无需依赖基于云的服务。  步骤 1:在 VSCode 中安装 Ollama 和 CodeGPT         要在本地运行 DeepSeek,我们首先需要安装Ollama,它允许我们在我们的机器上运行 LLM,以及CodeGPT,它是集成这些模型以提供编码辅助的 VSCode 扩展。 安装 Ollama Ollama 是一个轻量级平台,可以轻松运行本地 LLM。 下载Ollama 访问官方网站:https://ollama.com * 下载适合您的操作系统(Windows、macOS 或 Linux)的安装程序。 * 验证安装 安装后,打开终端并运行: ollama --version  如果 Ollama 安装正确,

By Ne0inhk
DeepSeek-R1是真码农福音?我们问了100位开发者……

DeepSeek-R1是真码农福音?我们问了100位开发者……

从GitHub Copilot到DeepSeek-R1,AI编程工具正在引发一场"效率革命",开发者们对这些工具的期待与质疑并存。据Gartner预测,到2028年,将有75%的企业软件工程师使用AI代码助手。 眼看着今年国产选手DeepSeek-R1凭借“深度思考”能力杀入战场,它究竟是真码农福音还是需要打补丁的"潜力股"? ZEEKLOG问卷调研了社区内来自全栈开发、算法工程师、数据工程师、前端、后端等多个技术方向的100位开发者(截止到2月25日),聚焦DeepSeek-R1的代码生成效果、编写效率、语法支持、IDE集成、复杂代码处理等多个维度,一探DeepSeek-R1的开发提效能力。 代码生成效果:有成效但仍需提升 * 代码匹配比例差强人意 在代码生成与实际需求的匹配方面,大部分开发者(58人)遇到生成代码与实际需求完全匹配无需修改的比例在40%-70%区间,12人遇到代码匹配比例在70%-100%这样较高的区间。 然而,有30人代码匹配比例低于40%。这说明DeepSeek-R1在代码生成方面有一定效果,但在部分复杂或特定场景下,仍有很大的提升空间。

By Ne0inhk
AI+游戏开发:如何用 DeepSeek 打造高性能贪吃蛇游戏

AI+游戏开发:如何用 DeepSeek 打造高性能贪吃蛇游戏

文章目录 * 一、技术选型与准备 * 1.1 传统开发 vs AI生成 * 1.2 环境搭建与工具选择 * 1.3 DeepSeek API 初步体验 * 二、贪吃蛇游戏基础实现 * 2.1 游戏结构设计 * 2.2 初始化游戏 * 2.3 DeepSeek 生成核心逻辑 * 三、游戏功能扩展 * 3.1 多人联机模式 * 3.2 游戏难度动态调整 * 3.3 游戏本地保存与回放 * 3.4 跨平台移植 * 《Vue.js项目开发全程实录/软件项目开发全程实录》 * 编辑推荐 * 内容简介 * 作者简介 * 目录 一、

By Ne0inhk
[DeepSeek] 入门详细指南(上)

[DeepSeek] 入门详细指南(上)

前言 今天的是 zty 写DeepSeek的第1篇文章,这个系列我也不知道能更多久,大约是一周一更吧,然后跟C++的知识详解换着更。 来冲个100赞兄弟们 最近啊,浙江出现了一匹AI界的黑马——DeepSeek。这个名字可能对很多人来说还比较陌生,但它已经在全球范围内引发了巨大的关注,甚至让一些科技巨头感到了压力。简单来说这 DeepSeek足以改变世界格局                                                   先   赞   后   看    养   成   习   惯  众所周知,一篇文章需要一个头图                                                   先   赞   后   看    养   成   习   惯   上面那行字怎么读呢,让大家来跟我一起读一遍吧,先~赞~后~看~养~成~习~惯~ 想要 DeepSeek从入门到精通.pdf 文件的加这个企鹅群:953793685(

By Ne0inhk