Zynq Linux中断控制led

Misc 是英文单词 miscellaneous 的缩写,意为杂乱项。在 linux 下不好明确定义种类的驱动都能定义为杂乱项,比如 led,按键,adc 些设备。这些设备有着相似的特性,所以在 Linux 中可以统一被封装成 MISC 设备来提高驱动 的可读性。misc 驱动其实是一种字符驱动设备,这个驱动会代替用户完成设备号申请,字符驱动注册等一系列的流程。

简单来说就是当想开发按键,led灯等驱动时,可以使用misc驱动来简化开发流程。

实验目的

按下按键button,触发中断,引起led灯状态的改变(0变1,1变0)

实验内容

驱动代码 misc_drv.c

//添加头文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>
#include <linux/atomic.h>
#include <linux/interrupt.h>
#include <linux/miscdevice.h>

#define ZYNQMP_GPIO_NR_GPIOS 174
#define MIO_PIN_45 (ARCH_NR_GPIOS - ZYNQMP_GPIO_NR_GPIOS + 45)
#define MIO_PIN_17 (ARCH_NR_GPIOS - ZYNQMP_GPIO_NR_GPIOS + 17)
#define MIO_PIN_23 (ARCH_NR_GPIOS - ZYNQMP_GPIO_NR_GPIOS + 23)
static int led_value=0;
//设置一个设备全局变量
static struct misc_device
{
	//dev_t devno;				 //设备号
	//struct cdev cdev;			 //字符设备
	//struct class *class;		 //设备类
	//struct device *device;	 //设备
	atomic64_t state;			 //原子变量,此处用作按键状态
	unsigned int irq;			 //中断
	wait_queue_head_t waitqueue; //等待队列
} misc_dev;						 //定义一个全局的设备结构体

//中断回调函数,此处会当按键检测到上升沿触发
static irqreturn_t key_handler(int irq, void *dev)
{
	char value;
	//读取按键状态,按下为1,松开为0
	value = gpio_get_value(MIO_PIN_45);
	if (value == 1) //按下的情况
	{
		//设置原子变量为1
		atomic_set(&misc_dev.state, 1);
		//唤醒等待队列
		wake_up_interruptible(&misc_dev.waitqueue);
	}
	return 0;
}

static int misc_open(struct inode *inode, struct file *filp)
{
	printk("-misc_open-\n");
	return 0;
}
static ssize_t misc_read(struct file *filp, char __user *buf, size_t count, loff_t *fops)
{
	int ret = 0, key = 0;
	//添加进阻塞队列,当状态为0时休眠,为1时唤醒,由第二个参数控制
	ret = wait_event_interruptible(misc_dev.waitqueue, atomic64_read(&misc_dev.state));
	if (ret)
		return ret;
	//上面的队列被唤醒后,读取当前按键状态
	key = atomic64_read(&misc_dev.state);
	if(key){
		led_value=1-led_value;
	}
	gpio_set_value(MIO_PIN_17, led_value);
	gpio_set_value(MIO_PIN_23, led_value);
	//发送到用户空间
	ret = copy_to_user(buf, &led_value, sizeof(led_value));
	if (ret)
		return ret;
	//将状态置0,等待下一次中断
	atomic_set(&misc_dev.state, 0);
	return 0;
}
static int misc_close(struct inode *inode, struct file *filp)
{
	printk("-misc_close-\n");
	return 0;
}

static struct file_operations misc_fops = {
	.open = misc_open,
	.read = misc_read,
	.release = misc_close,
};

static struct miscdevice miscdev = {
	.minor = 11,			   //次设备号
	.name = "misc_device", //设备名称
	.fops = &misc_fops,	   //文件操作描述
};

//实现装载入口函数和卸载入口函数
static __init int misc_drv_init(void)
{
	int ret = 0;
	printk("-misc_drv_init start-\n");

	/* 注册 misc 设备 */
	ret = misc_register(&miscdev);
	if (ret < 0)
	{
		printk("misc_register failed.\n");
		return ret;
	}

	//MIO_PIN_45申请GPIO口
	ret = gpio_request(MIO_PIN_45, "key");
	if (ret < 0)
	{
		printk("gpio request key error!\n");
		return ret;
	}

	//GPIO口方向设置成输入
	ret = gpio_direction_input(MIO_PIN_45);
	if (ret != 0)
	{
		printk("gpio direction input MIO_PIN_45 fail!\n");
	}

	//MIO_PIN_17申请GPIO口
	ret = gpio_request(MIO_PIN_17, "led0");
	if (ret < 0)
	{
		printk("gpio request led0 error!\n");
		return ret;
	}

	//GPIO口方向设置成输出
	ret = gpio_direction_output(MIO_PIN_17,0);
	if (ret != 0)
	{
		printk("gpio direction input MIO_PIN_45 fail!\n");
	}

	//MIO_PIN_23申请GPIO口
	ret = gpio_request(MIO_PIN_23, "led1");
	if (ret < 0)
	{
		printk("gpio request led1 error!\n");
		return ret;
	}

	//GPIO口方向设置成输出
	ret = gpio_direction_output(MIO_PIN_23,0);
	if (ret != 0)
	{
		printk("gpio direction input MIO_PIN_45 fail!\n");
	}

	//将原子变量置0,相当于初始化
	atomic64_set(&misc_dev.state, 0);

	//申请中断号
	misc_dev.irq = gpio_to_irq(MIO_PIN_45);
	//设置中断方式
	ret = request_irq(misc_dev.irq, key_handler, IRQF_TRIGGER_RISING, "key", NULL);
	if (ret < 0)
	{
		printk("request_irq error!\n");
		return ret;
	}

	//初始化等待队列头
	init_waitqueue_head(&misc_dev.waitqueue);

	printk("-misc_drv_init finish-\n");
	return 0;
}

static __exit void misc_drv_exit(void)
{
	printk("-misc_drv_exit start-\n");

	//释放中断
	free_irq(misc_dev.irq, NULL);
	//释放按键GPIO
	gpio_free(MIO_PIN_45);\
	//释放led
	gpio_free(MIO_PIN_17);
	gpio_free(MIO_PIN_23);

	misc_deregister(&miscdev);
	
	printk("-misc_drv_exit finish-\n");
}

//申明装载入口函数和卸载入口函数
module_init(misc_drv_init);
module_exit(misc_drv_exit);

//添加GPL协议
MODULE_LICENSE("GPL");
MODULE_AUTHOR("msxbo");

应用代码 misc_app.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(void)
{
	int fd = 0;
	int i = 0;
	int a = 0;

	fd = open("/dev/misc_device", O_RDWR);
	if (fd < 0)
	{
		perror("open fail!\n");
		exit(1);
	}

	while (1)
	{
        //持续打印led灯状态
		read(fd, &a, sizeof(a));
		printf("led value %d\n", a);
	}

	close(fd);

	return 0;
}

Makefile

#已经编译过的内核源码路径
KERNEL_DIR = /home/uisrc/uisrc-lab-xlnx/sources/kernel

export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-

#当前路径
CURRENT_DIR = $(shell pwd)

MODULE = misc_drv
APP = misc_app

all : 
#进入并调用内核源码目录中Makefile的规则, 将当前的目录中的源码编译成模块
	make -C $(KERNEL_DIR) M=$(CURRENT_DIR) modules 
	rm -rf *.mod.c *.mod.o *.symvers *.order *.o

ifneq ($(APP), )
	$(CROSS_COMPILE)gcc $(APP).c -o  $(APP)
endif

clean : 
	make -C $(KERNEL_DIR) M=$(CURRENT_DIR) clean
	rm $(APP)

#指定编译哪个文件
obj-m += $(MODULE).o

交叉编译上板执行,按下按键会使led灯由熄灭变为点亮,再次按下会从点亮变为熄灭,如此反复。


本文章使用limfx的vscode插件快速发布