LINUX 驱动之HSR04超声波模块,设备树配置

这篇文章主要是基于韦东山的板子IMX6ULL 进行开发的,原理方面大概讲一下吧,然后基于原理驱动方面需要实现啥。LINUX 驱动之HSR04超声波模块、设备树配置是这篇文章的主要内容。

硬件时序图讲解

几个比较重要的参数,VREF=0.8V,ILIM_SW=3.7A左右最优。要实现的功能也就是Trig引脚发射一个10us的高电平然后拉低,将其设置成GPIO OUTPUT功能,模块发出的那8个40Khz的脉冲信号是你看到的那两个大喇叭其中有一个发射的,然后碰到障碍物反弹另一个大喇叭接收这个脉冲信号,从发射开始Echo引脚拉高,接收到脉冲信号后拉低引脚。以上就是整个工作原理,所以说需要设置Echo引脚输入功能外部触发中断,检测其边缘跳变,然后一个定时器记录两个边缘跳变之间的时间。再用一个定时器当作看门狗,没有检测到阻塞了立马跳出。

设备树配置

明白需要初始化啥之后就开始配置设备树。

c 复制代码
    hsr04{
        #address-cells = <1>; 
        #size-cells = <1>;
        compatible = "ljj,hsr04";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_hsr04>;
        trig-gpios = <&gpio4 19 GPIO_ACTIVE_HIGH>;
        echo-gpios = <&gpio4 20 GPIO_ACTIVE_HIGH>;
        interrupt-parent = <&gpio4>;
        interrupts = <20 IRQ_TYPE_EDGE_BOTH>;
        status = "okay";
    };

根节点配置如图所示,将两个引脚拉高,gpio4_20为外部中断触发,

配置其为输入功能。

c 复制代码
  /*ljj gpio_hsr04 read*/
  pinctrl_hsr04: hsr04grp{
      fsl,pins = < 
          MX6UL_PAD_CSI_HSYNC__GPIO4_IO20            0x000010B0
          MX6UL_PAD_CSI_VSYNC__GPIO4_IO19            0x000010B0
      >;
  };

以上就是设备树方面的配置。

确保设备树和我一样的。

驱动初始化

接下来开始驱动初始化。
我初始化配置的习惯是将类,设备注册和引脚硬件初始化分开写。
static int __init imx6ull_init(void) 就是常规的字符设备初始化。

1.字符设备初始化

c 复制代码
/*  首先第一步imx6ull字符设备初始化 */
static int __init imx6ull_init(void)              
{
	int ret = 0;
	
	printk( "=== Starting IMX6U IRQ Driver Initialization ===\n");

	/* 1、构建设备号 */
	if (imx6uhsr04.major) {
		imx6uhsr04.devid = MKDEV(imx6uhsr04.major, 0);
		ret = register_chrdev_region(imx6uhsr04.devid, IMX6U_IRQ_ECHO_CNT, IMX6U_IRQ_ECHO_NAME);
		if (ret < 0) {
			printk( "Device number registration failed - ERROR: %d\n", ret);
			goto fail_init;
		}
	} else {
		ret = alloc_chrdev_region(&imx6uhsr04.devid, 0, IMX6U_IRQ_ECHO_CNT, IMX6U_IRQ_ECHO_NAME);
		if (ret < 0) {
			printk( "Device number allocation failed - ERROR: %d\n", ret);
			goto fail_init;
		}
		imx6uhsr04.major = MAJOR(imx6uhsr04.devid);
		imx6uhsr04.minor = MINOR(imx6uhsr04.devid);
	}
	printk( "Device number allocated: major=%d, minor=%d - SUCCESS\n", 
		   imx6uhsr04.major, imx6uhsr04.minor);

	/* 2、注册字符设备 */
	cdev_init(&imx6uhsr04.cdev, &imx6uhsr04_fops);
	imx6uhsr04.cdev.owner = THIS_MODULE;
	ret = cdev_add(&imx6uhsr04.cdev, imx6uhsr04.devid, IMX6U_IRQ_ECHO_CNT);
	if (ret) {
		printk( "Character device add failed - ERROR: %d\n", ret);
		goto fail_cdev;
	}
	printk( "Character device registered - SUCCESS\n");

	/* 3、创建类 */
	imx6uhsr04.class = class_create(THIS_MODULE,IMX6U_IRQ_ECHO_NAME);
	if (IS_ERR(imx6uhsr04.class)) {
		ret = PTR_ERR(imx6uhsr04.class);
		printk( "Class creation failed - ERROR: %d\n", ret);
		goto fail_class;
	}
	printk( "Device class created - SUCCESS\n");

	/* 4、创建设备 */
	imx6uhsr04.device = device_create(imx6uhsr04.class, NULL, imx6uhsr04.devid, NULL, IMX6U_IRQ_ECHO_NAME);
	if (IS_ERR(imx6uhsr04.device)) {
		ret = PTR_ERR(imx6uhsr04.device);
		printk( "Device creation failed - ERROR: %d\n", ret);
		goto fail_device;
	}
	printk( "Device node created - SUCCESS\n");

	/* 5. 初始化原子变量 */
    atomic_set(&imx6uhsr04.releasekey, 0);
    
    /* 6. 硬件初始化 */
    ret = hsr04io_init(&imx6uhsr04);  // 传递全局变量地址
	if (ret) {
		printk( "HSR04 IO initialization failed - ERROR: %d\n", ret);
		goto fail_hsr04io;
	}

	printk( "=== IMX6U IRQ Driver Loaded SUCCESSFULLY ===\n");
	return 0;

fail_hsr04io:
	device_destroy(imx6uhsr04.class, imx6uhsr04.devid);
fail_device:
	class_destroy(imx6uhsr04.class);
fail_class:
	cdev_del(&imx6uhsr04.cdev);
fail_cdev:
	unregister_chrdev_region(imx6uhsr04.devid, IMX6U_IRQ_ECHO_CNT);
fail_init:
	printk( "=== IMX6U IRQ Driver Initialization FAILED ===\n");
	return ret;
}

2.硬件初始化

这部分主要是设置引脚ECHO为输入功能,并且开启其外部中断。TRIG为输出功能,然后初始化定时器。一个定时器用来记录测量时间一个用来产生10us高电平信号的时间记录。

c 复制代码
/* 第三步硬件初始化 */
static int hsr04io_init(struct imx6uhsr04_dev *dev)
{
    int ret = 0;
    
    printk(KERN_INFO "Starting HSR04 ultrasonic sensor initialization...\n");
    
    /* 参数验证 */
    if (dev == NULL) {
        printk(KERN_ERR "HSR04: Device pointer is NULL\n");
        return -EINVAL;
    }
    
    /* 1. 查找设备树节点 - 使用传入的dev参数 */
    dev->nd = of_find_node_by_path("/hsr04");
    if (dev->nd == NULL) {
        printk(KERN_ERR "HSR04: HSR04 node not found in device tree!\n");
        return -ENODEV;
    }
    printk(KERN_INFO "HSR04: Device tree node found successfully\n");

    /* 2. 分别获取Trig和Echo引脚  */
    // 获取Trig引脚(输出引脚)
    dev->trig_gpio = of_get_named_gpio(dev->nd, "trig-gpios", 0);
    if (dev->trig_gpio < 0) {
        printk(KERN_ERR "HSR04: Failed to get Trig GPIO: %d\n", dev->trig_gpio);
        return dev->trig_gpio;
    }
    printk(KERN_INFO "HSR04: Trig GPIO obtained: %d\n", dev->trig_gpio);
    
    // 获取Echo引脚(输入引脚,带中断)
    dev->echo_gpio = of_get_named_gpio(dev->nd, "echo-gpios", 0);
    if (dev->echo_gpio < 0) {
        printk(KERN_ERR "HSR04: Failed to get Echo GPIO: %d\n", dev->echo_gpio);
        return dev->echo_gpio;
    }
    printk(KERN_INFO "HSR04: Echo GPIO obtained: %d\n", dev->echo_gpio);

    /* 3. 初始化Trig引脚(输出) */
    ret = gpio_request(dev->trig_gpio, "hsr04-trig");
    if (ret) {
        printk(KERN_ERR "HSR04: Failed to request Trig GPIO: %d\n", ret);
        goto fail_trig_request;
    }
    
    ret = gpio_direction_output(dev->trig_gpio, 0);  // 初始化为低电平
    if (ret) {
        printk(KERN_ERR "HSR04: Failed to set Trig GPIO as output: %d\n", ret);
        goto fail_trig_direction;
    }
    printk(KERN_INFO "HSR04: Trig GPIO configured as output\n");

    /* 4. 初始化Echo引脚(输入,带中断) */
    ret = gpio_request(dev->echo_gpio, "hsr04-echo");
    if (ret) {
        printk(KERN_ERR "HSR04: Failed to request Echo GPIO: %d\n", ret);
        goto fail_echo_request;
    }
    
    ret = gpio_direction_input(dev->echo_gpio);
    if (ret) {
        printk(KERN_ERR "HSR04: Failed to set Echo GPIO as input: %d\n", ret);
        goto fail_echo_direction;
    }
    printk(KERN_INFO "HSR04: Echo GPIO configured as input\n");

    /* 5. 获取Echo引脚的中断号 */
    dev->echo_irq = gpio_to_irq(dev->echo_gpio);
    if (dev->echo_irq < 0) {
        printk(KERN_ERR "HSR04: Failed to get IRQ for Echo GPIO: %d\n", dev->echo_irq);
        ret = dev->echo_irq;
        goto fail_irq_get;
    }
    printk(KERN_INFO "HSR04: Echo IRQ number: %d\n", dev->echo_irq);

    /* 6. 注册中断处理函数(双边沿触发) */
    ret = request_irq(dev->echo_irq, hsr04_echo_handler, 
                     IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
                     "hsr04-echo", dev);
    if (ret) {
        printk(KERN_ERR "HSR04: Failed to request Echo IRQ: %d\n", ret);
        goto fail_irq_request;
    }
    printk(KERN_INFO "HSR04: Echo IRQ registered successfully\n");

    /* 7. 初始化测量定时器 */
    init_timer(&dev->measure_timer);
    dev->measure_timer.function = hsr04_measure_timer_callback;
    dev->measure_timer.data = (unsigned long)dev;
    
    /* 8. 初始化触发定时器(用于生成10us脉冲) */
    init_timer(&dev->trig_timer);
    dev->trig_timer.function = hsr04_trig_timer_callback;
    dev->trig_timer.data = (unsigned long)dev;

    /* 9. 初始化距离测量相关成员 */
    dev->current_distance = -1;  /* 初始化为无效值 */
    atomic_set(&dev->data_ready, 0);
    atomic_set(&dev->measuring, 0);
    spin_lock_init(&dev->echo_lock);
    init_waitqueue_head(&dev->read_wait);
    
    printk(KERN_INFO "HSR04: Initialization completed successfully\n");
    return 0;

/* 错误处理(按申请顺序反向释放资源) */
fail_irq_request:
    // IRQ请求失败,无需释放
fail_irq_get:
fail_echo_direction:
    gpio_free(dev->echo_gpio);
fail_echo_request:
fail_trig_direction:
    gpio_free(dev->trig_gpio);
fail_trig_request:
    printk(KERN_ERR "HSR04: Initialization failed\n");
    return ret;
}

3.ko文件注销函数

c 复制代码
/*  第二步imx6ull字符设备销毁释放函数 */
static void __exit imx6ull_exit(void)
{
    unsigned int i ;
    printk(KERN_INFO "=== Starting IMX6U IRQ Driver Unloading ===\n");
    
    /* 1. 释放HSR04专用资源 */
    /* 释放Echo中断 */
    if (imx6uhsr04.echo_irq > 0) {
        free_irq(imx6uhsr04.echo_irq, &imx6uhsr04);
        printk(KERN_INFO "HSR04: Echo IRQ %d freed - SUCCESS\n", imx6uhsr04.echo_irq);
    }
    
    /* 释放GPIO引脚 */
    if (imx6uhsr04.echo_gpio > 0) {
        gpio_free(imx6uhsr04.echo_gpio);
        printk(KERN_INFO "HSR04: Echo GPIO %d freed - SUCCESS\n", imx6uhsr04.echo_gpio);
    }
    
    if (imx6uhsr04.trig_gpio > 0) {
        gpio_free(imx6uhsr04.trig_gpio);
        printk(KERN_INFO "HSR04: Trig GPIO %d freed - SUCCESS\n", imx6uhsr04.trig_gpio);
    }
    
    /* 2. 删除定时器  */
    del_timer(&imx6uhsr04.measure_timer);    
    del_timer(&imx6uhsr04.trig_timer);       
    printk(KERN_INFO "HSR04: Timers deleted - SUCCESS\n");
    
    /* 3. 释放原有的按键描述数组资源 */
    for (i = 0; i < HSR04_NUM; i++) 
    {
        if (imx6uhsr04.irqhsr04desc[i].irqnum > 0) 
        {
            free_irq(imx6uhsr04.irqhsr04desc[i].irqnum, &imx6uhsr04);
            printk(KERN_INFO "IRQ %d freed - SUCCESS\n", imx6uhsr04.irqhsr04desc[i].irqnum);
        }
        if (imx6uhsr04.irqhsr04desc[i].gpio > 0) 
        {
            gpio_free(imx6uhsr04.irqhsr04desc[i].gpio);
            printk(KERN_INFO "GPIO %d freed - SUCCESS\n", imx6uhsr04.irqhsr04desc[i].gpio);
        }
    }
    
    /* 4. 注销字符设备 */
    device_destroy(imx6uhsr04.class, imx6uhsr04.devid);
    printk(KERN_INFO "Device destroyed - SUCCESS\n");
    
    class_destroy(imx6uhsr04.class);
    printk(KERN_INFO "Class destroyed - SUCCESS\n");
    
    cdev_del(&imx6uhsr04.cdev);
    printk(KERN_INFO "Character device deleted - SUCCESS\n");
    
    unregister_chrdev_region(imx6uhsr04.devid, IMX6U_IRQ_ECHO_CNT);
    printk(KERN_INFO "Device number unregistered - SUCCESS\n");
    
    printk(KERN_INFO "=== IMX6U IRQ Driver Unloaded SUCCESSFULLY ===\n");
}

以上就是初始化的关键部分,还有部分就不展示了,在最后的总代码中。

4.功能函数

第二部分就是功能函数了

首先第一个定时器当作看门狗,当没检测到阻塞后,跳出当前阻塞情况。

c 复制代码
    init_timer(&dev->measure_timer);
    dev->measure_timer.function = hsr04_measure_timer_callback;
    dev->measure_timer.data = (unsigned long)dev;

初始化中有初始这个定时器的。

c 复制代码
// 处理测量超时情况
static void hsr04_measure_timer_callback(unsigned long data)
{
    struct imx6uhsr04_dev *dev = (struct imx6uhsr04_dev *)data;
    
    printk(KERN_WARNING "HSR04: Measurement timeout\n");
    
    /* 检查是否在超时前收到了有效的Echo信号 */
    if (!atomic_read(&dev->data_ready)) {
        dev->current_distance = -1;  // 超时错误
        atomic_set(&dev->measuring, 0);
        atomic_set(&dev->data_ready, 1);
        wake_up_interruptible(&dev->read_wait);
    }
}

唤醒在read_wait队列上阻塞的用户进程。

其实你会不会很好奇,我定时器只是初始化好了,那第一次调用是在哪里呢
第一次启动(设备打开时)

c 复制代码
/* 修改open函数 */
static int sr04_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &imx6uhsr04;
    start_measurement(&imx6uhsr04);
    printk(KERN_INFO "HSR04: Device opened and measurement started\n");
    return 0;
}

此处打开了定时器清空循环。

c 复制代码
static void start_measurement(struct imx6uhsr04_dev *dev)
{
    /* 设置周期性触发(每100ms测量一次) */
    mod_timer(&dev->trig_timer, jiffies + msecs_to_jiffies(100));
}

如果定时器回调函数中没有重新设置定时器,那么定时器只会执行一次。

计时定时器中断

c 复制代码
// 负责生成10us的Trig触发脉冲
static void hsr04_trig_timer_callback(unsigned long data)
{
    struct imx6uhsr04_dev *dev = (struct imx6uhsr04_dev *)data;
    
    /* 重置数据就绪标志 */
    atomic_set(&dev->data_ready, 0);
    atomic_set(&dev->measuring, 0);
    
    /* 生成10us高电平脉冲 */
    gpio_set_value(dev->trig_gpio, 1);
    udelay(10);  // 10微秒延迟
    gpio_set_value(dev->trig_gpio, 0);
    
    /* 启动测量超时定时器(60ms后超时) */
    mod_timer(&dev->measure_timer, jiffies + msecs_to_jiffies(120));
    /* 重新设置触发定时器,实现周期性测量 */
    mod_timer(&dev->trig_timer, jiffies + msecs_to_jiffies(100));
    printk(KERN_DEBUG "HSR04: Trigger pulse sent\n");
}

启动信号已经发送了,自然而然会有Echo的边缘触发信号。这时候若外部中断捕捉到信号后就需要进入外部中断服务函数。发送触发信号后,开启定时器中断。循环清空定时器检测。以上就可以实现Trig引脚的高电平启动信号发送。以及定时器中断的循环清空。

接下来就是外部中断服务函数了

初始化的时候已经实现了中断的配置当有边沿触发时,启动中断服务函数。

c 复制代码
// 负责测量Echo脉冲宽度
static irqreturn_t hsr04_echo_handler(int irq, void *dev_id)
{
    struct imx6uhsr04_dev *dev = (struct imx6uhsr04_dev *)dev_id;
    int echo_val;
    u64 echo_duration_ns;
    u64 temp;
    int distance_cm;
    unsigned long flags;
    
    /* 获取当前Echo引脚电平 */
    echo_val = gpio_get_value(dev->echo_gpio);
    
    /* 使用自旋锁保护临界区 */
    spin_lock_irqsave(&dev->echo_lock, flags);
    
    if (echo_val == 1) {
        /* 上升沿:记录开始时间 */
        dev->echo_start_time = ktime_get();
        atomic_set(&dev->measuring, 1);
        printk(KERN_DEBUG "HSR04: Echo rising edge detected\n");
    } else {
        /* 下降沿:记录结束时间并计算距离 */
        if (atomic_read(&dev->measuring) == 0) {
            /* 没有先收到上升沿,忽略这个下降沿 */
            spin_unlock_irqrestore(&dev->echo_lock, flags);
            return IRQ_HANDLED;
        }
        
        dev->echo_end_time = ktime_get();
        atomic_set(&dev->measuring, 0);
        
        /* 计算Echo高电平持续时间(纳秒) */
        echo_duration_ns = ktime_to_ns(ktime_sub(dev->echo_end_time, dev->echo_start_time));
        
        /* 使用 do_div 进行64位除法 */
        /* 公式:距离(cm) = 时间(ns) * 0.000017 = 时间(ns) * 17 / 1000000 */
        temp = echo_duration_ns;
        temp *= 17;  /* 先乘以17 */
        do_div(temp, 1000000);  /* 再除以1000000 */
        distance_cm = (int)temp;
        
        /* 限制有效距离范围(通常超声波传感器有效距离2-400cm) */
        if (distance_cm < 2) distance_cm = 2;
        if (distance_cm > 400) distance_cm = 400;
        
        dev->current_distance = distance_cm;
        
        /* 取消超时定时器 */
        del_timer(&dev->measure_timer);
        
        /* 设置数据就绪标志 */
        atomic_set(&dev->data_ready, 1);
        
        /* 唤醒等待读取的进程 */
        wake_up_interruptible(&dev->read_wait);
        
        printk(KERN_DEBUG "HSR04: Echo falling edge, duration=%llu ns, distance=%d cm\n", 
               echo_duration_ns, distance_cm);
    }
    
    spin_unlock_irqrestore(&dev->echo_lock, flags);
    return IRQ_HANDLED;
}

首先获取当前引脚的电平,如果读取到的是上升沿记录开始时间,echo_start_time = ktime_get()写入当前时刻,如果最开始的measuring没有接收到上升沿忽略后面的下降沿,跳出循环return IRQ_HANDLED。不然就是正常检测到了,记录dev->echo_end_time = ktime_get();结束时间。两个时间相减得到时间,然后进行距离公式的计算。正常工作了就可以取消超时定时器了,这不是清空,是关闭了超时定时器。唤醒等待读取的进程wake_up_interruptible(&dev->read_wait);,发送数据。不然就会一直在这卡死等数据。

以上最关键的功能已经实现了,接下来就是read函数的调用和应用程序打通。

c 复制代码
// 用户空间读取距离数据的接口
static ssize_t sr04_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    struct imx6uhsr04_dev *dev = (struct imx6uhsr04_dev *)filp->private_data;
    int ret;
    int distance;
    
    if (cnt < sizeof(int)) {
        return -EINVAL;
    }
    
    /* 阻塞等待数据就绪 */
    if (wait_event_interruptible(dev->read_wait, atomic_read(&dev->data_ready))) {
        return -ERESTARTSYS;  /* 信号中断 */
    }
    
    /* 获取距离值 */
    distance = dev->current_distance;
    
    /* 重置数据就绪标志 */
    atomic_set(&dev->data_ready, 0);
    
    /* 拷贝数据到用户空间 */
    if (copy_to_user(buf, &distance, sizeof(int))) {
        return -EFAULT;
    }
    
    return sizeof(int);
}

当调用read函数时,等待队列wait_event_interruptible(dev->read_wait, atomic_read(&dev->data_ready)阻塞进程直到数据就绪。获取距离值,将距离值拷贝给到用户空间。

基于以上就实现了驱动的关键流程。

完整的驱动代码

hsr01.c

c 复制代码
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/math64.h>  // 添加头文件
#include <asm/div64.h> 
#include <linux/interrupt.h>  // 添加这个头文件
#define IMX6U_IRQ_ECHO_CNT		1			/* 设备号个数 */
#define IMX6U_IRQ_ECHO_NAME		"hsr04irq"	    /* 名字 */


#define HSR04_NUM				1			/* 按键数量 */

/* 中断hsr04描述结构体 */
struct irq_hsr04desc {
	int gpio;								/* gpio */
	int irqnum;								/* 中断号 */
	char name[10];							/* 名字 */
	irqreturn_t (*handler)(int, void *);	/* 中断服务函数 */
};

/* imx6uhsr04设备结构体 */
struct imx6uhsr04_dev{
	dev_t devid;			/* 设备号 */
	struct cdev cdev;		/* cdev */
	struct class *class;	/* 类 */
	struct device *device;	/* 设备 */
	int major;				/* 主设备号 */
	int minor;				/* 次设备号 */
	struct device_node	*nd; /* 设备节点 */
	atomic_t releasekey;	/* 标记是否完成一次完整的按键过程 */

	/* HSR04专用成员 */
    int trig_gpio;                  /* Trig引脚GPIO号 */
    int echo_gpio;                  /* Echo引脚GPIO号 */
    int echo_irq;                   /* Echo中断号 */

    /* 定时器 */
    struct timer_list measure_timer; /* 测量定时器 */
    struct timer_list trig_timer;    /* 触发定时器 */
    
    /* 原有的按键描述数组 */
    struct irq_hsr04desc irqhsr04desc[HSR04_NUM];
    unsigned char curkeynum;

    /* 距离测量相关成员 */
    ktime_t echo_start_time;        /* Echo上升沿时间 */
    ktime_t echo_end_time;          /* Echo下降沿时间 */
    int current_distance;           /* 当前距离值(厘米) */
    atomic_t data_ready;            /* 数据就绪标志 */
    wait_queue_head_t read_wait;    /* 读取等待队列 */
    
    /* 添加:用于标记是否正在测量 */
    atomic_t measuring;             /* 测量状态标志 */
    /* 添加:保护临界区的自旋锁 */
    spinlock_t echo_lock;           /* 保护echo时间测量的锁 */
};


struct imx6uhsr04_dev imx6uhsr04;	/* irq设备 */

// 负责测量Echo脉冲宽度
static irqreturn_t hsr04_echo_handler(int irq, void *dev_id)
{
    struct imx6uhsr04_dev *dev = (struct imx6uhsr04_dev *)dev_id;
    int echo_val;
    u64 echo_duration_ns;
    u64 temp;
    int distance_cm;
    unsigned long flags;
    
    /* 获取当前Echo引脚电平 */
    echo_val = gpio_get_value(dev->echo_gpio);
    
    /* 使用自旋锁保护临界区 */
    spin_lock_irqsave(&dev->echo_lock, flags);
    
    if (echo_val == 1) {
        /* 上升沿:记录开始时间 */
        dev->echo_start_time = ktime_get();
        atomic_set(&dev->measuring, 1);
        printk(KERN_DEBUG "HSR04: Echo rising edge detected\n");
    } else {
        /* 下降沿:记录结束时间并计算距离 */
        if (atomic_read(&dev->measuring) == 0) {
            /* 没有先收到上升沿,忽略这个下降沿 */
            spin_unlock_irqrestore(&dev->echo_lock, flags);
            return IRQ_HANDLED;
        }
        
        dev->echo_end_time = ktime_get();
        atomic_set(&dev->measuring, 0);
        
        /* 计算Echo高电平持续时间(纳秒) */
        echo_duration_ns = ktime_to_ns(ktime_sub(dev->echo_end_time, dev->echo_start_time));
        
        /* 使用 do_div 进行64位除法 */
        /* 公式:距离(cm) = 时间(ns) * 0.000017 = 时间(ns) * 17 / 1000000 */
        temp = echo_duration_ns;
        temp *= 17;  /* 先乘以17 */
        do_div(temp, 1000000);  /* 再除以1000000 */
        distance_cm = (int)temp;
        
        /* 限制有效距离范围(通常超声波传感器有效距离2-400cm) */
        if (distance_cm < 2) distance_cm = 2;
        if (distance_cm > 400) distance_cm = 400;
        
        dev->current_distance = distance_cm;
        
        /* 取消超时定时器 */
        del_timer(&dev->measure_timer);
        
        /* 设置数据就绪标志 */
        atomic_set(&dev->data_ready, 1);
        
        /* 唤醒等待读取的进程 */
        wake_up_interruptible(&dev->read_wait);
        
        printk(KERN_DEBUG "HSR04: Echo falling edge, duration=%llu ns, distance=%d cm\n", 
               echo_duration_ns, distance_cm);
    }
    
    spin_unlock_irqrestore(&dev->echo_lock, flags);
    return IRQ_HANDLED;
}

// 处理测量超时情况
static void hsr04_measure_timer_callback(unsigned long data)
{
    struct imx6uhsr04_dev *dev = (struct imx6uhsr04_dev *)data;
    
    printk(KERN_WARNING "HSR04: Measurement timeout\n");
    
    /* 检查是否在超时前收到了有效的Echo信号 */
    if (!atomic_read(&dev->data_ready)) {
        dev->current_distance = -1;  // 超时错误
        atomic_set(&dev->measuring, 0);
        atomic_set(&dev->data_ready, 1);
        wake_up_interruptible(&dev->read_wait);
    }
}

// 负责生成10us的Trig触发脉冲
static void hsr04_trig_timer_callback(unsigned long data)
{
    struct imx6uhsr04_dev *dev = (struct imx6uhsr04_dev *)data;
    
    /* 重置数据就绪标志 */
    atomic_set(&dev->data_ready, 0);
    atomic_set(&dev->measuring, 0);
    
    /* 生成10us高电平脉冲 */
    gpio_set_value(dev->trig_gpio, 1);
    udelay(10);  // 10微秒延迟
    gpio_set_value(dev->trig_gpio, 0);
    
    /* 启动测量超时定时器(60ms后超时) */
    mod_timer(&dev->measure_timer, jiffies + msecs_to_jiffies(120));
    /* 重新设置触发定时器,实现周期性测量 */
    mod_timer(&dev->trig_timer, jiffies + msecs_to_jiffies(100));
    printk(KERN_DEBUG "HSR04: Trigger pulse sent\n");
}

// 用户空间读取距离数据的接口
static ssize_t sr04_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    struct imx6uhsr04_dev *dev = (struct imx6uhsr04_dev *)filp->private_data;
    int ret;
    int distance;
    
    if (cnt < sizeof(int)) {
        return -EINVAL;
    }
    
    /* 阻塞等待数据就绪 */
    if (wait_event_interruptible(dev->read_wait, atomic_read(&dev->data_ready))) {
        return -ERESTARTSYS;  /* 信号中断 */
    }
    
    /* 获取距离值 */
    distance = dev->current_distance;
    
    /* 重置数据就绪标志 */
    atomic_set(&dev->data_ready, 0);
    
    /* 拷贝数据到用户空间 */
    if (copy_to_user(buf, &distance, sizeof(int))) {
        return -EFAULT;
    }
    
    return sizeof(int);
}

static void start_measurement(struct imx6uhsr04_dev *dev)
{
    /* 设置周期性触发(每100ms测量一次) */
    mod_timer(&dev->trig_timer, jiffies + msecs_to_jiffies(100));
}

/* 修改open函数 */
static int sr04_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &imx6uhsr04;
    start_measurement(&imx6uhsr04);
    printk(KERN_INFO "HSR04: Device opened and measurement started\n");
    return 0;
}

static int sr04_release(struct inode *inode, struct file *filp)
{
    /* 停止定时器 */
    del_timer(&imx6uhsr04.trig_timer);
    del_timer(&imx6uhsr04.measure_timer);
    
    printk(KERN_INFO "HSR04: Device closed\n");
    return 0;
}

static struct file_operations imx6uhsr04_fops = {
	.owner = THIS_MODULE,
	.open = sr04_open,
    .release = sr04_release,
    .read = sr04_read,
};


/* 第三步硬件初始化 */
static int hsr04io_init(struct imx6uhsr04_dev *dev)
{
    int ret = 0;
    
    printk(KERN_INFO "Starting HSR04 ultrasonic sensor initialization...\n");
    
    /* 参数验证 */
    if (dev == NULL) {
        printk(KERN_ERR "HSR04: Device pointer is NULL\n");
        return -EINVAL;
    }
    
    /* 1. 查找设备树节点 - 使用传入的dev参数 */
    dev->nd = of_find_node_by_path("/hsr04");
    if (dev->nd == NULL) {
        printk(KERN_ERR "HSR04: HSR04 node not found in device tree!\n");
        return -ENODEV;
    }
    printk(KERN_INFO "HSR04: Device tree node found successfully\n");

    /* 2. 分别获取Trig和Echo引脚  */
    // 获取Trig引脚(输出引脚)
    dev->trig_gpio = of_get_named_gpio(dev->nd, "trig-gpios", 0);
    if (dev->trig_gpio < 0) {
        printk(KERN_ERR "HSR04: Failed to get Trig GPIO: %d\n", dev->trig_gpio);
        return dev->trig_gpio;
    }
    printk(KERN_INFO "HSR04: Trig GPIO obtained: %d\n", dev->trig_gpio);
    
    // 获取Echo引脚(输入引脚,带中断)
    dev->echo_gpio = of_get_named_gpio(dev->nd, "echo-gpios", 0);
    if (dev->echo_gpio < 0) {
        printk(KERN_ERR "HSR04: Failed to get Echo GPIO: %d\n", dev->echo_gpio);
        return dev->echo_gpio;
    }
    printk(KERN_INFO "HSR04: Echo GPIO obtained: %d\n", dev->echo_gpio);

    /* 3. 初始化Trig引脚(输出) */
    ret = gpio_request(dev->trig_gpio, "hsr04-trig");
    if (ret) {
        printk(KERN_ERR "HSR04: Failed to request Trig GPIO: %d\n", ret);
        goto fail_trig_request;
    }
    
    ret = gpio_direction_output(dev->trig_gpio, 0);  // 初始化为低电平
    if (ret) {
        printk(KERN_ERR "HSR04: Failed to set Trig GPIO as output: %d\n", ret);
        goto fail_trig_direction;
    }
    printk(KERN_INFO "HSR04: Trig GPIO configured as output\n");

    /* 4. 初始化Echo引脚(输入,带中断) */
    ret = gpio_request(dev->echo_gpio, "hsr04-echo");
    if (ret) {
        printk(KERN_ERR "HSR04: Failed to request Echo GPIO: %d\n", ret);
        goto fail_echo_request;
    }
    
    ret = gpio_direction_input(dev->echo_gpio);
    if (ret) {
        printk(KERN_ERR "HSR04: Failed to set Echo GPIO as input: %d\n", ret);
        goto fail_echo_direction;
    }
    printk(KERN_INFO "HSR04: Echo GPIO configured as input\n");

    /* 5. 获取Echo引脚的中断号 */
    dev->echo_irq = gpio_to_irq(dev->echo_gpio);
    if (dev->echo_irq < 0) {
        printk(KERN_ERR "HSR04: Failed to get IRQ for Echo GPIO: %d\n", dev->echo_irq);
        ret = dev->echo_irq;
        goto fail_irq_get;
    }
    printk(KERN_INFO "HSR04: Echo IRQ number: %d\n", dev->echo_irq);

    /* 6. 注册中断处理函数(双边沿触发) */
    ret = request_irq(dev->echo_irq, hsr04_echo_handler, 
                     IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
                     "hsr04-echo", dev);
    if (ret) {
        printk(KERN_ERR "HSR04: Failed to request Echo IRQ: %d\n", ret);
        goto fail_irq_request;
    }
    printk(KERN_INFO "HSR04: Echo IRQ registered successfully\n");

    /* 7. 初始化测量定时器 */
    init_timer(&dev->measure_timer);
    dev->measure_timer.function = hsr04_measure_timer_callback;
    dev->measure_timer.data = (unsigned long)dev;
    
    /* 8. 初始化触发定时器(用于生成10us脉冲) */
    init_timer(&dev->trig_timer);
    dev->trig_timer.function = hsr04_trig_timer_callback;
    dev->trig_timer.data = (unsigned long)dev;

    /* 9. 初始化距离测量相关成员 */
    dev->current_distance = -1;  /* 初始化为无效值 */
    atomic_set(&dev->data_ready, 0);
    atomic_set(&dev->measuring, 0);
    spin_lock_init(&dev->echo_lock);
    init_waitqueue_head(&dev->read_wait);
    
    printk(KERN_INFO "HSR04: Initialization completed successfully\n");
    return 0;

/* 错误处理(按申请顺序反向释放资源) */
fail_irq_request:
    // IRQ请求失败,无需释放
fail_irq_get:
fail_echo_direction:
    gpio_free(dev->echo_gpio);
fail_echo_request:
fail_trig_direction:
    gpio_free(dev->trig_gpio);
fail_trig_request:
    printk(KERN_ERR "HSR04: Initialization failed\n");
    return ret;
}

/*  首先第一步imx6ull字符设备初始化 */
static int __init imx6ull_init(void)              
{
	int ret = 0;
	
	printk( "=== Starting IMX6U IRQ Driver Initialization ===\n");

	/* 1、构建设备号 */
	if (imx6uhsr04.major) {
		imx6uhsr04.devid = MKDEV(imx6uhsr04.major, 0);
		ret = register_chrdev_region(imx6uhsr04.devid, IMX6U_IRQ_ECHO_CNT, IMX6U_IRQ_ECHO_NAME);
		if (ret < 0) {
			printk( "Device number registration failed - ERROR: %d\n", ret);
			goto fail_init;
		}
	} else {
		ret = alloc_chrdev_region(&imx6uhsr04.devid, 0, IMX6U_IRQ_ECHO_CNT, IMX6U_IRQ_ECHO_NAME);
		if (ret < 0) {
			printk( "Device number allocation failed - ERROR: %d\n", ret);
			goto fail_init;
		}
		imx6uhsr04.major = MAJOR(imx6uhsr04.devid);
		imx6uhsr04.minor = MINOR(imx6uhsr04.devid);
	}
	printk( "Device number allocated: major=%d, minor=%d - SUCCESS\n", 
		   imx6uhsr04.major, imx6uhsr04.minor);

	/* 2、注册字符设备 */
	cdev_init(&imx6uhsr04.cdev, &imx6uhsr04_fops);
	imx6uhsr04.cdev.owner = THIS_MODULE;
	ret = cdev_add(&imx6uhsr04.cdev, imx6uhsr04.devid, IMX6U_IRQ_ECHO_CNT);
	if (ret) {
		printk( "Character device add failed - ERROR: %d\n", ret);
		goto fail_cdev;
	}
	printk( "Character device registered - SUCCESS\n");

	/* 3、创建类 */
	imx6uhsr04.class = class_create(THIS_MODULE,IMX6U_IRQ_ECHO_NAME);
	if (IS_ERR(imx6uhsr04.class)) {
		ret = PTR_ERR(imx6uhsr04.class);
		printk( "Class creation failed - ERROR: %d\n", ret);
		goto fail_class;
	}
	printk( "Device class created - SUCCESS\n");

	/* 4、创建设备 */
	imx6uhsr04.device = device_create(imx6uhsr04.class, NULL, imx6uhsr04.devid, NULL, IMX6U_IRQ_ECHO_NAME);
	if (IS_ERR(imx6uhsr04.device)) {
		ret = PTR_ERR(imx6uhsr04.device);
		printk( "Device creation failed - ERROR: %d\n", ret);
		goto fail_device;
	}
	printk( "Device node created - SUCCESS\n");

	/* 5. 初始化原子变量 */
    atomic_set(&imx6uhsr04.releasekey, 0);
    
    /* 6. 硬件初始化 */
    ret = hsr04io_init(&imx6uhsr04);  // 传递全局变量地址
	if (ret) {
		printk( "HSR04 IO initialization failed - ERROR: %d\n", ret);
		goto fail_hsr04io;
	}

	printk( "=== IMX6U IRQ Driver Loaded SUCCESSFULLY ===\n");
	return 0;

fail_hsr04io:
	device_destroy(imx6uhsr04.class, imx6uhsr04.devid);
fail_device:
	class_destroy(imx6uhsr04.class);
fail_class:
	cdev_del(&imx6uhsr04.cdev);
fail_cdev:
	unregister_chrdev_region(imx6uhsr04.devid, IMX6U_IRQ_ECHO_CNT);
fail_init:
	printk( "=== IMX6U IRQ Driver Initialization FAILED ===\n");
	return ret;
}


/*  第二步imx6ull字符设备销毁释放函数 */
static void __exit imx6ull_exit(void)
{
    unsigned int i ;
    printk(KERN_INFO "=== Starting IMX6U IRQ Driver Unloading ===\n");
    
    /* 1. 释放HSR04专用资源 */
    /* 释放Echo中断 */
    if (imx6uhsr04.echo_irq > 0) {
        free_irq(imx6uhsr04.echo_irq, &imx6uhsr04);
        printk(KERN_INFO "HSR04: Echo IRQ %d freed - SUCCESS\n", imx6uhsr04.echo_irq);
    }
    
    /* 释放GPIO引脚 */
    if (imx6uhsr04.echo_gpio > 0) {
        gpio_free(imx6uhsr04.echo_gpio);
        printk(KERN_INFO "HSR04: Echo GPIO %d freed - SUCCESS\n", imx6uhsr04.echo_gpio);
    }
    
    if (imx6uhsr04.trig_gpio > 0) {
        gpio_free(imx6uhsr04.trig_gpio);
        printk(KERN_INFO "HSR04: Trig GPIO %d freed - SUCCESS\n", imx6uhsr04.trig_gpio);
    }
    
    /* 2. 删除定时器  */
    del_timer(&imx6uhsr04.measure_timer);    
    del_timer(&imx6uhsr04.trig_timer);       
    printk(KERN_INFO "HSR04: Timers deleted - SUCCESS\n");
    
    /* 3. 释放原有的按键描述数组资源*/
    for (i = 0; i < HSR04_NUM; i++) 
    {
        if (imx6uhsr04.irqhsr04desc[i].irqnum > 0) 
        {
            free_irq(imx6uhsr04.irqhsr04desc[i].irqnum, &imx6uhsr04);
            printk(KERN_INFO "IRQ %d freed - SUCCESS\n", imx6uhsr04.irqhsr04desc[i].irqnum);
        }
        if (imx6uhsr04.irqhsr04desc[i].gpio > 0) 
        {
            gpio_free(imx6uhsr04.irqhsr04desc[i].gpio);
            printk(KERN_INFO "GPIO %d freed - SUCCESS\n", imx6uhsr04.irqhsr04desc[i].gpio);
        }
    }
    
    /* 4. 注销字符设备 */
    device_destroy(imx6uhsr04.class, imx6uhsr04.devid);
    printk(KERN_INFO "Device destroyed - SUCCESS\n");
    
    class_destroy(imx6uhsr04.class);
    printk(KERN_INFO "Class destroyed - SUCCESS\n");
    
    cdev_del(&imx6uhsr04.cdev);
    printk(KERN_INFO "Character device deleted - SUCCESS\n");
    
    unregister_chrdev_region(imx6uhsr04.devid, IMX6U_IRQ_ECHO_CNT);
    printk(KERN_INFO "Device number unregistered - SUCCESS\n");
    
    printk(KERN_INFO "=== IMX6U IRQ Driver Unloaded SUCCESSFULLY ===\n");
}

module_init(imx6ull_init);
module_exit(imx6ull_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ljj");
MODULE_DESCRIPTION("IMX6U IRQ HSR04 Driver with detailed status reporting");

应用程序代码

hsr04app.c

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 <stdlib.h>

int main(int argc, char **argv)
{
    int fd;
    int distance_cm;
    int ret;
    
    if (argc != 2) {
        printf("Usage: %s <dev>\n", argv[0]);
        printf("Example: %s /dev/hsr04irq\n", argv[0]);
        return -1;
    }

    /* 阻塞模式打开 */
    fd = open(argv[1], O_RDWR);
    if (fd == -1) {
        printf("Cannot open device file %s\n", argv[1]);
        return -1;
    }
    
    printf("HSR04 Ultrasonic Sensor Test Program\n");
    printf("Reading distance measurements...\n");
    printf("Press Ctrl+C to exit\n\n");
    
    while (1) 
    {
        ret = read(fd, &distance_cm, 4);
        if (ret == 4) 
        {
            if (distance_cm > 0) 
            {
                printf("Distance: %d cm\n", distance_cm);
            } 
            else if (distance_cm == -1) 
            {
                printf("Measurement error or timeout\n");
            } 
            else 
            {
                printf("Invalid measurement: %d\n", distance_cm);
            }
        } 
        else if (ret < 0) {
            printf("Read error: ret = %d\n", ret);
            break;
        } 
        else {
            printf("Unexpected return value: %d\n", ret);
        }
        
        /* 等待一段时间再读下一个值 */
        sleep(1); // 1000ms
    }
    
    close(fd);
    printf("Program terminated\n");
    return 0;
}

makefile

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

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

obj-m += hsr04.o

加载模块没有问题。

中断没有问题。

最终测试结果:

相关推荐
xxp43211 小时前
Linux 根文件系统构建
linux·学习
边疆.1 小时前
【Linux】文件系统
linux·运维·服务器·磁盘·文件系统·软硬链接
_dindong1 小时前
Linux网络编程:Reactor反应堆模式
linux·服务器·网络·设计模式·php
知识分享小能手2 小时前
CentOS Stream 9入门学习教程,从入门到精通,CentOS Stream 9 进程管理 —语法详解与实战案例(8)
linux·学习·centos
零日失眠者2 小时前
⚠️ 警告!99%的开发者都踩过这个坑:Python3安装后系统彻底瘫痪!yum直接报废的真相
linux·python
Bigan(安)2 小时前
【奶茶Beta专项】【LVGL9.4源码分析】04-OS抽象层
linux·c语言·mcu·arm·unix
Bigan(安)2 小时前
【奶茶Beta专项】【LVGL9.4源码分析】06-tick时间管理
linux·c语言·mcu·arm·unix
2301_793069822 小时前
Linux Ubuntu/Windows 双系统 分区挂载指南
linux·windows·ubuntu
道路与代码之旅2 小时前
Windows 10 中以 WSL 驱 Ubuntu 记
linux·windows·ubuntu