(三)编译设备树
. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
elf@ubuntu:~/work/linux-imx-imx_4.1.15_2.0.0_ga$ make dtbs
编译生成的设备树文件为imx6ull-elf1-emmc.dtb,参考《01-0 ELF1、ELF1S开发板_快速启动手册_V1》4.4节单独更新设备树。
驱动源码myirq_key.c编译
(一)头文件引用
#include // 包含模块相关函数的头文件
#include // 包含文件系统相关函数的头文件
#include // 包含用户空间数据访问函数的头文件
#include //包含字符设备头文件
#include //包含设备节点相关的头文件
#include //包含gpio操作函数的相关头文件
#include //包含中断函数相关头文件
(二)创建相关宏定义和变量
#define DEVICE_NAME "button_irq" // 设备名称
#define GPIO_KEY_PIN_NUM 132 //定义操作的GPIO编号
#define BUTTON_IRQ gpio_to_irq(GPIO_KEY_PIN_NUM)//GPIO引脚中断号
static dev_t dev_num; //分配的设备号 struct cdev my_cdev; //字符设备指针
int major; //主设备号
int minor; //次设备号
static struct class *button_irq;
static struct device *my_device;
GPIO编号:
在imx6ull上确定GPIO编号的公式为:GPIOn_IOx=(n-1)*32+x;因为选择的引脚为GPIO5_IO4,所以通过(n-1)*32+x计算得到的编号为132。
gpio_to_irq()函数用于将GPIO引脚编号转换为对应的中断号。函数原型如下:
int gpio_to_irq(unsigned int gpio);
参数gpio是要转换的GPIO引脚号。该函数返回与给定GPIO引脚相关联的中断号。如果转换失败或引脚没有关联的中断,函数将返回一个负值。
(三)mydevice_init(void)函数的实现
static int __init mydevice_init(void)
{
int ret;
gpio_free(GPIO_KEY_PIN_NUM);
// 在这里执行驱动程序的初始化操作
if (gpio_request(GPIO_KEY_PIN_NUM, "button_irq")) {
printk("request %s gpio faile \n", "button_irq");
return -1;
}
//将引脚设置为输入模式
gpio_direction_input(GPIO_KEY_PIN_NUM);
// 注册中断处理函数
ret = request_irq(BUTTON_IRQ, button_interrupt_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "button_irq", NULL);
if (ret < 0) {
printk(KERN_ALERT "Failed to register interrupt handler\n");
gpio_free(GPIO_KEY_PIN_NUM);
return ret;
}
// 注册字符设备驱动程序
ret = alloc_chrdev_region(&dev_num,0,1,DEVICE_NAME);
if (ret < 0) {
printk(KERN_ALERT "Failed to allocate device number: %d\n", ret);
return ret;
}
major=MAJOR(dev_num);
minor=MINOR(dev_num);
printk(KERN_INFO "major number: %d\n",major);
printk(KERN_INFO "minor number: %d\n",minor);
my_cdev.owner = THIS_MODULE;
cdev_init(&my_cdev,&fops);
cdev_add(&my_cdev,dev_num,1);
// 创建设备类
button_irq = class_create(THIS_MODULE, "button_irq");
if (IS_ERR(button_irq)) {
pr_err("Failed to create class\n");
return PTR_ERR(button_irq);
}
// 创建设备节点并关联到设备类
my_device = device_create(button_irq, NULL, MKDEV(major, minor), NULL, DEVICE_NAME);
if (IS_ERR(my_device)) {
pr_err("Failed to create device\n");
class_destroy(button_irq);
return PTR_ERR(my_device);
}
printk(KERN_INFO "Device registered successfully.\n");
return 0;
}
与前面LED驱动的区别主要是使用gpio_direction_input函数将引脚配置为了输入模式,使用request_irq函数申请了中断,触发方式为:IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING。表示上升沿和下降沿都会触发中断。
(四)中断服务函数irqreturn_t button_interrupt_handler()实现
//中断服务函数
static irqreturn_t button_interrupt_handler(int irq, void *dev_id)
{
// 在此处执行按键中断处理代码
// 检查按键状态
int button_state = gpio_get_value(GPIO_KEY_PIN_NUM);
if (button_state) {
printk(KERN_INFO "Button released\n");
} else {
printk(KERN_INFO "Button pressed\n");
}
return IRQ_HANDLED;
}
使用gpio_get_value()函数获取gpio引脚的电平状态。函数原型如下:
int gpio_get_value(unsigned int gpio);
参数gpio是要获取值的GPIO引脚号。该函数返回GPIO引脚的当前值,如果引脚处于高电平状态,则返回1;如果引脚处于低电平状态,则返回0。如果读取GPIO值失败,函数将返回一个负值。通过判断电平引脚的电平状态,来输出对应的打印信息。
完整的驱动myirq_key.c示例源码
#include // 包含模块相关函数的头文件
#include // 包含文件系统相关函数的头文件
#include // 包含用户空间数据访问函数的头文件
#include //包含字符设备头文件
#include //包含设备节点相关的头文件
#include //包含gpio子系统相关函数头文件
#include //包含中断函数相关头文件
#define DEVICE_NAME "button_irq" // 设备名称
#define GPIO_KEY_PIN_NUM 132
#define BUTTON_IRQ gpio_to_irq(GPIO_KEY_PIN_NUM)//GPIO引脚中断号
static dev_t dev_num; //分配的设备号
struct cdev my_cdev; //字符设备指针
int major; //主设备号
int minor; //次设备号
static struct class *button_irq;
static struct device *my_device;
//中断服务函数
static irqreturn_t button_interrupt_handler(int irq, void *dev_id)
{
// 在此处执行按键中断处理代码
// 检查按键状态
int button_state = gpio_get_value(GPIO_KEY_PIN_NUM);
if (button_state) {
printk(KERN_INFO "Button released\n");
} else {
printk(KERN_INFO "Button pressed\n");
}
return IRQ_HANDLED;
}
static int device_open(struct inode *inode, struct file *file)
{
// 在这里处理设备打开的操作
printk(KERN_INFO "This is device_open.\n");
return 0;
}
static int device_release(struct inode *inode, struct file *file)
{
// 在这里处理设备关闭的操作
printk(KERN_INFO "This is device_release.\n");
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = device_open,
.release = device_release,
};
static int __init mydevice_init(void)
{
int ret;
//申请GPIO前先释放资源
gpio_free(GPIO_KEY_PIN_NUM);
// 在这里执行驱动程序的初始化操作
if (gpio_request(GPIO_KEY_PIN_NUM, "button_irq")) {
printk("request %s gpio faile \n", "button_irq");
return -1;
}
//将引脚设置为输入模式
gpio_direction_input(GPIO_KEY_PIN_NUM);
// 注册中断处理函数
ret = request_irq(BUTTON_IRQ, button_interrupt_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "button_irq", NULL);
if (ret < 0) {
printk(KERN_ALERT "Failed to register interrupt handler\n");
gpio_free(GPIO_KEY_PIN_NUM);
return ret;
}
// 注册字符设备驱动程序
ret = alloc_chrdev_region(&dev_num,0,1,DEVICE_NAME);
if (ret < 0) {
printk(KERN_ALERT "Failed to allocate device number: %d\n", ret);
return ret;
}
major=MAJOR(dev_num);
minor=MINOR(dev_num);
printk(KERN_INFO "major number: %d\n",major);
printk(KERN_INFO "minor number: %d\n",minor);
my_cdev.owner = THIS_MODULE;
cdev_init(&my_cdev,&fops);
cdev_add(&my_cdev,dev_num,1);
// 创建设备类
button_irq = class_create(THIS_MODULE, "button_irq");
if (IS_ERR(button_irq)) {
pr_err("Failed to create class\n");
return PTR_ERR(button_irq);
}
// 创建设备节点并关联到设备类
my_device = device_create(button_irq, NULL, MKDEV(major, minor), NULL, DEVICE_NAME);
if (IS_ERR(my_device)) {
pr_err("Failed to create device\n");
class_destroy(button_irq);
return PTR_ERR(my_device);
}
printk(KERN_INFO "Device registered successfully.\n");
return 0;
}
static void __exit mydevice_exit(void)
{
// 释放中断
free_irq(BUTTON_IRQ, NULL);
// 在这里执行驱动程序的清理操作
gpio_free(GPIO_KEY_PIN_NUM);
// 销毁设备节点
device_destroy(button_irq, MKDEV(major, minor));
// 销毁设备类
class_destroy(button_irq);
// 删除字符设备
cdev_del(&my_cdev);
// 注销字符设备驱动程序
unregister_chrdev(0, DEVICE_NAME);
printk(KERN_INFO "Device unregistered.\n");
}
module_init(mydevice_init);
module_exit(mydevice_exit);
MODULE_LICENSE("GPL"); // 指定模块的许可证信息
MODULE_AUTHOR("Your Name"); // 指定模块的作者信息
MODULE_DESCRIPTION("A simple character device driver"); // 指定模块的描述信息
编译
复制7.5.4驱动中的Makefile文件,将其中的myled.o修改为myirq_key.o,效果如下:
. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
elf@ubuntu:~/work/test/05_按键中断驱动/myirq_key$ make
将生成的.ko文件拷贝到开发板。
测试
root@ELF1:~# insmod myirq_key.ko
major number: 247
minor number: 0
Device registered successfully.
root@ELF1:~# Button pressed
Button released
Button pressed
Button released
Button pressed
Button released
Button pressed
Button released
root@ELF1:~# rmmod myirq_key.ko
Device unregistered.
加载驱动后,按下K1按键,打印Button pressed,抬起按键,打印Button released。