从0开始学linux韦东山教程Linux驱动入门实验班(5)

本人从0开始学习linux,使用的是韦东山的教程,在跟着课程学习的情况下的所遇到的问题的总结,理论虽枯燥但是是基础。本人将前几章的内容大致学完之后,考虑到后续驱动方面得更多的开始实操,后续的内容将以韦东山教程Linux驱动入门实验班的内容为主,学习其中的代码并手敲。做到锻炼动手能力的同时钻研其中的理论知识点。

摘要:这篇文档主要介绍的是以下几个方面,首先本人模块是自己买的的所以会有所不同,这节内容主要也是讲解超声波模块的使用,以及代码的讲解。以及其中问题的细节部分。
摘要关键词:超声波模块

本文详细介绍以下问题,如果你遇到了以下问题,看看我的方案能否解决。

c 复制代码
1.原理图参考引脚设置
2.hsr04驱动代码撰写
3.led_test.c应用代码详注
4.代码优化

1.原理图参考引脚设置

1.引脚对应原理图

由于本人没有买他的拓展板,只好看原理图来找引脚的分布,然后发现原理图对不上只能看封装图。

想着确定图片中红色框的引脚位置,找半天没找着j5,说实在的原理图画的有点不好。

然后也是在另一张原理图中找着了j7,决定测试一下看对应得上吗?

引脚输出5V测试。

引脚输出3.3V测试。

测试引脚功能正常且和引脚编号对应的上那就没问题了。

文档中的trig和echo选择的是GPIO4 19,GPIO4 20

它原理图中的gpio也不是从0开始的从1开始所以,32x3+19=115,32x3+16=116

2.hsr04驱动代码撰写

代码中将有详细注释介绍此部分内容

首先你得大概想好超声波模块是怎么工作的,得想好接收信号和发出信号用啥引脚,需要配置中断和定时器。因为得上下沿触发中断后,开启定时器记录发送到接收的时间。然后是不是得把这个获取的时间发个应用代码让其去计算距离呢?有了以上大概思路就可以开始撰写代码了。

驱动代码1:引脚配置,功能配置

c 复制代码
#include <linux/timer.h>
#include <linux/delay.h>...
//#include <sys/ioctl.h>

#define CMD_TRIG 100
struct gpio_desc {
    int gpio;               // GPIO引脚编号(如115、116)
    int irq;                // 中断号(初始化为0,需动态申请)
    char *name;             // 引脚功能描述(如"trig"触发、"echo"响应)
    int key;                // 按键值或状态标识(未初始化,可能用于存储键值)
    struct timer_list key_timer; // 内核定时器,用于防抖或超时处理
};
static struct gpio_desc gpios[2] = {
    {115, 0, "trig", },
    {116, 0, "echo", },
};

想好配置哪些GPIO引脚以及它的功能。

2.环形缓冲区

c 复制代码
/* 环形缓冲区 */
#define BUF_LEN 128
static int g_keys[BUF_LEN];
static int r, w;

struct fasync_struct *button_fasync;

#define NEXT_POS(x) ((x+1) % BUF_LEN)

static int is_key_buf_empty(void)
{
	return (r == w);
}

static int is_key_buf_full(void)
{
	return (r == NEXT_POS(w));
}

static void put_key(int key)
{
	if (!is_key_buf_full())
	{
		g_keys[w] = key;
		w = NEXT_POS(w);
	}
}

static int get_key(void)
{
	int key = 0;
	if (!is_key_buf_empty())
	{
		key = g_keys[r];
		r = NEXT_POS(r);
	}
	return key;
}

3.hsr04驱动部分读取函数以及引脚控制函数

c 复制代码
/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t sr04_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	int err;
	int key;

	if (is_key_buf_empty() && (file->f_flags & O_NONBLOCK))    // 非阻塞模式检查
		return -EAGAIN;
	
	wait_event_interruptible(gpio_wait, !is_key_buf_empty());  // 阻塞进程直至条件满足
	key = get_key();  // 从环形缓冲区读取数据
	err = copy_to_user(buf, &key, 4);  // 数据拷贝至用户空间
	
	return 4;
}

/*
 * Forward ioctls to the underlying block device.
 iocrtl(fd,CMD,ARG)
 */
static long sr04_ioctl(struct file *filp, unsigned int command, unsigned long arg)
{
	switch(command)   
	{
		case CMD_TRIG:
		{
			gpio_set_value(gpios[0].gpio,1);
			udelay(20);
			gpio_set_value(gpios[0].gpio,0);
		}
	}
	 return 0;  // 需要在函数结束时返回一个值
}

/* 定义自己的file_operations结构体                                              */
static struct file_operations sr04_drv = {
	.owner	 = THIS_MODULE,
	.read    = sr04_drv_read,
	.unlocked_ioctl = sr04_ioctl,
};

当调用ioctl函数的时候,判断条件。应用程序只需要读取数据,以及控制引脚的信号发射。

4.触发中断函数

这段函数主要是判断接受的上下沿,当接收上沿时val为1,读取时间记录开始时间,当为下降沿时记录终止时间。将时间信息发送给应用程序。

c 复制代码
static irqreturn_t sr04_isr(int irq, void *dev_id)
{
	struct gpio_desc *gpio_desc = dev_id;
	int val;
	static u64 rising_time = 0;
	static u64 falling_time = 0;

	val = gpio_get_value(gpio_desc->gpio);
	if(val)
	{   //记录上升沿开始时间
		rising_time = ktime_get_ns();
	}
	else
	{
		if(rising_time == 0)
		{
			//printk("mising rising interrupt\n");
			return IRQ_HANDLED;
		}
		//记录下降沿终止时间
		falling_time = ktime_get_ns()-rising_time;
		//printk("key_timer_expire key %d %d\n", gpio_desc->gpio, val);
		rising_time = 0;
		put_key(falling_time);

		wake_up_interruptible(&gpio_wait);
		kill_fasync(&button_fasync, SIGIO, POLL_IN);
	}
	return IRQ_HANDLED;

}

5.入口出口函数

入口函数部分主要是注册引脚,注册trig引脚为输出模式,echo引脚注册的中断模式是 双边沿触发(上升沿和下降沿都会触发中断)。出口函数释放中断,释放引脚即可。

c 复制代码
/* 在入口函数 */
static int __init sr04_drv_init(void)
{
    int err;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		
	/* set pin as output */
	// trig pin
	err = gpio_request(gpios[0].gpio, gpios[0].name);
	if (err < 0) {
		printk("can not request gpio %s %d\n", gpios[0].name, gpios[0].gpio);
		return -ENODEV;
	}
	gpio_direction_output(gpios[0].gpio, 0);

	// echo pin 
	gpios[1].irq = gpio_to_irq(gpios[1].gpio);
	err = request_irq(gpios[1].irq,sr04_isr,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,gpios[1].name,&gpios[1]);
	
	/* 注册file_operations 	*/
	major = register_chrdev(0, "sr04", &sr04_drv);  /* /dev/gpio_desc */

	gpio_class = class_create(THIS_MODULE, "sr04_class");
	if (IS_ERR(gpio_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "sr04_class");
		return PTR_ERR(gpio_class);
	}

	device_create(gpio_class, NULL, MKDEV(major, 0), NULL, "sr04"); /* /dev/100ask_gpio */
	
	return err;
}

/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 */
static void __exit sr04_drv_exit(void)
{

    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	device_destroy(gpio_class, MKDEV(major, 0));
	class_destroy(gpio_class);
	unregister_chrdev(major, "sr04");

	// trig pin
	gpio_free(gpios[0].gpio);	

	// echo pin	
	free_irq(gpios[1].irq, &gpios[1]);
	
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(sr04_drv_init);
module_exit(sr04_drv_exit);

MODULE_LICENSE("GPL");

完整驱动代码

c 复制代码
#include "asm-generic/errno-base.h"
#include "asm-generic/gpio.h"
#include "asm/gpio.h"
#include "asm/uaccess.h"
#include "linux/irqreturn.h"
#include "linux/timekeeping.h"
#include <linux/module.h>
#include <linux/poll.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
#include <linux/delay.h>
//#include <sys/ioctl.h>

#define CMD_TRIG 100
struct gpio_desc{       
	int gpio;
	int irq;
    char *name;
    int key;
	struct timer_list key_timer;
} ;

static struct gpio_desc gpios[2] = {
    {115, 0, "trig", },
    {116, 0, "echo", },
};

/* 环形缓冲区 */
#define BUF_LEN 128
static int g_keys[BUF_LEN];    // 存储数据的固定数组,容量为 BUF_LEN(128)
static int r, w;               // 读指针(r)和写指针(w),分别指向下一个待读/写位置

struct fasync_struct *button_fasync;    // 异步通知机制​​:用于在数据到达时通知用户空间程序

#define NEXT_POS(x) ((x+1) % BUF_LEN)   // 计算下一个指针位置:(x+1) % BUF_LEN,实现缓冲区循环

static int is_key_buf_empty(void)       // 判断缓冲区空:读指针 r 等于写指针 w
{
	return (r == w);
}

static int is_key_buf_full(void)       // 判断缓冲区满:读指针 r 等于写指针的下一个位置(NEXT_POS(w))
{
	return (r == NEXT_POS(w));
}

static void put_key(int key)           // 写入数据:若缓冲区未满,存入 g_keys[w] 并更新 w
{
	if (!is_key_buf_full())
	{
		g_keys[w] = key;
		w = NEXT_POS(w);
	}
}

static int get_key(void)               // 读取数据:若缓冲区非空,返回 g_keys[r] 并更新 r
{
	int key = 0;
	if (!is_key_buf_empty())
	{
		key = g_keys[r];
		r = NEXT_POS(r);
	}
	return key;
}

// 初始化一个等待队列,用于管理因资源不足而休眠的进程。当按键事件发生时,通过该队列唤醒进程
static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);  
/* 主设备号                                                                 */
static int major = 0;
static struct class *gpio_class;


/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t sr04_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	int err;
	int key;

	if (is_key_buf_empty() && (file->f_flags & O_NONBLOCK))    // 非阻塞模式检查
		return -EAGAIN;
	
	wait_event_interruptible(gpio_wait, !is_key_buf_empty());  // 阻塞进程直至条件满足
	key = get_key();                   // 从环形缓冲区读取数据
	err = copy_to_user(buf, &key, 4);  // 数据拷贝至用户空间
	
	return 4;
}

/*
 * Forward ioctls to the underlying block device.
 iocrtl(fd,CMD,ARG)
 */
static long sr04_ioctl(struct file *filp, unsigned int command, unsigned long arg)
{
	switch(command)   
	{
		case CMD_TRIG:   // 当调用ioctl函数的时候,判断条件
		{
			gpio_set_value(gpios[0].gpio,1);
			udelay(20);
			gpio_set_value(gpios[0].gpio,0);
		}
	}
	 return 0;  // 需要在函数结束时返回一个值
}

/* 定义自己的file_operations结构体                                              */
static struct file_operations sr04_drv = {  
	.owner	 = THIS_MODULE,
	.read    = sr04_drv_read,
	.unlocked_ioctl = sr04_ioctl,
};

static irqreturn_t sr04_isr(int irq, void *dev_id)
{
	struct gpio_desc *gpio_desc = dev_id;
	int val;
	static u64 rising_time = 0;
	static u64 falling_time = 0;

	val = gpio_get_value(gpio_desc->gpio);
	if(val)
	{   //记录上升沿开始时间
		rising_time = ktime_get_ns();
	}
	else
	{
		if(rising_time == 0)
		{
			//printk("mising rising interrupt\n");
			return IRQ_HANDLED;
		}
		//记录下降沿终止时间
		falling_time = ktime_get_ns()-rising_time;
		//printk("key_timer_expire key %d %d\n", gpio_desc->gpio, val);
		rising_time = 0;
		put_key(falling_time);

		wake_up_interruptible(&gpio_wait);
		kill_fasync(&button_fasync, SIGIO, POLL_IN);
	}
	return IRQ_HANDLED;

}

/* 在入口函数 */
static int __init sr04_drv_init(void)
{
    int err;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		
	/* set pin as output */
	// trig pin
	err = gpio_request(gpios[0].gpio, gpios[0].name);
	if (err < 0) {
		printk("can not request gpio %s %d\n", gpios[0].name, gpios[0].gpio);
		return -ENODEV;
	}
	gpio_direction_output(gpios[0].gpio, 0);

	// echo pin 
	gpios[1].irq = gpio_to_irq(gpios[1].gpio);
	err = request_irq(gpios[1].irq,sr04_isr,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,gpios[1].name,&gpios[1]);
	
	/* 注册file_operations 	*/
	major = register_chrdev(0, "sr04", &sr04_drv);  /* /dev/gpio_desc */

	gpio_class = class_create(THIS_MODULE, "sr04_class");
	if (IS_ERR(gpio_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "sr04_class");
		return PTR_ERR(gpio_class);
	}

	device_create(gpio_class, NULL, MKDEV(major, 0), NULL, "sr04"); /* /dev/100ask_gpio */
	
	return err;
}

/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 */
static void __exit sr04_drv_exit(void)
{

    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	device_destroy(gpio_class, MKDEV(major, 0));
	class_destroy(gpio_class);
	unregister_chrdev(major, "sr04");

	// trig pin
	gpio_free(gpios[0].gpio);	

	// echo pin	
	free_irq(gpios[1].irq, &gpios[1]);
	
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(sr04_drv_init);
module_exit(sr04_drv_exit);

MODULE_LICENSE("GPL");

注意你要调用的头文件。

c 复制代码
book@100ask:~/test/H-SR04$ man 2 ioctl

3.应用代码

应用代码没有啥好讲的,他就是在不断循环,调用ioctl不断发送超声波信号,然后读取返回的值,每1秒执行1次。

c 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <signal.h>
#include <sys/ioctl.h>
static int fd;

#define CMD_TRIG 100
/*
 * ./button_test /dev/100ask_button0
 *
 */
int main(int argc, char **argv)
{
	int val;
	struct pollfd fds[1];
	int timeout_ms = 5000;
	int ret;
	int	flags;

	int i;
	
	/* 1. 判断参数 */
	if (argc != 2) 
	{
		printf("Usage: %s <dev>\n", argv[0]);
		return -1;
	}


	/* 2. 打开文件 */
	fd = open(argv[1], O_RDWR);
	if (fd == -1)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}

	while (1)
	{
		ioctl(fd,CMD_TRIG);
		if (read(fd, &val, 4) == 4)
			printf("get distance: %d cm\n", val*17/1000000);
		else
			printf("while distance err\n");
		sleep(1);
	}
	
	close(fd);
	
	return 0;
}

中断注册查看

c 复制代码
cat /proc/interrupts 

可以看到echo的中断为边缘触发,引脚编号为20。

c 复制代码
adb push hsr04_test hsr04_drv.ko root
insmod hsr04_drv.ko
./hsr04_test /dev/sr04

4.代码优化

优化部分本人只加了,驱动部分的优化,因为本人认为,应该做中断错误返回等一系列任务应该是驱动部分该干的。

思路部分:首先当未在指定时间内跳出,一直在阻塞的话,定时器中断跳出。以上就是大致思路。

首先得注册定时器在初始化函数中:setup_timer(&gpios[1].key_timer,sr04_timer_func, &gpios[1]);注册定时器,每个引脚好像都有定时器。

c 复制代码
/* 在入口函数 */
static int __init sr04_drv_init(void)
{
    int err;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		
	/* set pin as output */
	// trig pin
	err = gpio_request(gpios[0].gpio, gpios[0].name);
	if (err < 0) {
		printk("can not request gpio %s %d\n", gpios[0].name, gpios[0].gpio);
		return -ENODEV;
	}
	gpio_direction_output(gpios[0].gpio, 0);

	// echo pin 
	gpios[1].irq = gpio_to_irq(gpios[1].gpio);
	err = request_irq(gpios[1].irq,sr04_isr,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,gpios[1].name,&gpios[1]);
	
	// timer
	setup_timer(&gpios[1].key_timer,sr04_timer_func, &gpios[1]);
	/* 注册file_operations 	*/
	major = register_chrdev(0, "sr04", &sr04_drv);  /* /dev/gpio_desc */

	gpio_class = class_create(THIS_MODULE, "sr04_class");
	if (IS_ERR(gpio_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "sr04_class");
		return PTR_ERR(gpio_class);
	}

	device_create(gpio_class, NULL, MKDEV(major, 0), NULL, "sr04"); /* /dev/100ask_gpio */
	
	return err;
}

定时器中断函数

c 复制代码
static void sr04_timer_func(unsigned long data)
{
	put_key(-1);

	wake_up_interruptible(&gpio_wait);
	kill_fasync(&button_fasync, SIGIO, POLL_IN);
}

定时器中断函数,需要的就是超时后,跳出阻塞。

sr04引脚控制函数

在这部分内容中将,启动定时器开始计时,计时50ms。

c 复制代码
static long sr04_ioctl(struct file *filp, unsigned int command, unsigned long arg)
{
	switch(command)   
	{
		case CMD_TRIG:   // 当调用ioctl函数的时候,判断条件
		{
			gpio_set_value(gpios[0].gpio,1);
			udelay(20);
			gpio_set_value(gpios[0].gpio,0);

			// start timer 
			mod_timer(&gpios[1].key_timer,jiffies + msecs_to_jiffies(50));
		}
	}
	 return 0;  // 需要在函数结束时返回一个值
}

sr04中断函数中,若正常执行的话,结束定时器中断,删除定时器,否则会执行以上定时器中断函数的内容。

c 复制代码
static irqreturn_t sr04_isr(int irq, void *dev_id)
{
	struct gpio_desc *gpio_desc = dev_id;
	int val;
	static u64 rising_time = 0;
	static u64 falling_time = 0;

	val = gpio_get_value(gpio_desc->gpio);
	if(val)
	{   //记录上升沿开始时间
		rising_time = ktime_get_ns();
	}
	else
	{
		if(rising_time == 0)
		{
			//printk("mising rising interrupt\n");
			return IRQ_HANDLED;
		}
		// stop timer
		del_timer(&gpios[1].key_timer);
		//记录下降沿终止时间
		falling_time = ktime_get_ns()-rising_time;
		//printk("key_timer_expire key %d %d\n", gpio_desc->gpio, val);
		rising_time = 0;
		put_key(falling_time);

		wake_up_interruptible(&gpio_wait);
		kill_fasync(&button_fasync, SIGIO, POLL_IN);
	}
	return IRQ_HANDLED;

}

驱动终止函数,删除各种进程模块包括定时器。

c 复制代码
static void __exit sr04_drv_exit(void)
{

    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	device_destroy(gpio_class, MKDEV(major, 0));
	class_destroy(gpio_class);
	unregister_chrdev(major, "sr04");

	// trig pin
	gpio_free(gpios[0].gpio);	

	// echo pin	
	free_irq(gpios[1].irq, &gpios[1]);
	del_timer(&gpios[1].key_timer);
	
}

升级版完整代码

驱动代码:

c 复制代码
#include "asm-generic/errno-base.h"
#include "asm-generic/gpio.h"
#include "asm/gpio.h"
#include "asm/uaccess.h"
#include "linux/irqreturn.h"
#include "linux/jiffies.h"
#include "linux/timekeeping.h"
#include <linux/module.h>
#include <linux/poll.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>
#include <linux/delay.h>
//#include <sys/ioctl.h>

#define CMD_TRIG 100
struct gpio_desc{       
	int gpio;
	int irq;
    char *name;
    int key;
	struct timer_list key_timer;
} ;

static struct gpio_desc gpios[2] = {
    {115, 0, "trig", },
    {116, 0, "echo", },
};

/* 环形缓冲区 */
#define BUF_LEN 128
static int g_keys[BUF_LEN];    // 存储数据的固定数组,容量为 BUF_LEN(128)
static int r, w;               // 读指针(r)和写指针(w),分别指向下一个待读/写位置

struct fasync_struct *button_fasync;    // 异步通知机制​​:用于在数据到达时通知用户空间程序

#define NEXT_POS(x) ((x+1) % BUF_LEN)   // 计算下一个指针位置:(x+1) % BUF_LEN,实现缓冲区循环

static int is_key_buf_empty(void)       // 判断缓冲区空:读指针 r 等于写指针 w
{
	return (r == w);
}

static int is_key_buf_full(void)       // 判断缓冲区满:读指针 r 等于写指针的下一个位置(NEXT_POS(w))
{
	return (r == NEXT_POS(w));
}

static void put_key(int key)           // 写入数据:若缓冲区未满,存入 g_keys[w] 并更新 w
{
	if (!is_key_buf_full())
	{
		g_keys[w] = key;
		w = NEXT_POS(w);
	}
}

static int get_key(void)               // 读取数据:若缓冲区非空,返回 g_keys[r] 并更新 r
{
	int key = 0;
	if (!is_key_buf_empty())
	{
		key = g_keys[r];
		r = NEXT_POS(r);
	}
	return key;
}

// 初始化一个等待队列,用于管理因资源不足而休眠的进程。当按键事件发生时,通过该队列唤醒进程
static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);  
/* 主设备号                                                                 */
static int major = 0;
static struct class *gpio_class;


/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t sr04_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	int err;
	int key;

	if (is_key_buf_empty() && (file->f_flags & O_NONBLOCK))    // 非阻塞模式检查
		return -EAGAIN;
	
	wait_event_interruptible(gpio_wait, !is_key_buf_empty());  // 阻塞进程直至条件满足
	key = get_key();                   // 从环形缓冲区读取数据
	if(key == -1)  return -ENODATA;
	err = copy_to_user(buf, &key, 4);  // 数据拷贝至用户空间
	
	return 4;
}

/*
 * Forward ioctls to the underlying block device.
 iocrtl(fd,CMD,ARG)
 */
static long sr04_ioctl(struct file *filp, unsigned int command, unsigned long arg)
{
	switch(command)   
	{
		case CMD_TRIG:   // 当调用ioctl函数的时候,判断条件
		{
			gpio_set_value(gpios[0].gpio,1);
			udelay(20);
			gpio_set_value(gpios[0].gpio,0);

			// start timer 
			mod_timer(&gpios[1].key_timer,jiffies + msecs_to_jiffies(50));
		}
	}
	 return 0;  // 需要在函数结束时返回一个值
}

/* 定义自己的file_operations结构体                                              */
static struct file_operations sr04_drv = {  
	.owner	 = THIS_MODULE,
	.read    = sr04_drv_read,
	.unlocked_ioctl = sr04_ioctl,
};

static irqreturn_t sr04_isr(int irq, void *dev_id)
{
	struct gpio_desc *gpio_desc = dev_id;
	int val;
	static u64 rising_time = 0;
	static u64 falling_time = 0;

	val = gpio_get_value(gpio_desc->gpio);
	if(val)
	{   //记录上升沿开始时间
		rising_time = ktime_get_ns();
	}
	else
	{
		if(rising_time == 0)
		{
			//printk("mising rising interrupt\n");
			return IRQ_HANDLED;
		}
		// stop timer
		del_timer(&gpios[1].key_timer);
		//记录下降沿终止时间
		falling_time = ktime_get_ns()-rising_time;
		//printk("key_timer_expire key %d %d\n", gpio_desc->gpio, val);
		rising_time = 0;
		put_key(falling_time);

		wake_up_interruptible(&gpio_wait);
		kill_fasync(&button_fasync, SIGIO, POLL_IN);
	}
	return IRQ_HANDLED;

}


static void sr04_timer_func(unsigned long data)
{
	put_key(-1);

	wake_up_interruptible(&gpio_wait);
	kill_fasync(&button_fasync, SIGIO, POLL_IN);
}
/* 在入口函数 */
static int __init sr04_drv_init(void)
{
    int err;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		
	/* set pin as output */
	// trig pin
	err = gpio_request(gpios[0].gpio, gpios[0].name);
	if (err < 0) {
		printk("can not request gpio %s %d\n", gpios[0].name, gpios[0].gpio);
		return -ENODEV;
	}
	gpio_direction_output(gpios[0].gpio, 0);

	// echo pin 
	gpios[1].irq = gpio_to_irq(gpios[1].gpio);
	err = request_irq(gpios[1].irq,sr04_isr,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,gpios[1].name,&gpios[1]);
	
	// timer
	setup_timer(&gpios[1].key_timer,sr04_timer_func, &gpios[1]);
	/* 注册file_operations 	*/
	major = register_chrdev(0, "sr04", &sr04_drv);  /* /dev/gpio_desc */

	gpio_class = class_create(THIS_MODULE, "sr04_class");
	if (IS_ERR(gpio_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "sr04_class");
		return PTR_ERR(gpio_class);
	}

	device_create(gpio_class, NULL, MKDEV(major, 0), NULL, "sr04"); /* /dev/100ask_gpio */
	
	return err;
}

/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 */
static void __exit sr04_drv_exit(void)
{

    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	device_destroy(gpio_class, MKDEV(major, 0));
	class_destroy(gpio_class);
	unregister_chrdev(major, "sr04");

	// trig pin
	gpio_free(gpios[0].gpio);	

	// echo pin	
	free_irq(gpios[1].irq, &gpios[1]);
	del_timer(&gpios[1].key_timer);
	
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(sr04_drv_init);
module_exit(sr04_drv_exit);

MODULE_LICENSE("GPL");
相关推荐
van叶~2 小时前
Linux网络-------1.socket编程基础---(TCP-socket)
linux·网络·tcp/ip
风吹落叶花飘荡2 小时前
Ubuntu系统 系统盘和数据盘扩容具体操作
linux·运维·ubuntu
zoulingzhi_yjs2 小时前
haproxy配置详解
linux·云原生
bingbingyihao2 小时前
Node.js 模拟 Linux 环境
linux·node.js
小码过河.3 小时前
CentOS 搭建 Docker 私有镜像仓库
linux·docker·centos
贾斯汀玛尔斯4 小时前
ubuntu/centos系统ping 不通域名的解决方案
linux·ubuntu·centos
呆瑜nuage5 小时前
Linux的工具
linux
唐青枫6 小时前
Linux vimgrep 详解
linux·vim
麦子邪6 小时前
C语言中奇技淫巧04-仅对指定函数启用编译优化
linux·c语言·开发语言