【韦东山驱动入门实验班】通用驱动框架1之超声波测距模块驱动

1. 超声波测试模块简介

 超声波测距模块是利用超声波来测距。模块先发送超声波,然后接收反射回来的超声波,由反射经历的时间和声音的传播速度 340m/s,计算得出距离。
 SR04 是一款常见的超声波传感器,模块自动发送 8 个 40KHz 的方波,自动检测是否有信号返回,用户只需提供一个触发信号,随后检测回响信号的时间长短即可
 SR04 采用 5V 电压,静态电流小于 2mA,感应角度最大约 15 度,探测距离约 2cm-450cm

硬件设计
SR04 模块上面有四个引脚,分别为:VCC、Trig、Echo、GND

  • Trig 是脉冲触发引脚,即控制该脚让 SR04 模块开始发送超声波。
  • Echo 是回响接收引脚,即 SR04 模块一旦接收到超声波的返回信号则输出回响信号,回响信号的脉冲宽度与所测距离成正比

时序图:
Alt

  1. 触发信号:向 Trig(脉冲触发引脚)发出一个大约 10us 的高电平。注意:两次触发信号的时间间隔应该大于 50us
  2. 发出超声波,接收反射信号:模块就自动发出 840Khz 的超声波,超声波遇到障碍物后反射回来,模块收到返回来的超声波。
  3. 回响信号:模块接收到反射回来的超声波后,Echo 引脚输出一个与检测距离成比例的高电平。

总而言之,我们只要在Echo 引脚电平为高时,开启定时器计数,在该引脚变为低时,结束定时器计数。根据定时器的计数和定时器频率就可以算出经历时间,根据时间即可推导出距离

假设 Echo引脚高电平持续时间为 T(单位:ns),则Distance(单位:cm) 的计算公式为:Distance = 340*T/1000000(cm)

2. 应用程序的操作

Alt
应用程序的逻辑:

  • 打开设备节点
  • 在循环中,使用使用ioctl函数调用驱动中的ioctl函数发出Trig信号
  • 然后读取时间范围T,然后根据公式计算出距离;

3. 在通用框架基础上修改驱动程序

从入口函数开始,入口函数包含 GPIO 操作、注册 file_operations 结构体、以及其他辅助信息。

首先是GPIO的操作:

  • 对于Trig引脚,它不需要申请中断,只需要发出Trig信号即可。因此只需要申请占用引脚(request_gpio),并设置初始的方向(Trig引脚平时为低电平,故默认初始化为低电平)
  • 对于Echo引脚,需要申请中断。当上升沿时,记录开始时间;当下降沿时,记录结束时间,由此计算时间差。

然后,分析和修改驱动的核心 file_operations 结构体:

  • 根据硬件原理可知,超声波测距模块不需要写数据,因此删除write操作函数;
  • 增加ioctl操作函数,该函数一般用于设置或者获取硬件的属性,这里用来触发Trig信号(可以查看内核源码中其他驱动程序如何使用)
    • 在 ioctl 函数中,发送触发信号:维持 `10us1 的高电平,然后恢复为低电平

发送触发信号后,我们需要在中断处理函数中处理中断。值得注意的是,上升沿和下降沿触发需要做不同的处理:

  • 核心函数:ktime_get_ns();用于获取当前的高精度时间(monotonic time),返回单位为纳秒的系统时间;
  • 上升沿触发中断时,记录开始时间;
  • 下降沿触发中断时,记录结束时间,并计算时间间隔;
  • 注意:容错处理——当下降沿触发时,如果时间间隔为0,表示丢失了中断;
    Alt
    其他信息:
  • 根据需求修改类名、设备名等等

出口函数:

  • 完成入口函数的反操作
#include "asm-generic/gpio.h"
#include "linux/irqreturn.h"
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/delay.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>

#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] = {
    {5, 0, "trig", },
    {56, 0, "echo", },
};

/* 主设备号 */
static int major = 0;
static struct class *gpio_class;

/* 环形缓冲区 */
#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;
}


static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);

/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t sr04_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;
}


static unsigned int sr04_poll(struct file *fp, poll_table * wait)
{
	//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	poll_wait(fp, &gpio_wait, wait);
	return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
}

static int sr04_fasync(int fd, struct file *file, int on)
{
	if (fasync_helper(fd, file, on, &button_fasync) >= 0)
		return 0;
	else
		return -EIO;
}

static long sr04_ioctl(struct file *filp, unsigned int command, unsigned long arg)
{
	//send trig
	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_read,
	.poll    = sr04_poll,
	.fasync  = sr04_fasync,
	.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;

	val = gpio_get_value(gpio_desc->gpio);

	if(val)
	{
		/* 上升沿记录起始时间*/
		rising_time = ktime_get_ns();
	}
	else
	{
		if(rising_time == 0)
		{
			printk("missing rising interrupt\n");
			return IRQ_HANDLED;
		}
		
		/* 下降沿记录结束时间,并记录时间差 */
		rising_time = ktime_get_ns() - rising_time;
		put_key(rising_time);
		wake_up_interruptible(&gpio_wait);
		kill_fasync(&button_fasync, SIGIO, POLL_IN);
	}
	return IRQ_HANDLED;
}


/* 在入口函数 */
static int __init sr04_init(void)
{
    int err;
    
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	//trig gpio
	gpio_request(gpios[0].gpio, gpios[0].name);
	err = gpio_direction_output(gpios[0].gpio, 0);//默认低电平
	//echo gpio
	gpios[1].irq  = gpio_to_irq(gpios[1].gpio);
	err = request_irq(gpios[1].irq, sr04_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED, "sr04_irq", &gpios[1]);
	
	/* 注册file_operations 	*/
	major = register_chrdev(0, "100ask_sr04", &sr04_drv);

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

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

/* 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 */
static void __exit sr04_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, "100ask_sr04");
	free_irq(gpios[1].irq, &gpios[1]);
	gpio_free(gpios[0].gpio);
}


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

module_init(sr04_init);
module_exit(sr04_exit);
MODULE_LICENSE("GPL");

4. 上机测试

根据上述所描述的应用程序操作逻辑,完成测试程序:

#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>

#define CMD_TRIG 100

static int fd;

/*
 * ./test_sr04 /dev/sr04 *
 */
int main(int argc, char **argv)
{
	int val;
	int ret;
	
	/* 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("get Distance \n");
		sleep(1);
	}
	
	close(fd);
	return 0;
}

最终的测试结果如下图所示:
在这里插入图片描述

5. 小结

  1. 打开调试信息:echo “7 4 1 7” > /proc/sys/kernel/printk
  2. 中断处理函数中最好不要使用 printk,因为 printk 非常耗时,会导致中断丢失;
  3. 如果需要接收中断,执行中断处理函数时,要禁止使用 printk(printk 会关闭中断),避免打扰中断处理函数工作;

以上是本篇文章的全部内容,如有问题请留言!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cifeng79

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值