Linux驱动基础(HC-SR04超声波模块)

简介: Linux驱动基础(HC-SR04超声波模块)

前言

本篇文章将讲解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超声波驱动程序的编写,大家只要掌握好了驱动的基本框架,那么剩下的工作和单片机中的就是一样了。

相关文章
|
2月前
|
Unix Linux 网络安全
python中连接linux好用的模块paramiko(附带案例)
该文章详细介绍了如何使用Python的Paramiko模块来连接Linux服务器,包括安装配置及通过密码或密钥进行身份验证的示例。
83 1
|
2月前
|
编解码 Linux 开发工具
Linux平台x86_64|aarch64架构RTMP推送|轻量级RTSP服务模块集成说明
支持x64_64架构、aarch64架构(需要glibc-2.21及以上版本的Linux系统, 需要libX11.so.6, 需要GLib–2.0, 需安装 libstdc++.so.6.0.21、GLIBCXX_3.4.21、 CXXABI_1.3.9)。
|
3月前
|
Java Linux API
Linux设备驱动开发详解2
Linux设备驱动开发详解
44 6
|
3月前
|
消息中间件 算法 Unix
Linux设备驱动开发详解1
Linux设备驱动开发详解
50 5
|
3月前
|
NoSQL Linux Android开发
内核实验(三):编写简单Linux内核模块,使用Qemu加载ko做测试
本文介绍了如何在QEMU中挂载虚拟分区、创建和编译简单的Linux内核模块,并在QEMU虚拟机中加载和测试这些内核模块,包括创建虚拟分区、编写内核模块代码、编译、部署以及在QEMU中的加载和测试过程。
202 0
内核实验(三):编写简单Linux内核模块,使用Qemu加载ko做测试
|
3月前
|
Ubuntu NoSQL Linux
Linux内核和驱动
Linux内核和驱动
29 2
|
3月前
|
负载均衡 应用服务中间件 Linux
在Linux中,常用的 Nginx 模块有哪些,常来做什么?
在Linux中,常用的 Nginx 模块有哪些,常来做什么?
|
3月前
|
安全 Linux 开发者
在Linux中,内核模块是什么以及如何加载和卸载它们?
在Linux中,内核模块是什么以及如何加载和卸载它们?
|
3月前
|
数据采集 Linux
Linux源码阅读笔记20-PCI设备驱动详解
Linux源码阅读笔记20-PCI设备驱动详解
|
2月前
|
Linux API
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】