嵌入式Linux--Beep驱动模块

前言

嵌入式Linux
软件
U-Boot
内核-Kernel
设备树-DTB
根文件系统-RootFileSystem

一、操作顺序

已有的基础:

  • 硬件:正点原子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;   // cdev
	struct 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)
{
	// TODO
	return 0;
}
// 向设备写数据
static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	// TODO
	return 0;
}
// 关闭/释放设备
static int xxx_release(struct inode *inode, struct file *filp)
{
	// TODO
	return 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)
{
	// TODO
	return 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_alientek

CURRENT_PATH := $(shell pwd)

obj-m := chrdevbase.o

build : kernel_modules

kernel_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
=> fatls mmc 1:1
  6788696   zimage
    38823   imx6ull-14x14-emmc-4.3-800x480-c.dtb
    39655   imx6ull-14x14-emmc-hdmi.dtb

3 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_alientek

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

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;   // cdev
	struct 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 data
	return 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)
{
	// TODO
	return 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:beep
	beep.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 num
	beep.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 closed
	ret = gpio_direction_output(beep.beep_gpio, 1);
	if(ret < 0)
	{
		printk("can't set gpio!\r\n");
	}
	
	// register char device drive
	// 1.create device num
	if(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 num
		beep.minor = MINOR(beep.devid); // get sub device num
	}
	printk("beep major = %d, minor = %d\r\n", beep.major, beep.minor);

	// 2.init cdev
	beep.cdev.owner = THIS_MODULE;
	cdev_init(&beep.cdev, &beep_fops);

	// 3.add cdev
	cdev_add(&beep.cdev, beep.devid, BEEP_CNT);

	// 4.create class
	beep.class = class_create(THIS_MODULE, BEEP_NAME);
	if(IS_ERR(beep.class))
	{
		return PTR_ERR(beep.class);
	}

	// 5.create device
	beep.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 driver
	cdev_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驱动。

Beep函数是一个Windows API函数,用于在计算机上发出声音。它的原型为:WINBASEAPI WINBOOL WINAPI Beep (DWORD dwFreq, DWORD dwDuration)。\[1\]该函数接受两个参数,dwFreq表示要发出的声音的频率,dwDuration表示声音的持续时间。通过调用Beep函数,可以在程序中产生不同频率和持续时间的声音效果。 在引用\[2\]的代码示例中,可以看到Beep函数被用来产生一段音乐。通过调用Beep函数并传入不同的频率和持续时间参数,可以实现不同音符的发声效果。这段代码使用了一系列的Beep函数调用来演奏了一首曲子。 除了Beep函数,Windows.h头文件中还定义了其他一些函数,比如MessageBox函数。MessageBox函数用于在窗口中显示一个消息框,可以用来向用户显示一些提示信息或者询问用户是否执行某个操作。\[3\]该函数也接受多个参数,包括消息框的标题、消息内容和按钮样式等。 总结起来,Beep函数是一个用于在计算机上发出声音的函数,可以通过调用该函数并传入不同的参数来产生不同的声音效果。而MessageBox函数则是用于在窗口中显示消息框的函数,可以用来向用户显示提示信息或者询问用户是否执行某个操作。 #### 引用[.reference_title] - *1* *2* *3* [C++ Beep、MessageBox函数详解](https://blog.csdn.net/Dpi168/article/details/110731492)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值