013——超声波模块驱动开发(基于I.MX6uLL与SR04)

目录

[一、 模块介绍](#一、 模块介绍)

[1.1 产品特色](#1.1 产品特色)

[1.2 产品实物图](#1.2 产品实物图)

[1.3 接口定义](#1.3 接口定义)

[1.4 测距调节](#1.4 测距调节)

[1.5 模块工作原理](#1.5 模块工作原理)

[1.6 注意](#1.6 注意)

[二、 编码思路](#二、 编码思路)

[三、 驱动程序](#三、 驱动程序)

[四、 应用程序](#四、 应用程序)

[五、 Makefile](#五、 Makefile)

[六、 其它及实验](#六、 其它及实验)


一、 模块介绍

超声波测距模块是利用超声波来测距。模块先发送超声波,然后接收反射回来的超声波,由反射经历的时间和声音的传播速度 340m/s,计算得出距离。

SR04 是一款常见的超声波传感器,模块自动发送 8 个 40KHz 的方波,自动检测是否有信号返回,用户只需提供一个触发信号,随后检测回响信号的时间长短即可。

SR04 采用 5V 电压,静态电流小于 2mA,感应角度最大约 15 度,探测距离约 2cm-450cm。

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

⚫ Trig 是脉冲触发引脚,即控制该脚让 SR04 模块开始发送超声波。

⚫ Echo 是回响接收引脚,即 SR04 模块一旦接收到超声波的返回信号则输出

回响信号,回响信号的脉冲宽度与所测距离成正比。

1.1 产品特色

1、典型工作用电压:5V

2、超小静态工作电流:小于 5mA

3、感应角度(R3 电阻越大,增益越高,探测角度越大):

R3 电阻为 392,不大于 15 度

R3 电阻为 472, 不大于 30 度

4、探测距离(R3 电阻可调节增益,即调节探测距离):

R3 电阻为 392 2cm-450cm

R3 电阻为 472 2cm-700cm

5、高精度:可达 0.3cm

6、盲区(2cm)超近

1.2 产品实物图

1.3 接口定义

Vcc、 Trig(控制端)、 Echo(接收端)、 Gnd

本产品使用方法:控制口发一个 10US 以上的高电平,就可以在接收口等待高电平输出.一有输出就可以开定时器计时,当此口变为低电平时就可以读定时器的值,此时就为此次测距的时间,方可算出距离.如此不断的周期测,就可以达到你移动测量的值了。

1.4 测距调节

上图标志电阻即 R3,可以调节最大探测距离。R3 电阻为 392,探测距离最大 4.5M 左右,探测角度小于 15 度;R3 电阻为 472,探测距离最大 7M 左右,探测角度小于 30 度;出厂默认 392,即最大探测距离 4.5M 左右。R3 电阻大,接收部分增益高,检测距离大,但检测角度会相应变大,容易检测到前方旁边的物体。当然,客户在不要求很高的测试距离的条件下,可以改小 R3 来减小探测角度,这时最大测距会减小。

1.5 模块工作原理

(1)采用 IO 触发测距,给至少 10us 的高电平信号;

(2)模块自动发送 8 个 40khz 的方波,自动检测是否有信号返回;

(3)有信号返回,通过 IO 输出一高电平,高电平持续的时间就是

(4)超声波从发射到返回的时间.测试距离=(高电平时间*声速(340M/S))/2;

1.6 注意

1:此模块不宜带电连接,如果要带电连接,则先让模块的 Gnd 端先连接。否则会影响模块工作。

2:测距时,被测物体的面积不少于 0.5 平方米且要尽量平整。否则会影响测试结果。

二、 编码思路

我们需要找到trig和echo两个引脚,根据原理给trig发送指定的脉冲信号设备就会发送一个超声波在撞到物体返回后echo会有信号发送我们。回响信号的脉冲宽度和距离成正比。

我们需要用程序记录开始发送高脉冲的时间和接收到高脉冲的时间,这个时间就是实际距离乘以二所对应的时间,声音的传播速度是340米每秒我们就能求出距离了。

三、 驱动程序

cpp 复制代码
#include "asm-generic/gpio.h"
#include "asm/delay.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>

#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", },
};

/* 主设备号                                                                 */
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;

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

	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;
}


// ioctl(fd, CMD, ARG)
static long sr04_ioctl(struct file *filp, unsigned int command, unsigned long arg)
{
	// send trig 
	switch (command)
	{
		case CMD_TRIG:
		{
			//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
			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;
	u64 time;

	val = gpio_get_value(gpio_desc->gpio);
	//printk("sr04_isr echo pin %d is %d\n", gpio_desc->gpio, val);

	if (val)
	{
		/* 上升沿记录起始时间 */
		rising_time = ktime_get_ns();
	}
	else
	{
		if (rising_time == 0)
		{
			//printk("missing rising interrupt\n");
			return IRQ_HANDLED;
		}

		/* 下降沿记录结束时间, 并计算时间差, 计算距离 */
		time = ktime_get_ns() - rising_time;
		rising_time = 0;

		put_key(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 pin
	err = gpio_request(gpios[0].gpio, gpios[0].name);
	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, "100ask_sr04", &sr04_drv);  /* /dev/gpio_desc */

	gpio_class = class_create(THIS_MODULE, "100ask_sr04_class");
	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");

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

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


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

module_init(sr04_init);
module_exit(sr04_exit);

MODULE_LICENSE("GPL");

四、 应用程序

cpp 复制代码
#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;

/*
 * ./button_test /dev/sr04
 *
 */
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);
		printf("I am goning to read distance: \n");
		if (read(fd, &val, 4) == 4)
			printf("get distance: %d cm\n", val*17/1000000);
		else
			printf("get distance err\n");

		sleep(1);
	}

	close(fd);
	
	return 0;
}

五、 Makefile

我又做了一些优化增加了卸载模块功能

cpp 复制代码
CC := $(CROSS_COMPILE)gcc
MODE_NAME = sr04
FILE_NAME = $(MODE_NAME)_test
DRIVER_NAME = $(MODE_NAME)_drv
# 定义NFS根文件系统目录  
FS_FILE = ~/nfs_rootfs/

KERN_DIR =  /home/book/program/100ask_imx6ull_mini-sdk/Linux-4.9.88 # 板子所用内核源码的目录

# 默认目标  
all:  
	@echo "Starting build process..."  
	@echo "Building kernel modules..."  
	make -C $(KERN_DIR) M=$(PWD) modules  
	@echo "Building $(FILE_NAME) test program..."  
	$(CC) -o $(FILE_NAME) $(FILE_NAME).c  

# 安装目标  
install:  
	@echo "Installing $(DRIVER_NAME).ko to $(FS_FILE)..."  
	cp ./$(DRIVER_NAME).ko $(FS_FILE)  
	@echo "$(DRIVER_NAME).ko installed."  
	@echo "Installing $(FILE_NAME) to $(FS_FILE)..."  
	cp ./$(FILE_NAME) $(FS_FILE)  
	@echo "$(FILE_NAME) installed."  

uninstall:
	rm -rf $(FS_FILE)$(FILE_NAME)
	rm -rf $(FS_FILE)$(DRIVER_NAME).ko

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order  $(FILE_NAME)

# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o

obj-m += $(DRIVER_NAME).o
# 声明伪目标  
.PHONY: all clean install uninstall

六、 其它及实验

cpp 复制代码
echo "7 4 1 7" > /proc/sys/kernel/printk

韦东山老师的6ull打印是默认关闭的上面的命令可以打开。

注意:两次触发时间要大于60ms

注意:相比于中断来说打印是十分耗时的操作会错过下降沿的接收

查看中断

查看gpio

测试

相关推荐
ICscholar7 小时前
ExaDigiT/RAPS
linux·服务器·ubuntu·系统架构·运维开发
sim20207 小时前
systemctl isolate graphical.target命令不能随便敲
linux·mysql
米高梅狮子8 小时前
4. Linux 进程调度管理
linux·运维·服务器
再创世纪9 小时前
让USB打印机变网络打印机,秀才USB打印服务器
linux·运维·网络
fengyehongWorld10 小时前
Linux ssh端口转发
linux·ssh
知识分享小能手11 小时前
Ubuntu入门学习教程,从入门到精通, Ubuntu 22.04中的Shell编程详细知识点(含案例代码)(17)
linux·学习·ubuntu
Xの哲學12 小时前
深入解析 Linux systemd: 现代初始化系统的设计与实现
linux·服务器·网络·算法·边缘计算
龙月12 小时前
journalctl命令以及参数详解
linux·运维
小当家.10512 小时前
操作系统期末考试基础知识点速成:高频考点与题集精要
考研·操作系统·计算机基础·速成·大学·期末考试
seasonsyy13 小时前
为虚拟机分配内存和磁盘容量
windows·操作系统·内存·vmware·磁盘空间