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

测试

相关推荐
哎呦喂-ll31 分钟前
Linux进阶:环境变量
linux
Rverdoser32 分钟前
Linux环境开启MongoDB的安全认证
linux·安全·mongodb
PigeonGuan43 分钟前
【jupyter】linux服务器怎么使用jupyter
linux·ide·jupyter
东华果汁哥1 小时前
【linux 免密登录】快速设置kafka01、kafka02、kafka03 三台机器免密登录
linux·运维·服务器
咖喱鱼蛋2 小时前
Ubuntu安装Electron环境
linux·ubuntu·electron
2202_754421542 小时前
一个计算频率的模块
驱动开发·fpga开发
ac.char2 小时前
在 Ubuntu 系统上安装 npm 环境以及 nvm(Node Version Manager)
linux·ubuntu·npm
肖永威2 小时前
CentOS环境上离线安装python3及相关包
linux·运维·机器学习·centos
tian2kong2 小时前
Centos 7 修改YUM镜像源地址为阿里云镜像地址
linux·阿里云·centos