这篇文章主要是基于韦东山的板子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

加载模块没有问题。

中断没有问题。
最终测试结果:
