嵌入式Linux--Beep驱动模块
目录
- 前言
- 一、操作顺序
- 二、代码模板
- 三、模块代码
- 建立驱动的VSCode工程
- 修改设备树
- 1、添加 pinctrl 节点
- 2、添加 BEEP 设备数节点
- 1、添加设备树节点
- 2、编译设备树文件
- 3、更新设备树文件
- 3、检查 PIN 是否被其他外设使用
- 代码
- 测试
前言
一、操作顺序
已有的基础:
- 硬件:正点原子Mini Linux开发板(EMMC 8GB)
- 内核:linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek.tar.bz2
- U-Boot:uboot-imx-rel_imx_4.1.15_2.1.0_ga_alientek.tar.bz2
- 配置网络文件系统:NFS
操作顺序:
- 修改设备树文件.dts
- 编译设备树文件生成.dtb文件
- 更新设备树文件.dtb
- 创建驱动工程文件
- beep.c:驱动程序
- beepApp.c:驱动应用程序
- Makefile
- c_cpp_properties.json
- 编译驱动模块.ko文件:
make -j16
- 编译beepApp.c:
arm-linux-gnueabihf-gcc beepApp.c -o beepApp
- 拷贝beep.ko和beepApp到NFS目录下
- 在目标机里面安装驱动模块(.ko文件):
modprobe beep.ko
- 在目标机里面测试驱动:
./beepApp /dev/beep 1 # 打开
- 在目标机里面卸载驱动:
rmmod beep.ko
- 完成。。。
二、代码模板
驱动模块代码模板:
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>// 设备结构体
struct xxx_dev {dev_t devid; // 设备号struct cdev cdev; // cdevstruct class *class; // 类struct device *device; // 设备int major; // 主设备号int minor; // 次设备号struct device_node *nd; // 设备节点int xxx_gpio; // xxx所使用的GPIO编号
};// 打开设备
static int xxx_open(struct inode *inode, struct file *filp)
{// TODOreturn 0;
}
// 向设备写数据
static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{// TODOreturn 0;
}
// 关闭/释放设备
static int xxx_release(struct inode *inode, struct file *filp)
{// TODOreturn 0;
}// 设备操作函数
static struct file_operations xxx_fops = {.owner = THIS_MODULE,.open = xxx_open,.write = xxx_write,.release = xxx_release,
};
// 驱动入口函数
static int __init xxx_init(void)
{// TODOreturn 0;
}
// 驱动出口函数
static void __exit xxx_exit(void)
{// TODO
}module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("liefyuan");
驱动模块应用代码模板:
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"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("XXX 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;
}
Makefile文件
KERNELDIR := /home/liefyuan/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientekCURRENT_PATH := $(shell pwd)obj-m := chrdevbase.obuild : kernel_moduleskernel_modules: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
c_cpp_properties.json
{"configurations": [{"name": "Linux","includePath": ["${workspaceFolder}/**","/home/liefyuan/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/include", "/home/liefyuan/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include", "/home/liefyuan/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/generated/"],"defines": [],"compilerPath": "/usr/bin/gcc","cStandard": "c11","cppStandard": "c++17","intelliSenseMode": "clang-x64"}],"version": 4
}
三、模块代码
使用VSCode
- 建立驱动VSCode工程
建立驱动的VSCode工程
1、创建 VSCode 工程 在 Ubuntu 中创建一个目录用来存放 Linux 驱动程序,比如我创建了一个名为 Linux_Drivers 的目录来存放所有的 Linux 驱动。
在 1_chrdevbase 目录中新建 VSCode 工程,并且新建 chrdevbase.c 文件,完成以后 1_chrdevbase 目录中的文件
2、添加头文件路径 因为是编写Linux驱动,因此会用到Linux源码中的函数。我们需要在VSCode中添加Linux 源码中的头文件路径。打开 VSCode,按下“Crtl+Shift+P”打开 VSCode 的控制台,然后输入 “C/C++: Edit configurations(JSON) ”,打开 C/C++编辑配置文件:
打开以后会自动在.vscode 目录下生成一个名为 c_cpp_properties.json 的文件,此文件默认 内容如下所示:
c_cpp_properties.json 文件原内容
{ "configurations": [ { "name": "Linux", "includePath": [ "${workspaceFolder}/**", ], "defines": [], "compilerPath": "/usr/bin/clang", "cStandard": "c11", "cppStandard": "c++17", "intelliSenseMode": "clang-x64" } ], "version": 4 }
第 5 行的 includePath 表示头文件路径,需要将 Linux 源码里面的头文件路径添加进来,也 就是我们前面移植的 Linux 源码中的头文件路径。添加头文件路径以后的 c_cpp_properties.json 的文件内容如下所示
添加头文件路径后的 c_cpp_properties.json
{ "configurations": [ { "name": "Linux","includePath": [ "${workspaceFolder}/**", "/home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imxrel_imx_4.1.15_2.1.0_ga_alientek/include", "/home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imxrel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include", "/home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imxrel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/gener ated/" ], "defines": [], ...... } ], "version": 4 }
第7~9行就是添加好的Linux头文件路径。分别是开发板所使用的Linux源码下的include、 arch/arm/include 和 arch/arm/include/generated 这三个目录的路径,注意,这里使用了绝对路径。
修改设备树
- 添加pinctrl节点
- 添加BEEP设备节点
- 检查 PIN 是否被其他外设使
设备树文件是放在Linux内核工程文件下的:/arch/arm/boot/dts/imx6ull-alientek-emmc.dts
1、添加 pinctrl 节点
I.MX6U-ALPHA开发板上的BEEP使用了SNVS_TAMPER1这个PIN,打开imx6ull-alientekemmc.dts,在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_beep”的子节点,节点 内容如下所示:
SNVS_TAMPER1 pinctrl 节点
pinctrl_beep: beepgrp { fsl,pins = < MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x10B0 /* beep */ >;
};
第 3 行,将 SNVS_TAMPER1 这个 PIN 复用为 GPIO5_IO01 ,宏 MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 定义在 arch/arm/boot/dts/imx6ull-pinfunc-snvs.h 文件中。
2、添加 BEEP 设备数节点
1、添加设备树节点
在根节点“/”下创建 BEEP 节点,节点名为“beep”,节点内容如下:
创建 BEEP 蜂鸣器节点
beep { #address-cells = <1>; #size-cells = <1>; compatible = "atkalpha-beep"; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_beep>; beep-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>; status = "okay";
};
- pinctrl-0 属性设置蜂鸣器所使用的 PIN 对应的 pinctrl 节点。
- beep-gpio 属性指定了蜂鸣器所使用的 GPIO。
2、编译设备树文件
设备树文件是放在Linux内核工程文件下的:arch/arm/boot/dts/xxx.dts 所以在编译的时候就要在Linux内核工程的根目录下编译指定的dts文件:
make imx6ull-liefyuan-emmc.dtb
等编译完成后就会在arch/arm/boot/dts/ 目录下看到 .dtb文件了。(如果该目录下有同名文件可能编译不成功(make[1]: ‘xxxx’ is up to data))
3、更新设备树文件
上面编译出一个 imx6ull-liefyuan-emmc.dtb
文件,位于:arch/arm/boot/dts目录下,现在通过NFS更新设备树文件。
- uboot进入命令行模式
- 查看EMMC中的文件架构
- Uboot命令:
fatls mmc 1:1
- Uboot命令:
=> fatls mmc 1:16788696 zimage38823 imx6ull-14x14-emmc-4.3-800x480-c.dtb39655 imx6ull-14x14-emmc-hdmi.dtb3 file(s), 0 dir(s)
从tft服务器下载设备树文件到内存(DDR):
=> tftp 83000000 imx6ull-liefyuan-emmc.dtb
Using FEC1 device
TFTP from server 192.168.0.120; our IP address is 192.168.0.121
Filename 'imx6ull-liefyuan-emmc.dtb'.
Load address: 0x83000000
Loading: ###2.1 MiB/s
done
Bytes transferred = 38823 (97a7 hex)
从内存写入EMMC:
=> fatwrite mmc 1:1 83000000 imx6ull-liefyuan-emmc.dtb 0x97a7
writing imx6ull-liefyuan-emmc.dtb
38823 bytes written
设置设备树文件作为开机使用的文件:
=> setenv bootcmd 'fatload mmc 1:1 80800000 zimage; fatload mmc 1:1 83000000 imx6ull-liefyuan-emmc.dtb; bootz 80800000 - 83000000'
=> saveenv
Saving Environment to MMC...
Writing to MMC(1)... done
=> boot
3、检查 PIN 是否被其他外设使用
在蜂鸣器使用的PIN 为SNVS_TAMPER1,因此先检查PIN 为SNVS_TAMPER1 这个 PIN 有没有被其他的 pinctrl 节点使用,如果有使用的话就要屏蔽掉,然后再检查 GPIO5_IO01 这个 GPIO 有没有被其他外设使用,如果有的话也要屏蔽掉。
设备树编写完成以后使用“make dtbs”命令重新编译设备树,然后使用新编译出来的 imx6ull-alientek-emmc.dtb 文件启动 Linux 系统。启动成功以后进入“/proc/device-tree”目录中 查看“beep”节点是否存在,如果存在的话就说明设备树基本修改成功(具体还要驱动验证)
代码
- beep.c
- beep.App.c
- Makefile
- c_cpp_properties.json
c_cpp_properties.json
{"configurations": [{"name": "Linux","includePath": ["${workspaceFolder}/**","/home/liefyuan/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/include", "/home/liefyuan/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include", "/home/liefyuan/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/generated/"],"defines": [],"compilerPath": "/usr/bin/gcc","cStandard": "c11","cppStandard": "c++17","intelliSenseMode": "clang-x64"}],"version": 4
}
Makefile
KERNELDIR := /home/liefyuan/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientekCURRENT_PATH := $(shell pwd)obj-m := beep.obuild : kernel_moduleskernel_modules: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
beep.c
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h> /*************************************************************** Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 文件名 : beep.c 作者 : liefyuan 版本 : V1.0 描述 : 蜂鸣器驱动程序 其他 : 无 论坛 : www.liefyuan.top 日志 : 初版 V1.0 2019/11/29 for liefyuan
***************************************************************/#define BEEP_CNT 1 /* 设备号个数 */
#define BEEP_NAME "beep" /* 名字 */
#define BEEPOFF 0 /* 关蜂鸣器 */
#define BEEPON 1 /* 开蜂鸣器 */ // 设备结构体
struct beep_dev {dev_t devid; // 设备号struct cdev cdev; // cdevstruct class *class; // 类struct device *device; // 设备int major; // 主设备号int minor; // 次设备号struct device_node *nd; // 设备节点int beep_gpio; // beep所使用的GPIO编号
};struct beep_dev beep; /* beep 设备 */// 打开设备
static int beep_open(struct inode *inode, struct file *filp)
{filp->private_data = &beep; // private datareturn 0;
}
// 向设备写数据
static ssize_t beep_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int ret;unsigned char databuf[1];unsigned char beepstat;struct beep_dev *dev = filp->private_data;ret = copy_from_user(databuf, buf, cnt);if(ret < 0){printk("kernel write failed!\r\n");return -EFAULT;}beepstat = databuf[0];if(beepstat == BEEPON){gpio_set_value(dev->beep_gpio, 0); // open beep} else if (beepstat == BEEPOFF){gpio_set_value(dev->beep_gpio, 1); // close beep}return 0;
}// 关闭/释放设备
static int beep_release(struct inode *inode, struct file *filp)
{// TODOreturn 0;
}// 设备操作函数
static struct file_operations beep_fops = {.owner = THIS_MODULE,.open = beep_open,.write = beep_write,.release = beep_release,
};
// 驱动入口函数
static int __init beep_init(void)
{int ret = 0;// set beep map gpio// 1.get device node:beepbeep.nd = of_find_node_by_path("/beep");if(beep.nd == NULL){printk("beep node not find!\r\n");return -EFAULT;}else{printk("beep node find!\r\n");}// 2.get device tree gpio proper,get beep used gpio numbeep.beep_gpio = of_get_named_gpio(beep.nd, "beep-gpio", 0);if(beep.beep_gpio < 0){printk("can't get beep-gpio");return -EFAULT;}printk("led-gpio num = %d\r\n", beep.beep_gpio);// 3.set GPIO5_IO01 output mode, default state is high level beep default closedret = gpio_direction_output(beep.beep_gpio, 1);if(ret < 0){printk("can't set gpio!\r\n");}// register char device drive// 1.create device numif(beep.major) // if define device num{beep.devid = MKDEV(beep.major, 0);register_chrdev_region(beep.devid, BEEP_CNT, BEEP_NAME);}else // no define device num{alloc_chrdev_region(&beep.devid, 0, BEEP_CNT, BEEP_NAME);beep.major = MAJOR(beep.devid); // get main device numbeep.minor = MINOR(beep.devid); // get sub device num}printk("beep major = %d, minor = %d\r\n", beep.major, beep.minor);// 2.init cdevbeep.cdev.owner = THIS_MODULE;cdev_init(&beep.cdev, &beep_fops);// 3.add cdevcdev_add(&beep.cdev, beep.devid, BEEP_CNT);// 4.create classbeep.class = class_create(THIS_MODULE, BEEP_NAME);if(IS_ERR(beep.class)){return PTR_ERR(beep.class);}// 5.create devicebeep.device = device_create(beep.class, NULL, beep.devid, NULL, BEEP_NAME);if(IS_ERR(beep.device)){return PTR_ERR(beep.device);}return 0;
}
// 驱动出口函数
static void __exit beep_exit(void)
{// dismiss char device drivercdev_del(&beep.cdev);unregister_chrdev_region(beep.devid, BEEP_CNT);device_destroy(beep.class, beep.devid);class_destroy(beep.class);
}module_init(beep_init);
module_exit(beep_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("liefyuan");
beepApp.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"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;
}
设置从emmc上启动Linux kernel和设备树
=> setenv bootcmd 'fatload mmc 1:1 80800000 zimage; fatload mmc 1:1 83000000 imx6ull-liefyuan-emmc.dtb; bootz 80800000 - 83000000'
=> saveenv
Saving Environment to MMC...
Writing to MMC(1)... done
=> boot
设置从nfs上启动根文件系统
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.0.120:/home/liefyuan/linux/nfs/rootfs,proto=tcp rw ip=192.168.0.121:192.168.1.120:192.168.0.1:255.255.255.0::eth0:off' //设置 bootargs
saveenv
测试
- 加载beep.ko模块:
- 第一次加载驱动模块的时候先执行这个命令:
depmod
- 加载驱动模块:
modprobe beep.ko
- 第一次加载驱动模块的时候先执行这个命令:
/lib/modules/4.1.15/kernel/drivers/beep # depmod
/lib/modules/4.1.15/kernel/drivers/beep # modprobe beep.ko
beep node find!
led-gpio num = 129
beep major = 249, minor = 0
/lib/modules/4.1.15/kernel/drivers/beep # ./beepApp /dev/beep 1
/lib/modules/4.1.15/kernel/drivers/beep # ./beepApp /dev/beep 0
完成beep驱动。
嵌入式Linux--Beep驱动模块相关推荐
- 收集整理的ARM嵌入式linux开发入门视频教程
嵌入式Linux作为一个系统学科,具有知识点多,知识难度大,实践操作性强等特点,很多踌躇满志的同学最终倒在了学习嵌入式Linux的道路上,绝大部分也是因为没有掌握嵌入式Linux 的知识规律和学习方法 ...
- STM32MP157开发板嵌入式Linux指南资料
iTOP-STM32MP157开发板资料全面升级了!现手册资料,由两部分组成: 开发板使用手册+开发指南手册 目前两份手册资料编写有1700页左右,后续资料会不断更新,不断完善,帮助用户快速入门,大大 ...
- 嵌入式linux的调试时间,嵌入式LINUX启动时间优化
目录: 1. 实践过程 2. 参考帖子 2.1 嵌入式Linux-启动时间优化: 要点如下: 优化第一步: ?? 修改u-boot,把QSPI的读取速度进行提升 优化第二步: ??? 对内核进行裁剪, ...
- 基于嵌入式linux和s32410平台的视频采集
随着多媒体技术.网络技术的迅猛发展和后PC机时代的到来,利用嵌入式系统实现远程视频监控.可视电话和视频会议等应用已成为可能.为了实现这些应用,实时获得视频数据是一个重要环节.针对这一点,本文在基于嵌入 ...
- 通俗易懂,嵌入式Linux驱动基础
前言 上一篇分享的:<从单片机工程师的角度看嵌入式Linux>中有简单提到Linux的三大类驱动: 我们学习编程的时候都会从hello程序开始.同样的,学习Linux驱动我们也从最简单的h ...
- 嵌入式linux系统移植的四大步骤_嵌入式系统移植步骤
在嵌入式系统移植中重要的一部分是操作系统的移植,与其它操作系统相比,Linux大的特点:它是一款遵循GPL的操作系统,我们可以自由地使用.修改.和扩展它.正是由于这一特色,嵌入式系统移植过程中Linu ...
- 嵌入式Linux初始化硬件RTC,嵌入式Linux系统中的快速启动技术研究
嵌入式Linux系统主要特点在于使用Bootloader替代了桌面系统的BIOS,同时对系统进行了规模上的裁剪,但硬件上的劣势往往导致系统启动速度较慢,而嵌入式产品使用者又对系统的开机速度比较敏感,样 ...
- loading linux img2a,嵌入式Linux中initrd的应用--浅析ramdisk、ramfs、initrd和initramfs
看到这样的一句话很让我费解,因为似乎我理解为这样做能够提高一点嵌入式linux启动速度,我对此是非常地感兴趣 自此,就开始了我的解惑之旅 首先需要知道的是ramdisk和ramfs http://ww ...
- linux cached释放_正点原子Linux第四十一章嵌入式Linux LED驱动开发实验
1)资料下载:点击资料即可下载 2)对正点原子Linux感兴趣的同学可以加群讨论:935446741 3)关注正点原子公众号,获取最新资料更新 第四十一章嵌入式Linux LED驱动开发实验 上一章我 ...
- 嵌入式linux只读保护,如何使用squashfs只读文件系统制作Linux系统文件
在使用嵌入式Linux 系统的时,会出现由于设备意外断电引起文件系统损坏而最终使该设备无法启动的现象.为了应对这种情况,通常会从硬件设计如采用备用电源,无论是锂电池还是超级电容等,或者从系统软件设上加 ...
最新文章
- c语言文件 写入 换行,关于文件操作,碰到空格就换行
- MyBatis-动态SQL
- CUDA编程指南阅读笔记(六)
- 20172307 2017-2018-2 《程序设计与数据结构》第9 周学习总结
- a标签href不跳转_[网页编程]-06 HTML5 超链接标签
- 盘点三个JavaScript案例——实现限时秒杀、定时跳转、改变盒子大小
- wurfl_比较OpenDDR与WURFL
- DP~数塔(hrbustoj1004)
- 前端图片有时候能显示有时候不显示_web前端基础教程:两种数据存储思路
- 根据拼音首字母查询人名【转】
- “潜力工作者”会不会成为明年24届秋招统计参数中的受害者?
- 可见光通信系统的调制效率
- c语言字符串输出大写字母个数,欧洲区预选赛视频直播 -官方网站
- Linux的命令回收站在哪,Trash-Cli:Linux 上的命令行回收站工具
- 移动端网页签名,附上DOM,效果图
- 浅析Python文件操作
- Java中23种设计模式(随时不定时更新)
- linux-DNS服务器的搭建
- 七种图像阈值分割技术
- C. Chocolate Bunny