以蜂鸣器驱动为例
1 确定引脚名称
SNVS_TAMPER1
2在设备树中添加pinctrl 信息
在 dts文件中找到iomuxc,在imx6ul-evk子节点下,添加下面信息。
pinctrl_beep: beepgrp {
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x10B0 /* beep */
>;
};
MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01
#define MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x000C 0x0050 0x0000 0x5 0x0
作用是将SNVS_TAMPER1这个引脚配置为 GPIO5_IO01
分别为mux_reg conf_reg input_reg mux_mode input_val
0x000c mux_reg 寄存器偏移地址
即配置引脚复用寄存器的偏移地址
0x0050 conf_reg 寄存器偏移地址
即配置引脚电气属性的偏移地址
0x0000 input_reg 寄存器偏移地址
input_reg 寄存器偏移地址,有些外设有 input_reg 寄存器,有 input_reg 寄存器的 外设需要配置 input_reg 寄存器。没有的话就不需要设置,UART1_RTS_B 这个 PIN 在做 GPIO1_IO19 的时候是没有 input_reg 寄存器,因此这里 intput_reg 是无效的。
0x5 mux_reg 寄 存 器 值
即设置到复用寄存器里的值 0x5,表示设置为gpio5-io1
0x0 input_reg 寄存器值
这里无效
0x10B0
用来配置电气属性的,即上下拉,引脚速度等
对应 conf_reg 寄存器的值
10B0对应 0001 0000 1011 0000
对应下图寄存器设置,高16位为保留位,均位0
- "HYS": "Hyst. Enable Field,选择一个值来启用或禁用迟滞(hysteresis)。迟滞是一种现象,其中输入信号在阈值附近反复变化时,输出信号以更稳定的方式变化。", (0, 即禁用)
- "PUS": "Pull Up / Down Config. Field,上拉或下拉电阻配置字段。(00,即100k的下拉电阻)
- "PUE": "Pull / Keep Select Field,上拉或保持选择字段。上拉通常指的是将信号线拉升到高电平(逻辑1),而保持则指的是将信号线保持在中间电平(逻辑0)(0,保持)
- "PKE": "Pull / Keep Enable Field,上拉或保持使能字段 (1,使能)
- "ODE": "Open Drain Enable Field,开漏使能字段。(0,不开启开漏) (000,保留三位)
- "SPEED": "Speed Field,速度字段。 这个速度通常与信号的频率或传输速率有关(10,100mhz)
- "DSE": "Drive Strength Field,驱动强度字段。(110,R0/6的驱动能力) (00,保留两位)
- "SRE": "Slew Rate Field,斜率速率字段。斜率速率通常指的是信号从一种状态切换到另一种状态所需的时间 (0,慢速率)
3创建蜂鸣器节点,加入gpio信息
在根节点下添加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";
};
- '#address-cells': 该属性表示在设备地址中使用的单元数量。在这种情况下,它是1,表示设备地址有一个单元。
- '#size-cells': 该属性表示在设备大小中使用的单元数量。在这种情况下,它也是1,表示设备大小有一个单元。
- 'compatible': 该属性定义了设备的兼容性字符串。在这种情况下,它是'atkalpha-beep',表示该设备是ATK Alpha Beep设备。
- 'pinctrl-names': 该属性定义了设备使用的pin control的名称。在这种情况下,它只有一个名为'default'的pin control。
- 'pinctrl-0': 该属性定义了设备使用的第一个pin control。链接到上一步设置的在iomux下的 pinctrl_beep节点上
- 'beep-gpio': 该属性定义了用于控制beep设备的GPIO。在这种情况下,它使用的是名为'gpio5'的GPIO,且是gpio1 并且当GPIO处于高电平时,beep设备会被激活。
- 'status': 该属性表示设备的状态。在这种情况下,设备的状态是'okay',表示设备可以正常工作。
4检测PIN是否被其他外设引用
本次使用的引脚为SNVS_TAMPER1,先检查 PIN 为 SNVS_TAMPER1 这个 PIN 有没有被其他的 pinctrl 节点使用,如果有使用的话就要屏蔽掉,然后再检查 GPIO5_IO01 这个 GPIO 有没有被其他外设使用,如果有的话也要屏蔽掉。
使用make dtbs指令编译设备树,将编译后的文件下载到系统中
进入“/proc/device-tree”查看beep节点是否存在。
如果存在就表示设备树添加成功。
5驱动程序编写
1 创建vscode工程
创建.vscode 文件夹
在里面添加c_cpp_properties.json和settings.json文件。
在生成的c_cpp_properties.json文件中添加Linux源码文件夹
在"${workspaceFolder}/**"下添加如下目录
"/home/zzk/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/include",
"/home/zzk/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include",
"/home/zzk/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/generated/"
在setting.json中过滤掉一些不用看的文件
{
"search.exclude": {
"**/node_modules": true,
"**/bower_components": true,
"**/*.o":true,
"**/*.su":true,
"**/*.cmd":true,
"Documentation":true,
},
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/*.o":true,
"**/*.su":true,
"**/*.cmd":true,
"Documentation":true,
}
}
2编写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>
#define BEEP_CNT 1 /* 设备号个数 */
#define BEEP_NAME "beep" /* 名字 */
#define BEEPOFF 0 /* 关蜂鸣器 */
#define BEEPON 1 /* 开蜂鸣器 */
/* beep设备结构体 */
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设备 */
设置设备号和名字,还有一些常用的宏定义
beep设备结构体
存放beep所必须的一些变量
dev_t devid; /* 设备号 */
dev_t是u32的重定义 上面等价于 u32 devid 。devid用来存储设备号 ,大小为32位
struct cdev cdev; /* cdev */
cdev 是一个管理字符设备。使用时在入口函数先用cdev_init初始化函数,初始化后在用cdev_add 向linux添加设备。卸载驱动时候用cdev_del 删除函数。
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
在 cdev 中有两个重要的成员变量:ops 和 dev,这两个就是字符设备文件操作函数集合 file_operations 以及设备号 dev_t。编写字符设备驱动之前需要定义一个 cdev 结构体变量,这个 变量就表示一个字符设备,如下所示: struct beep_dev beep;
cdev_init 函数
定义好 cdev 变量以后就要使用 cdev_init 函数对其进行初始化,cdev_init 函数原型如下: void cdev_init(struct cdev *cdev, const struct file_operations *fops)
/* 2、初始化cdev */
beep.cdev.owner = THIS_MODULE;
cdev_init(&beep.cdev, &beep_fops);
cdev_add 函数
cdev_add 函数用于向 Linux 系统添加字符设备(cdev 结构体变量),首先使用 cdev_init 函数完成对 cdev 结构体变量的初始化,然后使用 cdev_add 函数向 Linux 系统添加这个字符设备。 cdev_add 函数原型如下: int cdev_add(struct cdev *p, dev_t dev, unsigned count)
/* 3、添加一个cdev */
cdev_add(&beep.cdev, beep.devid, BEEP_CNT);
3、cdev_del 函数
卸载驱动的时候一定要使用 cdev_del 函数从 Linux 内核中删除相应的字符设备,cdev_del 函数原型如下: void cdev_del(struct cdev *p)
/* 注销字符设备驱动 */
cdev_del(&beep.cdev);/* 删除cdev */
struct class *class; /* 类 */ struct device *device; /* 设备 */
类和设备的作用是用来自动创建设备节点
使用时候在入口函数cdev_add 函数后用class_create创建类,然后在用device_create函数创建设备。卸载驱动时候在exit函数下要用device_destroy,class_destroy删除掉类和设备
/* 4、创建类 */
beep.class = class_create(THIS_MODULE, BEEP_NAME);
if (IS_ERR(beep.class)) {
return PTR_ERR(beep.class);
}
/* 5、创建设备 */
beep.device = device_create(beep.class, NULL, beep.devid, NULL, BEEP_NAME);
if (IS_ERR(beep.device)) {
return PTR_ERR(beep.device);
}
device_destroy(beep.class, beep.devid);
class_destroy(beep.class);
int major; /* 主设备号 */
设备号的高12位
int minor; /* 次设备号 */
设备号的低20位
struct device_node *nd; /* 设备节点 */
用来存储设备树中的节点, 如下所示
使用方法
beep.nd = of_find_node_by_path("/beep");
通过of_find_node_by_path就可以找到设备树中的节点。通过of系列函数,可以找到在设备树中保存的信息,比如名字寄存器等,通过这些信息就可以对开饭版上的资源进行配置和使用。
int beep_gpio; /* beep所使用的GPIO编号 */
可以通过下面方法来获取gpio编号。
beep.beep_gpio = of_get_named_gpio(beep.nd, "beep-gpio", 0);
设备操作函数
Linux中,设备驱动程序需要实现一组标准的函数,以便操作系统能够识别和操作设备。下面是一部分常用的操作函数。即读写和开关函数。
/* 设备操作函数 */
static struct file_operations beep_fops = {
.owner = THIS_MODULE,
.read = beep_read,
.open = beep_open,
.write = beep_write,
.release = beep_release,
};
因为beep只需要控制,不需要读取信息,这里就没有写,
open函数里面就做了一个工作,将数据设置为私有数据,这是为了保证beep中的数据不会被其他设备改变。
重点讲下write函数,下面write函数实现的功能就是把用户在Linux 命令行中向beep写入的数据解析
并作出相应的操作。
如 ./beepApp /dev/beep 1 就是运行beepAPP 向beep中写如数字1
只需要看beepAPP中的一部分
/* 向/dev/beep文件写入数据 */ retvalue = write(fd, databuf, sizeof(databuf));
fd就是指beep这个设备 ,databuf就是写入的数字1 在beep_write函数中通过copy_from_user将数字接受,在根据是1来开启蜂鸣器。
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int beep_open(struct inode *inode, struct file *filp)
{
filp->private_data = &beep; /* 设置私有数据 */
return 0;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t beep_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char beepstat;
struct beep_dev *dev = filp->private_data;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
beepstat = databuf[0]; /* 获取状态值 */
if(beepstat == BEEPON) {
gpio_set_value(dev->beep_gpio, 0); /* 打开蜂鸣器 */
} else if(beepstat == BEEPOFF) {
gpio_set_value(dev->beep_gpio, 1); /* 关闭蜂鸣器 */
}
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int beep_release(struct inode *inode, struct file *filp)
{
return 0;
}
驱动入口和出口函数
驱动入口函数是在函数注册时候所执行的函数,用来设备号的设定,节点注册等。 驱动出口函数是在驱动卸载时候运行的,用来清除设备号和设备节点等。
用下面函数来指定驱动的出口和入口函数。 module_init(beep_init);module_exit(beep_exit);