前言
本篇文章将讲解HC-SR04超声波模块的驱动程序编写,有了上篇SR501模块驱动程序编写的基础后这篇文章大家将会学的非常轻松。
一、HC-SR04超声波模块介绍
HC-SR04超声波模块是一种常用于距离测量和障碍物检测的模块。它通过发射超声波信号并接收回波来计算所测量物体与传感器之间的距离。
HC-SR04超声波模块内置有发射器、接收器和控制电路。当模块接收到输入信号后,发射器将发射出一定频率的超声波脉冲信号,该信号在空气中传播并被障碍物反射后,被接收器检测到并转换成电信号返回给模块。模块通过计算从发射到接收所经历时间的差值,即回波延迟时间,乘以声波在空气中的行进速度,得出传感器与障碍物之间的距离。
HC-SR04超声波模块的工作范围一般在2厘米到4米之间,并且可以通过调节工作电压和发送脉冲的频率来改变其工作范围。该模块体积小、功耗低,常用于机器人导航、无人机、汽车避障、智能安防等场景中。
二、超声波时序原理讲解
超声波时序原理的讲解可以看我前面STM32的文章,原理上都是一样的。
地址:超声波模块原理
三、设备树编写
超声波模块需要使用到两个引脚一个是trig触发信号引脚,一个是echo接收信号引脚。trig需要被配置为输出引脚,echo配置为输入引脚,并且配置为中断模式。
这里特别需要注意的是在gpios前面加上trig和echo,这样在驱动里面就可以直接通过名字来找到对应的引脚了。
sr04 { /* for imx6ull */ compatible = "my,sr04"; trig-gpios = <&gpio4 19 GPIO_ACTIVE_HIGH>; echo-gpios = <&gpio4 20 GPIO_ACTIVE_HIGH>; };
四、驱动程序编写
1.确定主设备号
主设备设置为0让系统自动帮我们分配主设备号。
static int major=0;/*主设备号*/
2.编写file_operations结构体
我们需要提供这个结构体并且编写其中的open和read函数,供应用程序使用。
static ssize_t sr04_read (struct file *file, char __user *buf, size_t size, loff_t *off) { int err; int timeout; /*发出至少10us的触发信号*/ gpiod_set_value(sr04_trig, 1); udelay(15); gpiod_set_value(sr04_trig, 0); /* 等待数据 */ timeout = wait_event_interruptible_timeout(sr04_wq, sr04_data_ns, HZ); if (timeout) { err = copy_to_user(buf, &sr04_data_ns, 4); sr04_data_ns = 0; return 4; } else { return -EAGAIN; } return 0; } static int sr04_open (struct inode *inode, struct file *file) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); return 0; } static struct file_operations sr501_ops={ .owner = THIS_MODULE, .open = sr501_open, .read = sr501_read, };
3.注册file_operations结构体
在Linux中注册其实就是指在Linux内核中添加我们自己编写的这个file_operations结构体。这个注册的工作在入口函数中完成。
static int __init sr04_init(void) { int err; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); /*确定主设备号*/ major=register_chrdev(major, "mysr04", &sr04_ops); /*创建类*/ sr04_class=class_create(THIS_MODULE, "sr04"); if (IS_ERR(sr04_class)) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); unregister_chrdev(major, "mysr04"); return PTR_ERR(sr04_class); } init_waitqueue_head(&sr04_wq);//初始化队列 err=platform_driver_register(&sr04); return 0; }
4.出口函数编写
有入口函数就会有出口函数,在入口函数中做的是设备的注册等工作,那么出口函数就是做相反的工作,将设备注销。
static void __exit sr04_exit(void) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); platform_driver_unregister(&sr04); class_destroy(sr04_class); unregister_chrdev(major, "mysr04"); } module_init(sr04_init); module_exit(sr04_exit); MODULE_LICENSE("GPL");
5.probe函数和remove函数编写
创建platform_driver结构体和of_device_id结构体,使用of_device_id结构体中的compatible 属性和设备树进行匹配,匹配完成后会调用到probe函数。
static int sr04_probe(struct platform_device *pdev) { int err; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); /*1.获取硬件信息*/ sr04_echo=gpiod_get(&pdev->dev, "echo", GPIOD_IN); if (IS_ERR(sr04_echo)) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); } sr04_trig=gpiod_get(&pdev->dev, "trig", GPIOD_OUT_LOW); if (IS_ERR(sr04_trig)) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); } /*得到irq*/ irq = gpiod_to_irq(sr04_echo); /*申请中断并设置为双边沿触发*/ err = request_irq(irq, sr04_isr, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "sr04", NULL); if (err != 0) { printk("request_irq is err\n"); } /*2.创建设备节点*/ device_create(sr04_class, NULL, MKDEV(major, 0), NULL, "sr04"); return 0; } static int sr04_remove(struct platform_device *pdev) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); device_destroy(sr04_class, MKDEV(major, 0)); free_irq(irq, NULL); gpiod_put(sr04_trig); gpiod_put(sr04_echo); return 0; } static const struct of_device_id my_sr04[] = { { .compatible = "my,sr04" }, { }, }; static struct platform_driver sr04={ .driver = { .name = "sr04", .of_match_table = my_sr04, }, .probe = sr04_probe, .remove = sr04_remove, };
7.测试程序编写
#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 <unistd.h> /* * ./sr04_test /dev/sr04 * */ int main(int argc, char **argv) { int fd; int ns; int i; /* 1. 判断参数 */ if (argc != 2) { printf("Usage: %s <dev>\n", argv[0]); return -1; } /* 2. 打开文件 */ // fd = open(argv[1], O_RDWR | O_NONBLOCK); 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); return 0; }
8.全部驱动程序
#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/workqueue.h> #include <asm/current.h> #include <linux/delay.h> #include <linux/timex.h> static int major=0; static struct class *sr04_class; static struct gpio_desc *sr04_echo; static struct gpio_desc *sr04_trig; static int irq; static u64 sr04_data_ns = 0; static wait_queue_head_t sr04_wq; static ssize_t sr04_read (struct file *file, char __user *buf, size_t size, loff_t *off) { int err; int timeout; /*发出至少10us的触发信号*/ gpiod_set_value(sr04_trig, 1); udelay(15); gpiod_set_value(sr04_trig, 0); /* 等待数据 */ timeout = wait_event_interruptible_timeout(sr04_wq, sr04_data_ns, HZ); if (timeout) { err = copy_to_user(buf, &sr04_data_ns, 4); sr04_data_ns = 0; return 4; } else { return -EAGAIN; } return 0; } static int sr04_open (struct inode *inode, struct file *file) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); return 0; } static struct file_operations sr04_ops={ .owner = THIS_MODULE, .open = sr04_open, .read = sr04_read, }; static irqreturn_t sr04_isr(int irq, void *dev_id) { int val = gpiod_get_value(sr04_echo); if(val) { /*上升沿*/ sr04_data_ns = ktime_get_ns(); } else { /*下降沿*/ sr04_data_ns = ktime_get_ns() - sr04_data_ns; /* 2. 唤醒APP:去同一个链表把APP唤醒 */ wake_up(&sr04_wq); } return IRQ_HANDLED; // IRQ_WAKE_THREAD; } static int sr04_probe(struct platform_device *pdev) { int err; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); /*1.获取硬件信息*/ sr04_echo=gpiod_get(&pdev->dev, "echo", GPIOD_IN); if (IS_ERR(sr04_echo)) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); } sr04_trig=gpiod_get(&pdev->dev, "trig", GPIOD_OUT_LOW); if (IS_ERR(sr04_trig)) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); } /*得到irq*/ irq = gpiod_to_irq(sr04_echo); /*申请中断并设置为双边沿触发*/ err = request_irq(irq, sr04_isr, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "sr04", NULL); if (err != 0) { printk("request_irq is err\n"); } /*2.创建设备节点*/ device_create(sr04_class, NULL, MKDEV(major, 0), NULL, "sr04"); return 0; } static int sr04_remove(struct platform_device *pdev) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); device_destroy(sr04_class, MKDEV(major, 0)); free_irq(irq, NULL); gpiod_put(sr04_trig); gpiod_put(sr04_echo); return 0; } static const struct of_device_id my_sr04[] = { { .compatible = "my,sr04" }, { }, }; static struct platform_driver sr04={ .driver = { .name = "sr04", .of_match_table = my_sr04, }, .probe = sr04_probe, .remove = sr04_remove, }; static int __init sr04_init(void) { int err; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); /*确定主设备号*/ major=register_chrdev(major, "mysr04", &sr04_ops); /*创建类*/ sr04_class=class_create(THIS_MODULE, "sr04"); if (IS_ERR(sr04_class)) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); unregister_chrdev(major, "mysr04"); return PTR_ERR(sr04_class); } init_waitqueue_head(&sr04_wq);//初始化队列 err=platform_driver_register(&sr04); return 0; } static void __exit sr04_exit(void) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); platform_driver_unregister(&sr04); class_destroy(sr04_class); unregister_chrdev(major, "mysr04"); } module_init(sr04_init); module_exit(sr04_exit); MODULE_LICENSE("GPL");
总结
本篇文章讲解HC-SR04超声波驱动程序的编写,大家只要掌握好了驱动的基本框架,那么剩下的工作和单片机中的就是一样了。