【Linux驱动模块】四、SR04-超声波测距模块
【Linux驱动模块】四、SR04-超声波测距模块
本文最后更新于317 天前,其中的信息可能已经过时,如有错误请发送邮件到273925452@qq.com
Avatar
基于IMX6ULL_PRO开发板😊
内核版本Linux-4.9.88

概述

介绍:

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

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

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

SR04 是市面上比较成熟模块,价格低廉,用户一般不用考虑重新设计硬件,直接购买使用即可,参考电路如下。

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

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

⚫ Echo 是回响接收引脚,即 SR04 模块一旦接收到超声波的返回信号则输出回响信号,回响信号的脉冲宽度与所测距离成正比。


SR04 时序及编程思路:

时序图:

要测距,需如下操作:

① 触发:向 Trig(脉冲触发引脚)发出一个大约 10us 的高电平。

② 发出超声波,接收反射信号:模块就自动发出 8 个 40Khz 的超声波,超声波遇到障碍物后反射回来,模块收到返回来的超声波。

③ 回响:模块接收到反射回来的超声波后,Echo 引脚输出一个与检测距离成

比例的高电平。

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


程序

涉及重要知识点:

– GPIO设备结构体
– GPIO中断
– 定时器中断
– ioctl
– poll机制
– 环形缓冲区

sr04_drv.c

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

#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_sr04[BUF_LEN];   // 缓冲区数组
static int r, w;  // 读写索引

struct fasync_struct *Sr04_fasync;  // 异步通知结构体

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

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

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

static void put_sr04(int sr04_val)
{
    if (!is_sr04_buf_full())
    {
        g_sr04[w] = sr04_val;
        w = NEXT_POS(w);
    }
}

static int get_sr04(void)
{
    int sr04_val = 0;
    if (!is_sr04_buf_empty())
    {
        sr04_val = g_sr04[r];
        r = NEXT_POS(r);
    }
    return sr04_val;
}

// 创建等待队列
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)
{
    /* 在读函数和中断里面不能添加打印 */
    int err;
    int sr04;

    
    if (is_sr04_buf_empty() && (file->f_flags & O_NONBLOCK))
        return -EAGAIN;

    /* 等待缓冲区非空 */
    wait_event_interruptible(gpio_wait, !is_sr04_buf_empty());

    /* 从缓冲区读取数据 */
    sr04 = get_sr04(); 
    if (sr04 == -1)
    {
        return -EAGAIN;
    }

    /* 向用户程序返回数据 */
    err = copy_to_user(buf, &sr04, sizeof(sr04));  

    return sizeof(sr04);
}




/* poll轮询 */
static unsigned int sr04_poll(struct file *fp, poll_table * wait)
{
    /* 将当前进程加入到等待队列gpio_wait中,直到传感器缓冲区非空 */
    poll_wait(fp, &gpio_wait, wait);    //wait: 超时时间

    /* 返回POLLIN | POLLRDNORM,表示可以进行读取操作。*/
    return is_sr04_buf_empty() ? 0 : POLLIN | POLLRDNORM;
}


/* 异步通知 */
static int sr04_fasync(int fd, struct file *file, int on)
{
    // 添加异步通知
    if (fasync_helper(fd, file, on, &Sr04_fasync) >= 0)
        return 0;
    else
        return -EIO;
}

/* ioctl函数 */
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);
            
            /* 启动定时器 -设置超时时间,避免丢失信号 */
            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_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;

    /* 读取gpio值 */
    val = gpio_get_value(gpio_desc->gpio);
    if(val)
    {
        /* 上升沿记录起始时间 */
        rising_time = ktime_get_ns();
    }
    else
    {
        if(rising_time == 0)
        {
            return IRQ_HANDLED;
        }
        /* 下降沿记录结束时间,并计算时间间隔*/
        //停止定时器
        del_timer(&gpios[1].key_timer);

        //计算时间间隔
        time = ktime_get_ns() - rising_time;
        rising_time = 0;

        //放入缓冲区
        put_sr04(time);

        /* 通知等待队列可以读数据 */
        wake_up_interruptible(&gpio_wait);
        kill_fasync(&Sr04_fasync, SIGIO, POLL_IN); 
    }




    return IRQ_HANDLED;
}

//定时器函数
static void sr04_timer_func(unsigned long arg)
{
    /* 将-1放入缓冲区 */
    put_sr04(-1);
    //唤醒等待队列
    wake_up_interruptible(&gpio_wait);
    // 发送一个SIGIO信号给与sr04_fasync相关的异步I/O操作,告知它们有新的数据可读取。这里的POLL_IN表示可读事件。
    kill_fasync(&Sr04_fasync, SIGIO, POLL_IN);
}

/* 在入口函数 */
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引脚
    gpio_direction_output(gpios[0].gpio, 0);            //设置为输出引脚

    // echo pin
    {
        /* 获取gpio对应的中断号 */
        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]);

        /* 设置定时器 */
        setup_timer(&gpios[1].key_timer, sr04_timer_func, (unsigned long)&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]);
        del_timer(&gpios[1].key_timer);
    }
}


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

module_init(sr04_init);
module_exit(sr04_exit);

MODULE_LICENSE("GPL");


sr04_test.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>

#define CMD_TRIG 100

static int fd;

/*
 * ./sr04_test /dev/sr04
 *
 */
int main(int argc, char **argv)
{
    int val;
    struct pollfd fds[1];       // poll 监听的文件描述符

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

        /* poll */
        fds[0].fd = fd;
        fds[0].events = POLLIN;

        if (1 == poll(fds, 1, 500)) // 监督fds事件,1个时间,5000ms,如果定时器超时或者有事假发生,则被唤醒。
        {
            /* 读4字节数据 */
            if (read(fd, &val, 4) == 4)
                printf("get distance: %d\n", val * 17 / 1000000);
            else
                printf("get distance: err\n");
        }
        else
        {
            printf("get distance poll timeout\n");
        }
        
        /* sr04必须控制一定延迟 */
        sleep(1); 
    }
    close(fd);
    
    return 0;
}


Makefile


# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin 
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册

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

all:
    make -C $(KERN_DIR) M=`pwd` modules 
    $(CROSS_COMPILE)gcc -o sr04_test sr04_test.c
clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order  sr04_test

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



obj-m += sr04_drv.o


上机测试

IMX6ULL 先断电,按下图所示,将模块插在扩展板的 GPIO0,将扩展板插在底板上。

注意:为了防止用户接错方向,模块和扩展板都有一条长白线,连接时需要模块上的白线和扩展板的白线在同一侧。

注意:注意 SR04 模块中的红线、黑线、绿线、黄线的位置,如下图接线。

编译、运行程序,打开串口观察。将手由远到近靠近超声波模块正面,可以看到如下效果,注意模块距离最小识别距离为 2cm:

💡商业转载请联系作者获得授权,非商业转载请注明出处。
协议(License):署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)。
使用这些免费资源的时候应遵守版权意识,切勿非法利用,售卖,尊重原创内容知识产权。未经允许严禁转载。

评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇