Linux驱动 | HC-SR04超声波模块驱动

HC-SR04超声波模块

工作原理参考: 超声波模块_star-air的博客-CSDN博客_超声波模块
https://blog.csdn.net/qq_41262681/article/details/95940707

在这里插入图片描述
使用超声波测距的操作步骤:

  • 触发:向T(脉冲触发引脚)发出一个大约10us的高电平。
    • 模块就自动发出8个40Khz的超声波,超声波遇到障碍物后反射回来,模块收到返回来的超声波。
  • 回响:模块接收到反射回来的超声波后,Echo引脚输出一个与检测距离成比例的高电平。
  • 计算距离原理: 时间 x 速度 = 距离,获取echo引脚维持高电平的时间就可以知道时间T,声速大概340m/s, 由此可计算出距离。

编程实现的两种方法:

  • 顺序执行,通过死等计算高电平时间

    伪代码:
    disable_irq();
    while(Echo == 0);      /* 等待变为高电平 */
    while (Echo)      /* 等待变为低电平和计算高电平时间 */
    {
    	udelay(1)
    	us++;
    }
    enable_irq();
    
  • 利用中断计算

    设置Echo引脚为双边沿触发,在上升沿触发中断时记录此刻时刻T0,在下降沿触发中断时记录时刻T1
    高电平时间 = T1 - T0
    
    内核中获取时间的API :
    ktime_get_ns();          // 获取内核启动到现在的时间,在挂起时会暂停
    ktime_get_boottime_ns(); // 获取内核启动到现在的时间,不受挂起影响,是绝对时间
    ktime_get_real_ns();     // 获取Unix时间(1970年)到现在的时间,可能涉及闰秒更新,用得比较少
    ktime_get_raw_ns();      // 类似ktime_get_ns(),不涉及闰秒更新,用得比较少
    

    参考文档:https://www.kernel.org/doc/html/latest/core-api/timekeeping.html#c.ktime_get_ns

驱动实现

1、设备树编写

imx6ull-mmc-npi.dts

根节点下添加HC-SR04的节点:

hc_sr04 {
		compatible = "hc_sr04";
		pinctrl-names = "default";
	 	pinctrl-0 = <&pinctrl_sr04_1
		             &pinctrl_sr04_2>;
		trig-gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>;   
		echo-gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>;
		status = "okay"; 
	};
  • trig-gpios,触发引脚定义
  • echo-gpios,接收信号引脚
  • status ,可设置为"okay"或"disabled",表示启用或者禁用,当其他模块需要使用该gpio,可以直接将状态设置为disabled就可以取消占用,其他模块就可以使用该gpio

设备树编译:

ares@ubuntu:~/work/ebf_linux_kernel-ebf_4.19.35_imx6ul$ cat make_dtb.sh
#!/bin/sh

make ARCH=arm -j4 CROSS_COMPILE=arm-linux-gnueabihf- dtbs

将设备树拷贝系统目录:

debian@npi:~/nfs_root/driver$ cat cp_dtb_to_linux.sh
#!/bin/sh

sudo cp imx6ull-mmc-npi.dtb /usr/lib/linux-image-4.19.35-carp-imx6/
  • /usr/lib/linux-image-4.19.35-carp-imx6/ ,系统存放设备树的目录

重启系统设备树生效:

sudo reboot

2、驱动编写

头文件数据结构:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/timer.h>
// #include <asm/spinlock.h>
#include <linux/jiffies.h>
#include <linux/platform_device.h>
#include <linux/of_irq.h>
#include <linux/wait.h>
#include <linux/sched/signal.h> 
#include <linux/poll.h>
// #include <asm/atomic.h>  
#include <linux/atomic.h>

#define HC_SR04_DTS_NAME    "hc_sr04"

#define DEV_NAME            "hc-sr04"

#define USE_GPIO_LIB        0

struct hc_sr04 {
    int irq;                        /* 中断号 */
    enum of_gpio_flags flag;
    struct gpio_desc *trig_gpio;    /* trig-gpio */
    struct gpio_desc *echo_gpio;    /* echo-gpio */
    dev_t dev_no;                   /* 设备号 */    
    struct cdev chrdev;             
    struct class *class;
    struct mutex  m_lock;           
    wait_queue_head_t  wq;          /* 等待队列 */
};

static struct hc_sr04  sr04;
static int sr04_trig_gpio;
static int sr04_echo_gpio;
static int sr04_data_ns = 0;

两种方式实现一致的地方:

static atomic_t sr04_atomic = ATOMIC_INIT(1);   /*  定义原子变量 */

/* 使设备只能被一个进程打开 */
static int _drv_open (struct inode *node, struct file *file)
{
    if (!atomic_dec_and_test(&sr04_atomic))  {
       atomic_inc(&sr04_atomic);
       return  -EBUSY;               /*  已经打开 */
    }

    gpio_direction_input(sr04_echo_gpio);   
    gpio_direction_output(sr04_trig_gpio, 0);
  
    return 0;
}

/* 使驱动支持多路复用IO */
static __poll_t _drv_poll(struct file *filp, struct poll_table_struct *wait)
{
    __poll_t mask = 0;

    // // wait_event_interruptible
    // mutex_lock(&sr04.m_lock);

    // poll_wait(filp, &sr04.wq, wait); 

    // if (sr04_val)
    // {
    //     mask |= POLLIN | POLLRDNORM;
    // }

    // mutex_unlock(&sr501.m_lock);

    return mask;
}

static int _drv_release(struct inode *node, struct file *file)
{
    atomic_set(&sr04_atomic, 1);      /* 释放时设置原子变量值为1 */
    printk("hc-sr04 release\n");
    return 0;
}


static struct file_operations sr04_drv_ops = { 
	.owner	= THIS_MODULE,
	.open   = _drv_open,
    .read   = _drv_read,
    .poll   = _drv_poll,
    .release = _drv_release,
};

/* 设备树的匹配列表 */
static struct of_device_id dts_match_table[] = {
    {.compatible = HC_SR04_DTS_NAME, },                     /* 通过设备树来匹配 */
};

static struct platform_driver _platform_driver = {
      .probe = _driver_probe,
      .remove = _driver_remove,
      .driver = {
        .name = HC_SR04_DTS_NAME,
        .owner = THIS_MODULE,
        .of_match_table = dts_match_table,         /* 通过设备树匹配 */
      },
};

/* 入口函数 */ 
static int __init _driver_init(void)
{
    int ret;
    printk("hc-sr04 %s\n", __FUNCTION__);
    
    ret = platform_driver_register(&_platform_driver);   //注册platform驱动
    return ret;
}

/*  出口函数 */
static void __exit _driver_exit(void)
{
    printk("hc-sr04  %s\n", __FUNCTION__);
    platform_driver_unregister(&_platform_driver);
}

module_init(_driver_init);
module_exit(_driver_exit);

MODULE_AUTHOR("Ares");
MODULE_LICENSE("GPL");

方式1

static ssize_t _drv_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
    int ret;
    int time_us = 0;
    int timeout = 1000000;

    unsigned long flags;
    /* 中断屏蔽 */
    local_irq_save(flags);

    /* 启动触发信号 */
    gpio_set_value(sr04_trig_gpio, 1);  
    udelay(40);
    gpio_set_value(sr04_trig_gpio, 0); 

    /* 等接收信号GPIO变为高电平*/
    while (gpio_get_value(sr04_echo_gpio)==0 && timeout)   
    {
        udelay(1);
        timeout--;
    }   
    if (timeout == 0) 
    {
        local_irq_restore(flags);
        return -EAGAIN;
    }

    timeout = 1000000;
    while (gpio_get_value(sr04_echo_gpio)==1 && timeout)  
    {
        udelay(1);  
        time_us++;                                /* 计算信号高电平时间 */
        timeout--;                                /* 超时计算 */
    }

    if (timeout == 0) 
    {
        printk("timeout 2\r\n");
        local_irq_restore(flags);
        return -EAGAIN;   
    }    
    
    /* 恢复中断 */
    local_irq_restore(flags);

    size = size > 4 ? 4 : size;
	if (copy_to_user(buf, &time_us, size))     /* 将获取的时间拷贝到用户空间 */
    {
        ret = -EFAULT;
    } 
    else 
    {
        ret = size;
    }
    
    return ret;
}
  • 需要注意在死等的时候要加超时机制返回,否则等不信号时系统就会卡死

方式2

与普通死等获取的方式不同的是

  • 使用中断方式,在_drv_read中只会发起启动信号
  • 在中断函数hc_sr04_isr中获取超声波时间
  • _driver_probe申请中断:request_irq(sr04.irq, hc_sr04_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, DEV_NAME, NULL);
  • _driver_remove中记得要释放中断,否则下次再insmod内核模块就使用不了该中断,必须要重启系统
static ssize_t _drv_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
    int ret;
    int timeout;

    unsigned long flags;
    /* 中断屏蔽 */
    local_irq_save(flags);
    /* 启动触发信号 */
    gpio_set_value(sr04_trig_gpio, 1);  //gpiod_set_value(sr04.trig_gpio, 1);
    udelay(40);
    gpio_set_value(sr04_trig_gpio, 0);//gpiod_set_value(sr04.trig_gpio, 0);    
    /* 恢复中断 */
    local_irq_restore(flags);

    timeout = wait_event_interruptible_timeout(sr04.wq, sr04_data_ns, HZ);	  /* wait 1 sec */
    if (!timeout) return -EAGAIN;

	if (copy_to_user(buf, &sr04_data_ns, size > 4 ? 4 : size)) 
    {
        ret = -EFAULT;
    } 
    else 
    {
        ret = size;
    }
    sr04_data_ns = 0;
    return ret;
}

static irqreturn_t hc_sr04_isr(int irq_num, void *dev)
{
    if (gpio_get_value(sr04_echo_gpio))
    {
        sr04_data_ns = ktime_get_ns();
    }
    else
    {
        sr04_data_ns = ktime_get_ns() - sr04_data_ns;
        wake_up(&sr04.wq);                             /* 唤醒等待队列中进入休眠的进程 */
    }
    
    return IRQ_RETVAL(IRQ_HANDLED);   
}

static int _driver_probe(struct platform_device *pdev)
{ 
    int err;
    struct device *sr04_dev;
    
    struct device_node *node = pdev->dev.of_node;

    if (!node) {          
        printk("hc-sr501 dts node can not found!\r\n");    
        return -EINVAL; 
    }

#if USE_GPIO_LIB
    sr04.trig_gpio = gpiod_get(&pdev->dev, "trig", GPIOD_OUT_LOW);
    if (IS_ERR(sr04.trig_gpio)) {              
        dev_err(&pdev->dev, "Failed to get trig-gpio for hc-sr04\n");             
        return PTR_ERR(sr04.trig_gpio);      
    }

    sr04.echo_gpio = gpiod_get(&pdev->dev, "echo", GPIOD_IN);
    if (IS_ERR(sr04.echo_gpio)) {              
        dev_err(&pdev->dev, "Failed to get trig-gpio for hc-sr04\n");     
        gpiod_put(sr04.trig_gpio);           /* 释放trig-gpio */        
        return PTR_ERR(sr04.echo_gpio);      
    }
#else
    struct device_node *dev_node = of_find_node_by_path("/hc_sr04");       /* 找到hc-sr04的设备树节点  */
    if (IS_ERR(dev_node)) {          
        printk("hc-sr04 DTS Node not found!\r\n"); 
        return PTR_ERR(dev_node); 
    }

    sr04_trig_gpio = of_get_named_gpio(dev_node, "trig-gpios", 0);   /* 获取trig-gpio的编号 */
    if (sr04_trig_gpio < 0) {
        printk("trig-gpio not found!\r\n"); 
        return -EINVAL;
    }

    err = gpio_request(sr04_trig_gpio, "trig-gpios");  
	if(err) 
    {
		printk("gpio_request trig-gpios is failed!\n");
        return -EINVAL;
	}

    sr04_echo_gpio = of_get_named_gpio(dev_node, "echo-gpios", 0);   /* 获取echo-gpio的编号 */
    if ( sr04_echo_gpio < 0) {
        printk("echo-gpio not found!\r\n"); 
        return -EINVAL;
    }
    err = gpio_request(sr04_echo_gpio, "echo-gpios");  
    if(err) 
    {
        gpio_free(sr04_trig_gpio);
		printk("gpio_request echo-gpios is failed!\n");
        return -EINVAL;
	}

    printk("trig-gpio %d  echo-gpio %d\n", sr04_trig_gpio, sr04_echo_gpio);

    sr04.irq = gpio_to_irq(sr04_echo_gpio);
#endif

    err = request_irq(sr04.irq, hc_sr04_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, DEV_NAME, NULL);    /* 申请中断 */
    if (err) {
        printk(KERN_INFO"failed to request irq %d\r\n", sr04.irq);
        gpio_free(sr04_trig_gpio);
        gpio_free(sr04_echo_gpio);
        return err;
    }
    /* 内核自动分配设备号 */
    err = alloc_chrdev_region(&sr04.dev_no, 0, 1, DEV_NAME);        
	if (err < 0) {
		pr_err("Error: failed to register mbochs_dev, err: %d\n", err);
		return err;
	}

	cdev_init(&sr04.chrdev, &sr04_drv_ops);

	cdev_add(&sr04.chrdev, sr04.dev_no, 1);

    sr04.class = class_create(THIS_MODULE, DEV_NAME);
	if (IS_ERR(sr04.class)) { 
        err = PTR_ERR(sr04.class);
        goto failed1;
	}

    /* 创建设备节点 */
    sr04_dev = device_create(sr04.class , NULL, sr04.dev_no, NULL, DEV_NAME); 
    if (IS_ERR(sr04_dev)) {       /* 判断指针是否合法 */
        err = PTR_ERR(sr04_dev);
		goto failed2;
	}

    init_waitqueue_head(&sr04.wq);     /* 初始化等待队列头  */
    mutex_init(&sr04.m_lock);                 /* 初始化互斥锁  */   

    printk("hc-sr04 probe success\r\n");
    return 0;
failed2:
    device_destroy(sr04.class, sr04.dev_no);
    class_destroy(sr04.class);
failed1:
    unregister_chrdev_region(sr04.dev_no, 1);
    cdev_del(&sr04.chrdev);
#if USE_GPIO_LIB
    gpiod_put(sr04.echo_gpio);           /* 释放echo-gpio*/
    gpiod_put(sr04.trig_gpio);           /* 释放trig-gpio*/
#else
    gpio_free(sr04_trig_gpio);
    gpio_free(sr04_echo_gpio);
#endif
    return err;
}

static int _driver_remove(struct platform_device *pdev)
{
    device_destroy(sr04.class, sr04.dev_no);
	class_destroy(sr04.class);
	unregister_chrdev_region(sr04.dev_no, 1);
    cdev_del(&sr04.chrdev);
    free_irq(sr04.irq, NULL);             /* 释放中断*/
#if USE_GPIO_LIB
    gpiod_put(sr04.echo_gpio);           /* 释放echo-gpio*/
    gpiod_put(sr04.trig_gpio);           /* 释放trig-gpio*/
#else
    gpio_free(sr04_trig_gpio);
    gpio_free(sr04_echo_gpio);
#endif
    printk(KERN_INFO"hc-sr04 drv remove success\n");

    return 0;
}


3、测试程序编写

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <poll.h>

#define DEV_NAME   "/dev/hc-sr04"

void sleep_ms(unsigned int ms)
{
    struct timeval delay;
	delay.tv_sec = 0;
	delay.tv_usec = ms * 1000; 
	select(0, NULL, NULL, NULL, &delay);
}

int main(int argc, char **argv)
{
	int fd;
    int ret;
  
    struct pollfd fds[1];
	
	/* 2. 打开文件 */
	fd = open(DEV_NAME, O_RDWR);   // | O_NONBLOCK

	if (fd < 0)
	{
		printf("can not open file %s, %d\n", DEV_NAME, fd);
		return -1;
	}

    int time_ns;
    while (1)
    {
        if ((ret = read(fd, &time_ns, 4)) == 4)
        {
            printf("time %d ns %d ms, distance %d mm %d cm\r\n", time_ns, time_ns/1000000, time_ns*340/2/1000000, time_ns*340/2/1000000/10);
        }
        else
        {
            printf("not get time, err %d\r\n", ret);
        }
        sleep_ms(500);
    }
}
通过逻辑分析仪测量获取时间是否准确

逻辑分析仪测量的启动信号:
在这里插入图片描述
逻辑分析仪测量的距离的高电平时间:
在这里插入图片描述

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论
HC-SR04超声波模块是一种常用的测距传感器模块。它通过发射超声波脉冲并接收回波来测量物体与模块之间的距离。该模块具有以下特点和使用介绍: 1. 外观:HC-SR04超声波模块通常由一个发射器和一个接收器组成,外形小巧。 2. 原理图:该模块的工作原理是利用超声波在空气中的传播速度来计算距离。 3. 相关参数:HC-SR04模块的一些相关参数包括工作电压、测量范围、角度范围等。 4. 测量范围:该模块可以测量的距离通常为2厘米到400厘米之间。 5. 计算公式:通过测量超声波的往返时间,可以使用公式来计算距离。 6. 优点:HC-SR04模块具有测量精度高、响应速度快、使用简单等优点。 7. 适用场所:该模块适用于各种需要测量距离的场所,如智能车、机器人、安防系统等。 总结起来,HC-SR04超声波模块是一种常用的测距传感器模块,它通过发射超声波脉冲并接收回波来测量物体与模块之间的距离。该模块具有测量精度高、响应速度快和使用简单等优点,并适用于各种需要测量距离的场所。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [超声波测距模块HC-SR04模块)特点及使用介绍](https://blog.csdn.net/qq_51712037/article/details/119851725)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

欲盖弥彰1314

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

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

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

打赏作者

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

抵扣说明:

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

余额充值