前言
超声波测距模块 是利用超声波来测距。模块先发送超声波,然后接收反射回来的超声波,由反射经历的时间和声音的传播速度 340m/s,计算得出距离。本实验采用 中断 的方法,来进行测距。
一、SR04 模块介绍
引脚 :VCC
、Trig
、Echo
、GND
。
Trig 是 脉冲触发 引脚.
Echo 是 回响接收 引脚
测距原理 :
- 触发:
向Trig(脉冲触发引脚)发出一个大约10us的高电平。 - 发出超声波,接收反射信号:
模块就自动发出8个40Khz的超声波,超声波遇到障碍物后反射回来,模块收到返回来的超声波。 - 回响:
模块接收到反射回来的超声波后,Echo引脚输出一个与检测距离成比例的高电平。
我们只要在该引脚为高时,开启定时器计数,在该引脚变为低时,结束定时器计数。根据定时器的计数和定时器频率就可以算出经历
- 时间,根据时间即可推导出距离。
二、设备树设置
设备树 中 compatible 与 驱动程序 进行匹配。
通过原理图可知 Trig 和 Echo 引脚是低电平有效,将其分别接到 开发板的 gpio4-19, gpio4-20 引脚。每一组 GPIO 有 32 个引脚。
配置设备树需要对 GPIO 引脚 以及相关的 pincontrol 配置。由于本实验是使用 SR04 模块,所以不需要配置 pincontrol 。
三、驱动程序
- 首先 定义、注册一个file_operations 结构体。read 函数便于读取引脚电平。major 是返回的主设备号。
在入口函数里进行 class_create 创建类 , device_create 创建设备节点,register_chrdev 注册 file_operations 结构体。
出口函数里 device_destroy,class_destroy 将其逐个销毁 ,platform_driver_unregister 卸载 file_operations 结构体 。
函数的详细使用可参考 上一篇文章:SR501人体红外模块
static struct file_operations sr04_fops = { .owner = THIS_MODULE, .read = sr04_drv_read, }; /* 注册结构体 */ major = register_chrdev(0, "sr04", &sr04_fops);
- 定义、注册一个platform_driver。
ask100_sr04 用于 设备树和驱动设备匹配。
static const struct of_device_id ask100_sr04[] = { { .compatible = "my,sr04"}, { }, }; static struct platform_driver sr04s_driver = { .probe = sr04_probe, .remove = sr04_remove, .driver = { .name = "100ask_sr04", .of_match_table = ask100_sr04, }, }; /* 注册 platform_driver */ err = platform_driver_register(&sr04s_driver);
- 在probe 函数里进行 获取引脚,并对其引脚 初始化。
使用 gpiod_get 获取对应引脚。参数二 是对应引脚的名字(设备树中自定义节点中的引脚名)。
/* 设置 trig 初始化时为低电平状态 */ trig_gpio = gpiod_get(&pdev->dev, "trig",GPIOD_OUT_LOW); /* 设置 echo初始化时为输入引脚 */ echo_gpio = gpiod_get(&pdev->dev, "echo",GPIOD_IN);
- 获取中断号 irq ,request_irq 请求中断。
前面了解到 echo 为输入引脚,trig 为 输出引脚。 (获取中断号 和 请求中断 可以在 probe 函数里实现。)
/* 获取中断号 */ irq = gpiod_to_irq(echo_gpio); /* 申请中断 */ request_irq(irq, sr04_isr, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "sr04", NULL);
那什么时候发生中断呢?
在 request_irq 函数里 可以看到 参数三 IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING。当 电平处于上升沿 或者 下降沿时发生中断(电平发生变化)。
- 当 电平由 低变高 时,触发中断,记录时间为 t1。此时并不唤醒处于休眠的函数。
- 当 电平由 高变低 时,触发中断,记录时间为 t2。
t = t2 - t1 。 t 是 超声波 从发出到接受的时间,就是声波在待测距离上的往返时间。
这时 就可以唤醒休眠的 read 函数了。
read 函数读取时间 t 后,即可在测试程序中 算出距离 D = 340 * t / 2
。
- 在 入口函数里 初始化等待队列头。
static wait_queue_head_t sr04_wq; // 定义等待队列头对象 init_waitqueue_head(&sr04_wq); // 初始化等待队列头
- 中断处理函数,wake_up 唤醒 休眠函数。
触发中断后调用中断处理函数。
gpiod_get_value
获取相应引脚电平。
ktime_get_ns();
获取内核启动到现在的时间,在挂起时会暂停。单位是 ns (纳秒)
wake_up
唤醒 在 read 函数里休眠的队列。
static irqreturn_t sr04_isr(int irq, void *dev_id) { int val = gpiod_get_value(echo_gpio); if(val) { sr04_data_ns = ktime_get_ns(); //获取上升沿时的时间 } else { sr04_data_ns = ktime_get_ns() - sr04_data_ns; //获取下降沿时的时间,并相减得到高电平时间 wake_up(&sr04_wq); //唤醒队列 } return IRQ_HANDLED; // IRQ_WAKE_THREAD; }
实现 read 函数。
wait_event_interruptible_timeout
负责 等待队列和超时控制。它的作用是使当前执行的线程(或进程)进入睡眠状
- 态,直到满足指定的条件,或者经过指定的时间。
gpiod_set_value
设置 trig 输出 不少于 10 us 的高电平。
static ssize_t sr04_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset) { int timeout=0; /* 发送10us高电平 , 测量距离 2cm-450cm */ gpiod_set_value(trig_gpio, 1); udelay(15); gpiod_set_value(trig_gpio, 0); timeout = wait_event_interruptible_timeout(sr04_wq, sr04_data_ns, HZ); if(timeout) { copy_to_user(buf, &sr04_data_ns, 4); sr04_data_ns = 0; return 4; } else { return -EAGAIN; } }
四、测试程序
判断参数,打开文件,读取电平。若引脚为高电平 则读取距离,否则读取错误。
if (argc != 2) { printf("Usage: %s <dev>\n", argv[0]); return -1; } fd = open(argv[1], O_RDWR); if (fd == -1) { printf("can not open file %s\n", argv[1]); return -1; } while (1) { if (read(fd, &ns, 4) == 4) { printf("get distance: %d ns\n", ns); printf("get distance: %d mm\n", ns*340/2/1000000); /* mm */ } else printf("get distance: -1\n"); sleep(1); } close(fd);
五、上级测试及效果
执行 insmod
命令可以将 .ko 文件加载到内核中,再 执行测试程序。(rmmod
命令可以卸载已加载的模块,lsmod
命令 可以观察已加载到内核的文件。)
将 遮挡物 置于 超声波模块 前,前后移动即可。 /dev/sr04 是 驱动程序中创建的设备节点( device_create )。